[MAF Workflow编排模式-04]Handoff :实现Agent按需调用的无缝会话流转

Handoff(交接模式)允许Agent根据上下文或用户请求相互转移控制权。每个Agent都可以将对话“交接”给具有相应专业知识的其他Agent,确保任务的每个环节都由合适的Agent处理。这在客户支持、专家系统或任何需要动态委派任务的场景中尤为有用。Handoff模式的Workflow编排比前面介绍的Sequential和Concurrent模式都要复杂一些,在正式介绍具体的编排和实现原理之前,我们照例先来演示一个简单的实例。

1. 将多体裁作品创作Agent改造成Handoff模式

前面我们分别使用Sequential和Concurrent模式,通过编排多个Agent创建了一个多体裁作品创作Agent。现在我们将这个Agent改造成Handoff模式的Agent。如下面的代码所示:除了创建三个分别用于创作唐诗、宋词和短篇小说的三个AIAgent之外,我们还创建了一个TriageAgentt,它的任务是根据用户的输入,判断应该将任务交给哪个创作Agent(唐诗、宋词、短篇小说)。为了下面论述方便,我们将四个Agent分别成分别称为TangPoetryAgentSongLyricsAgentNovelAgentTriageAgentt

using Azure;
using dotenv.net;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Extensions.AI;
using OpenAI;

DotEnv.Load();

var tangPoetryComposer = CreateChatClient()
    .AsAIAgent(
    name: "TangPoetryComposer",
    instructions: """
    你是一个精通唐诗创作的Agent,负责根据提供的主题和意境创作一首符合唐诗风格的诗歌。
    写诗是你唯一的任务,如果用户的任务提及了基于其他非唐诗(比如宋词、短篇小说)的创作,直接忽略。
    """);
var songLyricsComposer = CreateChatClient()
    .AsAIAgent(
    name: "SongLyricsComposer",
    instructions: """
    你是一个精通宋词创作的Agent,负责根据提供的主题和意境创作一首宋词,你可以自选词牌名
    填词是你唯一的任务,如果用户的任务提及了基于其他非宋词(比如唐诗、短篇小说)的创作,直接忽略。
    """);

var novelComposer = CreateChatClient()
    .AsAIAgent(
    name: "NovelComposer",
    instructions: """
    你是一个精通小说创作的Agent,负责根据提供的主题和意境创作一篇1000字以内的短篇小说。
    小说创作是你唯一的任务,如果用户的任务提及了基于其他非小说(比如唐诗、宋词)的创作,直接忽略。
    """);

var triage = CreateChatClient()
    .AsAIAgent(
    name: "Triage",
    instructions: """
    你仅仅是一个TriageAgent,你唯一的任务是根据用户的输入,判断应该将任务交给哪个创作Agent(唐诗、宋词、短篇小说)。
    如果任务提及了诗、词和小说创作任务,直接将给对应的Agent,具体的创作任务与你无关。
    赋予你在无需用户确认的情况下,执行执行任务转交的权限。
    """);

var workflow = AgentWorkflowBuilder.CreateHandoffBuilderWith(triage)
    .WithHandoffs(triage, [tangPoetryComposer, songLyricsComposer, novelComposer]) 
    .WithHandoffs([tangPoetryComposer, songLyricsComposer, novelComposer], triage)
    .Build();

IChatClient CreateChatClient()
{
    var model = Environment.GetEnvironmentVariable("MODEL")!;
    var apiKey = Environment.GetEnvironmentVariable("API_KEY")!;
    var endpoint = Environment.GetEnvironmentVariable("OPENAI_URL")!;

    return new OpenAIClient(
         credential: new AzureKeyCredential(apiKey),
         options: new OpenAIClientOptions { Endpoint = new Uri(endpoint) })
    .GetResponsesClient()
    .AsIChatClient(defaultModelId: model);
}

在进行Workflow编排时,我们先调用了静态类AgentWorkflowBuilderCreateHandoffBuilderWith方法根据TriageAgentt创建了一个HandoffWorkflowBuilder对象。顾名思义,HandoffWorkflowBuilder是一个用来构建Handoff模式Workflow的Builder对象。我们在调用Build方法之前,通过调用WithHandoffs方法将注册的了如下两种交接关系

  • TriageAgent可以将任务交接给三个创作Agent(唐诗、宋词、短篇小说),一个Superstep只有会有一个任务被交接;
  • 三个创作Agent(唐诗、宋词、短篇小说)在完成任务之后回到TriageAgent,后者继续安排下一步的任务交接。

