Microsoft代理框架工作流协调和编排 - Magentic

Magentic 编排是根据 AutoGen 发明的 Magentic-One 系统设计的。 它是一种灵活的常规用途多代理模式,专为需要动态协作的复杂开放式任务而设计。 在此模式中,Magentic 管理器协调一个专门代理团队,并根据不断变化的上下文、任务进度和代理能力选择哪个代理应在接下来的步骤中采取行动。

Magentic 经理维护共享上下文、跟踪进度并实时调整工作流。 这使系统能够分解复杂的问题、委托子任务,并通过代理协作迭代优化解决方案。 协调特别适用于方案路径事先未知的情况,这可能需要多轮推理、研究和计算。

磁性调度

Tip

磁性编排的体系结构与群聊编排模式相同,拥有一个非常强大的管理器,它通过计划来协调代理之间的协作。 如果你的方案需要更简单的协调而不进行复杂的规划,请考虑改用群聊模式。

注释

Magentic-One 论文中,4 个高度专业化的代理旨在解决一组非常具体的任务。 在 Agent Framework 中的 Magentic 业务流程中,可以定义自己的专用代理以满足特定的应用程序需求。 然而,在原始 Magentic-One 设计之外,Magentic 编排的表现如何尚未经过测试。

学习内容

  • 如何设置 Magentic 管理器以协调多个专用代理
  • 如何使用WorkflowEvent来处理流事件
  • 如何实现人在回路中的计划审查
  • 如何跟踪代理协作及在复杂任务中的进展

定义专用代理

在 Magentic 编排中,您定义的专用代理可以由管理器根据任务要求进行动态选择。

#pragma warning disable MAAIW001  // Magentic types are experimental
#pragma warning disable OPENAI001 // HostedCodeInterpreterTool is experimental

using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Agents.AI.Workflows.Specialized.Magentic;
using Microsoft.Extensions.AI;

string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
    ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
string deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";

AIProjectClient projectClient = new(new Uri(endpoint), new DefaultAzureCredential());

AIAgent researcherAgent = projectClient.AsAIAgent(
    deploymentName,
    name: "ResearcherAgent",
    description: "Specialist in research and information gathering.",
    instructions: "You are a researcher. Find relevant information without doing additional computation or quantitative analysis.");

AIAgent coderAgent = projectClient.AsAIAgent(
    deploymentName,
    name: "CoderAgent",
    description: "A helpful assistant that writes and executes code to analyze data.",
    instructions: "You solve quantitative questions by writing and running code. Show the analysis and the computation process clearly.",
    tools: [new HostedCodeInterpreterTool()]);

AIAgent managerAgent = projectClient.AsAIAgent(
    deploymentName,
    name: "MagenticManager",
    description: "Orchestrator that coordinates the research and coding workflow.",
    instructions: "You coordinate the team to complete complex tasks efficiently.");
import os

from agent_framework import Agent
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential

client = FoundryChatClient(
    project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
    model=os.environ["FOUNDRY_MODEL"],
    credential=AzureCliCredential(),
)

researcher_agent = Agent(
    name="ResearcherAgent",
    description="Specialist in research and information gathering",
    instructions=(
        "You are a Researcher. You find information without additional computation or quantitative analysis."
    ),
    client=client,
)

coder_agent = Agent(
    name="CoderAgent",
    description="A helpful assistant that writes and executes code to process and analyze data.",
    instructions="You solve questions using code. Please provide detailed analysis and computation process.",
    client=client,
    tools=client.get_code_interpreter_tool(),
)

# Create a manager agent for orchestration
manager_agent = Agent(
    name="MagenticManager",
    description="Orchestrator that coordinates the research and coding workflow",
    instructions="You coordinate a team to complete complex tasks efficiently.",
    client=client,
)

生成 磁性 工作流

使用 Magentic 工作流生成器通过经理和一组参与者配置工作流。 构建器还提供了内部循环限制(最大协调轮次、重新规划前允许的最大连续停滞次数、最大计划重置次数)以及用于人工参与计划审查的标志。

Workflow workflow = new MagenticWorkflowBuilder(managerAgent)
    .AddParticipants([researcherAgent, coderAgent])
    .WithName("Magentic Orchestration Workflow")
    .WithDescription("Coordinates a researcher and coder to solve a complex analytical task.")
    .RequirePlanSignoff(false)
    .WithMaxRounds(10)
    .WithMaxStalls(3)
    .WithMaxResets(2)
    .Build();
