AutoGen(.NET) 中基于 Gemini 实现函数调用:AutoGen.Gemini 工具调用(Function Calling)实战指南
本文围绕 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.SourceGenerator
AutoGen.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 或数据源。
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 StartedRust0623
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