在以流的方式调用Workflow的时候,我们指定了创作任务:根据诗经名篇《卫风·氓》的背景和情感基调,分别创作一首唐诗和一首宋词。在调用StreamingRunTrySendMessageAsync方法发送一个TurnToken作为发令枪之后,我们监听AgentResponseUpdateEvent事件,并输出它们的输出。

var originalPoem = """    
    氓之蚩蚩,抱布贸丝。匪来贸丝,来即我谋。
    送子涉淇,至于顿丘。匪我愆期,子无良媒。
    将子无怒,秋以为期。

    乘彼垝垣,以望复关。不见复关,泣涕涟涟。
    既见复关,载笑载言。尔卜尔筮,体无咎言。
    以尔车来,以我贿迁。

    桑之未落,其叶沃若。于嗟鸠兮,无食桑葚!
    于嗟女兮,无与士耽!士之耽兮,犹可说也;
    女之耽兮,不可说也。

    桑之落矣,其黄而陨。自我徂尔,三岁食贫。
    淇水汤汤,渐车帷裳。女也不爽,士贰其行。
    士也罔极,二三其德。

    三岁为妇,靡室劳矣;夙兴夜寐,靡有朝矣。
    言既遂矣,至于暴怒。兄弟不知,咥其笑矣。
    静言思之,躬自悼矣。

    及尔偕老,老使我怨。淇则有岸,隰则有泮。
    总角之宴,言笑晏晏。信誓旦旦,不思其反。
    反是不思,亦已焉哉!    
    """;

var prompt = $"""
    基于如下这首《卫风·氓》的背景和情感基调,分别创作一首唐诗和一首宋词。

    原文如下:
    {originalPoem}
    """;

await using (var run = await InProcessExecution.Default.RunStreamingAsync(workflow, prompt))
{
    await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
    string? lastExecutorId = null;
    await foreach (WorkflowEvent evt in run.WatchStreamAsync())
    {
        if (evt is AgentResponseUpdateEvent e)
        {
            if (e.ExecutorId != lastExecutorId)
            {
                lastExecutorId = e.ExecutorId;
                Console.WriteLine($"\n\n{new string('-', 20)}{e.ExecutorId}{new string('-', 20)}");
            }
            Console.Write(e.Update.Text);
        }
    }
}

输出:


--------------------Triage_d24e6cf6a08947079b393a61a85886f4--------------------
好的,这是一首典型的弃妇诗,情感深沉哀婉。我将同时为您转交给唐诗和SongLyricsAgent来处理

--------------------TangPoetryComposer_8cc635f9df8c43828fe2e0ba193f52f3--------------------
好的!《卫风·氓》确是一首经典的弃妇诗,情感层次丰富——从初恋的甜蜜、婚后的辛劳,到被弃的哀怨与最终的决绝。我将同时为您转交给唐诗和SongLyricsAgent来分别处理

--------------------Triage_d24e6cf6a08947079b393a61a85886f4--------------------
创作任务已由唐诗Agent接单,接下来也将宋词部分转交出去。

--------------------SongLyricsComposer_4a63e6d96ca54e39bf55289b30f5561b--------------------
好的!现在由我来为您创作宋词。基于《卫风·氓》的背景与情感基调——弃妇的哀怨与决绝,我选择用 **《蝶恋花》** 这一词牌来呈现。
---

## 《蝶恋花·淇水怨》

**淇水汤汤烟渺渺,忆昔春深,陌上青梅小。**
**抱布人来憨笑早,谁知暗把佳期讨。**

**三载糟糠颜色槁,夙夜辛劳,霜鬓催人老。**
**信誓如风花事了,决然一别休烦恼!**

---

### 创作说明:

- **上阕**对应原诗中"氓之蚩蚩,抱布贸丝"至"以尔车来,以我贿迁",以淇水起兴,追忆初恋时那人憨厚模样与暗订终身的甜蜜。

- **下阕**对应原诗"三岁为妇,靡室劳矣"至"反是不思,亦已焉哉",写婚后贫苦辛劳、容颜衰老,而那人却"信誓旦旦,不思其反",最终以决绝之笔收束——"决然一别休烦恼",呼应原诗"亦已焉哉"的清醒与刚烈。

- 全词以**淇水**贯穿,首尾呼应,既有哀婉缠绵,亦有斩断前尘的骨气。

