Capítulo 7: Memoria de agentes, caché de respuestas y trabajo duplicado

Mantén pequeño el estado de trabajo de un agente y deja de pagar dos veces por el mismo resultado.

Publicada el

Leer hasta 6 min completo

Visión general#

Un agente de programación lee archivos, revisa logs y prueba enfoques que fallan. Al cabo de una hora, su transcripción puede contener miles de líneas de salida. El siguiente paso rara vez las necesita todas, solo el objetivo, las restricciones, los hallazgos confirmados, los enfoques fallidos y dónde está la evidencia. Ese conjunto más pequeño es el estado de trabajo del agente.

La compactación del contexto, la caché de respuestas y la idempotencia evitan cada una un tipo distinto de trabajo repetido, así que trátalas como problemas separados.

Práctica#

Mantén tres capas: la conversación reciente para la continuidad, un estado de trabajo duradero para las decisiones y las restricciones, y la evidencia guardada en otro sitio con referencias recuperables. Un registro compacto podría tener este aspecto:

json
{
  "objective": "Fix the webhook retry bug",
  "constraints": ["No duplicate charges", "Rollback required"],
  "findings": ["Worker may time out after downstream success"],
  "failed_approaches": ["Blind retry duplicates the action"],
  "evidence": ["src/webhooks.py", "incident-1042"],
  "next_step": "Check downstream idempotency support"
}

Guarda las restricciones exactas del usuario y el estado de negocio autoritativo fuera de los resúmenes en prosa, que pierden información. La compactación cuesta tokens y puede romper la reutilización del prefijo del prompt, así que compacta cuando la reducción esperada del contexto activo lo justifique, no automáticamente después de cada turno.

Para operaciones completadas que pueden producir con seguridad la misma respuesta, usa una caché de respuestas exacta con una clave formada por las entradas y versiones relevantes. La clave de una extracción de documentos podría incluir el hash del contenido, la versión del esquema, la versión del preprocesado, la configuración del modelo y el ámbito del tenant. Aplica la autorización por separado, porque una clave de caché no controla el acceso.

Cuando llegan dos peticiones idénticas al mismo tiempo, una caché de resultados terminados no basta, porque ambas pueden dar fallo de caché y lanzar la inferencia. Usa deduplicación en vuelo o un registro de operación compartido para que los llamantes esperen al mismo trabajo.

Mide el trabajo duplicado antes de construir una caché. Registra un hash de la petición en cada llamada al modelo, calculado a partir de las entradas que usaría una clave de caché. Cada día, informa por operación de la proporción de llamadas que repiten un hash anterior y de lo que cuestan esas llamadas. Cuenta aparte las peticiones idénticas que coincidieron en el tiempo, porque solo la deduplicación en vuelo las elimina.

Ilustrativo: una operación de extracción hace 40.000 llamadas al día, y 6.000 repiten un hash visto antes ese mismo día: 6.000 / 40.000 = 15 %. A 0,004 $ por llamada, las repeticiones cuestan 6.000 × 0,004 $ = 24 $ al día, o 720 $ en 30 días.

Ledgerline. Alrededor del 10 % de las facturas que llegan a Ledgerline son el mismo archivo subido dos veces, por el proveedor y por el cliente. Un hash del contenido más la versión del esquema y del modelo forman ahora la clave de una caché exacta, limitada a la cuenta del cliente, así que 4.000 de las 40.000 facturas mensuales se saltan la inferencia. La extracción pasa de 720 $ a 648 $.

En profundidad#

La caché de respuestas pregunta: “¿Tenemos un resultado todavía válido para estas entradas?”. La idempotencia pregunta: “¿Ya se ha enviado esta operación concreta que se pretendía hacer?”. Un agente que reintenta enviar un correo tras perder la respuesta de una herramienta no debe enviar dos correos solo porque el texto sea idéntico. Evitarlo exige una identidad de operación duradera.