from agent_framework.orchestrations import MagenticBuilder

workflow = MagenticBuilder(
    participants=[researcher_agent, coder_agent],
    intermediate_output_from=[researcher_agent, coder_agent],
    manager_agent=manager_agent,
    max_round_count=10,
    max_stall_count=3,
    max_reset_count=2,
).build()

Tip

标准管理器基于 Magentic-One 设计,其固定提示取自原始论文。 通过将提示传递给 MagenticBuilder,自定义管理器的行为。

自定义初始事实数据表或计划提示时,还自定义相应的更新提示,以便重新规划保留格式。 自定义 progress_ledger_prompt 必须保留内置 JSON 响应架构。

有关提示参数及其可用占位符,请参阅 自定义管理器提示示例。 若要进一步自定义管理器,子类 MagenticManagerBase

中间输出

注释

本部分目前仅适用于 Python 分支。

intermediate_output_from=[...] 传递给 MagenticBuilder 会将特定参与者指定为中间输出源。 它们的 yield_output 调用会发出 "intermediate" 事件,而管理器最终合成的回答仍然是 "output"(终端)事件。 如果没有此参数(默认值),则仅显示管理器的终端 AgentResponse

这对于 Magentic 工作流特别有用,因为:

  • 任务通常需要较长时间运行,并涉及多个轮次的代理协作。
  • 当工作流在流式处理模式下进行时,可以实时显示每个代理的贡献
  • 它提供了对工作流中间推理步骤的可视化

通过事件流运行工作流

执行复杂任务并处理流式输出和编排更新的事件。 终端工作流输出包含管理器的合成最终答案。

const string TaskPrompt =
    "I am preparing a report on the energy efficiency of different machine learning model architectures. " +
    "Compare the estimated training and inference energy consumption of ResNet-50, BERT-base, and GPT-2 " +
    "on standard datasets (for example, ImageNet for ResNet, GLUE for BERT, WebText for GPT-2). " +
    "Then, estimate the CO2 emissions associated with each, assuming training on an Azure Standard_NC6s_v3 " +
    "VM for 24 hours. Provide tables for clarity, and recommend the most energy-efficient model " +
    "per task type (image classification, text classification, and text generation).";

await using StreamingRun run = await InProcessExecution.RunStreamingAsync(
    workflow,
    new List<ChatMessage> { new(ChatRole.User, TaskPrompt) });

await run.TrySendMessageAsync(new TurnToken(emitEvents: true));

string? lastResponseId = null;
WorkflowOutputEvent? finalOutput = null;

await foreach (WorkflowEvent workflowEvent in run.WatchStreamAsync())
{
    switch (workflowEvent)
    {
        case AgentResponseUpdateEvent updateEvent:
            // Stream per-participant deltas. Group by ResponseId / MessageId / ExecutorId so
            // each new contiguous response prints its executor header once.
            string responseId = updateEvent.Update.ResponseId
                ?? updateEvent.Update.MessageId
                ?? updateEvent.ExecutorId;
            if (!string.Equals(responseId, lastResponseId, StringComparison.Ordinal))
            {
                if (lastResponseId is not null)
                {
                    Console.WriteLine();
                }
                Console.Write($"- {updateEvent.ExecutorId}: ");
                lastResponseId = responseId;
            }
            Console.Write(updateEvent.Update.Text);
            break;

        case MagenticPlanCreatedEvent planCreated:
            Console.WriteLine($"\n[Magentic Initial Plan]\n{planCreated.FullTaskLedger.Text}");
            break;

        case MagenticReplannedEvent replanned:
            Console.WriteLine($"\n[Magentic Replanned]\n{replanned.FullTaskLedger.Text}");
            break;

        case MagenticProgressLedgerUpdatedEvent progressUpdated:
            MagenticProgressLedger ledger = progressUpdated.ProgressLedger;
            Console.WriteLine(
                $"\n[Magentic Progress Ledger] satisfied={ledger.IsRequestSatisfied}, " +
                $"inLoop={ledger.IsInLoop}, progressing={ledger.IsProgressBeingMade}, " +
                $"nextSpeaker={ledger.NextSpeaker}, instruction={ledger.InstructionOrQuestion}");
            break;

        case WorkflowOutputEvent outputEvent when outputEvent.Is<List<ChatMessage>>():
            finalOutput = outputEvent;
            break;

        case WorkflowErrorEvent workflowError:
            Console.Error.WriteLine(workflowError.Exception?.ToString() ?? "Unknown workflow error.");
            break;

        case ExecutorFailedEvent executorFailed:
            Console.Error.WriteLine(
                $"Executor '{executorFailed.ExecutorId}' failed: " +
                (executorFailed.Data?.ToString() ?? "unknown error"));
            break;
    }
}

