1 套插件、5 款模型:用 Semantic Kernel 让 LLM 自动调用你的 .NET 方法
模型月月换新,业务代码不能跟着重写。本文用 Semantic Kernel 的 Function Calling,把普通 C# 方法变成 LLM 可自动调用的「工具」,一套插件零改动适配 GPT-5.6 / Gemini 3.6 Flash / Qwen3.8-Max / Grok 4.5 / 本地 Ollama 五款模型。
为什么值得做:2026 年 7 月的模型格局
2026 年的模型迭代快到让人焦虑。短短 7 月,至少 5 个重量级模型集中发布:
•GPT-5.6 系列(2026-07-09):分为 Sol / Terra / Luna 三档,覆盖从边缘到旗舰的全场景;配套 Codex 周活已突破 500 万,Agent 化趋势已经确立。
•Gemini 3.6 Flash(2026-07-21):SWE 编程基准 DeepSWE 从 37% 提升到 49%,1M token 上下文,且原生支持 function calling。
•Qwen3.8-Max(2026-07-19):2.4T 参数的多模态旗舰,国产开源阵营的天花板。
•Grok 4.5(2026-07-09):专注编程与智能体,百万 token 价格落在 $2 / $6 区间,性价比突出。
•Poolside Laguna S 2.1(2026-07-21):118B 总参数 / 8B 活跃参数的开源编程 MoE,同样 1M 上下文,可自部署。

