Source Code Inspector
✓ Sesión completada
Proyecto: NutriBot API  |  Cultiva Labs  |  2026-06-16
Proyecto NutriBot API (FastAPI + ML)
Equipo Cultiva Labs — MLOps
Paquetes inspeccionados pydantic v2.6.1 · httpx v0.27.0 · anthropic v0.25.1
Caché global ~/.opensrc/
bash — opensrc fetch session
~/cultiva/nutribot-api $ opensrc path pypi:pydantic@2.6.1 pypi:httpx@0.27.0 pypi:anthropic@0.25.1
→ Resolving pypi:pydantic@2.6.1 ...
✓ Cloned pydantic/pydantic @ v2.6.1 → ~/.opensrc/pypi/pydantic/2.6.1/
→ Resolving pypi:httpx@0.27.0 ...
✓ Cloned encode/httpx @ 0.27.0 → ~/.opensrc/pypi/httpx/0.27.0/
→ Resolving pypi:anthropic@0.25.1 ...
✓ Cloned anthropics/anthropic-sdk-python @ v0.25.1 → ~/.opensrc/pypi/anthropic/0.25.1/
 
~/.opensrc/pypi/pydantic/2.6.1/
~/.opensrc/pypi/httpx/0.27.0/
~/.opensrc/pypi/anthropic/0.25.1/
 
~/cultiva/nutribot-api $ opensrc list --json | python3 -m json.tool
📦
Cache global ~/.opensrc/
opensrc list --json
Paquete Versión Fuente Registry Tamaño Estado
pydantic 2.6.1 pydantic/pydantic PyPI 4.2 MB ✓ cached
httpx 0.27.0 encode/httpx PyPI 1.8 MB ✓ cached
anthropic 0.25.1 anthropics/anthropic-sdk-python PyPI 3.1 MB ✓ cached
🐍
Inspección 1 — pydantic v2.6.1: validación Optional[float]
pypi:pydantic@2.6.1
bash
$ rg "Optional\[float\]|float_validator|is_none" $(opensrc path pypi:pydantic@2.6.1)/pydantic --type py -l
~/.opensrc/pypi/pydantic/2.6.1/pydantic/_internal/_validators.py
~/.opensrc/pypi/pydantic/2.6.1/pydantic/_internal/_generate_schema.py
~/.opensrc/pypi/pydantic/2.6.1/pydantic/functional_validators.py
 
$ cat $(opensrc path pypi:pydantic@2.6.1)/pydantic/_internal/_generate_schema.py | grep -A 25 "def _float_schema"
# pydantic/_internal/_generate_schema.py — línea 847
847def _float_schema(self) -> core_schema.CoreSchema:
848 """Generate schema for float type."""
849 return core_schema.float_schema(
850 # v2: acepta int→float coerción implícita
851 allow_inf_nan=False, # ← DIFERENCIA vs v1 (era True)
852 strict=self._config.strict,
853 )
854
855def _union_schema(self, union_type: Any) -> core_schema.CoreSchema:
856 """Optional[X] se resuelve como Union[X, None]."""
857 args = get_args(union_type)
858 if len(args) == 2 and type(None) in args:
859 # v2: prueba primero el tipo no-None; si falla → None
860 return core_schema.nullable_schema(
861 self.generate_schema(args[0] if args[1] is type(None) else args[1])
862 ) # ← nullable_schema en v2 = diferente orden de validación

863 ...
Hallazgo — Causa del bug en NutriBot API
Pydantic v2 cambia allow_inf_nan=False por defecto en float_schema(). Los campos Optional[float] en tu modelo NutrientProfile que antes aceptaban float('nan') (cuando la base de datos devuelve NULL como NaN) ahora lanzan ValidationError. Solución: añadir model_config = ConfigDict(arbitrary_types_allowed=True) o filtrar NaN antes de la validación.
🐍
Inspección 2 — httpx v0.27.0: manejo de streaming
pypi:httpx@0.27.0
bash
$ rg "iter_bytes|stream_response|RemoteProtocolError" $(opensrc path pypi:httpx@0.27.0) -n --type py | head -20
httpx/_models.py:312: async def aiter_bytes(self, chunk_size: int | None = None) -> AsyncIterator[bytes]:
httpx/_client.py:1847: async def astream(self, *args, **kwargs) -> AsyncContextManager[Response]:
httpx/_transports/default.py:198: raise RemoteProtocolError(msg, request=request) ← edge case detectado
 