if (finalOutput?.As<List<ChatMessage>>() is { } transcript)
{
    Console.WriteLine("\n\n=== Final Conversation Transcript ===\n");
    foreach (ChatMessage message in transcript)
    {
        Console.WriteLine($"{message.AuthorName ?? message.Role.ToString()}: {message.Text}");
    }
}
import json
import asyncio
from typing import cast

from agent_framework import (
    AgentResponseUpdate,
    Message,
    WorkflowEvent,
)
from agent_framework.orchestrations import MagenticProgressLedger

task = (
    "I am preparing a report on the energy efficiency of different machine learning model architectures. "
    "Compare the estimated training and inference energy consumption of ResNet-50, BERT-base, and GPT-2 "
    "on standard datasets (for example, ImageNet for ResNet, GLUE for BERT, WebText for GPT-2). "
    "Then, estimate the CO2 emissions associated with each, assuming training on an Azure Standard_NC6s_v3 "
    "VM for 24 hours. Provide tables for clarity, and recommend the most energy-efficient model "
    "per task type (image classification, text classification, and text generation)."
)

# Keep track of the last executor to format output nicely in streaming mode
last_message_id: str | None = None
stream = workflow.run(task, stream=True)
async for event in stream:
    if event.type in ("intermediate", "output") and isinstance(event.data, AgentResponseUpdate):
        message_id = event.data.message_id
        if message_id != last_message_id:
            if last_message_id is not None:
                print("\n")
            print(f"- {event.executor_id}:", end=" ", flush=True)
            last_message_id = message_id
        print(event.data, end="", flush=True)

    elif event.type == "magentic_orchestrator":
        print(f"\n[Magentic Orchestrator Event] Type: {event.data.event_type.name}")
        if isinstance(event.data.content, Message):
            print(f"Please review the plan:\n{event.data.content.text}")
        elif isinstance(event.data.content, MagenticProgressLedger):
            print(f"Please review progress ledger:\n{json.dumps(event.data.content.to_dict(), indent=2)}")
        else:
            print(f"Unknown data type in MagenticOrchestratorEvent: {type(event.data.content)}")

        # Block to allow user to read the plan/progress before continuing
        # Note: this is for demonstration only and is not the recommended way to handle human interaction.
        # Please refer to `with_plan_review` for proper human interaction during planning phases.
        await asyncio.get_event_loop().run_in_executor(None, input, "Press Enter to continue...")

result = await stream.get_final_response()
if outputs := result.get_outputs():
    print(outputs[-1])

Magentic 显示三个标记规划和进度里程碑的编排器事件:

  • 创建的初始计划 - 经理已生成初始任务计划。
  • 重新计划 — 生成了新计划,原因可能是停滞检测,或者是因为人类通过计划审查修订了计划。
  • 进度账本更新 — 每个协调轮发出一次;承载当前进度账本(是否满足请求、团队是否处于循环中、是否正在进行进度、下一位演讲者以及发送给他们的指令)。

在Python,这些项在单个 MagenticOrchestratorEvent中携带,其 event_type 枚举区分 PLAN_CREATEDREPLANNEDPROGRESS_LEDGER_UPDATED。 在.NET,它们作为三种不同的类型(MagenticPlanCreatedEventMagenticReplannedEventMagenticProgressLedgerUpdatedEvent)发出,所有这些类型都派生自 MagenticOrchestratorEvent

高级:人在回路中的计划审查

启用人力在环 (HITL),以允许用户在执行前审查和批准管理器提出的计划。 这可用于确保计划符合用户的期望和要求。

计划评审有两个选项:

  1. 修订:用户提供反馈来修改计划,该计划会触发经理根据反馈重新规划。
  2. 批准:用户批准计划 as-is,从而允许工作流继续。

在构建 Magentic 工作流时启用计划审核。 语言之间的默认值不同:在 Python 中,计划评审默认为 offenable_plan_review=False),并且你选择显式加入; 在 .NET 中,计划评审默认为 onRequirePlanSignoff 默认为 true),并且本页前面的基本示例已选择退出,因此它可以在无交互的情况下运行端到端。 下面的代码演示如何选择加入和处理生成的评审请求。

