首页
/ AutoGen .NET 实战:用 KernelPluginMiddleware 把 Semantic Kernel 插件能力挂载到 OpenAIChatAgent 等任意 Agent

AutoGen .NET 实战:用 KernelPluginMiddleware 把 Semantic Kernel 插件能力挂载到 OpenAIChatAgent 等任意 Agent

2026-09-04 12:28:20作者:郁楠烈Hubert

本文基于 AutoGen .NET 官方文档《Use kernel plugin in other agents》(原文)展开,讲解 AutoGen.SemanticKernel 包提供的 KernelPluginMiddleware 中间件:它让 Semantic Kernel 的 Kernel 插件(内置插件或自定义插件)可以直接服务于 OpenAIChatAgent 等非 Semantic Kernel 的 AutoGen Agent。读完本篇,你将能够亲手编写带 GetWeather 函数的自定义插件,并将其注册到 OpenAIChatAgent 上完成一次真实的工具调用对话,同时从源码层面理解参数如何被解析、函数如何被拦截与执行。

什么是 Semantic Kernel 插件

在 Semantic Kernel 中,kernel plugin(内核插件)是一组可以在 LLM 调用期间被调用的 kernel function 的集合。Semantic Kernel 提供了丰富的内置插件,例如核心插件(core plugins)、Web 搜索插件(web search plugin)等,你也可以创建自己的插件。Kernel 插件极大扩展了 Semantic Kernel 的能力,使其可以执行 Web 搜索、图像搜索、文本摘要等任务。

而在 AutoGen .NET 体系中,插件能力不必局限于 Semantic Kernel 自家的 Agent。AutoGen.SemanticKernel 包提供了一个名为 KernelPluginMiddleware 的中间件,允许你在 OpenAIChatAgent 等其他 AutoGen Agent 中使用 Semantic Kernel 插件。这正是本篇的实战主题。

AutoGen.SemanticKernel 包提供了什么

结合 AutoGen.SemanticKernel 概览文档,该包提供两类 Agent 和两个中间件:

组件 说明
SemanticKernelAgent Kernel 的轻量封装 Agent,仅支持通过 IMessage<ChatMessageContent> 使用原始 ChatMessageContent 类型;要支持更多 AutoGen 内置消息类型,需注册 SemanticKernelChatMessageContentConnector
SemanticKernelChatCompletionAgent Microsoft.SemanticKernel.Agents.ChatCompletionAgent 的轻量封装 Agent
SemanticKernelChatMessageContentConnector 在 AutoGen 内置消息类型与 ChatMessageContent 之间互转的连接器。当前阶段仅支持 TextMessageImageMessageMultiModalMessage 三类消息;尚不支持 ToolCallMessageToolCallResultMessage 等函数调用消息类型
KernelPluginMiddleware 允许在其他 AutoGen Agent(如 OpenAIChatAgent)中使用 Semantic Kernel 插件的中间件

安装方式:先按 安装指南 配置好 AutoGen 包源,然后在项目文件中添加包引用:

<ItemGroup>
    <PackageReference Include="AutoGen.SemanticKernel" Version="AUTOGEN_VERSION" />
</ItemGroup>

官方示例工程 AutoGen.SemanticKernel.Sample.csproj 中的实际依赖可作参考:它引用了 AutoGen.OpenAI(提供 OpenAIChatAgent)、AutoGen.SemanticKernel(提供 KernelPluginMiddleware)、AutoGen.SourceGenerator(作为 Analyzer,用于源码生成式的函数契约),以及 NuGet 包 Microsoft.SemanticKernel.Plugins.Web

实战:四步把 GetWeather 插件挂到 OpenAIChatAgent 上

下面的完整示例来自官方示例文件 Use_Kernel_Functions_With_Other_Agent.cs,定义一个只含 GetWeather 函数的简单插件,并在 OpenAIChatAgent 中使用它。

Step 1:添加 using 语句

using AutoGen.Core;
using AutoGen.OpenAI;
using AutoGen.OpenAI.Extension;
using Microsoft.SemanticKernel;
using OpenAI;

