智能体混合架构#

智能体混合架构是一种借鉴前馈神经网络架构的多智能体设计模式。

该模式包含两类智能体:工作智能体和单一协调智能体。工作智能体被组织成多个层级,每层包含固定数量的工作智能体。来自前一层工作智能体的消息会被拼接后发送给下一层的所有工作智能体。

本示例使用核心库实现了智能体混合架构模式,遵循原始实现的多层智能体混合方案。

以下是该模式的高层流程概述:

  1. 协调智能体接收用户任务后,首先将其分发给第一层的工作智能体

  2. 第一层工作智能体处理任务并将结果返回给协调智能体

  3. 协调智能体综合第一层结果,将包含先前结果的更新任务分发给第二层工作智能体

  4. 该过程持续进行直至到达最终层

  5. 在最终层,协调智能体聚合前一层结果,向用户返回单一最终结果

我们使用直接消息API send_message() 实现该模式,这便于未来添加工作智能体任务取消和错误处理等功能。

import asyncio
from dataclasses import dataclass
from typing import List

from autogen_core import AgentId, MessageContext, RoutedAgent, SingleThreadedAgentRuntime, message_handler
from autogen_core.models import ChatCompletionClient, SystemMessage, UserMessage
from autogen_ext.models.openai import OpenAIChatCompletionClient

消息协议#

智能体间使用以下消息进行通信:

@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:
            # 若提供了先前结果,我们需要将其综合成单一提示。
            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:
            # 若未提供先前结果,可直接将用户查询传递给模型。
            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}")
        # 为第一层创建任务。
        worker_task = WorkerTask(task=message.task, previous_results=[])
        # 遍历各层。
        for i in range(self._num_layers - 1):
            # 为本层分配工作代理。
            worker_ids = [
                AgentId(worker_type, f"{self.id.key}/layer_{i}/worker_{j}")
                for j, worker_type in enumerate(self._worker_agent_types)
            ]
            # 向工作代理分发任务。
            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}")
            # 为下一层准备任务。
            worker_task = WorkerTask(task=message.task, previous_results=[r.result for r in results])
        # 执行最终聚合。
        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)

运行混合代理#

让我们在一个数学任务上运行混合代理。你可以通过尝试国际数学奥林匹克的题目来增加任务难度。

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列表。

工作代理实例会在协调代理向其分派任务时自动创建。 更多关于代理生命周期的信息,请参阅代理身份与生命周期

runtime = SingleThreadedAgentRuntime()
model_client = OpenAIChatCompletionClient(model="gpt-4o-mini")
await WorkerAgent.register(runtime, "worker", lambda: WorkerAgent(model_client=model_client))
await OrchestratorAgent.register(
    runtime,
    "orchestrator",
    lambda: OrchestratorAgent(model_client=model_client, 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()
await model_client.close()

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.