虽然背后的执行流程没有输出开起来那么简单,但是我们依然可以看出整个任务执行的大致流程:

  • TriageAgent根据用户的输入,将任务交给了TangPoetryAgent
  • TangPoetryAgent在完成任务之后回到TriageAgent,后者继续安排下一步的任务交接;
  • TriageAgent将任务交给了SongLyricsAgent
  • SongLyricsAgent在完成任务之后回到TriageAgent,后者继续安排下一步的任务交接;
  • TriageAgent认为整个任务已经完成,在没有任务输出的情况下退出。

2. 看看Workflow的拓扑结构

要了解Workflow如何编排,肯定得先了解一下编排生成得Workflow究竟具有什么样的拓扑结构。我们依赖使用面定义的如下这个GenerateAndShowPngImageAsync方法将指定的Workflow对象转换成Mermaid图形,并将其保存为PNG图片文件。然后我们打开这个图片文件就可以看到Workflow的拓扑结构了。

public static class Utilities
{
    public static async Task GenerateAndShowPngImageAsync(Workflow workflow)
    {
        string mermaidCode = workflow.ToMermaidString();
        byte[] bytes = Encoding.UTF8.GetBytes(mermaidCode);
        string base64 = Convert.ToBase64String(bytes);
        string safeBase64 = base64.Replace("+", "-").Replace("/", "_").TrimEnd('=');
        string url = $"https://mermaid.ink/img/{safeBase64}";

        using (HttpClient client = new())
        {
            byte[] imageBytes = await client.GetByteArrayAsync(url);
            await File.WriteAllBytesAsync("workflow.png", imageBytes);
        }
        Process.Start(new ProcessStartInfo("workflow.png") { UseShellExecute = true });
    }
}

上面以Handoff模式编排的Workflow具有如下的拓扑结构。可以看出,除了我们指定的四个AIAgent对应的节点外,编排时还创建了额外两个作为起始和终止的节点,分别命名为HandoffStartHandoffEnd。Workflow具有一个类似于Mesh的拓扑结构,它没有一个中心协调器,节点完全根据注册的交接关系进行路由。TriageAgent节点与其他四个节点是一个Switch-Case模式的FanOutEdge,由于一个Superstep只允许一次任务交接,每个Case只有一个输出。这一个规则适用于所有的交接关系:当前节点与交接目标节点之间Switch-Case模式的FanOutEdge,在进行路由时,只有一个唯一的目标节点满足路由条件,并被选择作为下一个执行节点。从消息交换的角度来讲,任务交接采用的是广播模式,但是真正的目标却只有一个

Alternative Text

3. 任务交接是如何实现的?

对于任何一个涉及任务交接的节点,要实现精准的任务交接,需要完成如下两个关键任务:

  • 如何确定众多目标节点中哪一个是最合适目前的任务;
  • 在进行路由广播的时候,如何确保只有一个目标节点被选中作为下一个执行节点。

3.1 如何确定最合适的目标节点

直接说答案:Agent节点利用动态注册的工具,让LLM通过执行工具的方式来确定最合适的目标节点。以TriageAgent为例,由于它的交接目标节点是三个创作Agent(唐诗、宋词、短篇小说),所以它会注册如下三个工具函数:

  • handoff-1:
    • description:handoff to TangPoetryComposer
    • parameters:the reason for the handoff
  • handoff-2:
    • description:handoff to SongLyricsComposer
    • parameters:the reason for the handoff
  • handoff-3:
    • description:handoff to NovelComposer
    • parameters:the reason for the handoff

有两点需要注意:

  • 工具函数统一采用handoff-序号的命名方式,序号从1开始,依次递增;
  • 工具函数的参数是一个字符串类型的参数,表示交接的原因,在调用WithHandoffs方法注册交接关系时可以通过对应的参数指定。

TriageAgent接收到用户的输入后,让会将其作为输入调用LLM。为了提示LLM通过分析原始输入来决定任务类型,并通过调用对应的工具做出选择,在我们指定的系统指令后面还会附加一段额外的指令文本。完整的提示词如下所示。这段额外的指令文本并非TriageAgent所有,所有涉及任务交接的Agent节点会在系统指令后面附加这么一段。

你仅仅是一个TriageAgent,你唯一的任务是根据用户的输入,判断应该将任务交给哪个创作Agent(唐诗、宋词、短篇小说)。
如果任务提及了诗、词和小说创作任务,直接将给对应的Agent,具体的创作任务与你无关。
赋予你在无需用户确认的情况下,执行执行任务转交的权限。
You are one agent in a multi-agent system. You can hand off the conversation to another agent if appropriate. Handoffs are achieved
by calling a handoff function, named in the form `handoff_to_<agent_id>`; the description of the function provides details on the
target agent of that handoff. Handoffs between agents are handled seamlessly in the background; never mention or narrate these handoffs
in your conversation with the user.