要点:AutoGen.SemanticKernel 命名空间下的 KernelPluginMiddleware 所在 using 由全局 using 或项目配置提供(示例中通过 GlobalUsing/隐式 using 生效),而 Microsoft.SemanticKernel 用于构建 Kernel 与创建插件。

Step 2:创建插件

这一步创建一个包含单个 GetWeather 函数的简单插件:该函数接收一个 location 作为输入,返回该地点的天气信息。

var openAIKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new Exception("Please set OPENAI_API_KEY environment variable.");
var modelId = "gpt-4o-mini";
var kernelBuilder = Kernel.CreateBuilder();
var kernel = kernelBuilder.Build();
var getWeatherFunction = KernelFunctionFactory.CreateFromMethod(
               method: (string location) => $"The weather in {location} is 75 degrees Fahrenheit.",
               functionName: "GetWeather",
               description: "Get the weather for a location.");
var plugin = kernel.CreatePluginFromFunctions("my_plugin", [getWeatherFunction]);

参数说明:

  • KernelFunctionFactory.CreateFromMethod:把一个 .NET 委托/方法包装为 kernel function。functionNameGetWeather)是 LLM 发起工具调用时使用的函数名,description 会进入函数契约描述,直接影响 LLM 判断“何时调用该函数”;
  • 委托的形参 string location 会被 Semantic Kernel 元数据机制识别为函数参数,后续中间件正是依据这些元数据来做参数解析(见后文源码分析);
  • kernel.CreatePluginFromFunctions("my_plugin", ...):将函数聚合为名为 my_plugin 的插件实例,一个插件可以承载多个函数;
  • OPENAI_API_KEY 环境变量与模型名 gpt-4o-mini 是运行前提。

Step 3:创建 OpenAIChatAgent 并注册插件

这一步先创建 KernelPluginMiddleware 并把上一步的插件注册给它——KernelPluginMiddleware 会加载该插件,让其中的函数可以被其他 Agent 调用;随后创建 OpenAIChatAgent 并把该中间件注册上去。

// Create a middleware to handle the plugin functions
var kernelPluginMiddleware = new KernelPluginMiddleware(kernel, plugin);

var openAIClient = new OpenAIClient(openAIKey);
var openAIAgent = new OpenAIChatAgent(
    chatClient: openAIClient.GetChatClient(modelId),
    name: "assistant")
    .RegisterMessageConnector() // register message connector so it support AutoGen built-in message types like TextMessage.
    .RegisterMiddleware(kernelPluginMiddleware) // register the middleware to handle the plugin functions
    .RegisterPrintMessage(); // pretty print the message to the console

三个链式注册的分工:

  • RegisterMessageConnector():注册消息连接器,使 Agent 支持 TextMessage 等 AutoGen 内置消息类型(连接器负责 AutoGen 消息与 OpenAI SDK 消息格式之间的互转);
  • RegisterMiddleware(kernelPluginMiddleware):注册插件中间件,这是插件能力接入的关键一步——中间件会把插件函数注入 LLM 请求的函数列表,并拦截 LLM 返回的工具调用消息代为执行;
  • RegisterPrintMessage():把消息漂亮地打印到控制台,方便观察多轮工具调用过程。

Step 4:与 OpenAIChatAgent 对话

最后向 OpenAIChatAgent 提问西雅图的天气。OpenAIChatAgent 会使用插件中的 GetWeather 函数来获取西雅图的天气信息:

var toolAggregateMessage = await openAIAgent.SendAsync("Tell me the weather in Seattle");

// The aggregate message will be converted to [ToolCallMessage, ToolCallResultMessage] when flowing into the agent
// send the aggregated message to llm to generate the final response
var finalReply = await openAIAgent.SendAsync(toolAggregateMessage);

注意这里需要两次 SendAsync 调用,原因在于中间件的工作方式:

  1. 第一次 SendAsync 中,LLM 判定需要调用 GetWeather,返回 ToolCallMessage;中间件检测到该函数在插件的函数映射中,立即本地执行函数,并把“调用 + 结果”打包成 ToolCallAggregateMessage(内部对应 [ToolCallMessage, ToolCallResultMessage] 的组合)直接返回,LLM 并未看到函数结果
  2. 第二次 SendAsync(toolAggregateMessage) 把这个聚合消息送回 Agent/LLM,LLM 基于真实的函数执行结果生成最终自然语言回答。

