当前位置: 首页 > news >正文

让AI学会行动-FunctionCalling函数调用

任何操作

比如你问AI:"今天广州天气怎么样?",它只能抱歉地回答:"我无法获取实时天气信息。"

Function Calling(函数调用)就是解决这个问题的关键技术——让AI能够"决定"调用哪些外部工具,并基于工具返回的结果给出最终回答。

Function Calling的工作原理

很多人误解Function Calling是AI直接调用了函数。实际上,AI并不执行任何代码,它只是"告诉"你它想调用哪个函数、传什么参数。真正的执行逻辑由你的程序完成。

完整的交互流程如下:

┌──────────┐ ①用户提问 ┌──────────┐ │ │ ──────────────────→ │ │ │ 你的 │ │ 大模型 │ │ 程序 │ ②模型返回: │ │ │ │ "我想调用 │ │ │ │ GetWeatherInfo │ │ │ │ (city=广州)" │ │ │ │ ←────────────────── │ │ │ │ │ │ │ │ ③执行本地函数 │ │ │ │ GetWeatherInfo │ │ │ │ ("广州") │ │ │ │ → 得到天气数据 │ │ │ │ │ │ │ │ ④把函数结果回传 │ │ │ │ ──────────────────→ │ │ │ │ │ │ │ │ ⑤模型基于结果 │ │ │ │ 生成最终回答 │ │ │ │ ←────────────────── │ │ └──────────┘ └──────────┘

关键认知:模型做了决策(调什么函数、传什么参数),你的程序做了执行(实际调用函数),模型再做总结(基于结果生成回答)。

定义AI可调用的函数

在C#中,定义一个AI可调用函数只需要两步:

1. 编写函数并用[Description]描述