Un lock distribuido tampoco evita los cargos duplicados. Si un worker se cae o agota el tiempo de espera después de enviar una petición, el proveedor puede ejecutarla igualmente, y el reintento la vuelve a ejecutar. Puedes garantizar que solo se confirma un resultado y aun así pagar dos llamadas al proveedor.

La caché semántica es más arriesgada que la caché exacta. “¿Puedo cancelar?” y “¿Puedo cancelar y que me devuelvan el dinero?” son preguntas parecidas que necesitan respuestas distintas. La similitud por sí sola no establece equivalencia, vigencia ni autorización. Empieza con reutilización exacta y ejecución deduplicada; añade reutilización semántica solo cuando puedas definir límites seguros.

Cómo se acumula el crecimiento de la transcripción#

Un agente que reenvía toda su transcripción vuelve a pagar en cada paso nuevo por todos los pasos anteriores. Si cada paso añade t tokens, el paso k envía unos k × t tokens de entrada, y una ejecución de n pasos envía t × (1 + 2 + … + n) = t × n(n + 1) / 2. La entrada total crece aproximadamente con el cuadrado del número de pasos. Un agente que envía un estado de trabajo acotado más el último paso envía más o menos lo mismo cada vez, así que su total crece de forma lineal.

Ilustrativo: cada paso añade 2.000 tokens. El agente compacto envía un estado de trabajo de 4.000 tokens más el último paso de 2.000 tokens, 6.000 tokens por paso.

Pasos (n)Transcripción completa: 2.000 × n(n + 1) / 2Estado compacto: 6.000 × n
25650.000150.000
502.550.000300.000
10010.100.000600.000

Duplicar la ejecución de 50 a 100 pasos multiplica la entrada de la transcripción completa aproximadamente por cuatro y la entrada compacta por dos. La columna compacta no incluye los tokens gastados en actualizar el estado de trabajo. La caché de prompts abarata el prefijo repetido (Tokens, contexto y caché de prompts), pero el número de tokens crece de la misma forma.

Tokens de entrada enviados en cada paso cuando cada paso añade 2.000 tokens. Reenviar la transcripción crece en cada paso; un estado de trabajo compacto se queda en unos 6.000.

Una operación, un resultado en vuelo#

Una caché que comprueba get(key) y ejecuta la inferencia tras un fallo de caché permite que dos peticiones simultáneas lancen dos llamadas. La coordinación tiene que producirse antes de que empiece el trabajo caro.

Un registro de tarea duradero puede tener estos campos:

text
(tenant_id, idempotency_key)  UNIQUE
request_hash                   required
status                         pending | running | completed | failed
result_reference               nullable
lease_owner, lease_expires_at  nullable

Al llegar la petición, crea o recupera el registro de forma atómica. Si la clave existe con un request_hash distinto, rechaza la discrepancia. Si está completada, devuelve su resultado; si está en ejecución, devuelve el identificador de la tarea o espera a que termine. Un lease permite una recuperación controlada cuando un worker desaparece. Publica el resultado final solo si el worker sigue siendo el dueño del lease; una transacción de base de datos o un fencing token pueden proteger esa actualización.

Esto protege el estado de la aplicación, pero no es una garantía de ejecución exactamente una vez para el proveedor de inferencia. Si el worker pierde su lease después de lanzar una petición remota, un worker de recuperación puede lanzar otra, así que registra ambos intentos si generan cargos. Para las herramientas que crean un pago, envían un correo o modifican un registro remoto, usa el contrato de idempotencia del sistema de destino siempre que exista.

Mantén esto separado de la caché de resultados terminados. Un hash del documento más la versión del esquema de extracción puede justificar reutilizar un resultado entre operaciones legítimas distintas, mientras que una clave de idempotencia identifica los reintentos de la misma operación pretendida. Mezclar ambas cosas provoca errores cuando un usuario pide a propósito un procesamiento nuevo.

Referencias: AWS: API idempotentes, Redis: locks distribuidos, Estado de la conversación de OpenAI.