Módulo 1 Cuándo una respuesta del modelo cuenta como tarea terminada
Qué vas a construir: una integración que no se fía de un HTTP 200
14 min
Hasta ahora has usado el modelo a través de Claude Code, que se encarga de hablar con él por ti. Desde este módulo lo llamas tú, desde tu propio código. Y la primera llamada parece una función cualquiera: le mandas un texto, te devuelve otro texto y la petición responde con un HTTP 200, el código con el que un servidor dice «todo bien».
Esa sensación de «ya está» es la trampa de la que va este módulo.
Así es una llamada al modelo
Con el SDK oficial de TypeScript (la librería @anthropic-ai/sdk, que envuelve la API HTTP de Claude), una llamada mínima es esto:
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic(); // lee la clave de ANTHROPIC_API_KEY
const response = await client.messages.create({
model: "claude-sonnet-5-5",
max_tokens: 16000,
messages: [{ role: "user", content: "Clasifica esta solicitud de renovación: …" }],
});
modeles el modelo que contesta.max_tokenses el máximo de tokens que puede generar. Un token es un trozo de texto, a menudo una parte de palabra; por tokens se mide la longitud y por tokens se paga.messageses la conversación. Aquí, un solo mensaje del usuario.
¿Por qué 16.000 tokens para clasificar un email? En Sonnet 5.5 el modelo piensa antes de responder: el thinking viene activado por defecto. Lo que piensa también cuenta dentro de max_tokens y se cobra como salida. Con un límite justo, como 1.024, hasta una clasificación sencilla puede cortarse antes de llegar a la respuesta. 16.000 deja margen en una llamada sin streaming. Si la tarea es simple, puedes pedirle que piense menos con output_config: { effort: "low" }. Con low, en las pasadas grabadas de la lección 6, cada clasificación escribió unos 300 tokens de salida y no llegó a pensar (thinking_tokens: 0). Si aun así el pensamiento se come el presupuesto, la respuesta llega con stop_reason: "max_tokens" y sin texto, y el runtime la trata como cortada.
En el proyecto, el modelo, el max_tokens y el esfuerzo se eligen en un solo sitio, live.config.json: claude-sonnet-5-5, low para clasificar, medium para el bucle con herramientas del módulo 2 (su papel loop) y, en el objeto api, 16.000 tokens.
Si vienes de otros tutoriales, quizá eches de menos temperature. El SDK actual manda la versión de API 2026-10-21, y con ella la API rechaza temperature, top_p y top_k con un error 400 en todos los modelos. No los mandes (lo tienes con su fecha de revisión en los datos con fecha).
Lo que vuelve trae bastante más que el texto:
{
"content": [
{ "type": "thinking", "thinking": "", "signature": "…" },
{ "type": "text", "text": "…" }
],
"stop_reason": "end_turn",
"usage": { "input_tokens": 142, "output_tokens": 310 }
}
contentes una lista de bloques. Léelos por sutypey no por su posición. Si el modelo ha pensado, el primero es un bloquethinkingcon el texto vacío: por defecto la API no te enseña el razonamiento, solo una firma (signature) para poder reenviarlo. Después viene el texto. También puede llegar una petición para usar una herramienta (tool_use), una función tuya que el modelo quiere ejecutar.stop_reasondice por qué paró el modelo.end_turnsignifica que terminó.max_tokens, que se quedó sin espacio, a mitad de frase o incluso antes de escribir nada si se le fue el presupuesto pensando.refusal, que se ha negado a hacerlo, y entonces el campostop_detailsdice de qué tipo es la negativa.tool_use, que pide ejecutar una herramienta. Hay alguno más (stop_sequence,pause_turn,model_context_window_exceeded), y el runtime del proyecto los trata todos.usagecuenta los tokens de entrada y de salida, pensamiento incluido, que es lo que te van a cobrar.- Además, cada respuesta lleva un identificador único (el request ID, algo como
req_018E…). El SDK lo expone comoresponse._request_idy es lo que te pedirá soporte para investigar una llamada.
Fíjate en lo importante: una respuesta cortada por max_tokens o una negativa llegan también con HTTP 200. El 200 solo dice que el servidor ha contestado. Si tu código mira el 200 y el texto, ya está contando como éxito cosas que no lo son.
Prompt de sistema y varios turnos
Dos piezas más que vas a usar en casi todas las llamadas:
const system = "Eres el asistente de los gestores de cuentas de Nexo Renewals. Escribes en español.";
const messages: Anthropic.MessageParam[] = [
{ role: "user", content: "¿Qué pide este cliente? …" },
];
const first = await client.messages.create({ model, max_tokens: 16000, system, messages });
// Segundo turno: reenvías la conversación entera, con la respuesta tal como llegó.
messages.push({ role: "assistant", content: first.content });
messages.push({ role: "user", content: "La política permite como máximo un 10 %. ¿Qué propones?" });
const second = await client.messages.create({ model, max_tokens: 16000, system, messages });
systemes el prompt de sistema: las instrucciones de tu aplicación, separadas de lo que escribe el usuario. Ahí van el papel del modelo, el tono y los límites.- La API no guarda la conversación entre llamadas. En cada turno mandas todo otra vez, y por eso los tokens de entrada crecen con cada turno.
- La respuesta del asistente se reenvía tal como llegó, con su bloque
thinkingincluido. En el módulo 2 verás por qué no se puede retocar.
El proyecto hace exactamente esto en la función conversationTask de src/live/api.ts, que forma parte del camino opcional con clave de API.
El caso: Nexo Renewals
Todo el módulo gira alrededor de Nexo Renewals, una empresa B2B ficticia (vende a otras empresas) que gestiona renovaciones de contratos. Le llegan emails de clientes como este:
Cliente solicita renovar el 2027-10-01 en EUR y pide un descuento del 12%.
El modelo lee el email y propone cuatro datos: la intención del cliente (renovar, negociar, cancelar o falta información), la fecha, la moneda y el descuento pedido. Eso es todo lo que puede hacer. No puede autorizar el descuento ni declarar que su propia lectura es correcta.
Lo que vas a construir
Vas a convertir una integración ingenua, que da por bueno cualquier texto, en un runtime pequeño. Llamamos runtime a la capa de tu código que llama al modelo y decide qué hacer con lo que vuelve. El tuyo tendrá cuatro propiedades:
- Cada respuesta acaba en un estado con nombre propio (completada, cortada, negada, pide una acción, fallida) en lugar de un
ok: true. - Pone límites al tiempo total, a los tokens y al tamaño de lo que entra en tu sistema.
- Separa un JSON bien formado de una decisión que alguien ha verificado.
- Mide cuántas renovaciones quedan bien resueltas, además de cuántas peticiones responde el servidor.
Prepara el proyecto
Proyecto de este módulo: llm-fundamentals. Descárgalo desde la ficha del curso, en el apartado «El código», o pídele el enlace a tu Claude conectado al curso (lo conectas una vez en Tu Claude; si no lo has hecho, la descarga de la ficha basta). Descomprímelo y entra en la carpeta llm-fundamentals/: todos los comandos del módulo se lanzan desde ahí. Necesitas Node 22.12 o superior y una terminal de macOS, Linux o Windows con WSL. Para las secciones «Con tu Claude», además, Claude Code con tu sesión iniciada, el mismo del nivel 1.
En esa carpeta, instala las dependencias:
npm ci
No necesitas clave de API. Las demos y los tests funcionan en modo fake: en vez de llamar a la API de verdad, usan respuestas preparadas que reproducen los fallos reales (cortes, negativas, JSON roto) sin red y sin coste.
Lo que haces sin clave y lo que necesita clave, para que no haya sorpresas:
| Sin clave de API | Con clave de API (opcional, se paga aparte del plan) | |
|---|---|---|
Escribir tu llamada a client.messages.create |
Sí: en el laboratorio escribes la decisión estructurada y npm run student:api la prueba con el SDK de verdad contra un transporte falso que responde con la forma real de la API |
La misma función, lanzada contra la API con npm run student:api:live |
| Ver un modelo de verdad responder | Sí, con tu Claude: las secciones «Con tu Claude» de las lecciones 5 y 6 lanzan el mismo paso con claude -p y la sesión de tu plan |
npm run live:api hace el recorrido del proyecto con el SDK |
| Seguir la lección sin lanzar nada | Sí: cada sección enseña una pasada real grabada |
claude -p es Claude Code: un agente con sus propias instrucciones, que no es lo mismo que la llamada que harás en tu producto. Por eso la llamada a la API la escribes tú y la compruebas contra el transporte falso, que reproduce la API y no Claude Code.
Lo que hay dentro, para que te orientes:
demos/parte-1/: las demos de este módulo, de la01a la04. (demos/parte-2/es del módulo siguiente.)src/: el runtime completo, como referencia.starter/: el código ingenuo que vas a arreglar tú.checkpoints/: fotos del código en cada etapa, para comparar.tests/: los tests, que también sirven como especificación.
El curso deja en inglés los términos que vas a ver en la documentación y en los errores (runtime, streaming, tenant, payload…). Cada uno se explica donde sale por primera vez, y el glosario los reúne con la lección de cada uno.
Comprueba que todo funciona:
npm run checkpoint:00
Deberías ver:
{ checkpoint: 0, naiveTaskSuccessRate: 1, actualTaskSuccessRate: 0.25 }
Son cuatro respuestas con HTTP 200: una terminada, una cortada por max_tokens, una negativa y una petición de herramienta. Contando «200 y hay texto», la tasa de éxito es del 100 %. Contando tareas bien terminadas, del 25 %. Las dos cifras las calcula checkpoints/00-baseline.ts a partir de las cuatro respuestas: la segunda exige además stop_reason igual a end_turn. En la siguiente lección ves el mismo problema a escala, con el incidente que montó todo esto.