news 2026/9/11 1:31:05

Semantic Kernel 中的 OpenAI Function Calling 支持:从 ADR 决策到源码实现的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Semantic Kernel 中的 OpenAI Function Calling 支持:从 ADR 决策到源码实现的完整指南

Semantic Kernel 中的 OpenAI Function Calling 支持:从 ADR 决策到源码实现的完整指南

【免费下载链接】semantic-kernelIntegrate cutting-edge LLM technology quickly and easily into your apps项目地址: https://gitcode.com/GitHub_Trending/se/semantic-kernel

导读

本篇文章围绕 docs/decisions/0017-openai-function-calling.md 这份架构决策记录(ADR),深入剖析 Semantic Kernel 项目如何支持 OpenAI Chat Completions API 的 Function Calling 能力。你将理解该能力的 API 背景、当时的三大候选方案与最终决策理由,以及这一决策在当前仓库代码中的具体落地形态——包括请求设置对象、FunctionChoiceBehaviorToolCallBehavior的用法、自动调用机制与安全护栏。读完本文,你既能掌握在 SK 应用中启用函数调用的完整实操方法,也能从源码层面理解其设计边界。

背景:OpenAI 的 Function Calling 能力是什么

2023 年 OpenAI 为 Chat Completions 的/v1/chat/completions端点引入了 Function Calling 能力,允许开发者在请求中描述函数,由模型自行判断是否需要调用某个函数,并在回答中输出一个包含函数名与参数的 JSON 对象。这项能力由两个新的 API 参数启用:

参数取值作用
function_callauto(默认)、none、或指定某个函数名控制模型是否调用函数、调用哪个函数
functions一组 JSON 描述描述模型可用的函数(函数名、参数 schema 等)

文档中还明确了一个重要的成本特征:提供给模型的函数描述会被注入到 system message 中,并按输入 token 计费。这意味着函数列表越长,单次请求的输入成本越高——这一事实直接影响了后文 ADR 的决策驱动力。

从 Semantic Kernel 的角度看,社区多次提出希望在使用 SK 调用支持该能力的 OpenAI 聊天模型时,能够利用这一特性。这正是本 ADR 的起源。

架构决策:三个候选方案与最终选择

决策驱动力

ADR 记录了三个关键决策驱动力(Decision Drivers):

  1. 最小化对核心 Kernel 的改动:OpenAI 专属的功能不应污染核心抽象;
  2. 成本顾虑:请求中携带一长串函数描述会产生显著的 token 开销;
  3. 安全与成本顾虑:自动执行模型返回的函数存在风险,需要谨慎对待。

三个候选方案

方案一:修改接口以支持函数收发

修改IChatCompletionIChatResult接口,显式暴露函数信息相关的参数与方法。

  • 优点:为使用函数调用提供了清晰路径;应用可控制暴露给模型的函数(包括非 SK 函数)。
  • 缺点:对核心 Kernel 抽象产生破坏性变更(breaking change);OpenAI 专属功能会进入核心抽象,其他模型提供商必须忽略这些成员。
方案二:不修改接口,通过现有请求设置对象收发函数(最终选择)

复用现有的 request settings 对象,将函数描述随请求发给模型。应用开发者自行控制包含哪些函数,并负责校验与执行模型返回的函数结果

  • 优点:避免了对核心 Kernel 的破坏性变更;OpenAI 专属逻辑被限制在 OpenAI connector 包内;应用可完全控制暴露给模型的函数(包括非 SK 函数);为未来与 Planner 集成保留了可能性。
  • 中性:要求应用开发者自行校验并执行返回的函数。
  • 缺点:使用方式不如接口方案直观,函数结果的访问路径不够明显。
方案三:围绕函数调用实现一个 Planner

把"编排外部函数调用"纳入 SK 的规划(Planning)概念,实现一个类似 ActionPlanner 的 Planner,将函数调用结果转换为可由开发者执行的计划。

  • 优点:以计划(plan)形式返回结果,便于开发者执行所选函数。
  • 缺点:函数必须先注册到 Kernel 才能被执行;会造成"该用哪个 Planner"的混淆。

决策结果

最终选择方案二:不修改接口,利用现有请求设置对象发送函数到模型。ADR 同时记录了后续演进的重要注记:关于"模型返回的函数若已注册到 Kernel 是否自动执行"存在大量讨论,由于仍有诸多悬而未决的问题,初版实现不包含自动调用能力,留待未来版本探索。

决策在源码中的落地:从 ToolCallBehavior 到 FunctionChoiceBehavior

随着项目演进,这一 ADR 的决策在仓库中形成了两代实现,正好对应"先不自动调用、后支持自动调用"的演进脉络。

初代实现:ToolCallBehavior(OpenAI Connector 内)

在 ToolCallBehavior.cs 中,抽象类ToolCallBehavior定义了 OpenAI connector 内的工具调用行为,提供四个静态工厂:

工厂方法行为
EnableKernelFunctions向模型提供 Kernel 中所有插件函数的信息,但不自动执行,函数调用请求回传给调用方
AutoInvokeKernelFunctions向模型提供所有插件函数信息,并自动执行模型请求的函数,把结果回传给模型
EnableFunctions(functions, autoInvoke)向模型提供指定的函数列表,可选择性开启自动执行
RequireFunction(function, autoInvoke)强制模型使用指定的某个函数

关键安全护栏在类内常量中体现:

private const int DefaultMaximumAutoInvokeAttempts = 128;

单次用户请求内的自动调用轮次上限为 128 次。注释中说明这是防止模型反复请求同一函数导致"失控执行(runaway execution)"的保险机制,达到上限后AutoInvokeKernelFunctions会退化为EnableKernelFunctions的行为(只提供函数信息,不再自动执行)。

该行为对象被挂载到 OpenAIPromptExecutionSettings.cs 的ToolCallBehavior属性上。其文档注释清晰地给出了四种配置方式的语义:

  • null(默认)=完全禁用工具调用;
  • ToolCallBehavior.RequireFunction(...)= 要求模型使用指定函数;
  • ToolCallBehavior.EnableFunctions(...)= 允许模型从给定列表中选择函数;
  • ToolCallBehavior.EnableKernelFunctions/AutoInvokeKernelFunctions= 允许模型请求 Kernel 中的任意函数,后者附带自动执行能力。

演进后的实现:FunctionChoiceBehavior(核心抽象层)

后续版本将函数选择能力提升到了核心抽象层,位于 dotnet/src/SemanticKernel.Abstractions/AI/FunctionChoiceBehaviors/。FunctionChoiceBehavior是抽象基类,通过 JSON 多态反序列化支持三种派生类型(type discriminator 分别为auto/required/none),并提供了三个静态工厂方法:

工厂方法Choice 语义说明
FunctionChoiceBehavior.Auto()FunctionChoice.Auto由模型自行决定是否调用、调用哪些函数(对应 OpenAI 的function_call: auto
FunctionChoiceBehavior.Required()FunctionChoice.Required强制模型调用一个或多个函数
FunctionChoiceBehavior.None()FunctionChoice.None向模型提供函数描述,但指示其不要调用(可用于让模型先"描述"将要执行的函数供人工校验)

三个工厂方法均接受三个参数:functions(为null时提供 Kernel 中全部插件函数,为空集合时等价于禁用函数调用)、autoInvoke(是否由 AI connector 自动执行函数)、options(行为选项)。

从源码看,函数解析的核心逻辑在基类的 GetFunctions 方法 中,值得注意的推断结论:

  • 自动调用必须依赖 Kernel:当autoInvoke = true而 Kernel 为null时,直接抛出KernelException("Auto-invocation is not supported when no kernel is provided.")。其意图在注释中写明:绝不能向模型承诺"我能处理这些函数"却在执行时失败,因此宁可提前失败。
  • 显式指定的函数必须能被解析:指定了函数全名(FQN,如PluginName.FunctionName)却找不到时,自动调用模式下会提前抛错;非自动调用模式下才会回退到传入的KernelFunction实例列表。
  • functions为 null 时展开全部插件:遍历kernel.Plugins将所有函数加入候选列表。

Required行为还有一个值得注意的实现细节:在 RequiredFunctionChoiceBehavior.GetConfiguration 中,当RequestSequenceIndex >= 1(即多轮自动调用中的后续请求)时,会停止向模型继续广告函数,防止模型反复调用同一个函数——代码注释明确标注这是临时方案,未来将改为动态控制函数广告列表。

行为选项由 FunctionChoiceBehaviorOptions.cs 定义:

属性JSON 字段默认值作用
AllowParallelCallsallow_parallel_callsnull(采用模型默认)是否让模型优先并行调用多个函数
AllowConcurrentInvocationallow_concurrent_invocationfalse模型并行请求的多个函数是否允许并发执行(若函数不修改共享状态可设为true
AllowStrictSchemaAdherenceallow_strict_schema_adherencefalse是否要求模型严格遵循函数 schema
RetainArgumentTypes(非序列化字段,实验特性SKEXP0001false函数参数是否保留类型信息(默认 SK 将参数反序列化为字符串;为true时以JsonElement保留类型)

实践:在应用中启用函数调用

使用FunctionChoiceBehavior.Auto()的典型用法

仓库示例 FunctionCalling.cs 展示了最典型的用法——在OpenAIPromptExecutionSettings中配置行为,并传入 Kernel(因为自动调用需要 Kernel 来解析与执行函数):

OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }; Kernel kernel = new(); var result = await kernel.GetRequiredService<IChatCompletionService>() .GetChatMessageContentAsync(chatHistory, settings, kernel);

其中FunctionChoiceBehavior.Auto()等价于FunctionChoiceBehavior.Auto(functions: null, autoInvoke: true),即:把 Kernel 中全部插件函数广告给模型,并在模型请求调用时自动执行

如果只想把函数信息暴露给模型、由应用自行校验与执行(对应 ADR 决策中的"应用开发者负责校验和执行函数结果"),则显式关闭自动调用:

OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = Microsoft.SemanticKernel.FunctionChoiceBehavior.Auto(autoInvoke: false) };

同目录下的 AzureAIInference_FunctionCalling.cs 表明,该行为不仅是 OpenAI 专属——兼容 OpenAI 协议的 Azure AI Inference 服务同样通过FunctionChoiceBehavior.Auto()配置,印证了 ADR 中"OpenAI 专属功能收敛在 connector 包内、核心抽象保持通用"的设计意图。

函数描述的成本与数量控制

回到 ADR 记录的事实:函数描述按输入 token 计费。因此实践中的关键建议是:

  • 不要无条件地把 Kernel 中所有函数广告给模型,应根据场景用FunctionChoiceBehavior.Auto(functions: 指定函数集合, ...)Required/None收敛候选列表;
  • 多轮对话中模型可能反复请求同一函数,Required行为在第二轮起停止广告函数,ToolCallBehavior的 128 次自动调用上限,都是防止 token 失控与循环调用的内置护栏;
  • 在需要人工审批的场景(如敏感操作前),可先用FunctionChoiceBehavior.None()让模型"描述"将要执行的函数,由人确认后再真正执行。

执行入口与验证测试

自动调用的完整链路(模型返回函数调用 → connector 解析 → 调用 Kernel 函数 → 回传结果给模型)由 ClientCore.ChatCompletion.cs 承载。仓库中的集成测试提供了可直接参考的验证场景:

  • OpenAIChatCompletion_FunctionCallingTests.cs
  • AzureOpenAIChatCompletionFunctionCallingTests.cs
  • AutoFunctionInvocationFilterTests.cs(自动调用与过滤器协同的单元测试)

此外,该能力同样覆盖其他兼容 Function Calling 的 provider(如 Google Gemini 的 GeminiToolCallBehavior.cs、Mistral 的 MistralAIToolCallBehavior.cs),可见"函数选择行为抽象化"已成为 SK 多模型 connector 的通用模式。

演进脉络总结

阶段代表产物自动调用位置
ADR 决策(2023-09)0017-openai-function-calling.md明确暂不包含架构决策记录
初代实现ToolCallBehavior.csEnableKernelFunctions不自动,AutoInvokeKernelFunctions自动OpenAI connector 包内
通用抽象FunctionChoiceBehaviorAuto/Required默认自动,None不自动核心抽象层

这条演进路径清晰印证了 ADR 的决策智慧:先以最小侵入方式(不改接口、不自动执行)在 connector 内落地能力,待自动调用行为的安全性讨论成熟后,再以通用抽象的形式沉淀到核心层——既避免了破坏性变更,又保留了未来的扩展空间。

参考与延伸阅读

  • 决策原文:0017-openai-function-calling.md
  • 关联 ADR:0017-openai-function-calling.md 后续的 0061-function-call-behavior.md 与 0063-function-calling-reliability.md 记录了该能力后续的行为演进与可靠性设计
  • 行为实现:FunctionChoiceBehavior.cs、FunctionChoiceBehaviorOptions.cs
  • Connector 实现:ToolCallBehavior.cs、OpenAIPromptExecutionSettings.cs
  • 可运行示例:FunctionCalling.cs
  • 验证测试:OpenAIChatCompletion_FunctionCallingTests.cs

【免费下载链接】semantic-kernelIntegrate cutting-edge LLM technology quickly and easily into your apps项目地址: https://gitcode.com/GitHub_Trending/se/semantic-kernel

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 1:29:41

7行YAML跑通一条E2E测试:Maestro 凭什么让你少写一半自动化脚本

7行YAML跑通一条E2E测试&#xff1a;Maestro 凭什么让你少写一半自动化脚本 【免费下载链接】Maestro Painless E2E Automation for Mobile and Web 项目地址: https://gitcode.com/GitHub_Trending/ma/Maestro 上周我把一条登录流程测试从 87 行 Appium 代码改成 7 行 …

作者头像 李华
网站建设 2026/9/11 1:29:10

微网优化调度与粒子群算法:需求响应下的源储荷协调策略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 1:28:16

Claudian 使用指南:三步让 AI 帮你整理 Obsidian 知识库

Claudian 使用指南&#xff1a;三步让 AI 帮你整理 Obsidian 知识库 【免费下载链接】claudian An Obsidian plugin that embeds Claude Code/Codex as an AI collaborator in your vault 项目地址: https://gitcode.com/GitHub_Trending/cl/claudian 整理知识库的痛点&…

作者头像 李华
网站建设 2026/9/11 1:28:05

G-Helper 完整指南:华硕笔记本控制与 Armoury Crate 替代方案

G-Helper 完整指南&#xff1a;华硕笔记本控制与 Armoury Crate 替代方案 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops with nearly the same functionality. Works with ROG Zephyrus, Flow, TUF, Strix, Scar, ProArt, Vivobook, Zenb…

作者头像 李华
网站建设 2026/9/11 1:26:04

Docker容器文件与宿主机挂载:数据持久化实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华