LLM相应的内容会携带对应的工具函数的调用,由于当前节点维护了一组目标节点ID和工具函数名称之间的映射关系,所以它只要提取出工具函数名称,就可以确定最合适的目标节点了。为了确保消息结构的合法性,它会在对话历史中添加一个内容固定为Transferred的Tool消息。

3.2 如何确保只有一个目标节点被选中

交接的目标节点确定之后,为了通过Workflow的消息路由机制将任务交接给这个目标节点,当前节点会像所有下游节点广播一个HandoffState对象,它包含了当前的TurnToken、交接的目标节点ID以及前一个执行节点ID。

internal sealed record HandoffState(
    TurnToken TurnToken, 
    string? RequestedHandoffTargetAgentId, 
    string? PreviousAgentId = null);

下面的代码体现了这条用来完成任务交接的FanOutEdge的注册方式,可以看出每个Case的路由条件是HandoffState对象的RequestedHandoffTargetAgentId属性与目标节点ID相等,并且HandoffState对象的IsTerminated属性不为true。考虑到当前的任务与所有的交接目标节点都不匹配的情况,FanOutEdge还注册了一个默认的Case,它将任务交接给HandoffEndExecutor节点,这个节点的作用是终止整个Workflow的执行。FanOutEdge这样的注册方式确保了任务被精准地交接给了唯一的目标节点。

builder.AddSwitch(HandoffAgentExecutor.IdFor(agent), (SwitchBuilder sb) =>
{
    foreach (HandoffTarget handoff in handoffs)
    {
        string targetAgentId = handoff.Target.Id;
        sb.AddCase<HandoffState>(state =>
            state?.RequestedHandoffTargetAgentId == targetAgentId 
            && state.IsTerminated != true,
            HandoffAgentExecutor.IdFor(handoff.Target)); 
    }
    sb.WithDefault(HandoffEndExecutor.ExecutorId);
});

3.3 从对话历史角度解读任务交接

综上所述,在一个基于Handoff模式构建的Workflow中,某个Agent节点采用如下的执行方式:

  • 将对话历史作为核心输入,同时携带附加了任务交接的系统指令和用来确定交接目标的工具函数列表,调用LLM;
  • LLM根据输入给与对应的答复(比如完成诗词的创作)。如果需要将任务交接给其他Agent,它会在答复中携带对应的工具函数调用;
  • Agent节点在接收到作为响应的Assistant消息后,如果没有针对交接工具的调用,会直接返回;反之会根据调用工具的名称得到下一个交接的Agent节点;
  • 为了确保对话历史的合法有效,Agent节点会在对话历史中添加一个内容固定为Transferred的Tool消息;
  • 它针对交接的目标节点创建一个HandoffState对象,并将其广播出去。
  • 作为唯一满足路由规则的目标节点继续按照上述的流程执行;

对于上面介绍的关于Handoff模式采用的任务交接流程,我们可以采用如下的方式从整个流程生成的消息来印证。如代码片段所示,我们在调用Workflow的RunAsync方法之后,提取出所有的WorkflowOutputEvent事件,并从中获取所有的ChatMessage消息。然后我们遍历这些消息,并根据消息的类型输出不同的内容。

var response = await InProcessExecution.Default.RunAsync(workflow, prompt);
var messages = response.NewEvents.OfType<WorkflowOutputEvent>().SelectMany(e => e.As<IEnumerable<ChatMessage>>() ?? []);

var index = 1;
foreach (var message in messages)
{
    var role = message.Role;
    Console.WriteLine($"\n\n{new string('-', 20)}[{role}]Message {index++}{new string('-', 20)}");
    foreach(var content in message.Contents)
    {
        var text = content switch
        {
            FunctionCallContent functionCallContent => $"Function Call: {functionCallContent.Name}({string.Join(", ", functionCallContent.Arguments!.Select(kv => $"{kv.Key}: {kv.Value}"))})",
            FunctionResultContent functionResultContent => $"Function Result: {functionResultContent.Result}",
            _ => content.ToString(),
        };
        Console.WriteLine(text);
    };    
}

输出:

--------------------[user]Message 1--------------------
基于如下这首《卫风·氓》的背景和情感基调,分别创作一首唐诗和一首宋词。