计划审查暂停通过工作流的请求/响应机制以及 MagenticPlanReviewRequest 数据显示。 你在事件流中处理这些,并在人类批准或修订计划后使用 MagenticPlanReviewResponse 恢复工作流。

Tip

“请求和响应 ”指南中了解有关请求和响应的详细信息。

Workflow workflow = new MagenticWorkflowBuilder(managerAgent)
    .AddParticipants([researcherAgent, coderAgent])
    .RequirePlanSignoff(true)
    .WithMaxRounds(10)
    .WithMaxStalls(1)
    .WithMaxResets(2)
    .Build();

CheckpointManager checkpointManager = CheckpointManager.CreateInMemory();
InProcessExecutionEnvironment environment = ExecutionEnvironment.InProcess_Lockstep
    .ToWorkflowExecutionEnvironment()
    .WithCheckpointing(checkpointManager);

await using StreamingRun run = await environment.OpenStreamingAsync(workflow);
await run.TrySendMessageAsync(new List<ChatMessage> { new(ChatRole.User, TaskPrompt) });
await run.TrySendMessageAsync(new TurnToken(emitEvents: true));

ExternalRequest? pendingRequest = null;
CheckpointInfo? lastCheckpoint = null;
WorkflowOutputEvent? finalOutput = null;

async Task<WorkflowOutputEvent?> DrainAsync(StreamingRun activeRun)
{
    WorkflowOutputEvent? output = null;
    await foreach (WorkflowEvent evt in activeRun.WatchStreamAsync(blockOnPendingRequest: false))
    {
        switch (evt)
        {
            case AgentResponseUpdateEvent updateEvent:
                Console.Write(updateEvent.Update.Text);
                break;
            case RequestInfoEvent requestInfo
                when requestInfo.Request.Data.As<MagenticPlanReviewRequest>() is not null:
                pendingRequest = requestInfo.Request;
                break;
            case SuperStepCompletedEvent stepCompleted:
                lastCheckpoint = stepCompleted.CompletionInfo?.Checkpoint ?? lastCheckpoint;
                break;
            case WorkflowOutputEvent outputEvent when outputEvent.Is<List<ChatMessage>>():
                output = outputEvent;
                break;
        }
    }
    return output;
}

finalOutput = await DrainAsync(run);

// Loop until the workflow finishes or the user accepts a plan that runs to completion.
while (finalOutput is null && pendingRequest is not null)
{
    MagenticPlanReviewRequest reviewRequest = pendingRequest.Data.As<MagenticPlanReviewRequest>()!;

    Console.WriteLine("\n\n[Magentic Plan Review Request]");
    if (reviewRequest.CurrentProgress is { } progress)
    {
        Console.WriteLine(
            $"Current progress: satisfied={progress.IsRequestSatisfied}, " +
            $"inLoop={progress.IsInLoop}, progressing={progress.IsProgressBeingMade}");
    }
    if (reviewRequest.IsStalled)
    {
        Console.WriteLine("(Replan triggered by stall detection.)");
    }
    Console.WriteLine($"Proposed plan:\n{reviewRequest.Plan.Text}\n");
    Console.Write("Press Enter to approve, or type feedback to request a revision: ");

    string reply = Console.ReadLine() ?? string.Empty;
    MagenticPlanReviewResponse reviewResponse = string.IsNullOrWhiteSpace(reply)
        ? reviewRequest.Approve()
        : reviewRequest.Revise(reply);

    ExternalResponse response = pendingRequest.CreateResponse(reviewResponse);
    pendingRequest = null;

    await using StreamingRun resumed = await environment.ResumeStreamingAsync(workflow, lastCheckpoint!);
    await resumed.SendResponseAsync(response);
    finalOutput = await DrainAsync(resumed);
}

if (finalOutput?.As<List<ChatMessage>>() is { } transcript)
{
    Console.WriteLine("\n\n=== Final Conversation Transcript ===\n");
    foreach (ChatMessage message in transcript)
    {
        Console.WriteLine($"{message.AuthorName ?? message.Role.ToString()}: {message.Text}");
    }
}
import json
import asyncio
from typing import cast

from agent_framework import (
    AgentResponseUpdate,
    Agent,
    Message,
    WorkflowEvent,
)
from agent_framework.orchestrations import (
    MagenticBuilder,
    MagenticPlanReviewRequest,
    MagenticPlanReviewResponse,
)

workflow = MagenticBuilder(
    participants=[researcher_agent, coder_agent],
    intermediate_output_from=[researcher_agent, coder_agent],
    enable_plan_review=True,
    manager_agent=manager_agent,
    max_round_count=10,
    max_stall_count=1,
    max_reset_count=2,
).build()