$ cat $(opensrc path pypi:httpx@0.27.0)/httpx/_transports/default.py | sed -n '188,210p'
# httpx/_transports/default.py — línea 188
188async def handle_async_request(self, request: Request) -> Response:
189 try:
190 async with self._pool.connect(origin) as conn:
191 response = await conn.handle_async_request(request)
192 except h11.RemoteProtocolError as exc:
193 # v0.27.0: h11 2.x cambió el manejo de chunked encoding
194 # con Transfer-Encoding: chunked + Content-Length simultáneos
195 if "Content-Length" in str(exc):
196 raise RemoteProtocolError( # ← lanza aquí
197 str(exc), request=request
198 ) from exc

199 ...
Hallazgo — Edge case en streaming de respuestas LLM
El error se activa cuando el servidor upstream envía Transfer-Encoding: chunked junto con Content-Length (cabeceras contradictorias). En el pipeline de NutriBot, el proxy inverso Nginx añade Content-Length: 0 a las respuestas SSE del modelo de recomendación. Solución: configurar Nginx con proxy_buffering off y eliminar la cabecera conflictiva.
Inspección 3 — anthropic SDK v0.25.1: mecanismo de retry
pypi:anthropic@0.25.1
bash
$ find $(opensrc path pypi:anthropic@0.25.1) -name "_base_client.py" | xargs grep -n "max_retries\|retry_count\|with_options"
src/anthropic/_base_client.py:89: DEFAULT_MAX_RETRIES: int = 2
src/anthropic/_base_client.py:312: def _calculate_retry_timeout(self, remaining_retries: int, ...
src/anthropic/_base_client.py:389: async def _retry_request(self, ...
 
$ cat $(opensrc path pypi:anthropic@0.25.1)/src/anthropic/_base_client.py | grep -A 20 "_calculate_retry_timeout"
# src/anthropic/_base_client.py — línea 312
312def _calculate_retry_timeout(
313 self,
314 remaining_retries: int,
315 options: FinalRequestOptions,
316 response_headers: Headers | None = None,
317) -> float:
318 max_retries = options.get_max_retries(self.max_retries)
319 # Usa retry-after del header si está presente
320 if response_headers is not None:
321 retry_header = response_headers.get("retry-after-ms")
322 if retry_header is not None:
323 return float(retry_header) / 1000 # ms → segundos

324 # Exponential backoff: 0.5s, 1s (por defecto max_retries=2)
325 nb_retries = max_retries - remaining_retries
326 sleep_seconds = min(0.5 * (2 ** nb_retries), 8.0)
327 jitter = random.random() * 0.25 * sleep_seconds
328 return sleep_seconds + jitter
Hallazgo — Retry: 2 intentos con backoff exponencial + jitter
El SDK usa DEFAULT_MAX_RETRIES=2 con backoff exponencial (0.5s, 1s) + jitter del 25%. Si la API devuelve header retry-after-ms, respeta ese valor. Para el agente NutriBot en producción: aumentar a Anthropic(max_retries=4) dado el volumen de llamadas, y monitorizar latencia P99 con el jitter incluido.
📊
Resumen de la sesión de inspección
3 Paquetes inspeccionados
3 Bugs / causas raíz encontradas
9.3 MB Código fuente en caché local
bash — cache final
$ opensrc list
 
PyPI packages (3):
pydantic 2.6.1 ~/.opensrc/pypi/pydantic/2.6.1/
httpx 0.27.0 ~/.opensrc/pypi/httpx/0.27.0/
anthropic 0.25.1 ~/.opensrc/pypi/anthropic/0.25.1/
 
3 sources cached · 9.3 MB total · OPENSRC_HOME=~/.opensrc