claudio.cool
CCDV-FLecciones

CCDV-F · Selección y optimización de modelos

El SDK por dentro, REST, streaming con SSE, timeouts y reintentos

⏱ 3 min de lecturaBorrador

Un gateway corta a los 30 segundos y devuelve 504, pero el worker de atrás sigue esperando la respuesta varios minutos más. Nadie configuró nada: el problema está en los valores por defecto del SDK, que nadie leyó. Integrar Claude bien es saber qué hace el SDK sobre la API REST y cuándo hay que cambiarlo.

Lo que el SDK hace por vos

La API de mensajes es REST: un POST con JSON. El SDK de cliente (Python, TypeScript y otros) la envuelve y agrega:

  • autenticación con la clave y el encabezado de versión de la API;
  • reintentos automáticos (automatic retries) con backoff para errores de conexión, 429 y 5xx: dos por defecto, configurables con max_retries;
  • un tiempo de espera (timeout) por pedido: diez minutos por defecto, configurable en el cliente o por pedido (en Python, con with_options);
  • excepciones tipadas por código de error y el ID de cada solicitud para depurar;
  • un cliente asíncrono (async client) para muchas llamadas en paralelo.

Cuando hay un límite más corto adelante (un gateway, un usuario esperando), el timeout del SDK tiene que quedar por debajo, con menos reintentos, y la aplicación devuelve una respuesta alternativa. Si no, el worker queda trabado mucho después de que el gateway cortó.

Para paralelizar, concurrencia acotada: el cliente asíncrono con un límite de pedidos en vuelo, por ejemplo un semáforo. Lanzar todo junto con un gather sin límite termina en 429.

Streaming: SSE, no websockets

Con stream: true, la API manda la respuesta como eventos enviados por el servidor (server-sent events (SSE)): message_start, después los content_block_delta con cada fragmento (delta) de texto, y al final message_delta y message_stop.

Lo que más se pregunta:

  • No es un websocket. Si el frontend ya usa websockets con el navegador, el backend consume el stream con el SDK y reenvía los fragmentos por ese websocket. La clave nunca llega al navegador.
  • Un error puede llegar dentro del stream, como evento error (por ejemplo overloaded_error), después de un 200. Sin message_stop, la respuesta está incompleta: se descarta y se reintenta.
  • La entrada de una herramienta llega en partes. Los eventos input_json_delta traen fragmentos de JSON que por separado no son válidos: se acumulan y se parsean cuando cierra el bloque, o se usa el helper del SDK que arma el mensaje final.

Pedidos largos

Con un max_tokens muy grande sin streaming, la conexión queda inactiva y algunas redes la cortan. El SDK incluso rechaza un pedido sin streaming que espera que tarde más de unos diez minutos. La salida es usar el helper de streaming del SDK y pedirle el mensaje final completo: funciona igual que una llamada sin streaming, aunque no muestres nada en pantalla. Si el trabajo puede esperar, la otra opción es la Message Batches API.

Las trampas típicas

  • Abrir un websocket desde el navegador a la API, o poner la clave en el frontend.
  • Dejar los valores por defecto cuando adelante hay un límite de 30 segundos.
  • Sumar un bucle de reintentos propio encima de los del SDK.
  • Guardar como completa una respuesta cortada por un evento error.
  • Parsear cada fragmento de input_json_delta por separado.
  • Creer que el streaming solo sirve para mostrar progreso.

En el examen

Buscá el límite que manda en el escenario: un gateway, un usuario mirando la pantalla, un pedido que tarda minutos. Después elegí la opción que ajusta el SDK a ese límite (timeout, reintentos, streaming con el mensaje final) en lugar de la que cambia de modelo o reescribe la integración desde cero.

Pregunta rápida

Una pregunta del simulacro sobre este dominio. Cuenta para tu progreso.

A fintech team runs a nightly regression suite for its transaction-categorisation service built on the Claude API. Each test sends a fixed merchant description and asserts that the response string is byte-for-byte identical to a snapshot captured when the feature launched. The service is written in TypeScript and the suite has around 400 cases.

To make the snapshots stable, every request uses the same pinned model version, the same system prompt and the default sampling settings. Even so, between 5 and 15 cases fail each night, a different set every time, and rerunning a failed case usually passes. In the failing cases the category is correct; only the wording of the short justification differs slightly.

The tech lead wants the suite to catch real regressions without generating noise that the team has learned to ignore.

What is the best change to the test suite?

Practicá este dominio en el simulacro →