这种“一次调用拿到工具结果、二次调用生成最终答复”的模式,是该示例代码的固有流程,读者复制运行时应保留两次调用。

源码解析:KernelPluginMiddleware 如何把插件变成函数调用能力

下面结合源码说明这个中间件在幕后做了什么。

构造阶段:把插件函数转换为 AutoGen 的函数契约

KernelPluginMiddleware 的实现在 KernelPluginMiddleware.cs。其构造函数做了三件事:

public KernelPluginMiddleware(Kernel kernel, KernelPlugin kernelPlugin)
{
    _kernelPlugin = kernelPlugin;
    var functionContracts = kernelPlugin.Select(k => k.Metadata.ToFunctionContract());
    var functionMap = kernelPlugin.ToDictionary(kv => kv.Metadata.Name, kv => InvokeFunctionPartial(kernel, kv));
    _functionCallMiddleware = new FunctionCallMiddleware(functionContracts, functionMap, Name);
}
  • 契约转换:对插件中每个 KernelFunction 的元数据调用扩展方法 ToFunctionContract()(定义在 KernelExtension.cs),把 KernelFunctionMetadata 映射为 AutoGen 核心的 FunctionContract——包括函数名、描述、参数列表(名称、描述、默认值、是否必填、参数类型)以及返回值类型。这些契约随后会被合并进 LLM 请求的 Functions 列表,相当于“告诉 LLM 有哪些工具可用”;
  • 执行映射:为每个函数名建立到 InvokeFunctionPartial(kernel, function) 的映射,即“函数名 → 可执行的委托”;
  • 委托复用:自身不重复实现函数调用逻辑,而是构造一个 FunctionCallMiddleware 并把自己的 InvokeAsync 直接转发给它。KernelPluginMiddlewareInvokeAsync 实现只有一行:
public Task<IMessage> InvokeAsync(MiddlewareContext context, IAgent agent, CancellationToken cancellationToken = default)
{
    return _functionCallMiddleware.InvokeAsync(context, agent, cancellationToken);
}

也就是说,插件中间件本质上是一个由 kernel plugin 驱动的 FunctionCallMiddleware 工厂,真正的拦截与执行逻辑在 AutoGen 核心的 FunctionCallMiddleware.cs 中。

参数解析:LLM 的 JSON 参数如何变成 .NET 方法实参

当 LLM 返回工具调用时,函数参数是一段 JSON 字符串。InvokeFunctionAsyncKernelPluginMiddleware.cs)负责把这段 JSON 解析为 Semantic Kernel 能识别的 KernelArguments

var kernelArguments = new KernelArguments();
var parameters = function.Metadata.Parameters;
var jsonObject = JsonSerializer.Deserialize<JsonObject>(arguments) ?? new JsonObject();
foreach (var parameter in parameters)
{
    var parameterName = parameter.Name;
    if (jsonObject.ContainsKey(parameterName))
    {
        var parameterType = parameter.ParameterType ?? throw new ArgumentException($"Missing parameter type for {parameterName}");
        var parameterValue = jsonObject[parameterName];
        var parameterObject = parameterValue.Deserialize(parameterType);
        kernelArguments.Add(parameterName, parameterObject);
    }
    else
    {
        if (parameter.DefaultValue != null)
        {
            kernelArguments.Add(parameterName, parameter.DefaultValue);
        }
        else if (parameter.IsRequired)
        {
            throw new ArgumentException($"Missing required parameter: {parameterName}");
        }
    }
}
var result = await function.InvokeAsync(kernel, kernelArguments);
return result.ToString();

由此可以得出几个行为事实:

  • 按类型反序列化:LLM 给出的 JSON 值会按函数元数据中声明的 ParameterType 反序列化为对应 .NET 对象,因此插件方法签名(如 string location)决定了参数类型约束;
  • 默认值兜底:LLM 未提供某参数、但函数元数据有默认值时,使用默认值;
  • 必填参数缺失会抛异常:既无传入值又无默认值的必填参数会导致 ArgumentException,调试工具调用失败时这是常见排查点;
  • 执行入口是 function.InvokeAsync(kernel, ...):真正运行的是 Semantic Kernel 的函数调用管线,结果以 ToString() 字符串形式回传给 ToolCallResultMessage