[Description("Get weather information for the specified city")] string GetWeatherInfo( [Description("City name, for example: GuangDong")] string city) { // 模拟天气API调用 var weatherData = new { city, temperature = "30°C", condition = "Sunny", humidity = "65%", windSpeed = "10 km/h" }; return JsonSerializer.Serialize(weatherData); }

[Description]特性至关重要——模型正是通过这些描述来理解:

  • 这个函数是做什么的?
  • 参数应该怎么填?

描述越准确,模型的调用决策越正确。

2. 注册为AI工具

var options = new ChatOptions { Tools = [AIFunctionFactory.Create(GetWeatherInfo)] };

AIFunctionFactory.Create()会反射函数签名和Description特性,自动生成符合OpenAI Tool协议的工具描述JSON。

启用自动函数调用

默认的IChatClient不会自动执行函数调用。我们需要用ChatClientBuilder包装一层:

IChatClient client = new OpenAI.Chat.ChatClient( model, new ApiKeyCredential(apiKey), new OpenAI.OpenAIClientOptions { Endpoint = new Uri(baseUrl) }) .AsIChatClient(); // 关键:用UseFunctionInvocation()启用自动函数执行 using var functionCallingChatClient = new ChatClientBuilder(client) .UseFunctionInvocation() .Build();

UseFunctionInvocation()做了什么?它注册了一个中间件管道

  1. 拦截模型的响应
  2. 检测是否包含函数调用请求
  3. 如果有,自动执行对应的本地函数
  4. 把函数结果回传给模型
  5. 返回模型的最终回答

整个过程对开发者完全透明——你只需要像普通对话一样调用GetStreamingResponseAsync

完整的Function Calling对话

var options = new ChatOptions { Tools = [AIFunctionFactory.Create(GetWeatherInfo)] }; List<ChatMessage> chatMessages = [ new ChatMessage(ChatRole.System, "你是一个有用的AI助手,请用中文回答用户的问题。") ]; while (true) { Console.Write("\n你: "); var userInput = Console.ReadLine(); if (userInput.Equals("exit", StringComparison.OrdinalIgnoreCase)) { Console.WriteLine("再见!"); break; } chatMessages.Add(new ChatMessage(ChatRole.User, userInput)); Console.Write("\nAI: "); StringBuilder sb = new StringBuilder(); await foreach (var update in functionCallingChatClient .GetStreamingResponseAsync(chatMessages, options)) { foreach (var item in update.Contents) { if (item is TextReasoningContent textReasoning) { Console.Write(textReasoning.Text); // 思考过程 } else if (item is TextContent text) { Console.Write(text.Text); // 最终回答 sb.Append(text.Text); } } } }

代码和之前的对话程序几乎一样!唯一的区别是传入了options(包含工具列表)。函数调用的复杂逻辑全部被UseFunctionInvocation()封装了。

透视Function Calling的底层通信

项目中提供了一个基于HarmonyLib的HTTP拦截器,它通过HookHttpClient的构造函数,自动在所有HTTP请求中注入日志记录,无需修改业务代码。使用方式很简单,在代码入口加一行:

HttpClientInterceptor.StartInterception();

开启后你就能在控制台看到Function Calling的完整请求和响应报文(详见06-番外篇)。下面我们来看看Function Calling实际经历了两次API请求:

第一次请求:模型决策

请求体(关键部分):

{ "model": "glm-5.1", "messages": [ { "role": "system", "content": "你是一个有用的AI助手..." }, { "role": "user", "content": "广州今天天气怎么样?" } ], "tools": [ { "type": "function", "function": { "name": "GetWeatherInfo", "description": "Get weather information for the specified city", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "City name, for example: GuangDong" } }, "required": ["city"] } } } ] }

注意tools数组——这就是模型"看到的"可用工具列表。

响应体(关键部分):

{ "choices": [{ "message": { "role": "assistant", "tool_calls": [{ "id": "call_xxx", "type": "function", "function": { "name": "GetWeatherInfo", "arguments": "{\"city\":\"广州\"}" } }] } }] }

模型没有直接回答,而是返回了一个tool_calls——它决定调用GetWeatherInfo,参数是city=广州

中间执行:本地函数

SDK自动执行GetWeatherInfo("广州"),得到:

{"city":"广州","temperature":"30°C","condition":"Sunny","humidity":"65%","windSpeed":"10 km/h"}

第二次请求:结果回传

{ "messages": [ { "role": "system", "content": "..." }, { "role": "user", "content": "广州今天天气怎么样?" }, { "role": "assistant", "tool_calls": [...] }, { "role": "tool", "tool_call_id": "call_xxx", "content": "{\"city\":\"广州\",\"temperature\":\"30°C\",...}" } ] }

注意新增的tool角色消息——这就是函数执行结果。模型基于这个结果生成最终回答。

第二次响应:最终回答

模型这次不再请求调用函数,而是直接用自然语言总结:

"广州今天天气晴朗,气温30°C,湿度65%,风速10km/h,适合户外活动。"

多个函数的场景

你可以同时注册多个函数,模型会智能选择:

[Description("获取指定城市的天气信息")] string GetWeatherInfo([Description("城市名称")] string city) { ... } [Description("搜索网页信息")] string SearchWeb([Description("搜索关键词")] string query) { ... } [Description("计算数学表达式")] string Calculate([Description("数学表达式")] string expression) { ... } var options = new ChatOptions { Tools = [ AIFunctionFactory.Create(GetWeatherInfo), AIFunctionFactory.Create(SearchWeb), AIFunctionFactory.Create(Calculate) ] };

模型会根据用户的问题自动选择合适的函数——甚至可能在一个回答中调用多个函数。

最佳实践

Description的编写原则

// ❌ 太简略,模型可能误解 [Description("Get weather")] string GetWeather(string city) { ... } // ✅ 清晰描述功能和参数 [Description("获取指定城市的实时天气信息,包括温度、湿度、天气状况等")] string GetWeatherInfo( [Description("城市名称,如:北京、上海、广州,支持中文城市名")] string city) { ... }

函数设计的注意事项

原则说明
返回值用JSON字符串模型更容易解析结构化的JSON
函数要幂等模型可能在推理过程中多次调用同一函数
做好错误处理函数失败时返回错误信息而非抛异常,模型能理解错误并调整回答
参数尽量简单避免复杂的嵌套对象参数

从Function Calling到Agent

Function Calling是构建AI Agent的基石。Agent = LLM + Memory + Tools。我们目前已经拥有了:

  • ✅ LLM对话(第一篇)
  • ✅ 记忆系统(第二、三篇)
  • ✅ 工具调用(本篇)
http://www.jsqmd.com/news/1139980/

相关文章:

  • 探索Ryujinx:如何用开源C技术打造高精度Switch模拟器
  • NVIDIA Profile Inspector完整指南:深度解锁显卡隐藏性能的终极工具
  • 国内口碑好的DTSS咨询机构有哪几家
  • TLSv1.3流量解密实战:利用SSLKEYLOGFILE与Wireshark调试加密通信
  • Ryujinx:C实现的高性能Nintendo Switch模拟器技术解析
  • kml_adapter实战案例:在AI训练和科学仿真中的成功应用
  • 2026 五款主流数字人平台深度横评:实测效果与场景适配性解析
  • 数字孪生与产线毫秒级双向同步实战:边缘计算与TSN网络部署指南
  • 【大数据实验7-2】基于助睿ETL完成自媒体作品标题特征工程构建
  • 信号完整性分析误区:为何不能将6英寸传输线仅视为1.6pF电容?
  • 文件上传漏洞攻防:黑名单绕过原理、实战与防御方案
  • 3分钟永久解锁Office全功能:Ohook激活工具完全指南
  • x86-64 与 ARM64 架构对比:5 个关键维度解析性能、功耗与选型
  • 别人还在翻录音,你已拿到AI纪要——私有化视频会议平台EasyDSS会议效率拉满
  • 依托制度化建设咨询,破解中小民企增长瓶颈
  • 如何免费实现3D打印工作流一体化:Blender 3MF插件终极指南
  • HarmonyOS7特性解读
  • Bedrock Launcher:超越官方启动器的Minecraft基岩版终极管理方案
  • 逆向工程实战:调试器时间限制机制分析与绕过方法
  • 5步快速上手:免费开源的Switch模拟器Ryujinx完整使用指南
  • 局域网视频会议软件选型指南:功能、部署与成本全面解析
  • YOLO目标检测从零实战:数据标注到模型部署全流程指南
  • 免费开源风扇控制软件:让你的电脑风扇变得聪明又安静
  • 终极免费原神辅助工具Akebi-GC:如何提升游戏体验的专业指南
  • AI 电动工具智能功率 小封装、高可靠性 完整选型方案
  • 湿滑地面与甲板晃动下局部规划器对比实验的扰动建模方案
  • 从零开始-用CSharp调用你的第一个大模型
  • 两个 offer 纠结到失眠?我用一张五维加权打分表做了决定(附真实打分过程)
  • Aimmy终极指南:如何用AI智能瞄准技术提升你的游戏体验
  • Zotero-GPT:从文献管理工具到AI研究助手的范式跃迁