关键洞察:模型在变,业务不变。你那套订单查询、折扣计算、库存校验逻辑,不可能每换一个模型就重写一遍。Semantic Kernel 用统一的插件抽象层(Plugin / KernelFunction),让同一套 .NET 方法适配所有模型——切换成本只在 AddXxxChatCompletion 那一行。这才是 .NET 开发者该握住的「不变层」。
Function Calling 到底是什么
Function Calling(函数调用 / 工具调用)解决的是 LLM 的「行动力」问题。大模型本身只会生成文本,它不知道你数据库里订单 ORD-20260727-001 的状态,也算不出满减后的实付金额。
传统做法是你写一堆 if/else 去解析用户意图,再手动调方法——这叫「硬编排」,脆弱且难维护。Function Calling 反过来:你先把方法告诉模型,由模型自己判断「该不该调用、调用哪个、传什么参数」。
在 Semantic Kernel 里,这套机制的核心就三个东西:
[KernelFunction] + [Description]:把 C# 方法标记成「可被 LLM 调用的工具」,描述文字是写给模型看的说明书。
ToolCallBehavior.AutoInvokeKernelFunctions:让 Kernel 自动拦截模型的工具调用请求、执行你的 .NET 代码、再把结果喂回模型。
InvokePromptAsync:一句自然语言提问,背后自动完成「模型决策 → 执行函数 → 汇总答案」的闭环。
一句话:你只负责「把能力注册进去」,模型负责「决定怎么用」。
手把手:5 分钟跑通自动函数调用
先用一个最小可运行项目搭起来。下面是 .csproj 和插件类 OrderPlugin——注意 [Description] 不是写给人看的注释,而是写给 LLM 看的「工具说明」,写清楚它才会调用。
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net8.0</TargetFramework><ImplicitUsings>enable</ImplicitUsings><Nullable>enable</Nullable></PropertyGroup><ItemGroup><PackageReferenceInclude="Microsoft.SemanticKernel"Version="1.77.0" /><PackageReferenceInclude="Microsoft.SemanticKernel.Connectors.OpenAI"Version="1.77.0" /></ItemGroup></Project>using System.ComponentModel;using Microsoft.SemanticKernel;public class OrderPlugin{[KernelFunction("query_order")][Description("根据订单号查询订单状态与物流信息")]public string QueryOrder([Description("订单号,例如 ORD-20260727-001")] string orderId){// 真实项目里这里会查数据库或调用 ERP / 物流 APIreturn $"订单 {orderId}:已发货,预计 2026-07-28 送达,承运商 顺丰。";}[KernelFunction("calc_discount")][Description("计算满减后的实付金额")]public decimal CalcDiscount([Description("商品原价")] decimal price,[Description("满减门槛,例如满 200")] decimal threshold,[Description("减免金额,例如减 30")] decimal off){return price >= threshold ? price - off : price;}}
两个方法都很朴素:QueryOrder 模拟查物流,CalcDiscount 算满减。真实业务里,这里就是你的 DbContext 查询、ERP 接口调用、Redis 缓存读取——完全不用改 SK 的任何约定。CalcDiscount 返回 decimal,因为金额字段必须精确,别用 double 引入浮点误差。
主程序:InvokePromptAsync + AutoInvokeKernelFunctions
接下来是 Program.cs。整段逻辑只有几行,但每一行都有讲究:
using Microsoft.SemanticKernel;using Microsoft.SemanticKernel.Connectors.OpenAI;var builder = Kernel.CreateBuilder();builder.AddOpenAIChatCompletion(modelId: "gpt-4o", // 换模型只改这一行apiKey: Environment.GetEnvironmentVariable("OPENAI_API_KEY")!);builder.Plugins.AddFromType<OrderPlugin>();var kernel = builder.Build();var settings = new OpenAIPromptExecutionSettings{ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions};string question = "查一下订单 ORD-20260727-001 的状态,再算算原价 268 满 200 减 30 后的实付";var answer = await kernel.InvokePromptAsync(question, new(settings));Console.WriteLine(answer);
•AddOpenAIChatCompletion 注册模型。注释那句「换模型只改这一行」不是夸张——进阶节会验证。
•AddFromType<OrderPlugin>() 把插件类挂到 Kernel 上,SK 会自动扫描 [KernelFunction] 生成工具清单(含名称与描述)。
•ToolCallBehavior.AutoInvokeKernelFunctions 是灵魂开关:它让 Kernel 拦截模型返回的工具调用、自动执行你的 C# 方法、把结果回填给模型,最终吐出一句自然语言答案。你不需要手写任何 JSON 解析或循环重试。
•InvokePromptAsync(question, new(settings)) 一句提问搞定全流程。你给的 question 同时包含两个任务,模型会自己决定「先查订单、再算折扣」,并串联两次函数调用。
dotnet run 之后,你会看到类似输出:「订单 ORD-20260727-001:已发货……实付金额为 238。」——没有任何人工编排,模型自己把两步串起来了。这就是「自动函数调用」的威力。
函数调用是怎么发生的
把刚才那次 InvokePromptAsync 拆开看,链路是这样的:
你把问题 + 工具清单(含两个 [Description])一起发给 LLM。
LLM 返回「我要调用 query_order 和 calc_discount,参数如下」。
SK 拦截这个请求,在 .NET 进程里执行你的方法,拿到真实返回值。
SK 把结果回填,LLM 再生成最终的自然语言答案。

关键点:你的业务方法始终跑在本地 .NET 运行时里,LLM 只拿到「调用权」,拿不到你的数据库密码,也改不了你的代码。这既是能力的边界,也是安全的边界——模型是「手」,你的代码才是「脑」。
进阶:一套插件,零改动切换 5 款模型
现在见证「不变层」的价值。你的 OrderPlugin 一行都不用改,切到 Gemini / Ollama 只需换注册那一行:
// 换成 Google Gemini 3.6 Flash(2026-07-21 发布,DeepSWE 49%)builder.AddGoogleAIChatCompletion("gemini-3.6-flash", Environment.GetEnvironmentVariable("GEMINI_API_KEY")!);// 或换成完全免费的本地模型(Ollama 拉取 qwen3.8 后)// builder.AddOllamaChatCompletion("qwen3.8", new Uri("http://localhost:11434"));
注意:用 Gemini 需引用 Microsoft.SemanticKernel.Connectors.Google,用本地 Ollama 需引用 Microsoft.SemanticKernel.Connectors.Ollama,这两个包和 OpenAI 连接器是平级的,按需 dotnet add package 即可。其余代码——插件类、AutoInvokeKernelFunctions、InvokePromptAsync——完全不动。
同样的写法适配 GPT-5.6(Sol / Terra / Luna)、Qwen3.8-Max、Grok 4.5:都是 AddXxxChatCompletion 换一行 + 配好对应 API Key。这意味着你可以今天用 GPT-5.6 Luna 跑生产,明天用免费的 Ollama 本地 Qwen3.8 跑测试,业务零回归。这才是 provider-agnostic 的红利。
避坑指南
Function Calling 上手快,但几个坑会让你半夜被报警叫醒:
写操作务必加人工审批。本文示例都是只读查询,一旦你的 [KernelFunction] 是真删库、真扣款、真发邮件,就必须用 IFunctionInvocationFilter 在调用前拦截,弹出人工确认;或只对只读函数开 AutoInvokeKernelFunctions。原则:读可以自动,写必须设防。
[Description] 写不清,LLM 就不调用。模型靠描述判断「这个工具能不能解决用户问题」。如果你写「处理订单」这种含糊话,模型很可能直接编一个答案糊弄你。把参数含义、触发场景、返回值写具体,必要时在参数 [Description] 里举例(如「满 200」)。
密钥不要写死在代码里。永远用 Environment.GetEnvironmentVariable 或 IConfiguration 读 OPENAI_API_KEY / GEMINI_API_KEY,别把密钥提交进 Git。生产环境配合 Secret Manager / Key Vault 使用。
参数类型要对齐。LLM 传参是字符串驱动的,金额用 decimal、开关用 bool,并在 [Description] 里举例,能显著降低解析错误;参数越多、越抽象,越要在描述里约束取值范围。
小结
Semantic Kernel 的 Function Calling,本质是给 LLM 发了一张「你的 .NET 能力的通行证」。你用 [KernelFunction] 注册能力、AutoInvokeKernelFunctions 自动执行、AddXxxChatCompletion 一行切模型——业务方法始终是主角,模型只是随时可换的「大脑」。
在 2026 年模型井喷的节奏里,把精力放在不变的业务逻辑上,让 SK 帮你消化模型的差异,才是 .NET 开发者最稳的打法。
作者:Alan | 关注 AI + .NET 技术
👇 关注本公众号,获取每日 AI + .NET 技术干货
觉得有用?点个「在看」👍 分享给更多开发者
夜雨聆风