Saltar al contenido principal
Jordi Izquierdo Montalbán
Blog

Orquestador + 15 expertos: cómo construí mi sistema multi-agente sin magia

Las decisiones de arquitectura detrás de un sistema multi-agente real con 15 agentes especializados, multi-LLM con LiteLLM, CLI interactiva y dashboard web en tiempo real.

5 min de lectura -

Llevo varios meses construyendo un sistema multi-agente que ya está en su versión 3.0, con CLI interactiva, dashboard web por WebSocket, 15 expertos especializados y soporte para 60+ proveedores LLM vía LiteLLM. Este post no es teoría, es lo que he ido aprendiendo construyéndolo.

Cero hype. Solo lo que funciona en producción.

Patrón central: CEO + 15 expertos + Worker Pool

                  [ Usuario / Tarea ]
                          │
                  [ Orquestador (CEO) ]      stateless
                  descompone en HandoffTickets
                          │
                          ▼
              [ Worker Pool (≤3 paralelos) ]
                          │
        ┌─────────────────┼─────────────────┐
        ▼                 ▼                 ▼
   [ Developer ]    [ Security ]      [ Tester ]    (15 expertos)
        │                 │                 │
        ▼                 ▼                 ▼
            [ Tools: archivos, código, tests, git, shell, BD ]
                          │
            ┌─────────────┴─────────────┐
            ▼                           ▼
      [ Redis: estado ]      [ Qdrant: memoria semántica ]

El orquestador no piensa, enruta. Los expertos piensan en su dominio. Las herramientas hacen el trabajo real.

Los siete principios que sostienen el diseño

P1. Stateless Orchestrator

El CEO (core/orchestrator.py) no acumula contexto entre llamadas. Recibe la tarea, la descompone en HandoffTicket (Pydantic) y los reparte. Punto.

¿Por qué? Porque un orquestador con memoria se contradice a sí mismo después de tres pasos. Un orquestador stateless con contexto explícito (tope de 500 tokens por ticket) es predecible.

P2. Handoff Protocol con JSON estricto

Entre agentes nunca pasa texto libre. Pasa un objeto Pydantic. Si el experto recibe basura, falla rápido en vez de inventar.

class HandoffTicket(BaseModel):
    ticket_id: str
    expert_type: str       # "developer", "tester", "security", ...
    task: str
    context: dict          # ≤500 tokens
    priority: int
 
class ExpertResult(BaseModel):
    ticket_id: str
    expert_type: str
    output: str            # comprimido por SummaryAgent
    tools_used: list[str]
    tokens_used: int
    cost_usd: float

P3. BaseExpert: la lógica vive en la clase base

Los 15 expertos heredan de super_experts/base_expert.py. La clase base contiene el motor (build_prompt, run_tool_use_loop, compress_output, handle_error). Cada experto concreto declara solo lo suyo: keywords, tools permitidas y archivo de prompt.

class BaseExpert:
    def build_prompt(self, ticket): ...
    def run_tool_use_loop(self, response): ...
    def compress_output(self, output): ...
    def handle_error(self, error): ...
 
class DeveloperExpert(BaseExpert):
    KEYWORDS = ["code", "implement", "function", "class", "bug"]
    TOOLS = ["file_reader", "file_writer", "code_executor", "test_runner"]
    PROMPT_FILE = "config/prompts/developer.md"

Añadir un experto nuevo es escribir 20 líneas y un Markdown con el prompt. No es heroísmo, es ingeniería.

P4. Token Budget por nivel de agente

Cada agente tiene un techo de tokens. Si lo supera, el ticket falla rápido. Sin esto, el contexto crece y el coste se dispara. Cuando un experto produce output largo, un Summary Agent lo comprime antes de devolverlo al orquestador.

P5. Tool Use Loop con max_iterations

Los expertos llaman herramientas en bucle: prompt → tool_call → tool_result → prompt → tool_call... hasta llegar a la solución o tocar max_iterations. El bucle infinito es el bug más caro de los multi-agentes. Siempre tope.

P6. LiteLLM como capa universal

Detrás de cada llamada a un LLM no hay SDK de Anthropic, OpenAI ni Google. Hay LiteLLM. Cambiar de proveedor es una env var:

# Groq, rápido y gratis para pruebas
LLM_MODEL=groq/llama-3.3-70b-versatile
 
# Claude para razonamiento complejo
LLM_MODEL=anthropic/claude-sonnet-4-6
 
# GPT-4o
LLM_MODEL=openai/gpt-4o
 
# Local con Ollama, sin coste, sin internet
LLM_MODEL=ollama/llama3.2

Cero código tocado. Esta decisión salva semanas cuando un proveedor sube precios o saca un modelo nuevo.

P7. Observabilidad first

Cada llamada a LLM es un span de OpenTelemetry. Tokens entrantes y salientes, coste estimado en USD, latencia, modelo, agente, ticket_id. Sin esto vuelas a ciegas. El debug multi-agente sin trazas es arqueología, no ingeniería.

Worker Pool con tope de 3 paralelos

Una decisión menor pero importante: los expertos corren en paralelo (core/worker_pool.py), pero nunca más de 3 a la vez (MAX_PARALLEL_AGENTS=3 por defecto).

¿Por qué? Tres razones:

  1. Rate limit del proveedor LLM. Groq, Anthropic y OpenAI tienen límites RPM/TPM. Lanzar 10 agentes a la vez te tumba la sesión.
  2. Coste predecible. Cada experto factura tokens. Tres en paralelo es controlable; diez no.
  3. Debug humano. Un timeline con 3 agentes ejecutando es legible. Con 10 es ruido.

El pool corre con un semáforo de Redis. Cuando un slot se libera, el siguiente ticket entra. Simple y robusto.

Memoria a dos niveles

CapaTecnologíaPara qué
Estado runtimeRedis 5.0+Sesiones, tickets en curso, rate limiting (RPM/TPM), PubSub para WebSocket
Memoria semánticaQdrant 1.9+Resultados anteriores indexados por similaridad

La separación es clave. Mezclar las dos en un mismo backend (todo en Postgres con tablas TTL caseras, por ejemplo) parece más simple pero acaba lento y propenso a bugs de consistencia.

Embeddings vía Ollama local (nomic-embed-text, 768 dimensiones) o OpenAI (1536). Configurable. Y QDRANT_MODE=off para entornos donde la memoria semántica no aporta.

Lo que descarté

Frameworks "todo en uno"

Probé LangChain agents, AutoGen, CrewAI. Todos funcionan en un demo. Todos esconden la lógica detrás de abstracciones que dificultan debug y trazabilidad. El coste de aprenderlos es comparable a escribir tu propio orquestador en Python plano, y lo segundo da más control.

LangGraph sí lo consideré: aporta una máquina de estados explícita sin tanta magia. Pero al final, con LiteLLM + Pydantic + un orquestador propio de 300 líneas, no necesitaba más.

Peer-to-peer entre agentes

Probé sistemas donde los expertos se llamaban directamente entre ellos (Developer pide ayuda al Security Expert sin pasar por CEO). Resultado: emergent behavior interesante en demos, pesadilla en debug. El orquestador centralizado pierde algo de "magia" pero gana cordura operacional.

Memoria persistente entre tareas en el mismo agente

Tener un experto que "recuerda" todas las conversaciones previas suena potente. En la práctica genera dos problemas: el contexto crece y el coste se dispara, y decisiones viejas contaminan tareas nuevas. Mejor scratchpad efímero por sesión + memoria semántica explícita en vectorial cuando hace falta.

El resultado

Un sistema con CLI interactiva (python cli.py), API REST + WebSocket (POST /sessions), dashboard web en tiempo real, 15 expertos especializados, soporte de 60+ proveedores LLM vía LiteLLM, memoria semántica con Qdrant, control de coste por sesión y observabilidad por OpenTelemetry. Todo en Python 3.11+ tipado con Pydantic.

Cualquiera que entre al repo entiende qué pasa en una hora. Eso es lo que diferencia ingeniería de magia: que un compañero pueda leer el código y predecir el comportamiento sin ejecutarlo.

LLMs envueltos en lógica explícita son ingeniería. LLMs envueltos en magia son apuesta. Cuanto más visible es el grafo de decisión, más confío en el sistema.

Compartir