ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Semantic Kernel OpenAPI 函数 Payload 处理机制全解析:从静态传递到动态构建(ADR 0062)

2026/9/11 18:52:39 拓冰建站 浏览量
Semantic Kernel OpenAPI 函数 Payload 处理机制全解析:从静态传递到动态构建(ADR 0062) Semantic Kernel OpenAPI 函数 Payload 处理机制全解析从静态传递到动态构建ADR 0062【免费下载链接】semantic-kernelIntegrate cutting-edge LLM technology quickly and easily into your apps项目地址: https://gitcode.com/GitHub_Trending/se/semantic-kernel本文基于 Semantic Kernel 仓库中的架构决策记录ADR0062-open-api-payload.md系统梳理 .NET 版 Semantic Kernel 中 OpenAPI 函数请求体Payload的处理方式调用方手动构造、基于叶子属性动态构造、基于命名空间Namespace的叶子属性构造以及被评审后否决的基于根属性构造方案。读完本文你将掌握EnableDynamicPayload与EnablePayloadNamespacing两个核心开关的行为边界、适用场景与限制并能结合仓库源码理解其底层实现在实际项目中正确选择 Payload 处理策略。背景为什么需要专门讨论 OpenAPI 函数的 Payload在 Semantic Kernel下文简称 SK中将第三方 REST API 以 OpenAPI 文档形式导入为 Plugin 后每个 OpenAPI 操作operation会被转换成一个 Kernel Function。这些操作通常需要一个请求体request body / payload而 payload 的提供方式直接决定了调用方的编码复杂度。简单场景payload 结构扁平调用方手工拼一个 JSON 字符串即可复杂场景payload 嵌套多级对象、同一属性名在不同层级重复出现、甚至存在循环引用。ADR 0062状态为 proposed日期 2024-10-25正是围绕SK 如何在调用 OpenAPI 函数时获得 payload这一问题展开的它先盘点当时已存在的三种处理选项再提出一个新选项Option #4最后给出评审结论。该 ADR 对应的实现逻辑集中在 dotnet/src/Functions/Functions.OpenApi 目录下核心入口为OpenApiFunctionExecutionParameters类与OpenApiKernelPluginFactory类。在进入各选项之前需要先认识两个贯穿全文的配置开关定义见 OpenApiFunctionExecutionParameters.cs属性默认值作用EnableDynamicPayloadtrue是否根据 OpenAPI 元数据 传入参数动态构造 payload设为false时payload 必须通过payload参数由调用方提供EnablePayloadNamespacingfalse是否用父属性名作为前缀点分隔来消除同名属性冲突仅在EnableDynamicPayload true时生效两个默认值在构造函数中明确给出enableDynamicOperationPayload true、enablePayloadNamespacing false并在 OpenApiKernelPluginFactory.cs 组装RestApiOperationRunner时以executionParameters?.EnableDynamicPayload ?? true、executionParameters?.EnablePayloadNamespacing ?? false的方式落地。现有方案一payload与content-type参数调用方全权构造这是最直接、也最可控的方式调用方自己按照 OpenAPI schema 手工构造请求体以普通参数的形式传给 OpenAPI 函数。// 导入 OpenAPI 插件并关闭动态 payload 构造 KernelPlugin plugin await kernel.ImportPluginFromOpenApiAsync(plugin-name, new Uri(plugin-uri), new OpenApiFunctionExecutionParameters { EnableDynamicPayload false }); // 为 createEvent 函数构造 payload string payload { subject: IT Meeting, start: { dateTime: 2023-10-01T10:00:00, timeZone: UTC }, end: { dateTime: 2023-10-01T11:00:00, timeZone: UTC }, tags: [ { name: IT }, { name: Meeting } ] } ; // 构造函数参数 KernelArguments arguments new () { [payload] payload, [content-type] application/json }; // 调用 createEvent 函数 FunctionResult functionResult await kernel.InvokeAsync(plugin[createEvent], arguments);需要特别强调的是该方式下 SK 不会对 payload 做任何校验或修改。SK 只负责把payload参数原样作为 HTTP 请求体发出payload 是否合法、是否符合 OpenAPI schema完全由调用方负责。因此这一选项适合以下场景payload 结构复杂或特殊动态构造难以覆盖payload 中包含oneOf、allOf、anyOf等组合 schema见下文各方案的通用限制调用方已有现成的序列化对象或模板。从源码看这一机制对应RestApiOperationExtensions中的人工参数artificial parameter创建逻辑当关闭动态构造时SK 会为操作生成payload与content-typecontent_type两个特殊参数。在仓库示例 OpenApiPlugin_PayloadHandling.cs 中可以看到此时createMeeting函数的元数据参数列表恰好就是payload描述为 REST API request body.schema 为完整对象结构和content_typeContent type of REST API request body.两个参数。现有方案二基于叶子属性Leaf Properties的动态构造这是 SK 的默认行为EnableDynamicPayload默认即true。调用方无需提供完整 payload只需为 schema 中的叶子属性提供同名参数SK 负责按 schema 结构把它们组装成请求体。// 导入插件动态 payload 构造默认开启也可显式写出 KernelPlugin plugin await kernel.ImportPluginFromOpenApiAsync(plugin-name, new Uri(plugin-uri), new OpenApiFunctionExecutionParameters { EnableDynamicPayload true // 默认即为 true }); // 期望构造出的 payload 结构 //{ // subject: ..., // start: { // dateTime: ..., // timeZone: ... // }, // duration: PT1H, // tags:[{ // name: ..., // } // ], //} // 为 createEvent 函数提供叶子属性参数 KernelArguments arguments new() { [subject] IT Meeting, [dateTime] DateTimeOffset.Parse(2023-10-01T10:00:00), [timeZone] UTC, [duration] PT1H, [tags] new[] { new Tag(work), new Tag(important) } }; // 调用 createEvent 函数 FunctionResult functionResult await kernel.InvokeAsync(plugin[createEvent], arguments);工作原理SK 从 payload schema 的根属性出发向下遍历沿途收集所有叶子属性即不再拥有子属性的属性。调用方需要为这些叶子属性提供参数SK 再依据 schema 层级把它们嵌套组装成最终 JSON 结构。三个必须注意的限制同名属性冲突当 payload 在不同层级出现同名属性例如start.dateTime与end.dateTime时由于导入过程会为每个 OpenAPI 操作生成一个 Kernel Function而一个函数不可能拥有两个同名参数插件导入会直接失败报错信息为The function has two or more parameters with the same name property-name.循环引用检测若 schema 中两个或多个属性相互引用形成环路SK 会检测到循环引用并抛出异常导致操作导入失败。数组属性按叶子处理该方案不会继续遍历数组元素而是把数组属性本身视为叶子。也就是说调用方要为数组属性如tags提供整体值而不是为数组元素的字段逐个提供参数——上例中tags就是作为一个完整的对象数组传入的。对应到源码payload 的属性树由RestApiPayloadProperty表示见 RestApiPayloadProperty.cs其Name、Properties子属性列表、IsRequired、DefaultValue等字段完整刻画了 schema 树形结构而真正的组装逻辑位于 RestApiOperationRunner.cs 的BuildOperationPayload方法中运行时通过内部委托RestApiOperationPayloadFactory见 RestApiOperationPayloadFactory.cs把操作元数据与参数映射为最终 payload。现有方案三叶子属性 命名空间Namespaces动态构造方案二最大的痛点是同名属性冲突。方案三通过**给叶子属性名添加父属性名前缀点分隔**来生成全局唯一参数名从而化解冲突。// 导入插件同时开启动态构造与命名空间 KernelPlugin plugin await kernel.ImportPluginFromOpenApiAsync(plugin-name, new Uri(plugin-uri), new OpenApiFunctionExecutionParameters { EnableDynamicPayload true, EnablePayloadNamespacing true }); // 期望构造出的 payload 结构 //{ // subject: ..., // start: { // dateTime: ..., // timeZone: ... // }, // end: { // dateTime: ..., // timeZone: ... // }, // tags:[{ // name: ..., // } // ], //} // 使用带命名空间的参数名 KernelArguments arguments new() { [subject] IT Meeting, [start.dateTime] DateTimeOffset.Parse(2023-10-01T10:00:00), [start.timeZone] UTC, [end.dateTime] DateTimeOffset.Parse(2023-10-01T11:00:00), [end.timeZone] UTC, [tags] new[] { new Tag(work), new Tag(important) } }; // 调用 createEvent 函数 FunctionResult functionResult await kernel.InvokeAsync(plugin[createEvent], arguments);工作原理与差异点与方案二相同SK 仍然从根属性向下遍历收集叶子属性当遇到叶子属性时SK 检查其是否存在父属性若存在则以父属性名.叶子属性名的形式生成唯一名称。例如start对象下的dateTime属性参数名即为start.dateTime数组属性的处理方式与方案二一致仍被视为叶子调用方需要提供完整数组值。这一点在EnablePayloadNamespacing的 XML 文档注释中解释得很清楚见 OpenApiFunctionExecutionParameters.cs没有命名空间时sender与receiver两个父对象下的email参数都会从同一个email参数解析这显然是错误的启用命名空间后sender.email与receiver.email就能从同名参数分别正确解析。从函数元数据上也可以直观看到差异在示例 OpenApiPlugin_PayloadHandling.cs 中开启命名空间后createMeeting的参数列表变为subject、start_dateTime、start_timeZone、end_dateTime、end_timeZone、tags每个嵌套字段都被父名唯一化了。仍需注意方案三同样把数组属性视为叶子方案三同样对 schema 中的循环引用敏感——一旦检测到循环引用操作导入仍会失败。候选方案四基于根属性Root Properties的动态构造已否决提出动机前三种方案仍不能覆盖一类复杂场景payload 在不同层级存在同名属性而使用命名空间又不可行例如命名空间与既有函数命名规范冲突、或调用方就是希望按对象整体传参。为了既不让调用方手工拼整段 JSON又能处理这种嵌套结构ADR 提出了 Option #4由 SK 基于根属性构造 payload调用方为每个根属性提供完整对象值。// 注意这里关闭动态构造但开启命名空间仅为示例展示组合方式 KernelPlugin plugin await kernel.ImportPluginFromOpenApiAsync(plugin-name, new Uri(plugin-uri), new OpenApiFunctionExecutionParameters { EnableDynamicPayload false, EnablePayloadNamespacing true }); // 期望构造出的 payload 结构 //{ // subject: ..., // start: { // dateTime: ..., // timeZone: ... // }, // end: { // dateTime: ..., // timeZone: ... // }, // tags:[{ // name: ..., // } // ], //} // 为根属性提供完整对象值 KernelArguments arguments new() { [subject] IT Meeting, [start] new MeetingTime() { DateTime DateTimeOffset.Parse(2023-10-01T10:00:00), TimeZone TimeZoneInfo.Utc }, [end] new MeetingTime() { DateTime DateTimeOffset.Parse(2023-10-01T10:00:00), TimeZone TimeZoneInfo.Utc }, [tags] new[] { new Tag(work), new Tag(important) } }; // 调用 createEvent 函数 FunctionResult functionResult await kernel.InvokeAsync(plugin[createEvent], arguments);该方案的定位是自然介于方案一与方案二之间相对于方案一调用方无需手工拼 JSON 字符串相对于方案二嵌套对象的构建由调用方以强类型对象形式完成规避了叶子属性同名冲突。其代价是为根属性构造对象参数的复杂度转移到了调用方——ADR 也承认在不允许使用命名空间、且不同层级同名属性必须从扁平参数列表解析的前提下SK 能做的确实有限。决策结果经过评审该方案最终被否决理由是没有充分证据表明它能比现有方案一payloadcontent-type带来额外收益。因此当前仓库中并不存在基于根属性动态构造的落地实现读者应把该节视为一次完整的架构权衡记录而非可用功能。四方案总览对比ADR 用一张表格完整对比了各方案的职责划分与限制此处原样继承并补充说明方案调用方职责SK 职责限制1.payload与content-type参数构造完整 payload原样使用无限制4. 基于根属性动态构造已否决提供根属性参数构造 payload1. 不支持anyOf、allOf、oneOf2. 基于叶子属性动态构造提供叶子属性参数构造 payload1. 不支持anyOf、allOf、oneOf2. 叶子属性必须唯一3. 存在循环引用风险3. 叶子属性 命名空间动态构造提供命名空间化属性参数构造 payload1. 不支持anyOf、allOf、oneOf2. 存在循环引用风险表格中的两个共性限制值得展开其一组合 schemaanyOf/allOf/oneOf不支持动态构造。三种动态构造方案2、3、4都明确标注不支持anyOf、allOf、oneOf因为这类 schema 的合法结构取决于运行时多选一/多选多的分支难以从扁平的 KernelArguments 中无歧义还原。因此当 payload schema 含有组合类型时只能回退到方案一由调用方或交给 LLM 通过 Function Calling 生成提供完整 payload。仓库示例 OpenApiPlugin_PayloadHandling.cs 中的oneOfV3.json、allOfV3.json、anyOfV3.json三个 Pets 插件用例正是这一场景它们全部显式设置EnableDynamicPayload false再由 AI 通过FunctionChoiceBehavior.Auto()自主构造符合分支条件的 payload。其二动态构造仅覆盖 schema 的树形结构。从RestApiPayloadProperty的属性树模型可以看出动态构造依赖根 → 中间节点 → 叶子的完整遍历一旦出现循环引用遍历便无法终止SK 因此选择在导入阶段直接失败以保护运行时。实践指南如何选择 Payload 处理策略结合 ADR 与仓库实现给出如下决策路径基于 .NET 版 Semantic Kernel 当前仓库 dotnet/src/Functions/Functions.OpenApi 的实际行为payload 结构简单、无跨层同名属性→ 使用默认配置即可EnableDynamicPayload保持true默认按叶子属性传参SK 自动组装存在跨层同名属性如start.dateTime/end.dateTime→ 开启EnablePayloadNamespacing true使用parent.child形式的参数名schema 含anyOf/allOf/oneOf组合类型→ 关闭动态构造EnableDynamicPayload false通过payload与content-type参数传入完整请求体此场景也最适合结合 LLM Function Calling让模型依据用户意图生成符合分支 schema 的 payload需要完全掌控请求体如复用已有序列化 DTO、签名、加密等→ 同样关闭动态构造走方案一导入时遇到 The function has two or more parameters with the same name 或循环引用报错→ 按上述规则 2/3 调整配置若确认 schema 存在循环引用应先修正 OpenAPI 文档本身。验证手段仓库中可直接运行的验证素材完整示例dotnet/samples/Concepts/Plugins/OpenApiPlugin_PayloadHandling.cs —— 覆盖方案一、二、三以及oneOf/allOf/anyOf四种场景并通过StubHttpHandler拦截并打印实际发出的请求 payload方便对照预期结构参数默认值接线OpenApiKernelPluginFactory.cs 中EnableDynamicPayload与EnablePayloadNamespacing的默认值传递参数元数据生成RestApiOperationExtensions.cs 中CreatePayloadArtificialParameter对payload/content-type人工参数的处理执行期组装RestApiOperationRunner.cs 的BuildOperationPayload与RestApiOperationPayloadFactory委托。总结ADR 0062 完整记录了 Semantic Kernel .NET 在 OpenAPI 函数 payload 处理上的设计权衡方案一以完全可控换取完全自理方案二以零手工拼装换取叶子属性唯一性约束方案三用命名空间化解同名冲突但保留循环引用与组合 schema 限制方案四根属性构造则在评审后因缺乏相对方案一的增量价值而被否决。对使用者而言核心结论只有一条默认开启的动态构造适合结构规整的 payload一旦遇到跨层同名、组合 schema 或需要精确控制请求体就应关闭动态构造并显式提供 payload。理解这两个开关EnableDynamicPayload、EnablePayloadNamespacing及其边界是正确驾驭 SK OpenAPI 插件能力的关键。【免费下载链接】semantic-kernelIntegrate cutting-edge LLM technology quickly and easily into your apps项目地址: https://gitcode.com/GitHub_Trending/se/semantic-kernel创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考