· Hernán Pérez Rodal · Engineering  · 6 min read

Agentic Compliance System con LangGraph: patterns que funcionan en producción

No todos los multi-agent patterns sirven en dominios regulados. Contamos qué arquitectura de agentes usamos en Darwin, por qué, y qué anti-patterns evitamos.

No todos los multi-agent patterns sirven en dominios regulados. Contamos qué arquitectura de agentes usamos en Darwin, por qué, y qué anti-patterns evitamos.

TL;DR — Los tutoriales de agents hablan de “autonomous AI” como si fuera un superpoder mágico. En producción regulada, la autonomía sin guardrails es una pesadilla legal. En Darwin corremos un sistema multi-agent con LangGraph, y el valor no está en la autonomía — está en la orquestación determinista de componentes LLM-powered.

El problema

Darwin procesa miles de casos de compliance por día sobre datos de trazabilidad alimentaria. Cada caso requiere:

  1. Interpretar una pregunta regulatoria (texto libre del usuario o sistema)
  2. Descomponerla en sub-consultas (regulatoria, operativa, de evidencia)
  3. Delegar cada sub-consulta al agente adecuado
  4. Sintetizar los resultados en una respuesta con gap analysis + risk scoring
  5. Validar antes de presentar — especialmente datos numéricos

Intentar que un solo LLM haga todo end-to-end con un prompt gigante falla en producción. Los modelos se confunden, alucinan datos, pierden contexto intermedio. Cuando el output va a una auditoría FDA, “se equivocó un poquito” no es aceptable.

El pattern que usamos: supervisor + workers especializados

La arquitectura es:

           [Supervisor]

   ┌──────────┼──────────┬──────────┐
   │          │          │          │
 [Research] [Validation] [Analytics] [Reporting]
  • Supervisor — decide qué agentes invocar, en qué orden, y cuándo parar
  • Research Agent — busca contexto regulatorio + eventos de trazabilidad (usa el RAG híbrido que describí acá)
  • Validation Agent — cruza reglas de negocio determinísticas con hallazgos del Research
  • Analytics Agent — ejecuta queries estructuradas + anomaly detection sobre eventos
  • Reporting Agent — sintetiza todo en un output consumible (report PDF, JSON API, UI view)

El supervisor es la pieza crítica. Es el que decide, no los workers.

Por qué LangGraph y no otra cosa

Evaluamos varias opciones antes de elegir LangGraph:

FrameworkPor qué no / por qué sí
LangChain Agents (AgentExecutor)Muy mágico. Difícil de debuggear en producción.
CrewAIBueno para prototipos. Abstracción demasiado alta para patterns específicos.
AutoGenFuerte en conversaciones multi-agent. Menos ideal para flujos deterministas.
Custom state machineConsiderado. Para nuestro caso LangGraph da 80% con 20% del código.
LangGraphState machine explícito + checkpoints + streaming. Debuggeable, production-grade.

Lo clave: LangGraph no es “un framework de agents”. Es un state machine con superpoderes para LLM calls. Esa diferencia mental cambia cómo lo usás.

Patterns que funcionan

1. Supervisor determinista, no autónomo

Nuestro supervisor NO le pregunta al LLM “¿qué hago ahora?” en cada step. Eso es autonomía mágica y es un anti-pattern.

Lo que SÍ hace:

def supervisor_node(state: ComplianceState) -> str:
    """Decide next node based on state — deterministic rules first, LLM second."""
    if not state.query_classified:
        return "classify_query"
    if state.classification == "regulatory" and not state.regulatory_context:
        return "research"
    if state.has_numerical_claims and not state.validated:
        return "validate"
    if state.ready_for_report:
        return "report"
    return "END"

El LLM entra solo en los nodos individuales (classify, research, validate). El routing es código. Evita que un LLM “decida” sacar del circuito un step de validación crítico.

2. Checkpointing agresivo

Cada step emite un checkpoint. Si algo falla, no reejecutamos todo — retomamos desde el último estado bueno. Crítico cuando un caso toca 5-10 nodos y cada LLM call cuesta tiempo+$.

from langgraph.checkpoint.postgres import PostgresSaver

