ruff estaba configurado en pyproject.toml desde el primer commit y jamás se
había corrido. Tenía 15 hallazgos. Dos importan más allá del estilo:
- zip() sin strict= trunca en silencio al más corto. En las comparaciones de
lotes eso significa que un test podía pasar sin haber comparado todo. Donde
los largos deben coincidir ahora es strict=True; donde difieren a propósito
(pares consecutivos) queda strict=False, que documenta la intención.
- Un import sin usar delataba algo peor: BraveBackend se había escrito sin una
sola prueba. Se agregan ocho, contra una respuesta con la forma que devuelve
la API, incluidas la limpieza de etiquetas, el caso de límite de tasa —que
tiene que distinguirse de 'no respondió'— y que la credencial viaje en la
cabecera y nunca en la URL. El respaldo tiene que funcionar justo cuando el
primario ya falló; merecía la misma cobertura.
El resto es orden de imports, collections.abc y líneas largas.
Un lote de humo de 2 pedidos contra la Batch API confirmó el camino completo
—crear, consultar estado, recoger, normalizar— y dejó dos cosas a corregir:
- El costo estimado se redondeaba a 2 decimales, así que un trabajo chico
informaba US$ 0.0. Pasa a 4 decimales: en una prueba de humo importa saber
que gastaste algo, no que gastaste nada.
- cache_read_input_tokens quedó en 0. La causa es que el mínimo cacheable de
Opus 5 son 512 tokens y el prompt de sistema de la prueba tenía 86: la marca
cache_control no hace nada por debajo de ese umbral, y no avisa. Queda
documentado en el código y en la config, con cómo detectarlo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Etapa 3 del plan. Se elige la Batch API sobre llamadas sueltas porque cuesta
la mitad y generar un dataset no es sensible a la latencia: es exactamente su
caso de uso. A cambio el trabajo es asincrónico y puede tardar horas, así que
todo el estado vive en disco y es reanudable — matar el proceso, perder la
conexión o reiniciar el servidor no pierde nada ni gasta de nuevo.
Cuatro decisiones que sostienen eso:
- El custom_id se deriva del hash del contenido del pedido. El trabajo queda
idempotente: re-generar la misma especificación produce los mismos IDs, así
que lo ya resuelto se reconoce y no se vuelve a pagar.
- Los resultados se indexan por custom_id, nunca por posición. La API los
devuelve en cualquier orden, y emparejarlos por índice mezcla las respuestas
en silencio; un dataset mal alineado es peor que uno vacío porque parece
correcto. El cliente falso de los tests los invierte a propósito.
- Los fallos se registran en errors.jsonl con su motivo en vez de descartarse,
y --retry-failed reenvía solo esos.
- El prompt de sistema compartido va marcado para caché: es idéntico entre
miles de pedidos, y a ~0,1x del precio de entrada deja de contar.
Los lotes se persisten después de cada envío y no al final, para que una caída
a mitad de camino no deje lotes huérfanos sin registrar.
El SDK de anthropic se importa de forma perezosa y queda como extra opcional:
el servidor de entrenamiento no lo necesita y el paquete importa sin él.
21 tests nuevos (92 en total), todos contra un cliente falso — lo que hay que
verificar es el harness, no el SDK.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>