原文如下:
氓之蚩蚩,抱布贸丝。匪来贸丝,来即我谋。
送子涉淇,至于顿丘。匪我愆期,子无良媒。
将子无怒,秋以为期。

乘彼垝垣,以望复关。不见复关,泣涕涟涟。
既见复关,载笑载言。尔卜尔筮,体无咎言。
以尔车来,以我贿迁。

桑之未落,其叶沃若。于嗟鸠兮,无食桑葚!
于嗟女兮,无与士耽!士之耽兮,犹可说也;
女之耽兮,不可说也。

桑之落矣,其黄而陨。自我徂尔,三岁食贫。
淇水汤汤,渐车帷裳。女也不爽,士贰其行。
士也罔极,二三其德。

三岁为妇,靡室劳矣;夙兴夜寐,靡有朝矣。
言既遂矣,至于暴怒。兄弟不知,咥其笑矣。
静言思之,躬自悼矣。

及尔偕老,老使我怨。淇则有岸,隰则有泮。
总角之宴,言笑晏晏。信誓旦旦,不思其反。
反是不思,亦已焉哉!

--------------------[assistant]Message 2--------------------
好的,我收到了两个创作任务:一首唐诗和一首宋词,都基于《卫风·氓》的背景和情感基调。我现在将它们分别转交给对应的创作智能体。
Function Call: handoff_to_1(reasonForHandoff: 基于《卫风·氓》创作一首唐诗)

--------------------[tool]Message 3--------------------
Function Result: Transferred.

--------------------[assistant]Message 4--------------------
好的,我将这两项创作任务分别转交给唐诗创作智能体和宋词创作智能体。
Function Call: handoff_to_1(reasonForHandoff: 用户请求基于《卫风·氓》分别创作一首唐诗和一首宋词。现在需要将唐诗创作部分转交给唐诗创作智能体,将宋词创作部分转交给宋词创作智能体。)

--------------------[tool]Message 5--------------------
Function Result: Transferred.

--------------------[assistant]Message 6--------------------
唐诗创作任务已转交,接下来为您转交宋词创作任务
Function Call: handoff_to_2(reasonForHandoff: 基于《卫风·氓》创作一首宋词)

--------------------[tool]Message 7--------------------
Function Result: Transferred.

--------------------[assistant]Message 8--------------------


好的,现在由我来为您创作基于《卫风·氓》的宋词。我选用词牌 **《钗头凤》**——此调本为陆游悼念爱侣之作,声情凄婉急促,正合此篇弃妇自伤、怨而不怒的基调。

---

**《钗头凤·卫风怨》**

**抱丝来,笑颜开,顿丘烟雨送君回。**
**垝垣望,泪千行,一帘幽梦,几度秋霜。**
**茫,茫,茫。**

**桑叶落,人成各,三更机杼空垂络。**
**淇汤汤,心凉凉,旧誓犹在,薄情难藏。**
**伤,伤,伤。**

---

**【简注】** :
- 上阕追忆昔日初恋之甜蜜——"抱丝""顿丘""垝垣"皆化用原诗情节,末以三叠"茫"字,道尽女子回首前尘、恍如隔世之感。
- 下阕写婚后之苦与见弃之痛——"桑叶落"喻色衰爱弛,"淇汤汤"应原诗"淇水汤汤"之景,"心凉凉"直抒其哀,三叠"伤"字,将《氓》中"躬自悼矣"与"亦已焉哉"的决绝悲凉收束于无言。

输出的八条消息体现了Handoff模式的任务交接流程:

  • TriageAgent在接收到用户的输入后,按照上述流程将任务转交给了TangPoetryAgent
  • TangPoetryAgent在完成任务之后回到TriageAgent,按照上述流程将控制权交回给TriageAgent
  • TriageAgent按照上述流程将任务转交给了SongLyricsAgent
  • SongLyricsAgent在完成任务之后回到TriageAgent,按照上述流程将控制权交回给TriageAgent
  • TriageAgent认为整个任务已经完成,在没有任务输出的情况下退出。

3. 基于Handoff模式的Workflow的构建

静态类AgentWorkflowBuilderCreateHandoffBuilderWith方法根据起始Agent创建了一个HandoffWorkflowBuilder对象,并由后者的Build方法生成了一个Workflow对象。HandoffWorkflowBuilder继承自HandoffWorkflowBuilderCore类,后者定义了构建Handoff模式Workflow的各种方法。

public sealed class HandoffWorkflowBuilder : HandoffWorkflowBuilderCore<HandoffWorkflowBuilder>
{
	public HandoffWorkflowBuilder(AIAgent initialAgent)
		: base(initialAgent)
	{
	}
}

