
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 模型能否以廣告時的精確名稱調用函數。而現實是模型經?;糜X出錯誤的函數名。絕大多數情況下幻覺只發生在函數名中的一個字符上——恰恰就是 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??赡艿膶崿F方式// 操作級別配置 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 連接器中實現簡單??赡艿膶崿F方式// 操作級別配置 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 模型??赡艿膶崿F方式// 調用方代碼 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 模型因請求異常而無法自恢復??赡艿膶崿F方式// 在 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系列模型的自恢復率回傳前必須凈化非法字符確?;貍髂P偷臍v史消息中函數名符合^[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),僅供參考