04-让AI学会行动-FunctionCalling函数调用
unction Calling的工作原理很多人误解Function Calling是AI直接调用了函数。实际上AI并不执行任何代码它只是告诉你它想调用哪个函数、传什么参数。真正的执行逻辑由你的程序完成。完整的交互流程如下┌──────────┐ ①用户提问 ┌──────────┐│ │ ──────────────────→ │ ││ 你的 │ │ 大模型 ││ 程序 │ ②模型返回 │ ││ │ “我想调用 │ ││ │ GetWeatherInfo │ ││ │ (city广州)” │ ││ │ ←────────────────── │ ││ │ │ ││ │ ③执行本地函数 │ ││ │ GetWeatherInfo │ ││ │ (“广州”) │ ││ │ → 得到天气数据 │ ││ │ │ ││ │ ④把函数结果回传 │ ││ │ ──────────────────→ │ ││ │ │ ││ │ ⑤模型基于结果 │ ││ │ 生成最终回答 │ ││ │ ←────────────────── │ │└──────────┘ └──────────┘关键认知模型做了决策调什么函数、传什么参数你的程序做了执行实际调用函数模型再做总结基于结果生成回答。定义AI可调用的函数在C#中定义一个AI可调用函数只需要两步编写函数并用[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]特性至关重要——模型正是通过这些描述来理解这个函数是做什么的参数应该怎么填描述越准确模型的调用决策越正确。注册为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()做了什么它注册了一个中间件管道拦截模型的响应检测是否包含函数调用请求如果有自动执行对应的本地函数把函数结果回传给模型返回模型的最终回答整个过程对开发者完全透明——你只需要像普通对话一样调用GetStreamingResponseAsync。完整的Function Calling对话var options new ChatOptions{Tools [AIFunctionFactory.Create(GetWeatherInfo)]};List 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拦截器它通过Hook HttpClient的构造函数自动在所有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到AgentFunction Calling是构建AI Agent的基石。Agent LLM Memory Tools。我们目前已经拥有了✅ LLM对话第一篇✅ 记忆系统第二、三篇✅ 工具调用本篇一个基本的Agent已经初具雏形。但Function Calling还有一个局限工具必须在编译时定义。如果你想让AI动态接入各种第三方工具呢这就是下一篇文章的主角——MCP协议。小结