public class HandoffWorkflowBuilderCore<TBuilder> : OrchestrationBuilderBase<TBuilder>
    where TBuilder : HandoffWorkflowBuilderCore<TBuilder>
{
    public TBuilder WithHandoffInstructions(string? instructions);
    public TBuilder EmitAgentResponseUpdateEvents(bool emitAgentResponseUpdateEvents = true);
    public TBuilder EmitAgentResponseEvents(bool emitAgentResponseEvents = true);
    public TBuilder WithToolCallFilteringBehavior(HandoffToolCallFilteringBehavior behavior);
    public TBuilder EnableReturnToPrevious();
    public TBuilder WithHandoffs(AIAgent from, IEnumerable<AIAgent> to);
    public TBuilder WithHandoffs(IEnumerable<AIAgent> from, AIAgent to, string? handoffReason = null);
    public TBuilder WithHandoff(AIAgent from, AIAgent to, string? handoffReason = null);
    public TBuilder AddParticipants(params IEnumerable<AIAgent> agents);
    public TBuilder WithAutonomousMode(
        int? turnLimit = null,
        string? continuationPrompt = null,
        IEnumerable<AIAgent>? agents = null,
        IReadOnlyDictionary<AIAgent, int>? agentTurnLimits = null,
        IReadOnlyDictionary<AIAgent, string>? agentContinuationPrompts = null);
    public TBuilder WithTerminationCondition(Func<IReadOnlyList<ChatMessage>, bool> terminationCondition);
    public TBuilder WithTerminationCondition(Func<IReadOnlyList<ChatMessage>, ValueTask<bool>> terminationCondition);
    public Workflow Build();
}

相关方法说明如下:

  • WithHandoffInstructions:指定在进行任务交接时附加的系统指令;
  • WithName:指定Workflow的名称;
  • WithDescription:指定Workflow的描述信息;
  • EmitAgentResponseUpdateEvents:指定是否在执行过程中发出AgentResponseUpdateEvent事件;
  • EmitAgentResponseEvents:指定是否在执行过程中发出AgentResponseEvent事件;
  • WithToolCallFilteringBehavior:指定在执行过程中如何处理工具函数的调用;
  • EnableReturnToPrevious:指定是否允许在任务交接之后返回到前一个执行节点;
  • WithHandoffs/WithHandleoff:指定任务交接的关系;
  • AddParticipants:指定参与Workflow的Agent,由于没有指定交接关系,这样的节点将默认与其他节点进行连接;
  • WithAutonomousMode:指定Workflow的自主模式;
  • WithTerminationCondition:指定Workflow的终止条件。

由于交接过程涉及到工具调用,所以整个对话历史的消息会出现很多基于工具调用和响应的内容。由于这些消息基本上只服务于任务交接,如果在调用LLM一股脑地将整个对话历史作为输入,这些内容可能对LLM的推理照成干扰。为了避免这种情况,HandoffWorkflowBuilder提供了WithToolCallFilteringBehavior方法指定不同的策略过滤这些消息内容。具体的过滤策略由HandoffToolCallFilteringBehavior枚举类型定义:

public enum HandoffToolCallFilteringBehavior
{
    None,
    FilterToolMessages,
    FilterAllToolCalls,
}

三个选项体现了对应的消息过滤规则:

  • None:不进行任何过滤,整个对话历史原封不动地传递给LLM;
  • FilterToolMessages:过滤掉所有与任务交接工具(工具函数的名称以handoff_to作为前缀)相关的内容;
  • FilterAllToolCalls:过滤掉所有的工具调用消息和结果消息。

WithAutonomousMode方法是自动循环控制开关—,它决定一个Agent在没有发起交接时,是否会被自动再次调用、调用几次、用什么提示词调用、哪些Agent参与这个自动模式。在自主模式开启的情况下,当某个Agent的响应没有针对交接函数调用时,EndExecutor会用一个再次调用同一个Agent,我们可以指定允许循环的阈值。这一功能是通过从EndExecutor节点添加连接其他Agent节点的FanOutEdge实现的。对于我们演示的例子,如果开启自主模式,最终生成的Workflow将具有如下所示的拓扑结构。

在这里插入图片描述

如果我们没有显式注册交接的关系,而是直接调用AddParticipants方法注册了四个Agent节点,相当于会建立一个全连接的图。如果同时开启了自主模式,构建的Workflow将具有如下的拓扑结构。

![Alternative Text][1782455544558]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值