pending_request: WorkflowEvent | None = None
pending_responses: dict[str, MagenticPlanReviewResponse] | None = None
final_response: object | None = None

while not final_response:
    if pending_responses is not None:
        stream = workflow.run(stream=True, responses=pending_responses)
    else:
        stream = workflow.run(task, stream=True)

    last_message_id: str | None = None
    async for event in stream:
        if event.type in ("intermediate", "output") and isinstance(event.data, AgentResponseUpdate):
            message_id = event.data.message_id
            if message_id != last_message_id:
                if last_message_id is not None:
                    print("\n")
                print(f"- {event.executor_id}:", end=" ", flush=True)
                last_message_id = message_id
            print(event.data, end="", flush=True)

        elif event.type == "request_info" and event.request_type is MagenticPlanReviewRequest:
            pending_request = event

    result = await stream.get_final_response()
    if outputs := result.get_outputs():
        final_response = outputs[-1]

    pending_responses = None

    # Handle plan review request if any
    if pending_request is not None:
        event_data = cast(MagenticPlanReviewRequest, pending_request.data)

        print("\n\n[Magentic Plan Review Request]")
        if event_data.current_progress is not None:
            print("Current Progress Ledger:")
            print(json.dumps(event_data.current_progress.to_dict(), indent=2))
            print()
        print(f"Proposed Plan:\n{event_data.plan.text}\n")
        print("Please provide your feedback (press Enter to approve):")

        reply = await asyncio.get_event_loop().run_in_executor(None, input, "> ")
        if reply.strip() == "":
            print("Plan approved.\n")
            pending_responses = {pending_request.request_id: event_data.approve()}
        else:
            print("Plan revised by human.\n")
            pending_responses = {pending_request.request_id: event_data.revise(reply)}
        pending_request = None

一个 MagenticPlanReviewRequest 携带提议的计划、当前进度分类账(在初始审查时 null / None,在停滞触发的重新计划时填充),以及一个指示重新计划是否由停滞检测触发的标志。 通过调用 approve() 按原样接受该计划,或调用 revise(...) 并提供反馈以请经理重新规划,来生成响应。

关键概念

  • 动态协调:Magentic 管理器会根据不断变化的上下文动态选择接下来应由哪个代理执行。
  • Terminal Output:终端工作流的输出包含管理器综合生成的最终答案(在 Python 中为 AgentResponse;在 .NET 中为带有 WorkflowOutputEvent 负载的 List<ChatMessage>)。
  • 编排器事件:计划创建、重新计划和进度分类账更新里程碑通过 MagenticOrchestratorEvent 显示(在 Python 中是一个带有 event_type 枚举的事件;在 .NET 中是三种派生类型)。 每个参与者的流式增量通过框架的标准智能体响应更新事件传递。
  • 迭代优化:系统可以分解复杂的问题,并通过多个轮迭代优化解决方案。
  • 进度跟踪和停滞检测:进度记录会跟踪请求是否已得到满足、团队是否陷入循环,以及是否正在取得进展。 连续的非进展轮次会增加停滞计数器,超过配置的最大值会触发自动重置和重新计划。
  • 灵活协作:可以按经理确定的任何顺序多次调用代理。
  • 人工监督:通过 MagenticPlanReviewRequest / MagenticPlanReviewResponse 的可选人在回路计划审查。
  • 中间输出(目前仅支持 Python):指定哪些参与者的 yield_output 调用应与 manager 的终端输出一同显示为 "intermediate" 事件。

工作流执行过程

Magentic 编排遵循此执行模式:

  1. 规划阶段:经理分析任务并创建初始计划
  2. 可选计划评审:如果启用,人类可以审阅和批准/修改计划
  3. 代理选择:管理器为每个子任务选择最合适的代理
  4. 执行:所选代理执行其部分任务
  5. 进度评估:经理评估进度并更新计划
  6. 停滞检测:如果进度停滞,自动重新计划并附带可选的人工审查过程
  7. 迭代:步骤 3-6 重复,直到任务完成或达到限制
  8. 最终合成:管理器将所有代理输出合成到最终结果中

完整的示例

请参阅 Agent Framework 示例存储库中的完整示例。

请参阅 Agent Framework 示例存储库中的完整示例。

注释

Go 语言对该功能的支持即将推出。 有关最新状态,请参阅 Agent Framework Go 存储库

后续步骤