Comunicación y Supervisión entre Agentes
Implementa mensajes estructurados agente-a-agente, nodos supervisores, coordinación de estado compartido y patrones de orquestación multi-agente.
En sistemas multiagente de producción, los agentes necesitan comunicación estructurada y un supervisor que coordine su trabajo. Esta lección cubre protocolos de mensajes, patrones de supervisor y coordinación por estado compartido.
Mensajes Estructurados de Agente
Usa diccionarios tipados para los mensajes entre agentes:
from typing_extensions import TypedDict, Annotated
from typing import List, Optional
from datetime import datetime
from operator import add
class AgentMessage(TypedDict):
sender: str
recipient: str
content: str
message_type: str # task, result, question, error
timestamp: str
metadata: dict
class SupervisorState(TypedDict):
task: str
conversation: Annotated[List[AgentMessage], add]
current_agent: str
completed_agents: List[str]
final_output: strLos mensajes estructurados con campos de emisor, destinatario y tipo permiten un enrutamiento sofisticado. Los agentes pueden dirigir mensajes a destinatarios concretos o emitirlos en difusión a todos.
El Patrón Supervisor
Un supervisor es un agente coordinador que decide qué agente debe trabajar a continuación:
from langgraph.graph import StateGraph, START, END
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o")
class SupervisorState(TypedDict):
task: str
conversation: Annotated[List[AgentMessage], add]
next_agent: str
completed: bool
def supervisor_node(state: SupervisorState) -> dict:
"""Decides which agent should work next based on progress."""
recent = state["conversation"][-3:] if state["conversation"] else []
context = "\n".join(f"{m['sender']} → {m['recipient']}: {m['content']}"
for m in recent)
prompt = f"""Task: {state['task']}
Recent activity:
{context}
Available agents: researcher, writer, reviewer, finalizer
Which agent should work next? Or respond with 'complete' if done.
Reply with one word: researcher, writer, reviewer, finalizer, or complete."""
response = llm.invoke(prompt)
next_agent = response.content.strip().lower()
return {"next_agent": next_agent,
"completed": next_agent == "complete"}Nodos de Agente con Manejo de Mensajes
def researcher_agent(state: SupervisorState) -> dict:
result = llm.invoke(f"Research this topic: {state['task']}")
return {"conversation": [{
"sender": "researcher",
"recipient": "supervisor",
"content": result.content,
"message_type": "result",
"timestamp": datetime.now().isoformat(),
"metadata": {"sources": 3}
}]}
def writer_agent(state: SupervisorState) -> dict:
# Find the latest research result
research = [m for m in reversed(state["conversation"])
if m["sender"] == "researcher"][0]
content = llm.invoke(f"Write based on: {research['content']}")
return {"conversation": [{
"sender": "writer",
"recipient": "supervisor",
"content": content.content,
"message_type": "result",
"timestamp": datetime.now().isoformat(),
"metadata": {"word_count": len(content.content.split())}
}]}El supervisor actúa como un enrutador: lee la conversación, decide el siguiente paso y el grafo enruta la ejecución en consecuencia.
Enrutamiento del Supervisor
def route_from_supervisor(state: SupervisorState) -> str:
if state["completed"]:
return "finalize"
return state["next_agent"]
builder = StateGraph(SupervisorState)
builder.add_node("supervisor", supervisor_node)
builder.add_node("researcher", researcher_agent)
builder.add_node("writer", writer_agent)
builder.add_node("finalizer", finalizer_agent)
builder.add_edge(START, "supervisor")
builder.add_conditional_edges(
"supervisor",
route_from_supervisor,
{
"researcher": "researcher",
"writer": "writer",
"finalize": "finalizer"
}
)
# After any agent works, go back to supervisor
builder.add_edge("researcher", "supervisor")
builder.add_edge("writer", "supervisor")
builder.add_edge("finalizer", END)START → Supervisor → (researcher → Supervisor → writer → Supervisor → finalizer → END)
↑ ↑
└──── loop ────┘
Mensajes de Difusión y Dirigidos
Broadcast (para todos los agentes)
def supervisor_broadcast(state: SupervisorState) -> dict:
return {"conversation": [{
"sender": "supervisor",
"recipient": "*", # Broadcast
"content": f"New task: {state['task']}",
"message_type": "announcement",
"timestamp": datetime.now().isoformat(),
"metadata": {}
}]}Dirigido (a agente específico)
def researcher_requests_clarification(state: SupervisorState) -> dict:
return {"conversation": [{
"sender": "researcher",
"recipient": "supervisor",
"content": "Need clarification on the scope",
"message_type": "question",
"timestamp": datetime.now().isoformat(),
"metadata": {}
}]}Selección Dinámica de Agentes
Deja que el supervisor elija el agente adecuado según la tarea:
def dynamic_supervisor(state: SupervisorState) -> dict:
available_agents = [{
"name": "researcher",
"skills": "web research, fact-finding, data gathering",
"status": "ready"
}, {
"name": "analyst",
"skills": "data analysis, pattern recognition, statistics",
"status": "ready"
}, {
"name": "writer",
"skills": "content creation, summarization, formatting",
"status": "ready"
}]
prompt = f"""Task: {state['task']}
Available agents: {available_agents}
Recent messages: {state['conversation'][-2:]}
Which agent should work next, or has the task been completed?
Respond with: agent_name or 'complete'."""
response = llm.invoke(prompt)
return {"next_agent": response.content.strip().lower()}Multi-Agente Secuencial con Transferencia
Los agentes pueden pasarse el trabajo explícitamente:
def analyst_agent(state: SupervisorState) -> dict:
analysis = llm.invoke(f"Analyze: {state['task']}")
return {
"conversation": [{
"sender": "analyst",
"recipient": "writer",
"content": analysis.content,
"message_type": "handoff",
"timestamp": datetime.now().isoformat(),
"metadata": {}
}],
"current_agent": "writer" # Explicit handoff
}Ejemplo Completo de Supervisor
from langgraph.graph import StateGraph, START, END, add_messages
from langgraph.checkpoint.memory import MemorySaver
from langchain_openai import ChatOpenAI
from typing_extensions import TypedDict, Annotated
from typing import List, Any
from operator import add
from datetime import datetime
llm = ChatOpenAI(model="gpt-4o-mini")
class Message(TypedDict):
sender: str
content: str
type: str
class AgentState(TypedDict):
task: str
messages: Annotated[List[Message], add]
next_agent: str
done: bool
def supervisor(state: AgentState) -> dict:
context = "\n".join(f"{m['sender']}: {m['content']}" for m in state["messages"][-4:])
prompt = f"Task: {state['task']}\nRecent:\n{context}\n\nNext agent (researcher/writer/complete):"
resp = llm.invoke(prompt).content.strip().lower()
return {"next_agent": resp, "done": resp == "complete"}
def researcher(state: AgentState) -> dict:
resp = llm.invoke(f"Research: {state['task']}")
return {"messages": [{"sender": "researcher", "content": resp.content, "type": "research"}]}
def writer(state: AgentState) -> dict:
research = [m for m in reversed(state["messages"]) if m["sender"] == "researcher"]
content = research[0]["content"] if research else state["task"]
resp = llm.invoke(f"Write based on: {content}")
return {"messages": [{"sender": "writer", "content": resp.content, "type": "draft"}]}
def router(state: AgentState) -> str:
if state["done"]:
return "done"
return state["next_agent"]
builder = StateGraph(AgentState)
builder.add_node("supervisor", supervisor)
builder.add_node("researcher", researcher)
builder.add_node("writer", writer)
builder.add_edge(START, "supervisor")
builder.add_conditional_edges("supervisor", router, {
"researcher": "researcher",
"writer": "writer",
"done": END
})
builder.add_edge("researcher", "supervisor")
builder.add_edge("writer", "supervisor")
app = builder.compile(checkpointer=MemorySaver())Estado Compartido vs Estado con Ámbito de Agente
Pattern | Descripción | Use Case |
|---|---|---|
Estado compartido | Todos los agentes leen/escriben en el mismo diccionario de estado | Coordinación simple, pocos agentes |
Mensajes estructurados | Los agentes escriben en una lista de mensajes compartida | Multiagente complejo, seguimiento del historial |
Estado de subgrafo | Cada agente tiene su propio estado interno y se comunica mediante mensajes (próxima lección) | Agentes jerárquicos, encapsulamiento |
Preguntas de Práctica (Practice Questions)
Cómo responder
Pregunta de opción múltiple con 4 opciones. Selecciona una opción y elige Comprobar respuesta.
What is the role of a supervisor node in a multi-agent system?
Cómo responder
Pregunta de opción múltiple con 4 opciones. Selecciona una opción y elige Comprobar respuesta.
How does the supervisor communicate its decision to the graph?
Cómo responder
Pregunta de opción múltiple con 4 opciones. Selecciona una opción y elige Comprobar respuesta.
What is the loop pattern in a supervisor-based graph?
Cómo responder
Pregunta de opción múltiple con 4 opciones. Selecciona una opción y elige Comprobar respuesta.
What field structure enables targeted agent-to-agent messaging?
Cómo responder
Pregunta de opción múltiple con 4 opciones. Selecciona una opción y elige Comprobar respuesta.
How does the graph terminate in a supervisor pattern?
Cómo responder
Pregunta de opción múltiple con 4 opciones. Selecciona una opción y elige Comprobar respuesta.
What is a broadcast message in a multi-agent system?
Cómo responder
Pregunta de opción múltiple con 4 opciones. Selecciona una opción y elige Comprobar respuesta.
What happens after an agent completes its work in the supervisor loop?
Cómo responder
Pregunta de opción múltiple con 4 opciones. Selecciona una opción y elige Comprobar respuesta.
What is the benefit of structured messages over plain text in agent communication?
Cómo responder
Pregunta de opción múltiple con 4 opciones. Selecciona una opción y elige Comprobar respuesta.
What routing mechanism executes the supervisor's decision?
Cómo responder
Pregunta de opción múltiple con 4 opciones. Selecciona una opción y elige Comprobar respuesta.
What happens if an agent writes a message but the supervisor decides to route to a different agent?
Conclusiones Clave
- Los nodos de supervisor coordinan el trabajo multiagente decidiendo el siguiente paso
- Los mensajes estructurados (emisor, destinatario, contenido, tipo) permiten una comunicación sofisticada
- El patrón de bucle del supervisor: Supervisor → Agente → Supervisor → Agente → ... → END
- Las aristas condicionales enrutan según la decisión del supervisor
- Los mensajes en difusión van a todos los agentes; los mensajes dirigidos van a destinatarios concretos
- Los mensajes se acumulan en el estado para el historial completo de la conversación
- La selección dinámica de agentes permite al supervisor elegir el agente adecuado en cada paso