es un patrón de diseño multi-agente que se inspira en la arquitectura de una red neuronal feed-forward.
El patrón consiste en dos tipos de agentes: agentes trabajadores (worker agents) y un único agente orquestador (orchestrator agent).
Los agentes trabajadores están organizados en múltiples capas, donde cada capa contiene un número fijo de agentes.
Los mensajes provenientes de los agentes trabajadores en una capa anterior son concatenados y enviados a todos los agentes de la capa siguiente.
Este ejemplo implementa el patrón Mixture of Agents usando la librería core, siguiendo la implementación original de mezcla de agentes en múltiples capas.
A continuación, se muestra una descripción general del procedimiento del patrón:
El agente orquestador recibe como entrada una tarea del usuario y la distribuye a los agentes trabajadores de la primera capa.
Los agentes de la primera capa procesan la tarea y devuelven los resultados al agente orquestador.
El agente orquestador sintetiza los resultados de la primera capa y envía una tarea actualizada (incluyendo los resultados anteriores) a los agentes trabajadores de la segunda capa.
Este proceso continúa capa por capa hasta llegar a la última capa.
En la capa final, el agente orquestador agrega todos los resultados y devuelve un resultado final único al usuario.
Se utiliza la API de mensajería directasend_message() para implementar este patrón.
Esto facilita la incorporación futura de características como la cancelación de tareas de los trabajadores y el manejo de errores.
import asyncio
from dataclasses import dataclass
from typing import List
from saptiva_agents import LLAMA_MODEL
from saptiva_agents.core import AgentId, MessageContext, RoutedAgent, SingleThreadedAgentRuntime, message_handler
from saptiva_agents.models import SystemMessage, UserMessage
from saptiva_agents.base import SaptivaAIChatCompletionClient
Protocolo de Mensajes
Los agentes se comunican utilizando los siguientes mensajes:
@dataclass
class WorkerTask:
task: str
previous_results: List[str]
@dataclass
class WorkerTaskResult:
result: str
@dataclass
class UserTask:
task: str
@dataclass
class FinalResult:
result: str
Agente de Trabajo (Worker Agent)
Cada agente de trabajo recibe una tarea del agente orquestador y la procesa de manera independiente. Una vez completada la tarea, el agente de trabajo devuelve el resultado.
class WorkerAgent(RoutedAgent):
def __init__(
self,
model_client: SaptivaAIChatCompletionClient,
) -> None:
super().__init__(description="Worker Agent")
self._model_client = model_client
@message_handler
async def handle_task(self, message: WorkerTask, ctx: MessageContext) -> WorkerTaskResult:
if message.previous_results:
# If previous results are provided, we need to synthesize them to create a single prompt.
system_prompt = "You have been provided with a set of responses from various open-source models to the latest user query. Your task is to synthesize these responses into a single, high-quality response. It is crucial to critically evaluate the information provided in these responses, recognizing that some of it may be biased or incorrect. Your response should not simply replicate the given answers but should offer a refined, accurate, and comprehensive reply to the instruction. Ensure your response is well-structured, coherent, and adheres to the highest standards of accuracy and reliability.\n\nResponses from models:"
system_prompt += "\n" + "\n\n".join([f"{i+1}. {r}" for i, r in enumerate(message.previous_results)])
model_result = await self._model_client.create(
[SystemMessage(content=system_prompt), UserMessage(content=message.task, source="user")]
)
else:
# If no previous results are provided, we can simply pass the user query to the model.
model_result = await self._model_client.create([UserMessage(content=message.task, source="user")])
assert isinstance(model_result.content, str)
print(f"{'-'*80}\nWorker-{self.id}:\n{model_result.content}")
return WorkerTaskResult(result=model_result.content)
Agente Orquestador (Orchestrator Agent)
El agente orquestador recibe tareas del usuario y las distribuye entre los agentes de trabajo, iterando sobre múltiples capas de agentes de trabajo. Una vez que todos los agentes de trabajo han procesado la tarea, el agente orquestador agrega los resultados y publica el resultado final.
class OrchestratorAgent(RoutedAgent):
def __init__(
self,
model_client: SaptivaAIChatCompletionClient,
worker_agent_types: List[str],
num_layers: int,
) -> None:
super().__init__(description="Aggregator Agent")
self._model_client = model_client
self._worker_agent_types = worker_agent_types
self._num_layers = num_layers
@message_handler
async def handle_task(self, message: UserTask, ctx: MessageContext) -> FinalResult:
print(f"{'-'*80}\nOrchestrator-{self.id}:\nReceived task: {message.task}")
# Create task for the first layer.
worker_task = WorkerTask(task=message.task, previous_results=[])
# Iterate over layers.
for i in range(self._num_layers - 1):
# Assign workers for this layer.
worker_ids = [
AgentId(worker_type, f"{self.id.key}/layer_{i}/worker_{j}")
for j, worker_type in enumerate(self._worker_agent_types)
]
# Dispatch tasks to workers.
print(f"{'-'*80}\nOrchestrator-{self.id}:\nDispatch to workers at layer {i}")
results = await asyncio.gather(*[self.send_message(worker_task, worker_id) for worker_id in worker_ids])
print(f"{'-'*80}\nOrchestrator-{self.id}:\nReceived results from workers at layer {i}")
# Prepare task for the next layer.
worker_task = WorkerTask(task=message.task, previous_results=[r.result for r in results])
# Perform final aggregation.
print(f"{'-'*80}\nOrchestrator-{self.id}:\nPerforming final aggregation")
system_prompt = "You have been provided with a set of responses from various open-source models to the latest user query. Your task is to synthesize these responses into a single, high-quality response. It is crucial to critically evaluate the information provided in these responses, recognizing that some of it may be biased or incorrect. Your response should not simply replicate the given answers but should offer a refined, accurate, and comprehensive reply to the instruction. Ensure your response is well-structured, coherent, and adheres to the highest standards of accuracy and reliability.\n\nResponses from models:"
system_prompt += "\n" + "\n\n".join([f"{i+1}. {r}" for i, r in enumerate(worker_task.previous_results)])
model_result = await self._model_client.create(
[SystemMessage(content=system_prompt), UserMessage(content=message.task, source="user")]
)
assert isinstance(model_result.content, str)
return FinalResult(result=model_result.content)
Ejecutando Mixture of Agents (Mezcla de Agentes)
task = (
"I have 432 cookies, and divide them 3:4:2 between Alice, Bob, and Charlie. How many cookies does each person get?"
)
Configuremos el runtime con 3 capas de agentes trabajadores, cada una con 3 agentes. Solo necesitamos registrar un único tipo de agente trabajador, "worker", ya que usaremos la misma configuración del cliente de modelo (es decir, gpt-4o-mini) para todos los agentes trabajadores.
Nota
Si deseas utilizar diferentes modelos, deberás registrar múltiples tipos de agentes trabajadores (uno por modelo) y actualizar la lista worker_agent_types en la función de fábrica del agente orquestador.
✅ Las instancias de agentes trabajadores se crean automáticamente cuando el agente orquestador les asigna tareas.
--------------------------------------------------------------------------------
Orchestrator-orchestrator:default:
Received task: I have 432 cookies, and divide them 3:4:2 between Alice, Bob, and Charlie. How many cookies does each person get?
--------------------------------------------------------------------------------
Orchestrator-orchestrator:default:
Dispatch to workers at layer 0
--------------------------------------------------------------------------------
Worker-worker:default/layer_0/worker_1:
To divide 432 cookies in the ratio of 3:4:2 between Alice, Bob, and Charlie, you first need to determine the total number of parts in the ratio.
Add the parts together:
\[ 3 + 4 + 2 = 9 \]
Now, you can find the value of one part by dividing the total number of cookies by the total number of parts:
\[ \text{Value of one part} = \frac{432}{9} = 48 \]
Now, multiply the value of one part by the number of parts for each person:
- For Alice (3 parts):
\[ 3 \times 48 = 144 \]
- For Bob (4 parts):
\[ 4 \times 48 = 192 \]
- For Charlie (2 parts):
\[ 2 \times 48 = 96 \]
Thus, the number of cookies each person gets is:
- Alice: 144 cookies
- Bob: 192 cookies
- Charlie: 96 cookies
--------------------------------------------------------------------------------
Worker-worker:default/layer_0/worker_0:
To divide 432 cookies in the ratio of 3:4:2 between Alice, Bob, and Charlie, we will first determine the total number of parts in the ratio:
\[
3 + 4 + 2 = 9 \text{ parts}
\]
Next, we calculate the value of one part by dividing the total number of cookies by the total number of parts:
\[
\text{Value of one part} = \frac{432}{9} = 48
\]
Now, we can find out how many cookies each person receives by multiplying the value of one part by the number of parts each person receives:
- For Alice (3 parts):
\[
3 \times 48 = 144 \text{ cookies}
\]
- For Bob (4 parts):
\[
4 \times 48 = 192 \text{ cookies}
\]
- For Charlie (2 parts):
\[
2 \times 48 = 96 \text{ cookies}
\]
Thus, the number of cookies each person gets is:
- **Alice**: 144 cookies
- **Bob**: 192 cookies
- **Charlie**: 96 cookies
--------------------------------------------------------------------------------
Worker-worker:default/layer_0/worker_2:
To divide the cookies in the ratio of 3:4:2, we first need to find the total parts in the ratio.
The total parts are:
- Alice: 3 parts
- Bob: 4 parts
- Charlie: 2 parts
Adding these parts together gives:
\[ 3 + 4 + 2 = 9 \text{ parts} \]
Next, we can determine how many cookies each part represents by dividing the total number of cookies by the total parts:
\[ \text{Cookies per part} = \frac{432 \text{ cookies}}{9 \text{ parts}} = 48 \text{ cookies/part} \]
Now we can calculate the number of cookies for each person:
- Alice's share:
\[ 3 \text{ parts} \times 48 \text{ cookies/part} = 144 \text{ cookies} \]
- Bob's share:
\[ 4 \text{ parts} \times 48 \text{ cookies/part} = 192 \text{ cookies} \]
- Charlie's share:
\[ 2 \text{ parts} \times 48 \text{ cookies/part} = 96 \text{ cookies} \]
So, the final distribution of cookies is:
- Alice: 144 cookies
- Bob: 192 cookies
- Charlie: 96 cookies
--------------------------------------------------------------------------------
Orchestrator-orchestrator:default:
Received results from workers at layer 0
--------------------------------------------------------------------------------
Orchestrator-orchestrator:default:
Dispatch to workers at layer 1
--------------------------------------------------------------------------------
Worker-worker:default/layer_1/worker_2:
To divide 432 cookies in the ratio of 3:4:2 among Alice, Bob, and Charlie, follow these steps:
1. **Determine the total number of parts in the ratio**:
\[
3 + 4 + 2 = 9 \text{ parts}
\]
2. **Calculate the value of one part** by dividing the total number of cookies by the total number of parts:
\[
\text{Value of one part} = \frac{432}{9} = 48
\]
3. **Calculate the number of cookies each person receives** by multiplying the value of one part by the number of parts each individual gets:
- **For Alice (3 parts)**:
\[
3 \times 48 = 144 \text{ cookies}
\]
- **For Bob (4 parts)**:
\[
4 \times 48 = 192 \text{ cookies}
\]
- **For Charlie (2 parts)**:
\[
2 \times 48 = 96 \text{ cookies}
\]
Thus, the final distribution of cookies is:
- **Alice**: 144 cookies
- **Bob**: 192 cookies
- **Charlie**: 96 cookies
--------------------------------------------------------------------------------
Worker-worker:default/layer_1/worker_0:
To divide 432 cookies among Alice, Bob, and Charlie in the ratio of 3:4:2, we can follow these steps:
1. **Calculate the Total Parts**:
Add the parts of the ratio together:
\[
3 + 4 + 2 = 9 \text{ parts}
\]
2. **Determine the Value of One Part**:
Divide the total number of cookies by the total number of parts:
\[
\text{Value of one part} = \frac{432 \text{ cookies}}{9 \text{ parts}} = 48 \text{ cookies/part}
\]
3. **Calculate Each Person's Share**:
- **Alice's Share** (3 parts):
\[
3 \times 48 = 144 \text{ cookies}
\]
- **Bob's Share** (4 parts):
\[
4 \times 48 = 192 \text{ cookies}
\]
- **Charlie's Share** (2 parts):
\[
2 \times 48 = 96 \text{ cookies}
\]
4. **Final Distribution**:
- Alice: 144 cookies
- Bob: 192 cookies
- Charlie: 96 cookies
Thus, the distribution of cookies is:
- **Alice**: 144 cookies
- **Bob**: 192 cookies
- **Charlie**: 96 cookies
--------------------------------------------------------------------------------
Worker-worker:default/layer_1/worker_1:
To divide 432 cookies among Alice, Bob, and Charlie in the ratio of 3:4:2, we first need to determine the total number of parts in this ratio.
1. **Calculate Total Parts:**
\[
3 \text{ (Alice)} + 4 \text{ (Bob)} + 2 \text{ (Charlie)} = 9 \text{ parts}
\]
2. **Determine the Value of One Part:**
Next, we'll find out how many cookies correspond to one part by dividing the total number of cookies by the total number of parts:
\[
\text{Value of one part} = \frac{432 \text{ cookies}}{9 \text{ parts}} = 48 \text{ cookies/part}
\]
3. **Calculate the Share for Each Person:**
- **Alice's Share (3 parts):**
\[
3 \times 48 = 144 \text{ cookies}
\]
- **Bob's Share (4 parts):**
\[
4 \times 48 = 192 \text{ cookies}
\]
- **Charlie’s Share (2 parts):**
\[
2 \times 48 = 96 \text{ cookies}
\]
4. **Summary of the Distribution:**
- **Alice:** 144 cookies
- **Bob:** 192 cookies
- **Charlie:** 96 cookies
In conclusion, Alice receives 144 cookies, Bob receives 192 cookies, and Charlie receives 96 cookies.
--------------------------------------------------------------------------------
Orchestrator-orchestrator:default:
Received results from workers at layer 1
--------------------------------------------------------------------------------
Orchestrator-orchestrator:default:
Performing final aggregation
--------------------------------------------------------------------------------
Final result:
To divide 432 cookies among Alice, Bob, and Charlie in the ratio of 3:4:2, follow these steps:
1. **Calculate the Total Parts in the Ratio:**
Add the parts of the ratio together:
\[
3 + 4 + 2 = 9
\]
2. **Determine the Value of One Part:**
Divide the total number of cookies by the total number of parts:
\[
\text{Value of one part} = \frac{432}{9} = 48 \text{ cookies/part}
\]
3. **Calculate Each Person's Share:**
- **Alice's Share (3 parts):**
\[
3 \times 48 = 144 \text{ cookies}
\]
- **Bob's Share (4 parts):**
\[
4 \times 48 = 192 \text{ cookies}
\]
- **Charlie's Share (2 parts):**
\[
2 \times 48 = 96 \text{ cookies}
\]
Therefore, the distribution of cookies is as follows:
- **Alice:** 144 cookies
- **Bob:** 192 cookies
- **Charlie:** 96 cookies
In summary, Alice gets 144 cookies, Bob gets 192 cookies, and Charlie gets 96 cookies.
Vamos a ejecutar el patrón mixture of agents en una tarea matemática. Puedes cambiar la tarea para hacerla más desafiante, por ejemplo, utilizando problemas de la Olimpiada Internacional de Matemáticas .