Gemisch aus Agenten#

Das Muster „Gemisch aus Agenten“ ist ein Multi-Agenten-Designmuster, das der Architektur neuronaler Feed-Forward-Netzwerke nachempfunden ist.

Das Muster besteht aus zwei Arten von Agenten: Arbeitsagenten und einem einzigen Orchestrator-Agenten. Die Arbeitsagenten sind in mehreren Schichten organisiert, wobei jede Schicht aus einer festen Anzahl von Arbeitsagenten besteht. Nachrichten von den Arbeitsagenten einer vorherigen Schicht werden verkettet und an alle Arbeitsagenten der nächsten Schicht gesendet.

Dieses Beispiel implementiert das Muster „Gemisch aus Agenten“ unter Verwendung der Kernbibliothek, die der ursprünglichen Implementierung von Multi-Layer Mixture of Agents folgt.

Hier ist ein Überblick über das Verfahrensmuster

  1. Der Orchestrator-Agent nimmt eine Benutzereingabe entgegen und leitet sie zunächst an die Arbeitsagenten in der ersten Schicht weiter.

  2. Die Arbeitsagenten in der ersten Schicht verarbeiten die Aufgabe und geben die Ergebnisse an den Orchestrator-Agenten zurück.

  3. Der Orchestrator-Agent synthetisiert dann die Ergebnisse aus der ersten Schicht und leitet eine aktualisierte Aufgabe mit den vorherigen Ergebnissen an die Arbeitsagenten in der zweiten Schicht weiter.

  4. Der Prozess wird fortgesetzt, bis die letzte Schicht erreicht ist.

  5. In der letzten Schicht aggregiert der Orchestrator-Agent die Ergebnisse der vorherigen Schicht und gibt ein einzelnes Endergebnis an den Benutzer zurück.

Wir verwenden die direkte Nachrichten-API send_message(), um dieses Muster zu implementieren. Dies erleichtert zukünftige Erweiterungen wie die Abbruchfunktion für Arbeitsaufgaben und die Fehlerbehandlung.

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

Nachrichtenprotokoll#

Die Agenten kommunizieren über folgende Nachrichten

@dataclass
class WorkerTask:
    task: str
    previous_results: List[str]


@dataclass
class WorkerTaskResult:
    result: str


@dataclass
class UserTask:
    task: str


@dataclass
class FinalResult:
    result: str

Arbeitsagent#

Jeder Arbeitsagent erhält eine Aufgabe vom Orchestrator-Agenten und verarbeitet sie unabhängig. Sobald die Aufgabe abgeschlossen ist, gibt der Arbeitsagent das Ergebnis zurück.

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(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)

Orchestrator-Agent#

Der Orchestrator-Agent empfängt Aufgaben vom Benutzer und verteilt sie an die Arbeitsagenten, wobei er über mehrere Schichten von Arbeitsagenten iteriert. Sobald alle Arbeitsagenten die Aufgabe verarbeitet haben, aggregiert der Orchestrator-Agent die Ergebnisse und veröffentlicht das Endergebnis.

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(content=system_prompt), UserMessage(content=message.task, source="user")]
        )
        assert isinstance(model_result.content, str)
        return FinalResult(result=model_result.content)

Ausführung des Gemisches aus Agenten#

Lassen Sie uns das Gemisch aus Agenten für eine mathematische Aufgabe ausführen. Sie können die Aufgabe herausfordernder gestalten, indem Sie beispielsweise Aufgaben aus der International Mathematical Olympiad ausprobieren.

task = (
    "I have 432 cookies, and divide them 3:4:2 between Alice, Bob, and Charlie. How many cookies does each person get?"
)

Richten wir die Laufzeit mit 3 Schichten von Arbeitsagenten ein, wobei jede Schicht aus 3 Arbeitsagenten besteht. Wir müssen nur einen einzigen Typ von Arbeitsagenten, „worker“, registrieren, da wir für alle Arbeitsagenten dieselbe Modellclient-Konfiguration (d. h. gpt-4o-mini) verwenden. Wenn Sie unterschiedliche Modelle verwenden möchten, müssen Sie mehrere Typen von Arbeitsagenten registrieren, einen für jedes Modell, und die Liste worker_agent_types in der Factory-Funktion des Orchestrator-Agenten aktualisieren.

Die Instanzen der Arbeitsagenten werden automatisch erstellt, wenn der Orchestrator-Agent Aufgaben an sie verteilt. Weitere Informationen zum Lebenszyklus von Agenten finden Sie unter Agent Identity and Lifecycle.

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.