拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Semantic Kernel Function Calling 可靠性设计解析:从 FQN 幻觉到自恢复机制的 ADR 深度指南

Semantic Kernel Function Calling 可靠性设计解析从 FQN 幻觉到自恢复机制的 ADR 深度指南【免费下载链接】semantic-kernelIntegrate cutting-edge LLM technology quickly and easily into your apps项目地址: https://gitcode.com/GitHub_Trending/se/semantic-kernel导读Function Calling函数调用是 LLM 应用落地的关键能力而它的可靠性直接取决于 AI 模型能否精确使用我们广告advertise出去的函数全限定名Fully Qualified NameFQN。本文以 Semantic Kernel 官方架构决策记录 0063-function-calling-reliability.md 为主体逐条剖析函数名幻觉的三大根因下划线分隔符幻觉、点号分隔符幻觉、自恢复机制不可靠对比六种候选解决方案的取舍并结合当前仓库的 .NET 源码实现FunctionCallsProcessor、FunctionName、OpenAI Connector验证其落地情况。读完本文你将理解 SK 函数 FQN 的构造规则、错误回传链路的内部机制以及如何通过配置与系统提示词提升函数调用的自愈能力。1. 背景函数全限定名FQN与幻觉问题Semantic Kernel 在向 AI 模型广告函数时使用插件名plugin name与函数名function name拼接的方式构造函数的全限定名并以连字符-作为分隔符从而在多个插件之间唯一标识函数。例如插件foo中的函数bar其 FQN 为foo-bar。该拼接逻辑在当前仓库中的实现位于 FunctionName.cspublic static string ToFullyQualifiedName(string functionName, string? pluginName null, string functionNameSeparator -) { return string.IsNullOrEmpty(pluginName) ? functionName : ${pluginName}{functionNameSeparator}{functionName}; }在广告函数时SK 正是通过它生成模型可见的名称。例如 AutoFunctionChoiceBehavior.cs 中的this.Functions functions?.Select(f FunctionName.ToFullyQualifiedName(f.Name, f.PluginName, FunctionNameSeparator)).ToList();核心痛点在于决定 SK 函数调用可靠性的关键因素之一是 AI 模型能否以广告时的精确名称调用函数。而现实是模型经常幻觉出错误的函数名。绝大多数情况下幻觉只发生在函数名中的一个字符上——恰恰就是 SK 用来连接插件名与函数名的连字符-。目前已观察到的幻觉形态包括把foo-bar幻觉为foo_bar下划线把foo-bar幻觉为foo.bar点号这一现象构成了 ADR 中三大问题的共同背景。2. 三大问题剖析2.1 Issue #1下划线分隔符幻觉foo_bar当 AI 模型把连字符幻觉成下划线_时SK 能够检测到该错误——因为函数名并未以广告时的 FQN 出现。SK 的处理方式是将该错误作为函数结果的一部分回传给模型错误信息为Error: Function call request for a function that wasnt defined.并携带原始的 function call在后续请求中一起发送。部分模型可以据此自动恢复改用正确名称调用而另一些模型则无法恢复。2.2 Issue #2点号分隔符幻觉foo.bar该问题与 Issue #1 类似但分隔符是.。尽管 SK 同样检测到了错误并尝试在后续请求中将其回传给模型请求却会直接抛出异常Invalid messages[3].tool_calls[0].function.name: string does not match pattern. Expected a string that matches the pattern ^[a-zA-Z0-9_-]$.失败的原因在于幻觉出的.不是 OpenAI 函数名所允许的字符合法字符集仅为^[a-zA-Z0-9_-]$。本质上模型自己否决了自己幻觉出来的函数名导致自恢复流程根本无法启动——这是点号幻觉比下划线幻觉更致命的地方。2.3 Issue #3自恢复机制的可靠性当模型以非广告名称调用函数时函数查找失败SK 会向模型返回错误消息作为提示。理想情况下模型根据提示自我纠正、以正确名称重新调用。但实测显示自恢复机制在不同模型上的表现参差不齐✅gpt-4o-mini (2024-07-18)可以自动恢复❌gpt-4 (0613)无法恢复❌gpt-4o (2024-08-06)无法恢复无法恢复的模型最终只会返回类似下面的兜底话术Im sorry, but I cant provide the answer right now due to a system error. Please try again later.3. 决策驱动因素Decision DriversADR 明确了两个核心决策驱动最小化函数名幻觉的发生频率Minimize the occurrence of function name hallucinations增强自恢复机制的可靠性Enhance the reliability of the auto-recovery mechanism。所有候选方案均围绕这两点展开且各方案之间并非互斥可以组合使用。4. 六种候选方案详解4.1 Option 1仅使用函数名作为 FQN该方案建议放弃插件名前缀直接用函数名作为 FQN。例如插件foo中的函数bar其 FQN 就是bar。由于不再需要分隔符-幻觉的源头Issue #1 和 #2被直接消除。优点通过移除幻觉源减少或消除函数名幻觉解决 Issue #1 与 #2减少插件名在函数 FQN 中消耗的 token 数量。缺点函数名在跨插件场景下可能不唯一。例如两个插件都含有同名函数则两者都会被广告给模型SK 只会调用第一个遇到的函数ADR 评审会议补充若发现重名可动态地为重名函数或全部广告函数追加插件名缺少插件名会导致函数名上下文信息不足。例如GetData在Weather插件与Stocks插件中的含义截然不同ADR 评审会议补充插件名/上下文可由插件开发者加入函数名或描述也可由 SK 自动追加到函数描述中无法解决函数名本身的幻觉。例如模型把bar幻觉成b0r时本方案无能为力。可能的实现方式三选一// 方式一在操作operation级别配置 FunctionChoiceBehaviorOptions options new new() { UseFunctionNameAsFqn true }; var settings new AzureOpenAIPromptExecutionSettings() { FunctionChoiceBehavior FunctionChoiceBehavior.Auto(options) }; var result await this._chatCompletionService.GetChatMessageContentAsync(chatHistory, settings, this._kernel); // 方式二在 AI 连接器connector配置级别 IKernelBuilder builder Kernel.CreateBuilder(); builder.AddOpenAIChatCompletion(model-id, api-key, functionNamePolicy: FunctionNamePolicy.UseFunctionNameAsFqn); // 方式三在插件plugin级别 string pluginName string.Empty; // 若 pluginName 非空字符串则使用它作为插件名。 // 若 pluginName 为 null则从插件类型推断插件名。 // 若 pluginName 为空字符串则省略插件名该插件的所有函数均不带插件名广告。 kernel.ImportPluginFromTypeBar(pluginName);需要说明的是当前仓库中的 FunctionChoiceBehaviorOptions 实际包含的是AllowParallelCalls、AllowConcurrentInvocation、AllowStrictSchemaAdherence等选项UseFunctionNameAsFqn、FqnSeparator、FqnParser等属于该 ADR 的提案性 API尚未在现有代码中出现。4.2 Option 2自定义分隔符Custom Separator该方案建议将分隔符字符或字符序列做成可配置项由开发者指定一个更不容易被模型误生成的分隔符例如_或a1b。优点通过更换为不易被幻觉的分隔符降低函数名幻觉的发生概率缓解 Issue #1 与 #2。缺点当分隔符本身出现在插件名中时失效。例如插件名my_plugin中含下划线若同时用_作分隔符FQN 会变成my_plugin_myfunction歧义无法消除ADR 评审会议补充SK 可以在广告前动态剔除插件名与函数名中出现的分隔符无法解决函数名本身的幻觉。例如模型把MyPlugin_my_function幻觉成MyPlugin_my_func。可能的实现方式// 操作级别配置 FunctionChoiceBehaviorOptions options new new() { FqnSeparator _ }; var settings new AzureOpenAIPromptExecutionSettings() { FunctionChoiceBehavior FunctionChoiceBehavior.Auto(options) }; var result await this._chatCompletionService.GetChatMessageContentAsync(chatHistory, settings, this._kernel); // AI 连接器配置级别 IKernelBuilder builder Kernel.CreateBuilder(); builder.AddOpenAIChatCompletion(model-id, api-key, functionNamePolicy: FunctionNamePolicy.Custom(_));4.3 Option 3不使用分隔符该方案建议插件名与函数名之间不做任何分隔直接拼接。例如插件foo中的函数barFQN 为foobar。优点消除幻觉源降低函数名幻觉发生概率缓解 Issue #1 与 #2。缺点需要一种不同的函数查找启发式策略否则foobar无法可靠拆分为foobar。4.4 Option 4自定义 FQN 解析器该方案建议提供一个外部可定制的 FQN 解析器负责把模型调用的函数 FQN 拆分成插件名与函数名。解析器会尝试用多种分隔符依次解析并借助 Kernel 中的已注册函数校验结果。static (string? PluginName, string FunctionName) ParseFunctionFqn(ParseFunctionFqnContext context) { static (string? PluginName, string FunctionName)? Parse(ParseFunctionFqnContext context, char separator) { string? pluginName null; string functionName context.FunctionFqn; int separatorPos context.FunctionFqn.IndexOf(separator, StringComparison.Ordinal); if (separatorPos 0) { pluginName context.FunctionFqn.AsSpan(0, separatorPos).Trim().ToString(); functionName context.FunctionFqn.AsSpan(separatorPos 1).Trim().ToString(); } // 校验该函数是否已在 Kernel 中注册 if (context.Kernel is { } kernel kernel.Plugins.TryGetFunction(pluginName, functionName, out _)) { return (pluginName, functionName); } return null; } // 依次尝试使用连字符、点号、下划线作为分隔符进行解析 var result Parse(context, -) ?? Parse(context, .) ?? Parse(context, _); if (result is not null) { return result.Value; } // 若未找到任何分隔符则原样返回函数名交由 AI 连接器应用默认行为 return (null, context.FunctionFqn); }ADR 评审会议补充解析器也可以直接返回函数本身这需要进一步调研。ADR 中引用的 PR编号 10206可提供解析器使用位置与方式的更多线索。优点通过应用针对特定 AI 模型的自定义启发式规则解析函数 FQN可以缓解而非减少或完全消除分隔符幻觉在 SK AI 连接器中实现简单。可能的实现方式// 操作级别配置 static (string? PluginName, string FunctionName) ParseFunctionFqn(ParseFunctionFqnContext context) { ... } FunctionChoiceBehaviorOptions options new new() { FqnParser ParseFunctionFqn }; var settings new AzureOpenAIPromptExecutionSettings() { FunctionChoiceBehavior FunctionChoiceBehavior.Auto(options) }; var result await this._chatCompletionService.GetChatMessageContentAsync(chatHistory, settings, this._kernel); // AI 连接器配置级别 IKernelBuilder builder Kernel.CreateBuilder(); builder.AddOpenAIChatCompletion(model-id, api-key, functionNamePolicy: FunctionNamePolicy.Custom(_, ParseFunctionFqn));4.5 Option 5改进自恢复机制当前 SK 对调用未广告函数的响应是返回错误消息Error: Function call request for a function that wasnt defined.在gpt-4(0613)、gpt-4o-mini(2024-07-18)、gpt-4o(2024-08-06)三个模型中只有gpt-4o-mini能据此自动恢复并成功调用正确名称另外两个模型直接返回兜底话术。ADR 的实验结论是将函数名加入错误消息并在聊天历史中附加系统消息You can call tools. If a tool call failed, correct yourself.在两项改动同时生效时三个模型全部可以自动恢复以正确名称重新调用函数。优点更多模型可以从错误中自动恢复。缺点自恢复机制可能仍无法覆盖所有 AI 模型。可能的实现方式// 调用方代码 var chatHistory new ChatHistory(); chatHistory.AddSystemMessage(You can call tools. If a tool call failed, correct yourself.); chatHistory.AddUserMessage(prompt); // 函数调用处理器中 if (!checkIfFunctionAdvertised(functionCall)) { // errorMessage Error: Function call request for a function that wasnt defined.; errorMessage $Error: Function call request for the function that wasnt defined - {functionCall.FunctionName}.; return false; }4.6 Option 6剔除函数名中的非法字符该方案直击 Issue #2在把错误消息回传给模型之前先将函数 FQN 中的非法字符替换掉。这能阻止请求因Invalid messages[3].tool_calls[0].function.name: string does not match pattern...异常而失败从而让模型有机会自恢复。优点消除 Issue #2防止 AI 模型因请求异常而无法自恢复。可能的实现方式// 在 AI 连接器中 var fqn FunctionName.ToFullyQualifiedName(callRequest.FunctionName, callRequest.PluginName, OpenAIFunction.NameSeparator); // 将全部非法字符替换为下划线 fqn Regex.Replace(fqn, [^a-zA-Z0-9_-], _); toolCalls.Add(ChatToolCall.CreateFunctionToolCall(callRequest.Id, fqn, BinaryData.FromString(argument ?? string.Empty)));5. 决策结果Decision OutcomeADR 最终拍板优先实施不需要改动公共 API 表面的方案即 Option 5 与 Option 6后续再根据这两项方案的实际效果评估是否继续推进其他方案。这样的取舍逻辑清晰Option 5改进自恢复机制与 Option 6剔除非法字符都停留在内部处理逻辑层面不影响开发者既有的调用代码与公共 API风险最低、收益最直接。6. 当前仓库中的实现印证虽然该 ADR 状态为proposed提案日期 2025-01-21但当前仓库代码中已能找到与 Option 5、Option 6 高度对应的实现痕迹可作为理解其落地形态的参照6.1 错误消息链路对应 Option 5函数调用的集中处理器位于 FunctionCallsProcessor.cs。其校验函数TryValidateFunctionCall在函数未广告时会生成错误消息// Make sure the requested function is one of the functions that was advertised to the AI model. if (!checkIfFunctionAdvertised(functionCall)) { errorMessage Error: Function call request for a function that wasnt defined. Correct yourself.; return false; } // Look up the function in the kernel if (kernel?.Plugins.TryGetFunction(functionCall.PluginName, functionCall.FunctionName, out function) ?? false) { errorMessage null; return true; } errorMessage Error: Requested function could not be found. Correct yourself.; return false;注意这里实际错误消息已附带Correct yourself.引导后缀与 ADR 中建议的 You can call tools. If a tool call failed, correct yourself. 系统消息理念一脉相承——即通过显式指令引导模型自我纠正。对应的单元测试见 FunctionCallsProcessorTests.cs[Fact] public async Task ItShouldAddErrorToChatHistoryIfFunctionCallNotAdvertisedAsync() { ... // Return false to simulate that the function is not advertised checkIfFunctionAdvertised: (_) false, ... Assert.Equal(Error: Function call request for a function that wasnt defined. Correct yourself., functionResult.Result); }此外Gemini 连接器 GeminiChatCompletionClient.cs 也实现了相同的错误回传模式说明该机制已横跨多个 AI 连接器。6.2 非法字符剔除对应 Option 6OpenAI 连接器在把 tool calls 组装成 Assistant 消息时调用了SanitizeFunctionNames对函数名进行净化见 ClientCore.ChatCompletion.cstoolCalls.Add(ChatToolCall.CreateFunctionToolCall(callRequest.Id, FunctionName.ToFullyQualifiedName(callRequest.FunctionName, callRequest.PluginName, OpenAIFunction.NameSeparator), BinaryData.FromString(argument ?? string.Empty))); ... var assistantMessage new AssistantChatMessage(SanitizeFunctionNames(toolCalls)) { ParticipantName message.AuthorName };SanitizeFunctionNames的实现与 ADR Option 6 的代码示例几乎一致——用正则把非法字符统一替换为下划线ClientCore.ChatCompletion.csprivate static ListChatToolCall SanitizeFunctionNames(ListChatToolCall toolCalls) { for (int i 0; i toolCalls.Count; i) { ChatToolCall tool toolCalls[i]; // Check if function name contains disallowed characters and replace them with _. if (DisallowedFunctionNameCharactersRegex().IsMatch(tool.FunctionName)) { var sanitizedName DisallowedFunctionNameCharactersRegex().Replace(tool.FunctionName, _); toolCalls[i] ChatToolCall.CreateFunctionToolCall(tool.Id, sanitizedName, tool.FunctionArguments); } } return toolCalls; }这一实现从根本上规避了 Issue #2 中foo.bar触发 OpenAI 名称校验异常、导致整个请求失败的问题。6.3 防失控保护机制值得顺带一提的是FunctionCallsProcessor还内置了两道安全阀FunctionCallsProcessor.csMaxInflightAutoInvokes 128限制同一异步执行链中并发在途的自动调用数量防止 prompt 函数自我递归广告导致无限循环MaximumAutoInvokeAttempts 128限制单次用户请求内的自动调用迭代次数防止模型反复请求同一函数造成失控执行。当触发任一限制时GetConfiguration会将AutoInvoke置为 false 并记录日志确保函数调用链始终可控。7. 实战建议与工程启示结合 ADR 与当前实现针对函数调用可靠性可沉淀出以下可操作的工程经验广告侧尽量让 FQN 简单直观默认的-分隔符是幻觉高发点若你的模型对下划线更友好可关注FqnSeparator/UseFunctionNameAsFqn类配置的演进当前仍属提案 API需以新版 SDK 发布为准错误回传必须附带引导信息仅返回 function wasnt defined 远远不够附加 Correct yourself. 或系统消息 You can call tools. If a tool call failed, correct yourself. 可显著提升gpt-4系列模型的自恢复率回传前必须净化非法字符确保回传模型的历史消息中函数名符合^[a-zA-Z0-9_-]$模式避免因一个.导致整个请求 400 失败不要把自恢复当成唯一防线ADR 的结论也承认自恢复机制不可能覆盖所有模型因此在上层应用中对连续失败做好重试与降级策略仍是必要保障利用单元测试守护行为SK 仓库将错误消息与净化逻辑固化为单元测试如 FunctionCallsProcessorTests.cs在你的项目中同样建议为错误回传与名称净化写测试防止行为回归。8. 总结函数调用可靠性的本质是模型生成的名字与系统广告的名字之间的对齐问题。Semantic Kernel 通过这份 ADR 系统性地拆解了幻觉的三种形态-→_、-→.、自恢复失败并给出了从根除源头Option 1/2/3到容错解析Option 4再到错误自愈Option 5/6的完整方案谱系。最终选择的不改公共 API 的 Option 5 Option 6 组合已在当前仓库的FunctionCallsProcessor与 OpenAI 连接器代码中留下落地印证。对于使用 SK 构建生产级 Agent 的开发者而言这份 ADR 的价值不仅在于方案本身更在于其问题拆解与取舍方法论——理解模型在哪一步会出错、错误如何回流、如何用最小代价让系统自愈正是打造可靠 LLM 应用的核心基本功。【免费下载链接】semantic-kernelIntegrate cutting-edge LLM technology quickly and easily into your apps项目地址: https://gitcode.com/GitHub_Trending/se/semantic-kernel创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门