Semantic Kernel 提示词语法到补全服务数据模型的映射:ADR-0020 设计决策与落地实现解析
Semantic Kernel 提示词语法到补全服务数据模型的映射ADR-0020 设计决策与落地实现解析【免费下载链接】semantic-kernelIntegrate cutting-edge LLM technology quickly and easily into your apps项目地址: https://gitcode.com/GitHub_Trending/se/semantic-kernel导读本文围绕 docs/decisions/0020-prompt-syntax-mapping-to-completion-service-model.md 这篇已被采纳的架构决策记录ADR深入讲解 Semantic Kernel 如何将补全Completion服务特有的提示词语法如聊天补全中的message role...标签映射到对应服务的数据模型如ChatHistory。文章完整还原了该 ADR 的背景、候选方案与最终决策并结合当前仓库源码ChatPromptParser、XmlPromptParser及各服务扩展方法展示这一决策在实际代码中的落地形态。读完本文你将理解为何映射逻辑被放在连接器/服务侧而非内核函数侧以及现代 Semantic Kernel 中一段带角色的 XML 提示词是如何一步步被解析为ChatHistory并送入聊天补全服务的。一、问题背景提示词不再是原样直传在 Semantic Kernel 早期设计中所有提示词prompt都由文本补全服务text completion service运行——渲染后的提示词字符串不做任何修改、原样传给配置好的文本补全服务/连接器。这在只有纯文本补全的场景下是可行的但当聊天补全提示词、以及未来可能出现的图像image等其他提示词类型加入后问题就暴露了聊天补全服务接收的不是一段字符串而是一组带有角色role的消息列表即ChatHistory聊天补全提示词中使用message rolesystem之类的专用语法来表达这段话属于哪个角色文本补全与聊天补全对提示词的数据模型要求完全不同不能继续一刀切原样直传。因此ADR-0020 的核心命题是如何把补全服务特有的提示词语法映射到对应补全服务的数据模型以及这段映射逻辑应该放在哪里1.1 前置语法聊天补全角色的提示词表达在讨论映射位置之前需要先明确被映射的语法是什么。这源于另一篇 ADR docs/decisions/0014-chat-completion-roles-in-prompt.md2023-10-23 采纳它定义了 SK 中标记消息块 角色的提示词语法。该文档对比了三种为提示词添加消息/角色标签的方案由提示词中指定的函数生成标签依赖模板引擎调用函数的能力由预注册的内部函数输出message role...标签由提示词专用机制生成标签利用模板引擎自身的 helper/handler如 Handlebars 的 block helper在渲染时输出标签在模板引擎之上直接书写标签直接在提示词中书写message role...标签模板引擎将其当作普通文本不做解析处理。最终决策是不把方案限定为唯一一种因为未来新模板引擎可能无法套用同一种方案而是每次引入新模板引擎时逐一评估、择优采用当前阶段基于BasicPromptTemplateEngine采用方案 3——标签直接写在提示词中。这也是 ADR-0020 中message role...语法的来源。二、被映射的语法与映射目标从 XML 到 ChatHistoryADR-0020 给出了一个典型的聊天补全提示词及其映射结果这是全文的事实基线必须完整理解。聊天补全提示词中的角色语法message rolesystem You are a creative assistant helping individuals and businesses with their innovative projects. /message message roleuser I want to brainstorm the idea of {{$input}} /message该提示词应被映射为ChatHistory类的一个实例其中包含两条聊天消息。ADR 中以当时 API 给出的映射结果示意如下var messages new ChatHistory(); messages.Add(new ChatMessage(new AuthorRole(system), You are a creative assistant helping individuals and businesses with their innovative projects.)); messages.Add(new ChatMessage(new AuthorRole(user), I want to brainstorm the idea of {{$input}}));注意两点值得强调的细节{{$input}}变量在映射阶段并不求值。渲染render与解析parse是两个阶段IPromptTemplate.RenderAsync负责把模板变量替换成实际值得到最终字符串见 IPromptTemplate.cs而映射/解析发生在渲染之后、送入服务之前。因此上述映射示例中 user 消息的内容仍然是模板变量的字面形态。角色的取值由AuthorRole表达实际连接器支持的系统/用户/助手等角色都建立在这一抽象之上。三、候选方案对比映射逻辑放在哪一层ADR-0020 的核心决策点在于映射功能的归属位置原文给出了两个互斥的候选方案其取舍直接决定了内核各层的耦合关系与扩展成本。3.1 方案一补全连接器类Completion connector classes由补全连接器类负责提示词语法 → 补全服务数据模型的映射。至于映射逻辑是直接写在连接器类内部还是委托给独立的 mapper 类属于实现阶段的事不在本 ADR 范围内。优点当新增补全类型音频、视频等的连接器时SemanticFunction无需任何改动即可支持新提示词语法的映射提示词既可以由Kernel.RunAsync运行也可以由补全连接器直接运行调用途径灵活。缺点每一个新连接器——无论是已有类型的新连接器还是新补全类型的新连接器——都必须自行实现映射功能存在重复实现成本。3.2 方案二SemanticFunction 类由SemanticFunction类负责映射。与方案一类似具体是写在SemanticFunction内部还是委托给 mapper 类同样留到实现阶段决定。优点无论新增哪种类型新类型或已有类型的连接器都不需要实现映射功能连接器保持纯粹。缺点每当 SK 需要支持一种新的补全类型SemanticFunction类就必须被修改内核核心类被迫随外部服务形态变化而膨胀提示词只能通过Kernel.RunAsync方法运行无法被连接器直接驱动灵活性受限。3.3 决策结果经过权衡决策采纳方案一补全连接器类。理由很明确它更灵活允许在不修改SemanticFunction类的前提下持续添加新连接器。从后续仓库演进看这一决策与语义内核 v1 中SemanticFunction被拆分、重构成KernelFunction/KernelFunctionFromPrompt的走向是自洽的——将服务形态差异隔离在连接器/服务一侧正是该决策的长期收益。四、决策的落地现代仓库中的映射实现ADR-0020 于 2023-10-27 被采纳其思想在当前仓库中已固化为具体实现。这一节结合源码梳理现代 Semantic Kernel 中提示词是如何被解析和映射的。4.1 解析入口ChatPromptParser映射的核心实现位于 ChatPromptParser.csMicrosoft.SemanticKernel.ChatCompletion命名空间下的内部静态类。其公共入口TryParse(string prompt, out ChatHistory? chatHistory)执行三段式流程快速前置检查因为 XML 解析开销大先检查字符串是否包含message大小写不敏感——任何合法的角色提示词都必须包含该标签起点否则直接返回false见 ChatPromptParser.cs委托 XML 解析调用XmlPromptParser.TryParse把提示词解析为PromptNode节点集合节点到 ChatHistory 的映射遍历节点只保留TagName message且带role属性的有效聊天消息节点IsValidChatMessage见 ChatPromptParser.cs每个节点解析为一个ChatMessageContent加入ChatHistory。ParseChatNode还支持更丰富的消息内容结构message节点内可以嵌套text、image、audio、binary子标签分别映射为TextContent、ImageContent、AudioContent、BinaryContents_contentFactoryMapping见 ChatPromptParser.cs其中image/audio等二进制内容既支持data:开头的 Data URI也支持普通 URL 可选mimetype属性。只有当消息包含多个内容项时才构建ChatMessageContentItemCollection单文本内容则直接以字符串形式保存在node.Content中见 ChatPromptParser.cs。4.2 底层 XML 解析XmlPromptParserXmlPromptParser.cs 承担从提示词字符串到PromptNode集合的通用 XML 解析同样做了性能优化先做廉价检查字符串非空、包含、且在之后存在/或/不满足则直接判定为普通文本见 XmlPromptParser.cs将提示词包裹进root.../root后通过XmlDocument.LoadXml解析并设置PreserveWhitespace true——这一点对代码生成类提示词至关重要因为提示词中的空白/缩进可能是有效载荷的一部分例如要求 LLM 返回格式良好的代码时解析失败XmlException时静默返回false提示词会被当作普通文本处理。这两层解析器共同构成了 ADR-0020 中映射功能的现代形态连接器/服务侧负责解析内核函数侧不需要知道任何语法细节。4.3 服务扩展方法解析失败时优雅降级映射逻辑对用户几乎是透明的因为它被封装在服务扩展方法中。以 ChatCompletionServiceExtensions.cs 的GetChatMessageContentsAsync(IChatCompletionService, string prompt, ...)为例// Try to parse the text as a chat history if (ChatPromptParser.TryParse(prompt, out var chatHistoryFromPrompt)) { return chatCompletionService.GetChatMessageContentsAsync(chatHistoryFromPrompt, executionSettings, kernel, cancellationToken); } // Otherwise, use the prompt as the chat user message var chatHistory new ChatHistory(); chatHistory.AddUserMessage(prompt); return chatCompletionService.GetChatMessageContentsAsync(chatHistory, executionSettings, kernel, cancellationToken);逻辑清晰能解析成ChatHistory就按聊天历史走解析失败则把整段提示词降级为一条 user 消息。流式接口GetStreamingChatMessageContentsAsync采用完全相同的降级策略见 ChatCompletionServiceExtensions.cs。值得特别说明的是文本生成路径 TextGenerationExtensions.cs当用户面向ITextGenerationService传入一段带角色的提示词但实际配置的服务实现恰好是IChatCompletionService时扩展方法会先尝试ChatPromptParser.TryParse成功则走聊天补全并将结果转为TextContent否则才把提示词原样传给文本生成服务。这正对应 ADR 中提示词可以由补全连接器直接运行的优点——服务实现可以自主决定是否消费提示词中的结构化语法。4.4 模板引擎侧的配合以 Liquid 为例ADR-0014 提出每个模板引擎单独评估最优方案这一原则在 LiquidPromptTemplate.cs 中有直观体现Liquid 模板渲染完成后引擎会用正则把形如system: You are a creative assistant.的片段切分并转换成统一的message rolesystem.../message标签文本从而让下游的ChatPromptParser以同一套 XML 语法收敛所有模板引擎的输出。也就是说无论上层用哪种模板语法表达角色最终进入解析器的都是规范化的message标签——模板引擎语法不会泄漏进 SK 领域这正呼应了 ADR-0014 的抽象目标。4.5 测试验证映射逻辑有完整的单元测试保障见 ChatPromptParserTests.cs 与 XmlPromptParserTests.cs。其中已覆盖的关键场景包括角色解析单双引号属性、system/user/assistant 混合角色都能正确映射为对应AuthorRole内容保真多行内容、制表符、CDATA 片段均被原样保留依赖PreserveWhitespace嵌套内容text与image混排的消息能生成TextContentImageContent的组合内容项Data URI 图像data:前缀的图像内容映射为ImageContent.DataUri纯文本降级不含 XML 结构、无法解析的普通文本返回false交由上层按 user 消息处理。五、设计启示与总结回顾 ADR-0020 的完整脉络可以提炼出三个层面的结论职责边界补全服务特有的语法解析属于服务侧关注点与内核函数执行解耦。连接器/服务扩展方法负责语法 → 数据模型的映射内核函数只关心渲染出的字符串这是该 ADR 一锤定音的架构原则。演进弹性采纳方案一意味着未来接入新的补全类型图像、音频、视频等时只需在对应服务侧实现解析与映射SemanticFunction/KernelFunction无需感知新语法。当前仓库中ChatPromptParser对image/audio/binary子标签的支持正是这种弹性兑现的实例。统一抽象无论是 Handlebars、Liquid 还是基础模板引擎各引擎以最适合自己的方式表达角色但最终都归一为message role...的 XML 语法再由XmlPromptParser → ChatPromptParser两级解析完成向ChatHistory的映射——模板引擎语法被牢牢隔离在 SK 领域之外。对开发者而言理解这篇 ADR 及其实践意味着当你编写聊天补全提示词时可以放心使用message role...结构来表达多轮对话与系统提示Semantic Kernel 会在服务调用边界自动完成解析与降级当你要为 SK 贡献新的补全类型连接器时参考ChatPromptParser与各服务扩展方法的既有模式就是这条 ADR 决策给出的标准路径。输出文章【免费下载链接】semantic-kernelIntegrate cutting-edge LLM technology quickly and easily into your apps项目地址: https://gitcode.com/GitHub_Trending/se/semantic-kernel创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →