Orquestar Message Batches API para cargas offline con custom_id unico, fragmentacion selectiva de fallos parciales y reintento con cap.
Construye orquestadores de la Message Batches API de Anthropic para procesar cargas masivas offline. [DOC] El patrón asigna un custom_id único por request para correlacionar resultados y aísla los fallos: cuando una fracción del lote falla, solo se reintentan esos custom_id, nunca el batch completo. [DOC] Ciclo de vida: create → poll processing_status → results() → fragmentar → retry selectivo. [CÓDIGO]
Entradas: lista de items con un ID de negocio estable, model, max_tokens, contenido del prompt, y un max_retries. [INFERENCIA]
Salidas: mapa custom_id → message para éxitos persistidos + lista de custom_id no resueltos tras agotar el cap, más (si se exige evidencia) un reporte JSON que pasa scripts/check.sh. [CONFIG]
false_positive_realtime en evals. [CONFIG]custom_id duplicados y el usuario pide saltar la validación de unicidad, o pide explícitamente sin custom_id / sin aislar fallos → rechaza, no degrades el patrón. [CONFIG]custom_id único y estable, derivado del ID de negocio (no de un índice de loop), para correlacionar y deduplicar de forma idempotente entre reintentos. [CÓDIGO]requests, valida unicidad de custom_id antes de enviar, y respeta los límites de tamaño/conteo del endpoint. [CÓDIGO]client.messages.batches.create(requests=...) y persiste el batch.id (checkpoint: sobrevive a un crash del orquestador). [CÓDIGO]processing_status con backoff hasta ended; nunca asumas finalización inmediata. [CÓDIGO]results(), indexando por custom_id. [CÓDIGO]result.type: succeeded → persiste; errored / expired / canceled → agrupa en sub-lote de reintento por custom_id. [CÓDIGO]custom_id afectados, aplicando el cap de reintentos; al agotarlo, devuelve los irresolubles para inspección, no en silencio. [CÓDIGO]custom_id = ID de negocio, no índice de loop. El índice rompe la correlación si el orden de items cambia entre reintentos; el ID de negocio es idempotente. [INFERENCIA]batch.id antes de hacer polling. Permite reanudar tras un crash sin recrear el batch (evita doble cobro). [INFERENCIA]expired/errored recurrentes producen un bucle infinito de creación de batches. [INFERENCIA]retrieve en batches que tardan minutos/horas. [INFERENCIA]Usa los assets de assets/ para certificar planes de batch: [CONFIG]
assets/message-batch-orchestration-contract.json: campos JSON obligatorios del reporte.assets/workload-policy.json: criterios offline, latency-tolerant, no streaming.assets/custom-id-policy.json: unicidad y estabilidad de custom_id.assets/lifecycle-policy.json: lifecycle create → poll processing_status → results.assets/retry-fragmentation-policy.json: fragmentación y retry selectivo con cap.assets/evidence-policy.json: evidencia mínima aceptada.Cuando el entregable sea JSON, valida offline con scripts/validate_message_batch_orchestration.py. [CÓDIGO] Para la smoke determinística completa ejecuta scripts/check.sh, que acepta fixtures válidos y rechaza mutaciones inválidas. [CÓDIGO]
# GOOD: batch offline con custom_id único + fragmentación selectiva de fallos
import time
from anthropic import Anthropic
client = Anthropic()
def build_requests(items):
seen = set()
requests = []
for it in items:
cid = it["id"] # ID de negocio estable, no índice de loop
if cid in seen:
raise ValueError(f"duplicate custom_id: {cid}") # gate de unicidad
seen.add(cid)
requests.append({
"custom_id": cid,
"params": {
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"messages": [{"role": "user", "content": it["prompt"]}],
},
})
return requests
def run_batch(items, max_retries=2):
pending = items
succeeded = {}
for attempt in range(max_retries + 1):
batch = client.messages.batches.create(requests=build_requests(pending))
# persiste batch.id aquí para poder reanudar tras un crash
while True:
status = client.messages.batches.retrieve(batch.id).processing_status
if status == "ended":
break
time.sleep(min(30, 2 ** attempt)) # backoff en el polling
failed = []
for result in client.messages.batches.results(batch.id):
if result.result.type == "succeeded":
succeeded[result.custom_id] = result.result.message
else: # errored | expired | canceled -> aísla solo el fallo
failed.append(result.custom_id)
if not failed:
break
pending = [it for it in items if it["id"] in set(failed)] # retry selectivo
return succeeded, [it["id"] for it in pending if it["id"] not in succeeded]Modelo
claude-sonnet-4-5fijado por el contrato de evals de esta skill (large_dataset_checkpointing); cámbialo solo si el usuario nombra otro. [CONFIG]
# ANTI: loop síncrono real-time, sin custom_id, sin fail-isolation
for item in items: # uno por uno: caro y lento
resp = client.messages.create( # rompe rate limits a volumen
model="claude-sonnet-4-5",
max_tokens=1024,
messages=[{"role": "user", "content": item["prompt"]}],
)
results.append(resp) # un fallo aborta todo el lote;
# sin custom_id no hay retry selectivoSi te sorprendes haciendo cualquiera de esto, vuelve a "Cómo construir": [INFERENCIA]
client.messages.create() en un loop sobre items offline → debías batchear.enumerate()/índice como custom_id → usa el ID de negocio.failed.processing_status != "ended" como terminal, o asumiendo finalización inmediata → sigue en polling con backoff.expired/errored recurrentes.Marca TODAS antes de dar por hecho el entregable: [DOC]
custom_id único y estable derivado del ID de negocio.custom_id se valida antes de enviar (gate que lanza en duplicado).processing_status usa backoff y espera el estado ended.result.type (succeeded aparte de errored/expired/canceled).scripts/check.sh.expired/canceled. Trátalos como fallidos reintegrables igual que errored; entran al sub-lote de retry bajo el mismo cap. [CÓDIGO]build_requests (no envíes); deduplica aguas arriba o pide regla de desempate. No silencies el duplicado. [CÓDIGO]batch.id persistido, reanuda con retrieve/results sin recrear el batch. [INFERENCIA]custom_id irresolubles para inspección; nunca retornes éxito vacío como si fuera completo. [INFERENCIA]katas-message-batch-processing.fdad39c
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.