AutoGen.NET 函数调用实战:为 OpenAIChatAgent 接入工具调用能力(FunctionCallMiddleware 与类型安全函数定义)
本篇指南基于 AutoGen.NET 的官方文档与配套示例,完整演示如何为 AutoGen.OpenAI.OpenAIChatAgent 接入函数调用(Function Calling)能力:先通过源生成器以类型安全的方式定义 GetWeather 函数,再用 OpenAIChatRequestMessageConnector 与 FunctionCallMiddleware 把 ToolCallMessage / ToolCallResultMessage 串进 Agent 的消息流,最终实现"LLM 发起工具调用 → 框架自动执行函数 → 把结果交还给模型"的完整闭环。读完后你能够独立复现这一流程,并理解每个组件在源码层面的职责边界。
准备依赖:AutoGen.OpenAI 与 AutoGen.SourceGenerator
首先需要安装以下两个 NuGet 包(AUTOGEN_VERSION 为版本占位符,使用时替换为当前仓库实际发布的版本号):
<ItemGroup>
<PackageReference Include="AutoGen.OpenAI" Version="AUTOGEN_VERSION" />
<PackageReference Include="AutoGen.SourceGenerator" Version="AUTOGEN_VERSION" />
</ItemGroup>
[!Note]
AutoGen.SourceGenerator包内置了一个 Roslyn 源生成器,用于自动生成类型安全的函数定义(即FunctionContract属性与参数反序列化包装方法)。更多细节可参考仓库中的 Create type-safe function call 一文。[!NOTE] 如果你使用 VSCode 作为编辑器,可能需要重启编辑器才能看到生成的代码。
这两个包分别承担了两个职责:
AutoGen.OpenAI:提供 OpenAIChatAgent(封装 OpenAI SDK 的ChatClient的流式聊天 Agent)和 OpenAIChatRequestMessageConnector(负责 AutoGen 消息类型与 OpenAIChatMessage之间的双向转换)。AutoGen.SourceGenerator:提供 FunctionCallGenerator 源生成器,在编译期为标记了[Function]的方法生成契约代码。
定义类型安全函数:[Function] 特性与源生成器
导入所需的命名空间(见示例文件 OpenAICodeSnippet.cs):
using AutoGen.Core;
using AutoGen.OpenAI;
using AutoGen.OpenAI.Extension;
然后定义一个 public partial class:Functions,其中包含 GetWeather 方法:
public partial class Functions
{
[Function]
public async Task<string> GetWeather(string location)
{
return "The weather in " + location + " is sunny.";
}
}
几个关键点:
- 类必须是
partial:源生成器 FunctionCallGenerator 在扫描语法树时,只处理partial修饰的类,并为每个匹配类追加一段同名 partial 扩展(生成的文件名形如{className}_{guid}.generated.cs)。 [Function]特性:定义于 FunctionAttribute.cs,可选参数为functionName与description;不传时,函数名取方法名,描述优先取Description命名参数,其次从 XML 文档注释的<summary>段落提取(参数描述同理取自<param>段),都取不到时退化为方法前置注释或方法名本身。- 源生成器产物:从 FunctionCallTemplate.tt 模板可以看出,针对
GetWeather方法,编译期会在Functions类中追加两类成员:GetWeatherFunctionContract:一个返回FunctionContract(含名称、描述、返回类型、参数契约列表)的属性,供FunctionCallMiddleware向模型声明工具;GetWeatherWrapper(string arguments):把模型返回的 JSON 参数字符串反序列化为参数对象(CamelCase命名策略)后,调用真正的GetWeather方法。- 参数类型到 JSON Schema 的映射也在生成器中固定(见 FunctionCallGenerator.cs):
string→string、string[]→array、int/long→integer、float/double→number、bool→boolean、DateTime/Guid→string、其余类型默认object;带默认值的参数会被标记为可选。
FunctionContract 与 FunctionParameterContract 的完整定义见 FunctionAttribute.cs,它还提供了到 Microsoft.Extensions.AI.AIFunction 的隐式转换,便于与其他函数调用框架互通。
创建 OpenAIChatAgent 并注册消息连接器
接下来创建 OpenAIChatAgent 并通过扩展方法 RegisterMessageConnector() 注册 OpenAIChatRequestMessageConnector,使其支持 AutoGen.Core.ToolCallMessage 与 AutoGen.Core.ToolCallResultMessage。这两类消息是 FunctionCallMiddleware 处理函数调用的基础:
var openAIKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new Exception("Please set OPENAI_API_KEY environment variable.");
var modelId = "gpt-3.5-turbo";
var openAIClient = new OpenAIClient(openAIKey);
// create an open ai chat agent
var openAIChatAgent = new OpenAIChatAgent(
chatClient: openAIClient.GetChatClient(modelId),
name: "assistant",
systemMessage: "You are an assistant that help user to do some tasks.")
.RegisterMessageConnector();
构造函数参数说明(来自 OpenAIChatAgent.cs 的签名):
| 参数 | 说明 |
|---|---|
chatClient |
OpenAI SDK 的 ChatClient,决定实际调用的模型 |
name |
Agent 名称,用于消息的 From 溯源 |
systemMessage |
系统消息,默认 "You are a helpful AI assistant";若历史消息中已包含 SystemChatMessage 则不会重复注入 |
temperature / maxTokens / seed |
采样温度、最大输出 token、确定性种子,均可选 |
responseFormat |
设为 ChatResponseFormat 可启用 JSON 模式 |
functions |
可直接传入 IEnumerable<ChatTool> 的工具定义,是另一种不经过中间件的声明方式 |
RegisterMessageConnector() 本身是一个扩展方法(OpenAIAgentExtension.cs),内部就是 agent.RegisterStreamingMiddleware(connector),返回类型为 MiddlewareStreamingAgent<OpenAIChatAgent>。
从 OpenAIChatRequestMessageConnector 的源码可以确认它在函数调用链中的两个核心转换:
- 入站:
ProcessIncomingMessages支持TextMessage、ImageMessage、MultiModalMessage、ToolCallMessage、ToolCallResultMessage以及AggregateMessage<ToolCallMessage, ToolCallResultMessage>等类型,并把它们转换成 OpenAI 的ChatMessage;其中ToolCallMessage会被转为带工具调用部分的AssistantChatMessage,ToolCallResultMessage会被逐条转为ToolChatMessage。 - 出站:
PostProcessChatResponseMessage检查模型返回——若ChatCompletion.ToolCalls非空,则输出 AutoGen 的ToolCallMessage(保留ToolCallId与可选文本);否则输出TextMessage。正是这一步把"模型想调工具"翻译成中间件可以识别的ToolCallMessage。
创建 FunctionCallMiddleware 并注册到 Agent
创建 FunctionCallMiddleware,传入 GetWeather 函数,并注册到上面的 Agent:
var functions = new Functions();
var functionCallMiddleware = new FunctionCallMiddleware(
functions: [functions.GetWeatherFunctionContract], // GetWeatherFunctionContract is auto-generated from the GetWeather function
functionMap: new Dictionary<string, Func<string, Task<string>>>
{
{ functions.GetWeatherFunctionContract.Name, functions.GetWeatherWrapper } // GetWeatherWrapper is a wrapper function for GetWeather, which is also auto-generated
});
openAIChatAgent = openAIChatAgent.RegisterStreamingMiddleware(functionCallMiddleware);
注意 functionMap 的意义:它声明了"当 Agent 回复了一个 GetWeather 的函数调用时,由框架自动执行该函数"。这里传入的 GetWeatherWrapper 就是源生成器自动生成的包装方法,因此参数反序列化是类型安全的,无需手写 JSON 解析。
FunctionCallMiddleware 的实现逻辑值得深入看一下(FunctionCallMiddleware.cs):
- 拦截入站消息:如果上下文最后一条消息是
ToolCallMessage,且其函数都在functionMap中,则直接执行这些函数并返回ToolCallResultMessage,内层 Agent 被短路、不会发起 LLM 请求;若某个函数不在functionMap中,会生成一条包含 "Function {name} is not available. Available functions are: ..." 的错误结果消息。 - 注入函数声明:调用前会把中间件持有的
functions与GenerateReplyOptions.Functions合并后传给内层 Agent,最终由OpenAIChatAgent.CreateChatCompletionsOptions转为 OpenAI 的ChatTool列表。 - 处理模型回复:若内层 Agent 回复的是
ToolCallMessage且函数在functionMap中,则执行函数并把"工具调用消息 + 工具结果消息"打包为ToolCallAggregateMessage返回;否则原样返回模型回复。 - 流式支持:作为
IStreamingMiddleware,它会把流式的ToolCallMessageUpdate增量合并为完整的ToolCallMessage后再触发函数执行(见 InvokeAsync)。
构造函数还支持两种等价写法:直接传 FunctionContract 集合 + functionMap 字典(本例),或传 IEnumerable<AIFunction> 集合由框架自动建立 functionMap(参数通过 JSON 反序列化绑定,见 FunctionCallMiddleware.cs 与 AIToolInvokeWrapper)。
与 Agent 对话并验证函数调用
最后,向 Agent 提问即可触发 GetWeather 函数调用:
var reply = await openAIChatAgent.SendAsync("what is the weather in Seattle?");
reply.GetContent().Should().Be("The weather in Seattle is sunny.");
reply.GetToolCalls().Count.Should().Be(1);
reply.GetToolCalls().First().Should().Be(this.GetWeatherFunctionContract.Name);
官方示例测试用 FluentAssertions 断言了三件事:最终回复文本是函数返回值(说明工具结果已经被送回模型并综合成自然语言);本次回复携带了 1 个工具调用记录;该工具调用名与 GetWeather 函数契约名一致。完整的可运行代码位于 OpenAICodeSnippet.cs(OpenAIChatAgentGetWeatherFunctionCallAsync 方法)。
一次典型调用在消息流上的路径是:
SendAsync("what is the weather in Seattle?")
→ FunctionCallMiddleware 合并函数声明
→ OpenAIChatRequestMessageConnector 转成 ChatMessage
→ OpenAIChatAgent 调用 LLM,返回 ToolCalls
← Connector 将其还原为 ToolCallMessage
← FunctionCallMiddleware 查 functionMap,执行 GetWeatherWrapper
→ 返回 ToolCallAggregateMessage(ToolCallMessage + ToolCallResultMessage)
→ 结果消息经 Connector 转为 ToolChatMessage 再次提交给 LLM
→ LLM 基于 "The weather in Seattle is sunny." 生成自然语言回答
小结与延伸阅读
本流程中三个组件各司其职:源生成器负责类型安全的函数契约生成,OpenAIChatRequestMessageConnector 负责 AutoGen 消息与 OpenAI 协议消息的转换,FunctionCallMiddleware 负责函数声明注入与自动执行。相关源码与文档入口:
- 官方文档:OpenAIChatAgent 使用函数调用、创建类型安全函数、函数调用总览
- 核心实现:FunctionCallMiddleware.cs、OpenAIChatRequestMessageConnector.cs、OpenAIChatAgent.cs
- 源生成器:FunctionCallGenerator.cs、FunctionCallTemplate.tt
- 示例代码:OpenAICodeSnippet.cs
运行前提:设置 OPENAI_API_KEY 环境变量,并具备可调用目标模型(示例中为 gpt-3.5-turbo)的访问权限。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00