graph = builder.compile(
    checkpointer=PostgresSaver(conn_string=DATABASE_URL),
    interrupt_before=["report"],  # Pausa antes del reporte final
)

3. Human-in-the-loop configurable

Algunos casos son auto-approve (rutinas), otros require review (high-stakes). Usamos interrupt_before en LangGraph para pausar antes de los nodos que decidimos requieren intervención humana.

El operador revisa, aprueba o edita, y el graph continúa desde el checkpoint. Sin perder trabajo.

4. Tool use con validación estructurada

Cuando un agent llama una tool (query DB, buscar documento, computar métrica), todo output es validado contra un schema Pydantic antes de volver al LLM. Si el tool retorna algo inesperado, el error se maneja explícitamente — no se le pasa al LLM que inventará.

class TraceabilityQueryOutput(BaseModel):
    events: list[CriticalTrackingEvent]
    total_count: int
    date_range: DateRange

# Tool response validated. If fails → retry or escalate, NO LLM hallucination.

5. Observabilidad end-to-end

Cada node emite tracing con OpenTelemetry:

  • Input state
  • LLM prompt + response
  • Token usage + cost
  • Latency
  • Tool calls (con inputs/outputs)

Sin esto, debugear un agent system es imposible. Con esto, encontrás por qué un caso salió mal en minutos.

Anti-patterns que evitamos

❌ Autonomía total del supervisor. “El LLM decide cuándo parar” = runaway loops + costos inesperados. Nuestro supervisor tiene max iterations por nodo y timeout por caso.

❌ Agent-to-agent conversations largas. Los tutoriales muestran N agents “discutiendo”. En producción, cada intercambio es $$$ y latencia. Usamos mensaje único por agent, no conversación.

❌ Shared memory global sin estructura. Todos los agents escribiendo en un dict compartido → race conditions + hallucination sobre qué es el estado actual. Nosotros usamos state pydantic-typed con mutations explícitas.

❌ LLM-as-everything. Muchos tutoriales usan LLM para cosas que una regla determinística resuelve. Regla general: si podés escribirlo como código, escribilo como código. El LLM solo para lo que realmente requiere razonamiento natural.

Lo que no funcionó

V0: un solo agent con tool use — probamos con un solo Claude agent con acceso a todas las tools. Falló porque el agent a veces olvidaba validaciones críticas cuando el prompt se ponía largo. Refactoreamos a supervisor + workers.

Streaming continuo en UI — quisimos stream-ear las decisiones del supervisor a la UI en tiempo real. Resultó confuso para el usuario (veía “research → validate → research → validate” sin saber por qué). Cambiamos a mostrar solo los milestones principales y esconder la orquestación interna.

Lo que sí funcionó

Supervisor determinista — zero runaway loops desde que lo implementamos.

Checkpointing en PostgreSQL — podemos retomar cases que quedaron a medias por caída de infra o cuotas de API.

Validation agent con reglas + LLM — combinación de reglas hardcoded + LLM-as-judge para los edge cases da mejor precisión que cualquiera por separado.

Metrics por agent — sabemos exactly qué agent es el cuello de botella en latencia o costo. Nos permitió optimizar selectivamente (ej: swappear el Reporting Agent a un modelo más chico sin tocar Research).

Lessons learned

  1. Agentic ≠ autonomous — los mejores patterns son deterministas con LLM calls selectivas
  2. LangGraph es un state machine, no un framework de agents — pensalo así y todo simplifica
  3. Checkpointing agresivo desde el día 0 — el costo de agregarlo después es alto
  4. Validá tool outputs con schemas — nunca le pases al LLM algo sin validar
  5. Tracing + cost metrics por node — sin esto no hay iteración real

¿Y ahora?

Estamos explorando sub-graphs reutilizables — nuestro Validation Agent es suficientemente genérico para usar en otros flujos (no solo compliance). LangGraph soporta composabilidad de graphs, y eso abre la puerta a un catálogo interno de patterns que podemos remix.

Si estás buildeando AI agents en producción y te sorprende que tu sistema es más lento/más caro/más inestable que tu demo — probablemente es falta de orquestación determinista. Empezá por ahí.


¿Construís algo similar? Hablemos — nos interesa compartir arquitectura y patterns.

Compartir:
Back to Blog

Related Posts

View All Posts »