Mixture of Agents#
Mixture of Agents是一种模仿前馈神经网络架构的多代理设计模式。
该模式包含两种类型的代理:工作代理和单个编排代理。工作代理被组织成多个层,每层由固定数量的工作代理组成。来自前一层工作代理的消息被连接起来,并发送给下一层的所有工作代理。
这个示例按照多层代理混合的原始实现使用核心库实现了代理混合模式。
以下是该模式的高级过程概述:
编排代理接收用户任务作为输入,首先将其分派给第一层的工作代理。
第一层的工作代理处理任务并将结果返回给编排代理。
编排代理然后综合第一层的结果,并将带有前一层结果的更新任务分派给第二层的工作代理。
这个过程持续进行,直到达到最后一层。
在最后一层,编排代理汇总前一层的结果,并向用户返回单个最终结果。
我们使用直接消息传递 API send_message()
来实现这个模式。这使得将来更容易添加更多功能,如工作任务取消和错误处理。
import asyncio
from dataclasses import dataclass
from typing import List
from autogen_core.application import SingleThreadedAgentRuntime
from autogen_core.base import AgentId, MessageContext
from autogen_core.components import RoutedAgent, message_handler
from autogen_core.components.models import ChatCompletionClient, OpenAIChatCompletionClient, SystemMessage, UserMessage
/tmp/ipykernel_193889/204060853.py:8: FutureWarning: OpenAIChatCompletionClient moved to autogen_ext. Please import it from autogen_ext.modelsChatCompletionClient.
from autogen_core.components.models import ChatCompletionClient, OpenAIChatCompletionClient, SystemMessage, UserMessage
消息协议#
代理使用以下消息进行通信:
@dataclass
class WorkerTask:
task: str
previous_results: List[str]
@dataclass
class WorkerTaskResult:
result: str
@dataclass
class UserTask:
task: str
@dataclass
class FinalResult:
result: str
工作代理#
每个工作代理从编排代理接收任务并独立处理它们。一旦任务完成,工作代理就返回结果。
class WorkerAgent(RoutedAgent):
def __init__(
self,
model_client: ChatCompletionClient,
) -> 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(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)
编排代理#
编排代理接收来自用户的任务并将它们分发给工作代理,在多层工作代理之间迭代。一旦所有工作代理都处理完任务,编排代理就汇总结果并发布最终结果。
class OrchestratorAgent(RoutedAgent):
def __init__(
self,
model_client: ChatCompletionClient,
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(system_prompt), UserMessage(content=message.task, source="user")]
)
assert isinstance(model_result.content, str)
return FinalResult(result=model_result.content)
运行代理混合#
让我们在数学任务上运行代理混合。您可以通过尝试来自国际数学奥林匹克的任务等方式来改变任务,使其更具挑战性。
task = (
"I have 432 cookies, and divide them 3:4:2 between Alice, Bob, and Charlie. How many cookies does each person get?"
)
让我们设置运行时,包含 3 层工作代理,每层由 3 个工作代理组成。我们只需要注册一个工作代理类型 "worker",因为我们对所有工作代理使用相同的模型客户端配置(即 gpt-4o-mini)。如果您想使用不同的模型,您需要注册多个工作代理类型,每个模型一个,并更新编排代理工厂函数中的 worker_agent_types
列表。
当编排代理向工作代理分派任务时,工作代理的实例会自动创建。有关代理生命周期的更多信息,请参阅Agent Identity and Lifecycle。
runtime = SingleThreadedAgentRuntime()
await WorkerAgent.register(
runtime, "worker", lambda: WorkerAgent(model_client=OpenAIChatCompletionClient(model="gpt-4o-mini"))
)
await OrchestratorAgent.register(
runtime,
"orchestrator",
lambda: OrchestratorAgent(
model_client=OpenAIChatCompletionClient(model="gpt-4o"), worker_agent_types=["worker"] * 3, num_layers=3
),
)
runtime.start()
result = await runtime.send_message(UserTask(task=task), AgentId("orchestrator", "default"))
await runtime.stop_when_idle()
print(f"{'-'*80}\nFinal result:\n{result.result}")
--------------------------------------------------------------------------------
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.