CULTIVA IA · Agentes IA · Referencia Técnica
NutriBot × Claude API — Guía de Integración Python
FastAPI + Claude Opus 4.8 · Streaming · Tool Use · Batch Nocturno · Prompt Caching
Python 3.11 anthropic SDK claude-opus-4-8 Generado con skill: referencia-api-claude-anthropic
🏆
Modelos disponibles — Uso para NutriBot Actualizado 2026-06-04
Modelo Model ID Contexto Input / 1M Output / 1M Uso en NutriBot
Claude Fable 5 claude-fable-5 1M tokens $10.00 $50.00 Casos extremos / investigación
Claude Opus 4.8 claude-opus-4-8 1M tokens $5.00 $25.00 ✦ NutriBot default Chat + recomendaciones
Claude Sonnet 4.6 claude-sonnet-4-6 1M tokens $3.00 $15.00 — (no usar sin autorizar)
Claude Haiku 4.5 claude-haiku-4-5 200K tokens $1.00 $5.00 Clasificación ligera, si aplica
⚠️ Regla de oro: usar siempre claude-opus-4-8 salvo que el equipo autorice explícitamente otro modelo. No downgradear por coste — es decisión del equipo, no del código.
⚙️
1 — Inicialización del cliente Python
💡 Instalar: pip install anthropic. La API key se lee de ANTHROPIC_API_KEY.
import anthropic

# ── Cliente sync (FastAPI sync endpoints)
client = anthropic.Anthropic()  # key desde env

# ── Cliente async (FastAPI async endpoints)
async_client = anthropic.AsyncAnthropic()

# ── Con retries y timeout custom
client = anthropic.Anthropic(
    max_retries=3,       # 429/500 → backoff
    timeout=120.0,     # 2 min por request
)

# ── Override por request (sin mutar client global)
client.with_options(timeout=30.0).messages.create(...)
2 — Streaming para chat UI Python
💡 Usar .stream() + max_tokens=64000 para chat. El .get_final_message() acumula el resultado completo.
# ── Chat endpoint NutriBot (FastAPI + SSE)
async def chat_nutribot(pregunta: str, historial: list):
    historial.append({"role": "user", "content": pregunta})

    async with async_client.messages.stream(
        model="claude-opus-4-8",
        max_tokens=64000,       # streaming → sin timeout
        thinking={"type": "adaptive"},
        system=SYSTEM_NUTRIBOT,
        messages=historial,
    ) as stream:
        # Yield tokens en tiempo real a la UI
        async for text in stream.text_stream:
            yield text

        # Acumular mensaje final para historial
        msg = await stream.get_final_message()
        historial.append({
            "role": "assistant",
            "content": msg.content  # ← lista de bloques, no solo texto
        })
🔧
3 — Tool Use con Tool Runner (Beta) — Herramientas NutriBot Python
💡 El tool runner gestiona el loop automáticamente. Decorar con @beta_tool — el SDK genera el JSON schema desde el type hint y el docstring.
from anthropic import beta_tool

# ── Herramienta 1: buscar alimento en BD NutriBot
@beta_tool
def buscar_alimento(nombre: str, cantidad_g: float = 100.0) -> dict:
    """Busca un alimento en la base de datos y devuelve macronutrientes.

    Args:
        nombre: Nombre del alimento (ej. 'pollo a la plancha', 'aguacate').
        cantidad_g: Cantidad en gramos para el cálculo.
    """
    # Aquí iría la consulta real a la BD
    return {"alimento": nombre, "kcal": 165, "proteina_g": 31,
            "carbos_g": 0, "grasa_g": 3.6, "por_g": cantidad_g}

# ── Herramienta 2: calcular requerimientos calóricos
@beta_tool
def calcular_requerimientos(peso_kg: float, altura_cm: float,
                             edad: int, sexo: str, actividad: str) -> dict:
    """Calcula TMB y TDEE usando la fórmula Mifflin-St Jeor.

    Args:
        peso_kg: Peso corporal en kilogramos.
        altura_cm: Altura en centímetros.
        edad: Edad en años.
        sexo: 'M' para masculino, 'F' para femenino.
        actividad: sedentario | ligero | moderado | activo | muy_activo
    """
    factores = {"sedentario": 1.2, "ligero": 1.375, "moderado": 1.55,
               "activo": 1.725, "muy_activo": 1.9}
    if sexo == "M":
        tmb = 10 * peso_kg + 6.25 * altura_cm - 5 * edad + 5
    else:
        tmb = 10 * peso_kg + 6.25 * altura_cm - 5 * edad - 161
    return {"tmb_kcal": round(tmb), "tdee_kcal": round(tmb * factores[actividad]),
            "deficit_perdida_kcal": round(tmb * factores[actividad] - 500)}

# ── Ejecutar con tool runner — loop automático
runner = client.beta.messages.tool_runner(
    model="claude-opus-4-8",
    max_tokens=16000,
    tools=[buscar_alimento, calcular_requerimientos],
    messages=[{"role": "user",
               "content": "Crea un plan para Ana, 68kg 165cm 35 años, actividad moderada"}],
)
for msg in runner:  # SDK llama tools y sigue hasta que Claude termine
    print(msg)
