免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

Semantic Kernel 服务无关函数调用内容模型(FunctionCallContent / FunctionResultContent)设计解析与实战指南

Semantic Kernel 服务无关函数调用内容模型(FunctionCallContent / FunctionResultContent)设计解析与实战指南 Semantic Kernel 服务无关函数调用内容模型FunctionCallContent / FunctionResultContent设计解析与实战指南【免费下载链接】semantic-kernelIntegrate cutting-edge LLM technology quickly and easily into your apps项目地址: https://gitcode.com/GitHub_Trending/se/semantic-kernel导读本文围绕 Semantic Kernel 仓库中的架构决策记录 docs/decisions/0041-function-call-content.mdADR 0041展开系统讲解 .NET 版 Semantic Kernel 如何用服务无关service-agnostic的FunctionCallContent与FunctionResultContent两个内容模型统一表达LLM 请求调用函数与调用方返回函数结果这两个方向的数据流从而摆脱对 OpenAI 特定类型的强依赖。读完本文你将理解该设计的决策过程、ChatMessageContent.Items多模态内容集合的工作机制、函数调用协议的统一角色约定AuthorRole.Tool、函数结果序列化方案的选择依据以及流式场景下StreamingFunctionCallUpdateContent与FunctionCallContentBuilder的配套实现并能在自己的 C# 应用中直接落地这套调用与结果回传模式。1. 背景为什么需要服务无关的函数调用模型1.1 痛点OpenAI 专属模型带来的锁定在 ADR 0041 写作时2024 年 4 月status: acceptedSemantic Kernel 的函数调用Function Calling能力仅由 OpenAI 连接器支持且函数调用模型与连接器强绑定。当时有两类新的连接器正在加入函数调用支持每个连接器都有自己的专属模型类。这种每个连接器一套专属模型的设计存在两个突出问题从连接器开发角度看不可扩展每新增一个支持函数调用的连接器就要引入一套新的专属模型类开发成本随连接器数量线性上升。从消费方代码看无法多态使用调用方代码被强制耦合到 OpenAI 的OpenAIChatMessageContent、ChatCompletionsFunctionToolCall属于Azure.AI.OpenAI包等类型无法通过IChatCompletionService接口透明地切换不同连接器实现。ADR 中的原始示例展示了这种耦合IChatCompletionService chatCompletionService kernel.GetRequiredServiceIChatCompletionService(); ChatHistory chatHistory new ChatHistory(); chatHistory.AddUserMessage(Given the current time of day and weather, what is the likely color of the sky in Boston?); // The OpenAIChatMessageContent class is specific to OpenAI connectors - OpenAIChatCompletionService, AzureOpenAIChatCompletionService. OpenAIChatMessageContent result (OpenAIChatMessageContent)await chatCompletionService.GetChatMessageContentAsync(chatHistory, settings, kernel); // The ChatCompletionsFunctionToolCall belongs Azure.AI.OpenAI package that is OpenAI specific. ListChatCompletionsFunctionToolCall toolCalls result.ToolCalls.OfTypeChatCompletionsFunctionToolCall().ToList(); chatHistory.Add(result); foreach (ChatCompletionsFunctionToolCall toolCall in toolCalls) { string content kernel.Plugins.TryGetFunctionAndArguments(toolCall, out KernelFunction? function, out KernelArguments? arguments) ? JsonSerializer.Serialize((await function.InvokeAsync(kernel, arguments)).GetValueobject()) : Unable to find function. Please try again!; chatHistory.Add(new ChatMessageContent( AuthorRole.Tool, content, metadata: new Dictionarystring, object?(1) { { OpenAIChatMessageContent.ToolIdProperty, toolCall.Id } })); }1.2 第二个驱动力Agent 之间传递函数调用除了解耦连接器服务无关的函数调用模型还有一个重要应用场景——多 Agent 协作。基于 OpenAI Assistant API 的 Agent 可以把一个函数调用内容请求/模型传递给另一个基于 OpenAI Chat Completion API 构建的 Agent 去执行。这就要求函数调用与函数结果的表达方式不依赖于具体 LLM 服务。1.3 决策驱动因素Decision DriversADR 0041 明确了 7 条设计约束连接器应使用服务无关的函数模型类向调用方传达 LLM 的函数调用意图消费方应能用服务无关的函数模型类把函数结果回传给连接器所有现有函数调用行为必须继续工作服务无关的函数模型类可以在不引用 OpenAI 包或任何 LLM 专属包的情况下使用包含函数调用与结果类的聊天历史对象应可序列化以便日后重新水合rehydrate甚至可以用不同的 AI 模型重放函数调用应能在 Agent 之间传递多 Agent 场景中一个 Agent 可以创建函数调用让另一个 Agent 完成应支持模拟函数调用开发者可以把自己构造的函数调用聊天消息加入历史再交给任意 LLM 执行在 OpenAI 场景下可能需要模拟函数调用 ID。2. 核心设计基于ChatMessageContent.Items的内容集合2.1 复用多模态内容机制Semantic Kernel 的聊天补全模型类已经通过ChatMessageContent.Items集合支持多模态场景文本、图片、音频等内容混合。设计者决定把函数调用也作为一类内容项content item纳入这个集合连接器把 LLM 返回的函数调用映射为服务无关的函数内容模型类加入Items集合调用方执行函数后同样把执行结果以内容模型类的方式通过Items集合传回。在当前的 ChatMessageContent.cs 实现中可以看到Items属性类型为ChatMessageContentItemCollectionL67-L71构造函数同时支持(role, string? content)与(role, ChatMessageContentItemCollection items)两种形式L130-L165为函数调用内容的装载提供了天然载体。2.2 候选方案对比ADR 针对函数内容模型提出两种可选方案Option 1.1单一FunctionCallContent同时表达调用与结果class FunctionCallContent : KernelContent { public string? Id {get; private set;} public string? PluginName {get; private set;} public string FunctionName {get; private set;} public KernelArguments? Arguments {get; private set; } public object?/FunctionResult/string? Result {get; private set;} // 类型在下文讨论 public string GetFullyQualifiedName(string functionNameSeparator -) {...} public TaskFunctionResult InvokeAsync(Kernel kernel, CancellationToken cancellationToken default) { // 1. 在 kernel.Plugins 集合中查找 plugin/function // 2. 反序列化 Arguments 创建 KernelArguments // 3. 调用函数 } }优点只需一个模型类同时表示函数调用与函数结果。缺点类型本身不表达意图连接器需要分析父级ChatMessageContent的 role 才能判断是调用还是结果。不过 ADR 也指出如果协议为传递函数结果规定了特定 role如AuthorRole.Tool这个缺点可能并不成立。Option 1.2FunctionCallContent表示调用 FunctionResultContent表示结果最终采用class FunctionCallContent : KernelContent { public string? Id {get;} public string? PluginName {get;} public string FunctionName {get;} public KernelArguments? Arguments {get;} public Exception? Exception {get; init;} public TaskFunctionResultContent InvokeAsync(Kernel kernel, CancellationToken cancellationToken default) { // 1. 在 kernel.Plugins 集合中查找 plugin/function // 2. 反序列化 Arguments 创建 KernelArguments // 3. 调用函数 } public static IEnumerableFunctionCallContent GetFunctionCalls(ChatMessageContent messageContent) { // 从 ChatMessageContent.Items 集合中提取函数调用列表 } }class FunctionResultContent : KernelContent { public string? Id {get; private set;} public string? PluginName {get; private set;} public string? FunctionName {get; private set;} public object?/FunctionResult/string? Result {get; set;} public ChatMessageContent ToChatMessage() { // 创建 ChatMessageContent并把当前实例加入其 Items 集合 } }优点模型显式explicit无论父级ChatMessageContent的 role 是什么调用方都能从类型本身明确内容的意图。缺点多一个内容类。Decision Outcome基于显式性的优势Option 1.2 被采纳。这与约定优于配置的思路一致——类型即意图调用方无需猜测。3. 当前实现两个类的源码级剖析3.1FunctionCallContent调用请求方向当前实现位于 FunctionCallContent.cs与 ADR 中的设计一一对应成员说明源码位置Id函数调用 ID可为 null序列化时忽略L20-L21PluginName插件名可为 nullL26-L27FunctionName函数名必填L32Arguments函数原始参数KernelArgumentsL37-L38Exception将 AI 模型的函数调用映射到该模型类时发生的异常L43-L44InvokeAsync在Kernel.Plugins中解析并调用函数L70-L87GetFunctionCalls从消息的Items集合提取全部函数调用L94-L97关键实现细节构造函数签名FunctionCallContent(string functionName, string? pluginName null, string? id null, KernelArguments? arguments null)并标注[JsonConstructor]支持反序列化L53-L62。InvokeAsync的执行逻辑完全对应 ADR 中描述的三个步骤先在kernel.Plugins.TryGetFunction(pluginName, functionName, ...)中查找函数找到后以this.Arguments调用function.InvokeAsync并把结果包装为new FunctionResultContent(this, result)若函数不存在则抛出KeyNotFoundExceptionL79-L86。GetFunctionCalls的实现正是messageContent.Items.OfTypeFunctionCallContent()L94-L97即 ADR 中注释提到的或通过OfType手工提取的官方封装。3.2FunctionResultContent结果回传方向当前实现位于 FunctionResultContent.cs成员说明源码位置CallId对应函数调用的 IDL16-L17PluginName/FunctionName被调用函数的插件名与函数名L22-L29Result函数结果、调用异常或自定义错误消息object?类型即 ADR Option 3.1 的最终选择L34-L35ToChatMessage()生成new ChatMessageContent(AuthorRole.Tool, [this])即Tool 角色 单个结果项的便捷封装L81-L84值得注意的构造能力FunctionResultContent(FunctionCallContent functionCall, object? result null)直接从FunctionCallContent复制CallId/PluginName/FunctionNameL58-L64FunctionResultContent(FunctionCallContent functionCallContent, FunctionResult result)接收 SK 域内的FunctionResult取result.Value作为Result并把整个FunctionResult存入InnerContentL71-L75保证底层函数执行的完整信息不丢失。3.3 调用方代码范式ADR 原始示例的完整形态以IChatCompletionService多态使用为例ADR 文档原样继承// The GetChatMessageContentAsync method returns only one choice. However, there is a GetChatMessageContentsAsync method that can return multiple choices. ChatMessageContent messageContent await completionService.GetChatMessageContentAsync(chatHistory, settings, kernel); chatHistory.Add(messageContent); // 把包含函数调用的原始消息加入聊天历史 IEnumerableFunctionCallContent functionCalls FunctionCallContent.GetFunctionCalls(messageContent); // 获取函数调用列表 // 或者IEnumerableFunctionCallContent functionCalls messageContent.Items.OfTypeFunctionCallContent(); // 遍历请求的函数调用并逐一执行 foreach (FunctionCallContent functionCall in functionCalls) { FunctionResultContent? result null; try { result await functionCall.InvokeAsync(kernel); // 在 Kernel.Plugins 中解析并调用函数 } catch (Exception ex) { chatHistory.Add(new FunctionResultContent(functionCall, ex).ToChatMessage()); // 或者把便于 LLM 推理的错误描述传回去 // string message Error details that LLM can reason about.; // chatHistory.Add(new FunctionResultContent(functionCall, message).ToChatMessageContent()); continue; } chatHistory.Add(result.ToChatMessage()); // 或者chatHistory.Add(new ChatMessageContent(AuthorRole.Tool, new ChatMessageContentItemCollection() { result })); } // 把包含函数调用与函数结果的完整历史发送给 LLM获取最终回答 messageContent await completionService.GetChatMessageContentAsync(chatHistory, settings, kernel);3.4 单条 Tool 消息携带多个结果设计并不要求调用方为每个函数结果单独创建一条聊天消息多个FunctionResultContent可以放进同一条AuthorRole.Tool消息ADR 文档原样继承ChatMessageContent messageContent await completionService.GetChatMessageContentAsync(chatHistory, settings, kernel); chatHistory.Add(messageContent); // 把包含函数调用的原始消息加入聊天历史 IEnumerableFunctionCallContent functionCalls FunctionCallContent.GetFunctionCalls(messageContent); // 获取函数调用列表 ChatMessageContentItemCollection items new ChatMessageContentItemCollection(); // 遍历请求的函数调用并逐一执行 foreach (FunctionCallContent functionCall in functionCalls) { FunctionResultContent result await functionCall.InvokeAsync(kernel); items.Add(result); } chatHistory.Add(new ChatMessageContent(AuthorRole.Tool, items)); // 把包含函数调用与函数结果的完整历史发送给 LLM获取最终回答 messageContent await completionService.GetChatMessageContentAsync(chatHistory, settings, kernel);两种写法均被官方认可——后者减少了消息数量更贴合 OpenAI 等模型一次 tool 回合多条结果的惯用形态。4. 函数调用协议统一使用AuthorRole.Tool4.1 为什么需要统一角色不同连接器表达函数调用与结果的消息角色各不相同。例如{Azure}OpenAIChatCompletionService用Assistant角色的消息向调用方传达函数调用并期待调用方用Tool角色的消息返回结果。对调用方而言响应消息的角色并不重要——只要消息内容包含函数调用GetFunctionCalls方法就能无视角色提取出来ChatMessageContent messageContent await completionService.GetChatMessageContentAsync(chatHistory, settings, kernel); IEnumerableFunctionCallContent functionCalls FunctionCallContent.GetFunctionCalls(); // 无论 messageContent 的角色如何只要包含函数调用就能返回真正影响多态性的是回传结果时所用的角色。如果每个连接器都要求自己的专属角色调用方代码就会退化为一连串if/elseIChatCompletionService chatCompletionService new(); ... foreach (FunctionCallContent functionCall in functionCalls) { FunctionResultContent result await functionCall.InvokeAsync(kernel); // 使用连接器专属角色回传结果会破坏多态使用迫使调用方写 if/else 分支 if (chatCompletionService is OpenAIChatCompletionService || chatCompletionService is AzureOpenAIChatCompletionService) { chatHistory.Add(new ChatMessageContent(AuthorRole.Tool, new ChatMessageContentItemCollection() { result })); } else if (chatCompletionService is AnotherCompletionService) { chatHistory.Add(new ChatMessageContent(AuthorRole.Function, new ChatMessageContentItemCollection() { result })); } else if (chatCompletionService is SomeOtherCompletionService) { chatHistory.Add(new ChatMessageContent(AuthorRole.ServiceSpecificRole, new ChatMessageContentItemCollection() { result })); } }4.2 决策结论ADR 决定统一采用AuthorRole.Tool作为回传函数结果的角色理由是该角色广为人知且从概念上既能表示函数结果也能覆盖 SK 未来可能需要支持的其他工具tool类型。从当前实现看这一决定已被落地FunctionResultContent.ToChatMessage()固定生成new ChatMessageContent(AuthorRole.Tool, [this])见 FunctionResultContent.cs L81-L84。OpenAI 连接器端也在ClientCore.ChatCompletion.cs中按AuthorRole.Tool消息内的FunctionResultContent项来构造发给模型的工具结果参见 ClientCore.ChatCompletion.cs 中Handling function results represented by the FunctionResultContent type分支。5.FunctionResultContent.Result属性的类型抉择Result属性的数据类型需要同时满足两个场景可序列化/反序列化包含函数结果内容的聊天历史要能被序列化并在日后重新水合甚至用另一个模型重放可传达失败既可以把原始异常也可以把描述问题的字符串发给 LLM。ADR 逐一评估了四种候选类型并给出决策过程。5.1 Option 3.1 —object最终采用class FunctionResultContent : KernelContent { // 其他成员省略 public object? Result {get; set;} }优点序列化既可以由连接器完成也可以由调用方按需完成调用方可附带额外数据调用方能自行控制失败传达方式传Exception实例或问题描述字符串。注意点当函数结果对应的类型不在JsonSerializer默认支持范围内时聊天历史的序列化/反序列化可能需要借助 JSON 转换器converters或解析器resolvers。5.2 Option 3.2 —string写作 ADR 时的现状实现class FunctionResultContent : KernelContent { // 其他成员省略 public string? Result {get; set;} }优点聊天历史反序列化无需转换器调用方可附加额外数据可传序列化后的异常、异常消息或问题描述字符串。缺点序列化职责落在调用方对补全服务的多态使用而言是个问题。5.3 Option 3.3 —FunctionResultclass FunctionResultContent : KernelContent { // 其他成员省略 public FunctionResult? Result {get;set;} public Exception? Exception {get;set;} // 或 public object? Error { get; set; } // 可包含 Exception 实例或描述问题的字符串 }优点复用 SK 域内FunctionResult类。缺点不额外增加Exception/Error属性就无法向连接器/LLM 传达异常FunctionResult目前不可反序列化FunctionResult.ValueType属性的Type类型默认不被JsonSerializer序列化被认为不安全KernelReturnParameterMetadata.ParameterType与KernelParameterMetadata.ParameterType同理FunctionResult.Function属性无法反序列化需要标[JsonIgnore]并新增ctr(object? value null, IReadOnlyDictionarystring, object?? metadata null)构造器用于反序列化FunctionResult.Function属性必须可空这可能对函数过滤器filter用户构成破坏性变更——过滤器通过FunctionFilterContext.Function属性暴露 kernel function 实例。5.4 Option 3.4 —FunctionResult继承KernelContent第二轮评审时提出的探索性方案让FunctionResult直接继承KernelContent从而省去独立的FunctionResultContent类public class FunctionResult : KernelContent { .... }这样KernelFunction.InvokeAsync返回的结果可以直接放进ChatMessageContent.Itemsforeach (FunctionCallContent functionCall in functionCalls) { FunctionResult result await functionCall.InvokeAsync(kernel); chatHistory.Add(new ChatMessageContent(AuthorRole.Tool, new ChatMessageContentItemCollection { result })); // 替代chatHistory.Add(new ChatMessageContent(AuthorRole.Tool, new ChatMessageContentItemCollection { new FunctionResultContent(functionCall, result) })); // 当然也可以通过新增扩展方法简化语法 chatHistory.AddFunctionResultMessage(result); }ADR 记录了围绕该方案的一系列未决问题如何把原始FunctionCallContent连同结果一起传给连接器当前理由是一些模型可能期望函数调用的属性如 arguments随结果一并回传可以争辩连接器需要时能自己在历史中找但历史可能因节省 token、减少幻觉等原因被截断如何把函数 ID 传给连接器如何向连接器传达异常曾提议给FunctionResult增加Exception属性并由KernelFunction.InvokeAsync始终赋值但这会破坏 C# 函数调用语义——契约满足就执行、契约不满足就抛异常FunctionResult一旦作为非流式内容继承KernelContent将来需要表达StreamingKernelContent的流式能力时怎么办C# 不支持多继承。优点FunctionResult本身成为非流式内容可出现在所有期望内容的场景无需额外的FunctionResultContent类。 缺点FunctionResult与KernelContent的不必要耦合可能限制两者各自独立演进FunctionResult.Function需改为可空或应用自定义序列化需要为FunctionResult增加Id属性以表示 LLM 要求的函数 ID。5.5 最终决策第一轮决策为Option 3.1object因为它是三者中最灵活的连接器需要函数 schema 时可以轻松从自身可访问的kernel.Plugins集合获取函数结果元数据可以通过KernelContent.Metadata属性传给连接器。第二轮评审提出 Option 3.4 进行探索在原型验证 Option 3.4 之后鉴于其缺点最终回到 Option 3.1。当前实现印证了这一决策FunctionResultContent.cs L34-L35 中Result的类型就是object?且带[JsonIgnore(Condition JsonIgnoreCondition.WhenWritingNull)]以保证可序列化性。6. 模拟函数Simulated Functions绕过训练数据限制的实用技巧6.1 问题场景存在一种常见现象由于模型训练数据的限制LLM 可能忽略提示词中提供的某些数据但同样的数据如果通过函数结果提供给模型模型反而能正确处理。因此模拟函数成为把关键数据如天气警报、突发通知喂给模型的有效手段——前提是这些数据必须包装成一次合法的函数调用-结果回合。6.2 Option 4.1 — 用真实 SK 函数模拟创建一个真正的KernelFunction并执行它再把结果作为函数结果回传ChatMessageContent messageContent await completionService.GetChatMessageContentAsync(chatHistory, settings, kernel); // 模拟函数调用 FunctionCallContent simulatedFunctionCall new FunctionCallContent(name: weather-alert, id: call_123); messageContent.Items.Add(simulatedFunctionCall); // 把模拟的函数调用加入连接器响应消息 chatHistory.Add(messageContent); // 创建 SK 函数并执行 KernelFunction simulatedFunction KernelFunctionFactory.CreateFromMethod(() A Tornado Watch has been issued, with potential for severe ..... Stay informed and follow safety instructions from authorities.); FunctionResult simulatedFunctionResult await simulatedFunction.InvokeAsync(kernel); chatHistory.Add(new ChatMessageContent(AuthorRole.Tool, new ChatMessageContentItemCollection() { new FunctionResultContent(simulatedFunctionCall, simulatedFunctionResult) })); messageContent await completionService.GetChatMessageContentAsync(chatHistory, settings, kernel);优点调用方执行模拟函数时SK 函数过滤器/钩子filters/hooks可以被触发。缺点相比纯数据方案更重。6.3 Option 4.2 — 用纯对象模拟不创建函数直接把结果对象或字符串塞进FunctionResultContentChatMessageContent messageContent await completionService.GetChatMessageContentAsync(chatHistory, settings, kernel); // 模拟函数 FunctionCallContent simulatedFunctionCall new FunctionCallContent(name: weather-alert, id: call_123); messageContent.Items.Add(simulatedFunctionCall); chatHistory.Add(messageContent); // 创建模拟结果 string simulatedFunctionResult A Tornado Watch has been issued, with potential for severe ..... Stay informed and follow safety instructions from authorities.; // 或者使用强类型对象 WeatherAlert simulatedFunctionResult new WeatherAlert { Id 34SD7RTYE4, Text A Tornado Watch has been issued, with potential for severe ..... Stay informed and follow safety instructions from authorities. }; chatHistory.Add(new ChatMessageContent(AuthorRole.Tool, new ChatMessageContentItemCollection() { new FunctionResultContent(simulatedFunctionCall, simulatedFunctionResult) })); messageContent await completionService.GetChatMessageContentAsync(chatHistory, settings, kernel);优点轻量无需创建和执行 SK 函数。缺点无法触发 SK 函数过滤器/钩子。6.4 决策结论两个选项并非互斥可以根据具体场景二选一或混用需要钩子/过滤器链路时用 Option 4.1追求轻量时用 Option 4.2。7. 流式场景Streaming的配套设计7.1 流式与一次性返回的差异流式 API 与非流式 API 的本质区别在于内容按块chunk返回。ADR 以 OpenAI 连接器为例说明了流式函数调用的三个特征函数调用内容可能拆分为多个块第一个块携带函数 ID 和名称后续块携带函数参数arguments同一次响应可能流式返回多个函数的调用例如第一个块是函数 A 的 idname紧接着的块是函数 B 的 idname因此流式函数调用模型需要针对增量更新做专门设计若偏离过大将另行创建独立 ADR。7.2 当前实现StreamingFunctionCallUpdateContentFunctionCallContentBuilder流式设计在仓库中的落地由两个类完成StreamingFunctionCallUpdateContentStreamingFunctionCallUpdateContent.cs继承自StreamingKernelContent代表 LLM 请求的一次增量函数调用更新属性说明CallId函数调用 IDName函数名可能为空因为名称可能在前面的块中已给出Arguments函数参数可能是完整一段也可能是部分片段FunctionCallIndex函数调用索引用于区分同一响应中的多个函数RequestIndex产生该内容的请求索引标注SKEXP0001实验性特性FunctionCallContentBuilderFunctionCallContentBuilder.cs负责把零散的增量更新组装成完整的FunctionCallContent列表Append(StreamingChatMessageContent content)从流式消息的Items中提取StreamingFunctionCallUpdateContent按{RequestIndex}-{FunctionCallIndex}作为唯一索引分别用三个字典追踪每个函数调用的 ID、名称与参数参数用StringBuilder累积拼接见 L173-L207Build()遍历已追踪的 ID 字典用FunctionName.Parse(fqn)从全限定名中拆分 pluginName/functionName尝试把累积的参数 JSON 反序列化为KernelArguments若 JSON 非法则把异常封装进FunctionCallContent.ExceptionL63-L111构造函数可选传入JsonSerializerOptions以便在 AOT/裁剪场景下使用类型信息反序列化参数默认构造函数标注了RequiresUnreferencedCode/RequiresDynamicCode见 L26-L40。在 OpenAI 连接器中ClientCore.ChatCompletion.cs正是把流式工具调用增量包装为StreamingFunctionCallUpdateContent加入openAIStreamingChatMessageContent.Items参见 ClientCore.ChatCompletion.cs 中new StreamingFunctionCallUpdateContent(...)的构造处随后再调用GetFunctionCallContents翻译出FunctionCallContent数组用于诊断与兼容性处理。8. 测试佐证行为契约的可验证性仓库的单元测试完整覆盖了这套模型的关键行为可以作为接入时的行为契约参考FunctionCallContentTestsFunctionCallContentTests.csItShouldBeInitializedFromFunctionAndPluginNameL21-L32验证构造函数正确设置FunctionName/PluginName/Id/ArgumentsItShouldFindKernelFunctionAndInvokeItAsyncL34-L59把FunctionCallContent放入Kernel.Plugins后调用InvokeAsync验证参数原样传递且Result等于函数返回值——这正是 ADR 中InvokeAsync三步逻辑的实证ItShouldHandleFunctionCallRequestExceptionAsyncL61-L77当映射过程产生Exception时InvokeAsync直接抛出该异常验证了Exception属性的传播语义ItShouldReturnListOfFunctionCallRequestsL79-L101一条AuthorRole.Tool消息内含 3 个FunctionCallContentGetFunctionCalls正确返回全部 3 个——直接印证单条 Tool 消息可携带多个函数调用/结果的设计。9. 接入建议与边界说明使用场景任何需要LLM 请求函数 → 调用方执行 → 结果回传 → LLM 生成最终回答闭环的 C# 应用以及多 Agent 间函数调用传递、聊天历史序列化/重放场景都应优先使用这两个服务无关类而不是连接器专属类型。多态连接器通过IChatCompletionService接口引用连接器并互换实现代码无需感知具体连接器因为函数调用协议已统一为AuthorRole.ToolItems集合内的FunctionCallContent/FunctionResultContent。失败处理FunctionResultContent.Result是object?既可直接放异常实例也可放便于 LLM 推理的错误字符串FunctionCallContent.Exception则用于传达模型调用映射失败这一层错误。流式场景流式回复中请使用StreamingFunctionCallUpdateContent接收增量更新并通过FunctionCallContentBuilder.Append/Build聚合为完整的FunctionCallContent列表后再执行函数。序列化FunctionCallContent与FunctionResultContent的构造函数均标注[JsonConstructor]可空属性在序列化时被忽略聊天历史可整体序列化并在日后用不同模型重放若Result中放入非常规类型需自行准备对应的 JSON 转换器/解析器。模拟函数需要强制把提示词中的数据注入模型时可采用 ADR 第 4 节的两种模拟方式按是否需要函数过滤器/钩子决定选择。10. 延伸阅读仓库内决策文档原文docs/decisions/0041-function-call-content.md关联 ADR0017-openai-function-calling.mdOpenAI 函数调用引入、0061-function-call-behavior.md函数调用行为、0063-function-calling-reliability.md函数调用可靠性核心实现FunctionCallContent.cs、FunctionResultContent.cs、FunctionCallContentBuilder.cs、StreamingFunctionCallUpdateContent.cs连接器侧落地ClientCore.ChatCompletion.cs单元测试FunctionCallContentTests.cs、FunctionResultContentTests.cs实战示例dotnet 下的函数调用概念示例位于 dotnet/samples/Concepts/FunctionCalling自动函数调用入门可参考 dotnet/samples/GettingStarted/Step5_Chat_Prompt.cs 与 Python 侧 python/samples/concepts/auto_function_calling。【免费下载链接】semantic-kernelIntegrate cutting-edge LLM technology quickly and easily into your apps项目地址: https://gitcode.com/GitHub_Trending/se/semantic-kernel创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表