Handoffs y Coordinación Multi-Agente
Orquestación de enjambres autónomos, delegación punto a punto, herramientas sintéticas de transferencia y prevención de ciclos en chatflow-agent.
La coordinación multi-agente en chatflow-agent se modela según el concepto de enjambres autónomos punto a punto (peer-to-peer swarms). En lugar de depender de un coordinador centralizado o grafos rígidos codificados en duro, los agentes delegan tareas de forma autónoma a pares especializados mediante la invocación de herramientas de transferencia (handoffs) sintetizadas automáticamente.
Cuando un agente activa una transferencia, el contexto conversacional se preserva sin fisuras, el puntero del agente activo se actualiza y el nuevo agente continúa el turno de inmediato dentro del mismo ciclo de ejecución.
El Modelo Mental del Enjambre
En una arquitectura de enjambre, cada agente se especializa en un dominio definido (por ejemplo, triaje, facturación, soporte técnico o reembolsos). La coordinación ocurre de manera natural a través del diálogo:
- Delegación Descentralizada: Los agentes conocen a qué pares pueden transferir tareas mediante su lista
handoffs. - Toma de Decisiones Dinámica: El LLM decide en tiempo de ejecución si responder directamente, llamar a una herramienta de dominio o ceder el control a un agente par.
- Ejecución Transparente: El usuario interactúa con un único punto de entrada conversacional (
Runner), sin percibir que múltiples modelos o agentes colaboran en segundo plano.
Declarar Handoffs entre Agentes
Las relaciones de transferencia se declaran en el constructor o de forma imperativa:
Método 1: Declaración en el Constructor
Pasa una lista de instancias de Agent objetivo en el parámetro handoffs:
# [min-version: 0.3.0]
# [executable]
from chatflow_agent import Agent
# Definir primero los agentes especializados de destino
billing_agent = Agent(
name="BillingSpecialist",
instructions="Handle payments, invoice queries, and subscription upgrades.",
model="gemini-2.5-flash",
)
technical_agent = Agent(
name="TechnicalSupport",
instructions="Diagnose software bugs, API errors, and configuration issues.",
model="gemini-2.5-flash",
)
# El agente de triaje enruta a los especialistas
triage_agent = Agent(
name="TriageAgent",
instructions=(
"Welcome the user and determine their needs. "
"Transfer billing questions to BillingSpecialist and technical questions to TechnicalSupport."
),
handoffs=[billing_agent, technical_agent],
model="gemini-2.5-flash",
)Método 2: Transferencias Bidireccionales y Cíclicas
A menudo los especialistas necesitan reenviar usuarios entre sí. Utiliza agent.add_handoff() para establecer enlaces bidireccionales:
# [min-version: 0.3.0]
# [executable]
from chatflow_agent import Agent
support = Agent(name="Support", instructions="General support", model="gpt-4o")
billing = Agent(name="Billing", instructions="Billing support", model="gpt-4o")
# Establecer handoff bidireccional
support.add_handoff(billing)
billing.add_handoff(support)
assert any(h.name == "Billing" for h in support.handoffs)
assert any(h.name == "Support" for h in billing.handoffs)Prohibición de Auto-Transferencias
Un agente no puede registrar una transferencia hacia sí mismo. Invocar agent.add_handoff(agent) generará de inmediato un error ValueError("Agent 'X' cannot register a handoff to itself.").
Herramientas Sintéticas de Enrutamiento en Detalle
Cuando un agente define pares en handoffs, chatflow-agent sintetiza automáticamente herramientas de enrutamiento expuestas a través de agent.get_all_tools().
Para cada agente par:
- Nombre de la Herramienta: Se genera como
transfer_to_<sanitized_name>(ej.transfer_to_billingspecialist). - Descripción de la Herramienta: Combina el nombre del agente destino con un resumen de sus instrucciones:
"Hand off the dialogue to 'BillingSpecialist'. Target agent specialty: Handle payments, invoice queries..." - Firma: Acepta un parámetro opcional
reason: str | None = None. - Valor Retornado: Devuelve una carga útil tipada
Handoff(target_agent_name="BillingSpecialist", reason=...).
Dado que las herramientas de transferencia lucen y se comportan como llamadas a funciones estándar, el modelo aprende de forma natural cuándo transferir el control basándose en la intención del usuario y las descripciones de las herramientas.
Ciclo de Vida de Ejecución del Handoff en el Runner
Cuando runner.run_async() procesa un turno:
[Mensaje del Usuario]
│
▼
[Agente Activo] ──(Genera Llamada a Herramienta)──► transfer_to_billingspecialist(reason="...")
│
▼
[Ejecuta Handoff]
│
▼
[Cambia Agente Activo]
(session.set_active_agent)
│
▼
[Siguiente Generación]
(BillingSpecialist responde)
│
▼
[Respuesta Final]- Invocación de la Herramienta: El LLM emite una llamada a la herramienta
transfer_to_billingspecialist. - Verificación en el Registro: El
Runnercomprueba queBillingSpecialistexista en su registro interno de agentes. Si el agente no está registrado, lanza unAgentHandoffError. - Cambio de Agente Activo: El
Runnerejecutasession.set_active_agent(target_agent). - Inyección del Resultado de Transferencia: Se añade un
ToolResultconstatus: "HANDOFF_SUCCESS"y el motivo de transferencia al historial de la sesión. - Continuación Inmediata del Turno: Dentro de la misma llamada a
runner.run(), se consulta al nuevo agente activo con el historial actualizado para elaborar su respuesta.
Prevención de Ciclos y Límites de Turno
Transferencias incontroladas entre agentes podrían provocar bucles infinitos si las instrucciones son ambiguas. chatflow-agent protege la ejecución mediante límites integrados:
max_handoffs_per_turn(Predeterminado: 5): Número máximo de transferencias permitidas dentro del turno de un único mensaje de usuario. Si se supera este límite, el runner detiene nuevas transferencias e inyecta un resultado de error:"Maximum agent transfers exceeded for a single turn."max_tool_iterations(Predeterminado: 10): Límite global de ejecuciones de herramientas por turno, asegurando la terminación garantizada incluso si las herramientas encadenan llamadas reiteradas.
from chatflow_agent import Agent, Runner
triage = Agent(name="Triage", instructions="...", model="gpt-4o")
# Configurar límites más estrictos para entornos de alto rendimiento o bajo coste
runner = Runner(
starting_agent=triage,
max_handoffs_per_turn=3,
max_tool_iterations=6,
)Diferencias de Versión y Características de Pre-lanzamiento
La generación y sanitización de handoffs incorporan mejoras sustanciales en versiones recientes:
Versión Estable (v0.3.4)
- Stable (0.3.4) Sanitización básica con expresiones regulares:
clean = re.sub(r"[^a-zA-Z0-9_]", "_", name.strip().lower()). - Historial de sesión compartido que preserva el contexto conversacional íntegro entre transferencias.
Mejoras de Pre-lanzamiento (v0.4.0rc1)
- Prerelease (0.4.0rc1) Transliteración Unicode NFKD: Normaliza automáticamente caracteres acentuados a ASCII (ej.
AtenciónAlClientese convierte entransfer_to_atencionalcliente). - Prerelease (0.4.0rc1) Alternativa Determinista por Hash: Nombres de agentes en escrituras no latinas (japonés, árabe, cirílico) o con emojis recurren a un prefijo hash SHA-256 (
transfer_to_u<hash>) para garantizar sintaxis de identificador válida. - Prerelease (0.4.0rc1) Desambiguación en Colisiones: Si dos agentes generan nombres sanitizados conflictivos, se agregan sufijos numéricos (
_2,_3) automáticamente para evitar sobreescrituras en el esquema. - Prerelease (0.4.0rc1) Límite de Longitud: Los identificadores de herramientas de transferencia se limitan a 60 caracteres para cumplir con las restricciones de proveedores.
- Prerelease (0.4.0rc1) Reenvío de Actualizaciones de Contexto:
session.set_active_agent(agent, context_updates)permite propagar cambios en los metadatos junto con el evento de transferencia.
Ejemplo Completo: Enjambre de Soporte Multitarea
from chatflow_agent import Agent, Runner, tool
from chatflow_agent.core.memory import SessionStore
# 1. Herramientas especializadas
@tool
def issue_refund(transaction_id: str, amount: float) -> dict:
"""Process a refund back to customer original payment method."""
return {"status": "REFUNDED", "transaction_id": transaction_id, "amount": amount}
@tool
def lookup_kb_article(topic: str) -> str:
"""Search knowledge base for troubleshooting steps."""
return f"Knowledge base instructions for {topic}: Clear cache and restart application."
# 2. Agentes de destino especializados
billing_agent = Agent(
name="BillingAgent",
instructions="Assist with refunds and payment disputes. Use issue_refund when appropriate.",
tools=[issue_refund],
model="gemini-2.5-flash",
)
tech_agent = Agent(
name="TechSupportAgent",
instructions="Assist with software errors. Use lookup_kb_article for troubleshooting.",
tools=[lookup_kb_article],
model="gemini-2.5-flash",
)
# 3. Agente de triaje inicial
triage_agent = Agent(
name="TriageAgent",
instructions=(
"Welcome the user. Ask what they need help with. "
"Transfer billing/refund issues to BillingAgent and technical bugs to TechSupportAgent."
),
handoffs=[billing_agent, tech_agent],
model="gemini-2.5-flash",
)
# 4. Permitir que los especialistas devuelvan el control al triaje si es necesario
billing_agent.add_handoff(triage_agent)
tech_agent.add_handoff(triage_agent)
# 5. Inicializar el runner
store = SessionStore()
runner = Runner(starting_agent=triage_agent, session_store=store)
# 6. Ejecutar diálogo
response = runner.run(
session_id="customer-session-101",
user_message="I have an unexpected charge on my invoice and need a refund.",
)
print("Active Agent:", response.active_agent_name)
print("Response:", response.content)
print("Handoff:", response.handoff)Errores Comunes y Buenas Prácticas
1. Agentes No Accesibles en el Registro
El Runner indexa automáticamente a todos los agentes alcanzables a través del árbol de handoffs del starting_agent. Si un agente se introduce de forma dinámica y no está vinculado en el grafo, debe registrarse explícitamente:
# Si un agente se introduce dinámicamente:
runner.register_agent(specialist_agent)2. Instrucciones de Handoff Demasiado Genéricas
Asegúrate de que las instrucciones del agente de destino indiquen claramente qué problemas resuelve. Si dos agentes tienen descripciones superpuestas, los modelos pueden dudar o enrutar de forma inconsistente.
3. Preservar la Identidad de la Sesión
Nunca generes un nuevo session_id esperando que una transferencia continúe. El estado de la transferencia y la transición del agente quedan registrados dentro de la sesión activa.
Herramientas y Llamadas a Funciones
Guía arquitectónica completa sobre llamadas a funciones, esquemas de tipos de parámetros, despacho síncrono/asíncrono y aislamiento de errores en chatflow-agent.
Runner y Ciclo de Vida de Ejecución
Guía detallada sobre el bucle conversacional de múltiples turnos, resolución de motores, flujo de ejecución de herramientas, tiempos límite y reversión transaccional en chatflow-agent.