执行阶段:FunctionCallMiddleware 的拦截逻辑

FunctionCallMiddlewareFunctionCallMiddleware.cs)的核心流程在 InvokeAsync 中:

  1. 先检查入站消息:若上下文最后一条消息本身就是 ToolCallMessage(例如 Step 4 中第二次 SendAsync 传入的聚合消息被拆解后的场景),则直接执行映射中对应的函数并返回 ToolCallResultMessage短路内部 Agent,不再请求 LLM
  2. 合并函数列表:把中间件自带的 functions(即插件契约)与 GenerateReplyOptions.Functions 中已有的函数拼接,写入 options.Functions 后再调用 agent.GenerateReplyAsync(...)——这就是插件函数“出现在 LLM 可用工具列表”的机制;
  3. 拦截出站工具调用:若 Agent 的回复是 ToolCallMessage 且函数名在映射中,则执行函数并返回 ToolCallAggregateMessage(包含 ToolCallMessageToolCallResultMessage,即 Step 4 两次调用之间传递的聚合消息);若函数不在映射中或回复不是工具调用消息,则原样返回 Agent 的回复;
  4. 对不在映射中的函数名,InvokeToolCallMessagesBeforeInvokingAgentAsync 会把错误信息 "Function {name} is not available. Available functions are: ..." 作为该次工具调用的结果写回,帮助 LLM 自我纠正。

该中间件同时实现了 IStreamingMiddleware,支持流式场景下对 ToolCallMessageUpdate 的合并与执行,因此插件能力在流式对话中同样可用(从源码结构看,流式路径与非流式路径共用同一套函数映射与拦截逻辑)。

适用边界与注意事项

  • 消息类型支持范围KernelPluginMiddleware 走的是 AutoGen 核心消息体系(TextMessage/ToolCallMessage 等),与 SemanticKernelChatMessageContentConnector 的限制不同——后者的概览文档明确说明当前仅支持 TextMessageImageMessageMultiModalMessage 之间的转换,尚不支持 ToolCallMessage/ToolCallResultMessage。用插件中间件时消息流由 FunctionCallMiddleware 处理,不受该连接器限制;
  • 模型与密钥前提:示例硬编码 gpt-4o-mini 且要求 OPENAI_API_KEY 环境变量;OpenAIChatAgent 依赖 AutoGen.OpenAI 包提供的 OpenAIChatAgent 与消息连接器扩展(AutoGen.OpenAI.Extension 命名空间);
  • 两次 SendAsync 是流程设计使然:工具执行发生在中间件层,最终答复需要把工具结果送回 LLM,因此对话代码要包含第二次调用;
  • 插件函数名即契约名functionName(如 GetWeather)同时出现在 LLM 的工具列表与执行映射字典的键中,二者天然一致,无需额外对齐;若自定义插件包含多个函数,每个函数都会被转换为契约并进入映射。

小结

KernelPluginMiddleware 是 AutoGen .NET 打通“Semantic Kernel 插件生态 → 任意 AutoGen Agent”的桥梁:构造时把插件函数元数据转换为 AutoGen 的 FunctionContract 并建立“函数名 → 执行委托”映射,运行时由核心的 FunctionCallMiddleware 将函数注入 LLM 请求、拦截工具调用消息并代为执行,返回 ToolCallAggregateMessage 供第二轮生成最终答复。完整可运行的示例代码位于 dotnet/samples/AgentChat/AutoGen.SemanticKernel.Sample/Use_Kernel_Functions_With_Other_Agent.cs,配套入口为 Program.cs;中间件源码见 KernelPluginMiddleware.cs,契约转换扩展见 KernelExtension.cs

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
903
1.82 K
docsdocs
暂无描述
Markdown
888
5.78 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.51 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384
flutter_flutterflutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
Dart
1.17 K
341