首页
/ AutoGen(.NET) 中基于 Gemini 实现函数调用:AutoGen.Gemini 工具调用(Function Calling)实战指南

AutoGen(.NET) 中基于 Gemini 实现函数调用:AutoGen.Gemini 工具调用(Function Calling)实战指南

2026-09-04 17:01:35作者:裘晴惠Vivianne

本文围绕 AutoGen(.NET 版)的 AutoGen.Gemini 包,讲解如何让 GeminiChatAgent 完成函数调用(Function Calling):从 NuGet 依赖安装、使用 AutoGen.SourceGenerator 生成类型安全的函数契约、通过 Vertex AI 创建带 ToolConfig 的 Gemini Agent,到单轮与多轮工具调用的完整代码流程,并结合 GeminiMessageConnectorFunctionContractExtension 等源码剖析消息角色映射与工具声明转换的底层原理。读完本文,你可以在 .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:提供 GeminiChatAgentGoogleGeminiClientVertexGeminiClient 及消息转换中间件;
  • AutoGen.SourceGenerator:用于自动生成 AutoGen.Core.FunctionContract(函数契约)。它是一个 Roslyn 源生成器:只要给方法打上 Function 特性,就会基于方法签名和 XML 文档注释生成函数定义与类型安全的调用包装器。其使用细节参见同仓库文档 Create-type-safe-function-callAutoGen.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:提供 TextMessageRoleFunctionCallMiddleware 等核心消息与中间件类型;
  • Google.Cloud.AIPlatform.V1:提供 Vertex AI 的 protobuf 类型,例如 ToolConfigFunctionCallingConfig,示例中创建 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 为例):

  • FindMoviesFunctionContractAutoGen.Core.FunctionContract,包含函数名、描述、参数元数据,是与具体 LLM 无关的中间表示;
  • FindMoviesWrapper(string arguments):类型安全包装器,内部先把模型返回的 JSON 参数反序列化为参数对象,再调用真正的 FindMovies 方法。

这与 AutoGen.SourceGenerator README 中描述的生成模式一致(生成 XxxFunction 定义与 XxxWrapper 包装器),本文示例使用的是 AutoGen.CoreFunctionContract 变体,可无缝接入 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 的 Contentuser/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>();

流程说明:

  1. 用户消息 "What movies are showing in North Seattle tonight?" 进入 Agent;
  2. 由于 Mode.Auto,Gemini 判定需要查询正在上映的电影,返回一个对 FindMoviesFunctionCall(参数为 locationdescription);
  3. FunctionCallMiddleware 拦截该调用,通过 functionMap 找到 FindMoviesWrapper,执行 C# 函数并拿到结果;
  4. 最终 SendAsync 返回的 functionCallReplyToolCallAggregateMessage——它聚合了"模型发起的函数调用"与"函数执行结果"两段信息,示例用 FluentAssertions 断言了这一类型,证明工具链路确实被触发。

源码视角:一轮调用中消息如何流转

RegisterMessageConnector() 注册的 GeminiMessageConnectorGeminiMessageConnector.cs)在这条链路中承担了 Gemini 角色体系的映射:

  • 出站方向:用户 TextMessage 被转为 Role = "user"Content;模型产生的 ToolCallMessage 被转为 Role = "model" 且携带 FunctionCall Part 的 Content(见 ProcessToolCallMessage#L312-L341);函数执行结果 ToolCallResultMessage 被转为 Role = "function" 且携带 FunctionResponseContent,若结果本身不是 JSON 对象,连接器会将其包装为 {"result": ...} 后再序列化(见 ProcessToolCallResultMessage#L269-L310);
  • 入站方向GenerateContentResponse 中的 FunctionCall Part 会被收集并转换为 AutoGen 的 ToolCallMessage,文本 Part 则转换为 TextMessage(见 PostProcessMessage#L165-L200)。

因此,模型看到的对话历史始终是 Gemini 规范要求的 user / model / function 交替角色序列;GeminiChatAgent.BuildChatRequest 还会校验"首条消息必须来自 user 或 function、末条消息同样如此",并把连续同角色消息合并为一条(见 GeminiChatAgent.cs#L157-L267)。

另一个值得注意的实现细节:从 BuildChatRequest 源码看,所有 FunctionContract 会经 ToFunctionDeclaration() 转成 Gemini 的 FunctionDeclarationFunctionContractExtension.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>();

多轮调用的要点:

  1. 把上一轮的 questionfunctionCallReply(含函数调用与执行结果)一起作为聊天历史再次发送;
  2. GeminiMessageConnector 会将 ToolCallAggregateMessage 拆分为 modelFunctionCall Part)与 functionFunctionResponse Part)两条 Content 回灌给模型(见 ProcessToolCallAggregateMessage#L227-L250);
  3. Gemini 基于真实的函数返回结果(如 "Movies playing in North Seattle based on ... are: Barbie, Spiderman, Batman")生成自然语言答案,此时 SendAsync 返回的 finalReplyTextMessage,即完成了"用户提问 → 工具调用 → 执行 → 文本作答"的完整闭环。

对于"查询某影院某电影某日场次"这类问题,模型可能连续触发 FindMoviesFindTheatersGetShowtimes 多个函数,FunctionCallMiddleware 会循环执行直到模型认为信息充分、输出最终文本;示例中的两个 BeOfType 断言(见 Function_Call_With_Gemini.cs#L119-L129)正是在验证这一"第一轮工具消息、末轮文本消息"的行为契约。

八、常见问题与注意事项

  1. 模型资源路径:使用 Vertex AI 构造函数时,model 参数只需传模型短名(如 gemini-1.5-flash-001),完整路径由 GeminiChatAgent 构造函数 自动拼接;若改用 IGeminiClient 构造函数,则需自行传入完整资源路径。
  2. 系统消息的处理systemMessage 不会作为普通 Content 参与 user/model 交替序列,而是放入 SystemInstruction(见 GeminiChatAgent.cs#L197-L220);GeminiMessageConnector 对显式 Role.SystemTextMessage 在非严格模式下会降级为 user 消息处理(Gemini 对话轮次中不存在 system 角色)。
  3. 多 Tool 限制:如第六节所述,多个函数应通过 FunctionCallMiddlewarefunctions 集合声明,由 BuildChatRequest 统一聚合到单个 Tool 中下发。
  4. 运行验证:仓库为 AutoGen.Gemini 提供了测试工程 dotnet/test/AutoGen.Gemini.Tests,其中包含对 GeminiChatAgent 行为(如消息转换)的测试,可作为行为对照参考;本文示例本身则通过 FluentAssertions 断言在运行时自验证工具调用类型。
  5. 依赖包版本:文中 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 消息体系与 Gemini user/model/function 角色体系的互转,FunctionCallMiddleware 负责按函数名路由执行并回填结果;
  • 交互层:单轮 SendAsync 得到 ToolCallAggregateMessage(调用+结果聚合),多轮回灌历史后得到 TextMessage 最终答案。

掌握以上链路后,你可以将该模式直接迁移到任何需要 Gemini 工具调用的 .NET 场景——只需替换 MovieFunction 中的业务实现与 functionMap 注册,即可接入真实 API 或数据源。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
504
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384