AutoGen(.NET) 中基于 Gemini 实现函数调用:AutoGen.Gemini 工具调用(Function Calling)实战指南
【免费下载链接】autogenA programming framework for agentic AI项目地址: https://gitcode.com/GitHub_Trending/au/autogen
本文围绕 AutoGen(.NET 版)的AutoGen.Gemini包,讲解如何让GeminiChatAgent完成函数调用(Function Calling):从 NuGet 依赖安装、使用AutoGen.SourceGenerator生成类型安全的函数契约、通过 Vertex AI 创建带ToolConfig的 Gemini Agent,到单轮与多轮工具调用的完整代码流程,并结合GeminiMessageConnector、FunctionContractExtension等源码剖析消息角色映射与工具声明转换的底层原理。读完本文,你可以在 .NET 项目中复现一个能够响应"查电影/查影院/查场次"等自然语言请求、自动触发 C# 函数并返回最终答案的 Gemini 工具调用 Agent。
一、前置条件与运行环境
本示例基于 Google Vertex AI 提供的 Gemini 模型运行函数调用(Function Calling),示例逻辑改编自 Google 官方 Gemini API 的 function calling 教程。运行前需要满足:
- 拥有一个 Google Cloud 项目,并开通了 Vertex AI API 访问权限;
- 在运行环境设置环境变量
GCP_VERTEX_PROJECT_ID(示例代码会读取该变量,若未设置则直接退出并提示):
export GCP_VERTEX_PROJECT_ID="your-gcp-project-id" # Linux/macOS # Windows PowerShell: # $env:GCP_VERTEX_PROJECT_ID = "your-gcp-project-id"示例的完整可运行代码见 Function_Call_With_Gemini.cs,下文各步骤代码均取自该文件对应的#region片段。
二、Step 1:安装 AutoGen.Gemini 与 AutoGen.SourceGenerator
使用以下命令安装两个 NuGet 包:
dotnet add package AutoGen.Gemini dotnet add package AutoGen.SourceGeneratorAutoGen.Gemini:提供GeminiChatAgent、GoogleGeminiClient、VertexGeminiClient及消息转换中间件;AutoGen.SourceGenerator:用于自动生成AutoGen.Core.FunctionContract(函数契约)。它是一个 Roslyn 源生成器:只要给方法打上Function特性,就会基于方法签名和 XML 文档注释生成函数定义与类型安全的调用包装器。其使用细节参见同仓库文档 Create-type-safe-function-call 及 AutoGen.SourceGenerator 说明。
建议配置:为了让源生成器读取方法的 XML 文档注释(函数描述、参数说明会进入函数契约),在 csproj 中开启结构化文档生成:
<PropertyGroup> <!-- This enables structural xml document support --> <GenerateDocumentationFile>true</GenerateDocumentationFile> </PropertyGroup>
三、Step 2:添加 using 语句
using AutoGen.Core; using Google.Cloud.AIPlatform.V1;AutoGen.Core:提供TextMessage、Role、FunctionCallMiddleware等核心消息与中间件类型;Google.Cloud.AIPlatform.V1:提供 Vertex AI 的 protobuf 类型,例如ToolConfig、FunctionCallingConfig,示例中创建 Agent 时会用到。
四、Step 3:创建MovieFunction函数集
示例定义了一个MovieFunction类,包含三个模拟"电影查询业务"的函数,模拟 Google 官方教程中的电影查询场景:
public partial class MovieFunction { /// <summary> /// find movie titles currently playing in theaters based on any description, genre, title words, etc. /// </summary> /// <param name="location">The city and state, e.g. San Francisco, CA or a zip code e.g. 95616</param> /// <param name="description">Any kind of description including category or genre, title words, attributes, etc.</param> /// <returns></returns> [Function] public async Task<string> FindMovies(string location, string description) { // dummy implementation var movies = new List<string> { "Barbie", "Spiderman", "Batman" }; var result = $"Movies playing in {location} based on {description} are: {string.Join(", ", movies)}"; return result; } /// <summary> /// find theaters based on location and optionally movie title which is currently playing in theaters /// </summary> /// <param name="location">The city and state, e.g. San Francisco, CA or a zip code e.g. 95616</param> /// <param name="movie">Any movie title</param> [Function] public async Task<string> FindTheaters(string location, string movie) { // dummy implementation var theaters = new List<string> { "AMC", "Regal", "Cinemark" }; var result = $"Theaters playing {movie} in {location} are: {string.Join(", ", theaters)}"; return result; } /// <summary> /// Find the start times for movies playing in a specific theater /// </summary> /// <param name="location">The city and state, e.g. San Francisco, CA or a zip code e.g. 95616</param> /// <param name="movie">Any movie title</param> /// <param name="theater">Name of the theater</param> /// <param name="date">Date for requested showtime</param> /// <returns></returns> [Function] public async Task<string> GetShowtimes(string location, string movie, string theater, string date) { // dummy implementation var showtimes = new List<string> { "10:00 AM", "12:00 PM", "2:00 PM", "4:00 PM", "6:00 PM", "8:00 PM" }; var result = $"Showtimes for {movie} at {theater} in {location} are: {string.Join(", ", showtimes)}"; return result; } }对应源码位置:Function_Call_With_Gemini.cs#L13-L64。
编写这三个函数时需要注意源生成器的约束:
| 约束 | 说明 |
|---|---|
类必须是public partial | 源生成器需要 partial 类来注入生成的代码 |
方法必须是public实例方法,返回Task<string> | 函数调用包装器以字符串结果回传给模型 |
| 参数建议使用基本类型 | 从源生成器文档看,这是出于性能与 JSON 序列化稳定性的考虑 |
| 必须提供 XML 文档注释 | 方法<summary>与参数<param>注释会被写入函数契约,直接影响模型选择函数与填参的准确性 |
编译后,源生成器会为每个方法生成两个成员(以FindMovies为例):
FindMoviesFunctionContract:AutoGen.Core.FunctionContract,包含函数名、描述、参数元数据,是与具体 LLM 无关的中间表示;FindMoviesWrapper(string arguments):类型安全包装器,内部先把模型返回的 JSON 参数反序列化为参数对象,再调用真正的FindMovies方法。
这与 AutoGen.SourceGenerator README 中描述的生成模式一致(生成XxxFunction定义与XxxWrapper包装器),本文示例使用的是AutoGen.Core的FunctionContract变体,可无缝接入FunctionCallMiddleware。
五、Step 4:创建带工具配置的 Gemini Agent
var projectID = Environment.GetEnvironmentVariable("GCP_VERTEX_PROJECT_ID"); if (projectID is null) { Console.WriteLine("Please set GCP_VERTEX_PROJECT_ID environment variable."); return; } var movieFunction = new MovieFunction(); var functionMiddleware = new FunctionCallMiddleware( functions: [ movieFunction.FindMoviesFunctionContract, movieFunction.FindTheatersFunctionContract, movieFunction.GetShowtimesFunctionContract ], functionMap: new Dictionary<string, Func<string, Task<string>>> { { movieFunction.FindMoviesFunctionContract.Name!, movieFunction.FindMoviesWrapper }, { movieFunction.FindTheatersFunctionContract.Name!, movieFunction.FindTheatersWrapper }, { movieFunction.GetShowtimesFunctionContract.Name!, movieFunction.GetShowtimesWrapper }, }); var geminiAgent = new GeminiChatAgent( name: "gemini", model: "gemini-1.5-flash-001", location: "us-central1", project: projectID, systemMessage: "You are a helpful AI assistant", toolConfig: new ToolConfig() { FunctionCallingConfig = new FunctionCallingConfig() { Mode = FunctionCallingConfig.Types.Mode.Auto, } }) .RegisterMessageConnector() .RegisterPrintMessage() .RegisterStreamingMiddleware(functionMiddleware);对应源码位置:Function_Call_With_Gemini.cs#L73-L112。
5.1 关键参数说明
这里使用的是面向 Vertex AI 的GeminiChatAgent构造函数(见 GeminiChatAgent.cs#L113-L134),参数含义如下:
| 参数 | 取值/说明 |
|---|---|
name | Agent 名称,示例为"gemini";消息连接器会用它区分"自己发出的"与"用户侧"消息 |
model | Gemini 模型名,如gemini-1.5-flash-001;构造函数内部会拼接为projects/{project}/locations/{location}/publishers/{provider}/models/{model}的完整资源路径,provider默认google |
location | 模型服务位置,示例为us-central1 |
project | GCP 项目 ID,来自环境变量 |
systemMessage | 系统指令,示例为"You are a helpful AI assistant";源码中它会被放入请求的SystemInstruction字段,而非普通对话轮次 |
toolConfig | 工具调用配置,核心是FunctionCallingConfig.Mode |
关于FunctionCallingConfig.Types.Mode:
Mode.Auto(示例所用):模型自行判断是否需要调用函数;Mode.Any:强制模型至少调用一个函数;Mode.None:禁用函数调用。
5.2 三个注册方法各自的作用
RegisterMessageConnector():注册GeminiMessageConnector,负责把 AutoGen 的TextMessage/ToolCallMessage/ToolCallResultMessage等消息双向翻译成 Gemini 的Content(user/model/function角色)。它是函数调用消息闭环的关键,后文展开;RegisterPrintMessage():打印消息中间件,便于在控制台观察对话过程;RegisterStreamingMiddleware(functionMiddleware):注册FunctionCallMiddleware。当模型返回函数调用请求时,中间件按functionMap中注册的委托实际执行对应 C# 方法,并把结果封装为工具调用结果消息回灌给 Agent,从而让模型基于真实返回继续作答。
六、Step 5:单轮函数调用(Single-turn)
var question = new TextMessage(Role.User, "What movies are showing in North Seattle tonight?"); var functionCallReply = await geminiAgent.SendAsync(question);// 断言:第一轮回复应当是工具调用聚合消息 functionCallReply.Should().BeOfType<ToolCallAggregateMessage>();流程说明:
- 用户消息
"What movies are showing in North Seattle tonight?"进入 Agent; - 由于
Mode.Auto,Gemini 判定需要查询正在上映的电影,返回一个对FindMovies的FunctionCall(参数为location与description); FunctionCallMiddleware拦截该调用,通过functionMap找到FindMoviesWrapper,执行 C# 函数并拿到结果;- 最终
SendAsync返回的functionCallReply是ToolCallAggregateMessage——它聚合了"模型发起的函数调用"与"函数执行结果"两段信息,示例用 FluentAssertions 断言了这一类型,证明工具链路确实被触发。
源码视角:一轮调用中消息如何流转
RegisterMessageConnector()注册的GeminiMessageConnector(GeminiMessageConnector.cs)在这条链路中承担了 Gemini 角色体系的映射:
- 出站方向:用户
TextMessage被转为Role = "user"的Content;模型产生的ToolCallMessage被转为Role = "model"且携带FunctionCallPart 的Content(见 ProcessToolCallMessage#L312-L341);函数执行结果ToolCallResultMessage被转为Role = "function"且携带FunctionResponse的Content,若结果本身不是 JSON 对象,连接器会将其包装为{"result": ...}后再序列化(见 ProcessToolCallResultMessage#L269-L310); - 入站方向:
GenerateContentResponse中的FunctionCallPart 会被收集并转换为 AutoGen 的ToolCallMessage,文本 Part 则转换为TextMessage(见 PostProcessMessage#L165-L200)。
因此,模型看到的对话历史始终是 Gemini 规范要求的user / model / function交替角色序列;GeminiChatAgent.BuildChatRequest还会校验"首条消息必须来自 user 或 function、末条消息同样如此",并把连续同角色消息合并为一条(见 GeminiChatAgent.cs#L157-L267)。
另一个值得注意的实现细节:从BuildChatRequest源码看,所有FunctionContract会经ToFunctionDeclaration()转成 Gemini 的FunctionDeclaration(FunctionContractExtension.cs#L20-L53,其中参数的IsRequired映射到 OpenAPI 的Required列表、参数类型映射到 OpenAPI 类型),随后被合并进单个Tool——源码注释指出这是当前 Gemini 尚不支持多个Tool条目的规避方案,多函数场景应像本示例一样通过FunctionCallMiddleware传入多个函数契约,而不是传多个Tool。
七、Step 6:多轮函数调用(Multi-turn)
var finalReply = await geminiAgent.SendAsync(chatHistory: [question, functionCallReply]);// 断言:携带工具结果后再问一次,应当得到最终的文本回复 finalReply.Should().BeOfType<TextMessage>();多轮调用的要点:
- 把上一轮的
question与functionCallReply(含函数调用与执行结果)一起作为聊天历史再次发送; GeminiMessageConnector会将ToolCallAggregateMessage拆分为model(FunctionCallPart)与function(FunctionResponsePart)两条Content回灌给模型(见 ProcessToolCallAggregateMessage#L227-L250);- Gemini 基于真实的函数返回结果(如
"Movies playing in North Seattle based on ... are: Barbie, Spiderman, Batman")生成自然语言答案,此时SendAsync返回的finalReply是TextMessage,即完成了"用户提问 → 工具调用 → 执行 → 文本作答"的完整闭环。
对于"查询某影院某电影某日场次"这类问题,模型可能连续触发FindMovies、FindTheaters、GetShowtimes多个函数,FunctionCallMiddleware会循环执行直到模型认为信息充分、输出最终文本;示例中的两个BeOfType断言(见 Function_Call_With_Gemini.cs#L119-L129)正是在验证这一"第一轮工具消息、末轮文本消息"的行为契约。
八、常见问题与注意事项
- 模型资源路径:使用 Vertex AI 构造函数时,
model参数只需传模型短名(如gemini-1.5-flash-001),完整路径由 GeminiChatAgent 构造函数 自动拼接;若改用IGeminiClient构造函数,则需自行传入完整资源路径。 - 系统消息的处理:
systemMessage不会作为普通Content参与user/model交替序列,而是放入SystemInstruction(见 GeminiChatAgent.cs#L197-L220);GeminiMessageConnector对显式Role.System的TextMessage在非严格模式下会降级为user消息处理(Gemini 对话轮次中不存在 system 角色)。 - 多 Tool 限制:如第六节所述,多个函数应通过
FunctionCallMiddleware的functions集合声明,由BuildChatRequest统一聚合到单个Tool中下发。 - 运行验证:仓库为
AutoGen.Gemini提供了测试工程 dotnet/test/AutoGen.Gemini.Tests,其中包含对GeminiChatAgent行为(如消息转换)的测试,可作为行为对照参考;本文示例本身则通过FluentAssertions断言在运行时自验证工具调用类型。 - 依赖包版本:文中
dotnet add package AutoGen.Gemini安装的是 NuGet 上的发布版本;示例位于 AutoGen.Gemini.Sample 工程,若在本仓库内直接运行,建议以仓库的 Directory.Packages.props 中的集中版本为准。
九、小结
本文以 AutoGen(.NET) 官方文档《Function-call-with-gemini》为主线,完整复现了基于AutoGen.Gemini的函数调用实现路径:
- 依赖层:
AutoGen.Gemini+AutoGen.SourceGenerator,后者通过Function特性从 C# 方法签名与 XML 注释自动派生FunctionContract与类型安全包装器; - Agent 层:
GeminiChatAgent(Vertex AI 构造重载)+ToolConfig/FunctionCallingConfig(Auto)声明工具调用策略; - 中间件层:
GeminiMessageConnector完成 AutoGen 消息体系与 Geminiuser/model/function角色体系的互转,FunctionCallMiddleware负责按函数名路由执行并回填结果; - 交互层:单轮
SendAsync得到ToolCallAggregateMessage(调用+结果聚合),多轮回灌历史后得到TextMessage最终答案。
掌握以上链路后,你可以将该模式直接迁移到任何需要 Gemini 工具调用的 .NET 场景——只需替换MovieFunction中的业务实现与functionMap注册,即可接入真实 API 或数据源。
【免费下载链接】autogenA programming framework for agentic AI项目地址: https://gitcode.com/GitHub_Trending/au/autogen
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考