💾
4 — Prompt Caching en system prompt Python
💡 Render: tools → system → messages. El breakpoint al final del system cachea tools + system juntos. Verificar con usage.cache_read_input_tokens.
# ── System prompt estático NutriBot (≈ 2000 tokens, no cambia)
SYSTEM_NUTRIBOT = [
    {
        "type": "text",
        "text": """Eres NutriBot, asistente de nutrición clínica...
[2000 tokens de contexto: guías dietéticas, alergias comunes,
protocolos de la clínica, restricciones legales, formato de respuesta]""",
        "cache_control": {"type": "ephemeral"},  # ← cachear aquí
    }
]

response = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=16000,
    system=SYSTEM_NUTRIBOT,
    messages=[{"role": "user", "content": pregunta}],
)

# Verificar cache hit (debe ser > 0 desde la 2ª llamada)
usage = response.usage
print(f"Cache write: {usage.cache_creation_input_tokens}")
print(f"Cache read:  {usage.cache_read_input_tokens}")
# Si cache_read es 0 → revisar silent invalidators (timestamps en prompt)
🌙
5 — Batch nocturno (500 pacientes) Python
💡 50% de descuento en todos los tokens. Hasta 100K requests / 256MB por batch. Mayoría completan en <1h.
from anthropic.types.message_create_params import MessageCreateParamsNonStreaming
from anthropic.types.messages.batch_create_params import Request
import time

# ── 1. Crear batch con los 500 pacientes
requests = [
    Request(
        custom_id=f"paciente-{p['id']}",
        params=MessageCreateParamsNonStreaming(
            model="claude-opus-4-8",
            max_tokens=4096,
            system=SYSTEM_NUTRIBOT,
            messages=[{"role": "user",
                        "content": build_prompt(p)}],
        )
    )
    for p in pacientes  # lista de 500 dicts de pacientes
]
batch = client.messages.batches.create(requests=requests)
print(f"Batch ID: {batch.id}")

# ── 2. Esperar resultados (polling cada 60s)
while True:
    batch = client.messages.batches.retrieve(batch.id)
    if batch.processing_status == "ended": break
    time.sleep(60)

# ── 3. Recoger resultados y actualizar BD
for result in client.messages.batches.results(batch.id):
    if result.result.type == "succeeded":
        plan = result.result.message.content[0].text
        update_plan_paciente(result.custom_id, plan)
50%
descuento batch
100K
requests / batch
<1h
tiempo típico
29d
retención resultados
🧠
Thinking & Effort — Quick Reference por modelo
Fable 5 / Opus 4.8 / 4.7
Adaptive only — budget_tokens ELIMINADO
thinking={"type": "adaptive"}
output_config={"effort": "xhigh"}
Opus 4.6 / Sonnet 4.6
Adaptive recomendado — budget_tokens deprecado
thinking={"type": "adaptive"}
output_config={"effort": "high"}
Haiku 4.5 / modelos legacy
Sin thinking adaptivo — solo enabled/disabled
thinking={"type": "enabled",
"budget_tokens": 8192}
low
Sub-agentes
tareas simples
medium
Tareas rutinarias
coste moderado
high ★
Default · sweet spot
calidad / tokens
xhigh
Coding agentic
Opus 4.7+
max
Correctness crítica
coste > calidad
🚨
Errores comunes — NutriBot
🔴 budget_tokens en Opus 4.8 — Eliminado. Usar thinking={"type": "adaptive"} y output_config.effort. Devuelve 400.
🔴 Assistant prefill — Eliminado en la familia 4.6+. Usar output_config.format para controlar el formato.
🟡 Cache silencioso invalidado — Si hay datetime.now() o UUIDs en el system prompt, nada se cachea. Verificar con usage.cache_read_input_tokens.
🟡 max_tokens demasiado bajo — Default de 1024 trunca respuestas. Para non-streaming: 16000. Para streaming: 64000.
🟡 Tool inputs con string matching — Siempre parsear con json.loads(), nunca comparar el JSON crudo. El escaping puede variar entre modelos.
🔵 Compaction — Al guardar historial de conversación, guardar response.content completo (no solo .text) para preservar bloques de compaction.
🗺️
¿Qué superficie usar? — Decisión NutriBot
💬
Chat en tiempo real (UI paciente)
→ Claude API + Streaming (messages.stream())
🔧
Calcular macros / buscar alimentos / generar PDF
→ Claude API + Tool Use (@beta_tool + tool runner)
🌙
Actualizar planes de 500 pacientes cada noche
→ Batches API — 50% descuento, <1h, hasta 100K requests
🏗️
Arquitectura final NutriBot × Claude API
Chat + Streaming
FastAPI async endpoint · messages.stream() · tokens en tiempo real via SSE · historial con bloques completos
🔧
Tool Use (beta_tool)
Buscar alimentos · calcular macros · generar PDF de dieta · tool runner gestiona el loop sin código manual
💾
Prompt Caching
System prompt de 2K tokens cacheado · ahorra ~70% de input tokens en conversaciones largas · cache_control: ephemeral
🌙
Batch Nocturno
500 pacientes / noche · 50% descuento automático · polling con processing_status · resultados 29 días
🧠
Adaptive Thinking
Claude decide cuánto razonar · recomendaciones complejas con effort: high · sin budget_tokens fijo
🛡️
Error Handling
Auto-retry en 429/500 · Anthropic.RateLimitError tipado · timeout configurable · stop_reason verificado