ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Parlant Canned Responses 实战:四阶段响应合成、三种 Composition Mode 与防幻觉模板设计

Parlant Canned Responses 实战:四阶段响应合成、三种 Composition Mode 与防幻觉模板设计 Parlant Canned Responses 实战四阶段响应合成、三种 Composition Mode 与防幻觉模板设计【免费下载链接】parlantBuild reliable customer-facing AI agents with Parlant: an interaction control harness optimized for controlled, consistent, and predictable LLM interactions.项目地址: https://gitcode.com/GitHub_Trending/pa/parlantCanned responses预置响应是 Parlant 对 Agent 输出施加精确控制的核心机制它把 Agent 的回复从逐 token 自由生成收敛为从预审批模板集合中检索、渲染、选择从而在保持对话流畅性的同时彻底消除措辞漂移与细微幻觉。本文以 canned-responses.md 为主线结合 CannedResponseGenerator、CannedResponseStore 等源码实现完整讲解四阶段工作机制、Fluid/Composited/Strict 三种 composition mode 的取舍、std./generative./工具字段三类模板字段的用法、signals 检索优化以及 no-match 响应的两种自定义方式帮助你为生产环境中的高风险对话设计可控、可审计的响应体系。一、什么是 Canned Responses来自呼叫中心的概念Canned responses 的概念源自真实呼叫中心坐席从一组预先批准的话术中选择回复以保证沟通的一致性、准确性与品牌口径统一。在 Parlant 中这组预定义、预批准的响应集合把 Agent 的输出约束在固定风格与既定事实范围内完全消除即使是细微的、非预期的或幻觉性输出的风险。文档中用了一个很贴切的比喻canned responses 就像一手扑克牌——你给 Agent 提供一组可选的牌它基于对话上下文从中选出最合适的一张。一个直观示例不用 canned responses 时LLM 逐 token 生成客户Do you have it in stock?AgentYes, weve got this item in stock! Let me know if you need any help finding it.启用 canned responses 后引擎先有一个内部草稿draft message然后从候选模板中挑选# Draft message: Yes, weve got this item in stock! Let me know if you need any help finding it. # # Available templates: # - ... # - Hey, {{std.customer.name}}! What help do you need today? # - ... # - No, sorry, weve just sold the last ones. Would you like to see something similar? # - Yep, we have it. Should I add it to your cart? # - ...客户Do you have it in stock?AgentYep, we have it. Should I add it to your cart?最终发送的是被选中的模板Yep, we have it. Should I add it to your cart?而不是逐 token 生成的草稿——草稿只是检索与比对的基准。二、四阶段响应生成机制Canned responses 在引擎内部是一个四阶段流水线起草DraftAgent 基于当前的情境感知交互历史、guidelines、工具结果等先起草一条 fluid 消息检索Retrieve引擎根据草稿消息检索最相关的 canned response 模板作为候选渲染Render引擎渲染候选模板在适用处用工具提供的字段值做替换选择Select基于草稿消息Agent 从候选中选出最贴合的一条 canned response 输出。从源码结构看这四个阶段在 CannedResponseGenerator._generate_response 中逐一对应且每个阶段都有独立的计时直方图canrep.draft、canrep.retrieval、canrep.render、canrep.selection便于在生产环境做性能观测阶段 1调用self._canrep_draft_generator生成结构化草稿输出 schema 为CannedResponseDraftSchema其中response_body即最终草稿文本阶段 2调用 CannedResponseStore.filter_relevant_canned_responses 做向量相似度检索最多取max_count30条并按candidate_similarity_threshold 0.4过滤见 canned_response_generator.py#L553 与 #L2267-L2278阶段 3由_render_responses完成 Jinja2 渲染任一字段缺失即判定该模板渲染失败并被剔除#L2449-L2495阶段 4由选择器 LLM 输出chosen_template_id与match_quality取值low/partial/high随后按 composition mode 决定最终消息详见下一节。三、Composition Modes三种自由度级别Parlant Agent 的响应采用一种composition mode不同模式对输出的约束程度、以及对 canned responses 的使用方式各不相同模式说明适用场景FluidAgent 优先从 canned responses 中选择若能找到足够好的匹配否则回退到默认的消息生成。(A) 整体保持流畅只在特定场景/响应处施加控制(B) 原型设计阶段边构建 fluid 推荐边补充更多话术。CompositedAgent 只用 canned response 候选来改写recompose生成的草稿消息使其模仿检索到的候选的风格。对语气tone of voice敏感的品牌场景。StrictAgent 只能输出预置响应若找不到匹配项则发送一条可自定义的 no-match 消息。不允许出现任何细微或偶发幻觉的高风险场景。提示如果你面对高风险用例、对部署 GenAI Agent 心存顾虑建议从 strict mode 起步。Parlant 足够灵活等你准备好时可以平滑切换到更 fluid 的模式——切换 composition mode 时你的对话模型其余部分guidelines、journeys、tools 等依然完整保留并生效。设置 Agent 的 Composition Mode创建 Agent 时传入composition_mode即可await server.create_agent( nameMy Agent, descriptionAn agent that uses canned responses, composition_modep.CompositionMode.STRICT, # or FLUID or COMPOSITED )SDK 侧的默认值是CompositionMode.FLUID见 sdk.py#L5125。在核心层CompositionMode 枚举 定义了FLUID、CANNED_FLUID、CANNED_COMPOSITED、CANNED_STRICT等取值SDK 面向用户的STRICT/COMPOSITED对应内部的CANNED_*成员。从源码结构看各模式的运行时行为差异集中在 canned_response_generator.py 中Fluid若根本没有可用 canned responses或检索后没有任何相关候选草稿消息直接发送direct_draft_output_mode#L2172-L2174选择结果为low或partial时同样回退为发送草稿#L2354-L2403Composited不走模板选择而是调用_recompose让 LLM 以渲染后的候选为风格参考改写草稿且 prompt 中明确要求保留草稿的语义、只复制参考消息的语气与措辞#L2317-L2334 与 #L2497-L2560Strictpartial质量的匹配也会被采用#L2405 起无匹配或模板 ID 非法时走 no-match 流程见第七节。此外 strict 模式下还有一个细节发送第一条消息后引擎还会执行follow-up 选择判断草稿中是否有尚未被第一张模板覆盖的剩余内容若有则再从候选里补发第二张模板generate_follow_up_response#L2750-L2825这保证 strict 模式下草稿的信息量尽可能被预置话术完整表达。值得注意guidelines 也可以携带 composition mode运行时由 _resolve_composition_mode 按最严格者优先STRICT COMPOSITED FLUID在 Agent 级模式与命中的 guideline 级模式之间解析出最终生效模式——这意味着你可以在保持 Agent 全局 fluid 的同时对特定 guideline 触发的高风险话题局部收紧为 strict。四、创建 Canned ResponsesAgent 级创建最基本的用法await agent.create_canned_response(templateTEXT)SDK 侧 Agent.create_canned_response 接受template、tags、signals、metadata、field_dependencies五组参数底层调用CannedResponseStore.create_canned_response落库。创建时模板会先经过 Jinja2 语法校验CannedResponseVectorStore._validate_template非法模板会直接抛出ValueError。Journey-Scoped Responses旅程作用域你也可以创建旅程作用域的响应只有当某个 journey 处于激活状态时这些响应才可被选用。把响应限定到 journey 能收窄候选集合从而提高选中目标话术的概率。做法是把create_canned_response调用在具体 journey 实例上await journey.create_canned_response(templateTEXT)从源码看Journey 级实现 与普通创建唯一的区别是自动附加了一个 journey 归属 tag_Tag.for_journey_id(self.id)引擎在检索候选时通过 tag 过滤实现作用域隔离而 Agent 级创建则附加 agent 归属 tag#L3606-L3627。Preamble Responses前导响应Parlant 利用感知性能perceived performance原则增强对话体验在 Agent 生成完整、准确的回复之前先发送一条简短的preamble response如 Got it.、Understood.、Let me look into that来确认已收到客户输入。这些前导语默认由 Agent 根据上下文自动生成你也可以创建自定义的 canned preamble 供 Agent 从中挑选。创建方式为加上preamble()tagawait agent.create_canned_response( templateSure thing., tags[p.Tag.preamble()], )p.Tag.preamble()对应 SDK 中的 Tag.preamble 工厂方法。源码中的行为值得注意在strict 模式下preamble 只能从带preambletag 的 canned responses 中渲染选取#L817-L874且若 LLM 输出的 preamble 不在候选列表中会被直接丢弃并记录错误日志#L897-L902——strict 模式对前导语同样零自由。非 strict 模式下preamble 由 LLM 参考一组默认示例如 Just a moment、Let me check that for you 等#L3000-L3007就近生成。带preambletag 的响应在正文检索阶段会被显式排除#L945-L953不会与正文候选混淆。五、模板语法三类字段与 Jinja2Canned responses 以**模板template**定义。模板是字符串可包含静态文本以及在选择后被实际值替换的动态字段。标准字段std. 前缀使用std.前缀引用对话上下文中的动态信息。文档列出的可用值std.customer.nameString客户姓名未注册客户为Gueststd.agent.nameStringAgent 名称std.variables.NAMEAny名为NAME的变量内容std.missing_paramsString 列表基于工具洞察Tool Insights得到的缺失工具参数名列表。await agent.create_canned_response( templateHi {{std.customer.name}}, Yes, this product is available in stock. )从源码结构看StandardFieldExtraction 实际暴露的std命名空间比文档列表更宽除customer.name、agent.name、variables.*、missing_params外还包括std.invalid_params参数名到非法取值的映射与std.glossary术语名到定义的映射。如果你需要在模板中提示你提供的 XX 无效或引用术语定义可以直接利用这些字段。生成式字段generative. 前缀引用generative.前缀的字段时LLM 会基于字段名与上下文自动推断并替换其值。这是在严格模板中引入受控的、局部生成的利器await agent.create_canned_response( templateCan I ask why youd like to return {{generative.item_name}}? )实现上GenerativeFieldExtraction 用正则\{\{(generative\.[a-zA-Z0-9_])\}\}从模板中提取字段名为每个字段构造一个专门的字段抽取 prompt包含 Agent 身份、上下文变量、命中的 guidelines、交互历史、glossary 与暂存工具事件要求 LLM 输出一个可干净地嵌回模板的值所有字段生成失败任何一个整条模板即渲染失败并被剔除#L334-L348因此 generative 字段不会产出半成品句子。工具/检索器字段Tool/Retriever-Based FieldsCanned responses 还可以引用来自工具和检索器结果的字段字段必须声明在ToolResult或RetrieverResult的canned_response_fields属性中。这是最有用的字段类型因为它能把真正动态的数据引入 canned responsesp.tool def get_account_balance(context: p.ToolContext) - p.ToolResult: balance 1234.5 return p.ToolResult( # 注意仍需在 data 字段中提供结果 # 因为它会在 Agent 评估 guidelines、调用工具以及生成草稿消息时起作用。 data{fAccount balance is {balance}}, # 这里提供专门用于模板字段替换的动态值 canned_response_fields{account_balance: balance}, )模板引用方式await agent.create_canned_response(templateYour current balance is {{account_balance}})字段在防幻觉中的关键作用警告字段对避免后果性幻觉至关重要使用工具字段还有一个重要收益检索候选响应时引擎会同时参考canned_response_fields判断相关性。引用了上下文中不存在字段的响应永远不会被选中——即使它与草稿消息高度相似。这确保了 Agent 输出的响应锚定在真实可用的数据上。例如在 strict 模式下如果successful_transaction字段没有被某个成功运行的工具调用提供你的 Agent 就永远不会输出任何引用{{successful_transaction.id}}的消息。换句话说只要响应与工具协调得当你就能确保 Agent 绝不就数据或状态幻觉出误导性回复。这条字段依赖门控在源码中可精确验证_get_relevant_canned_responses 会先汇总会话中所有工具调用结果里的canned_response_fields键外加std、generative与额外注入字段得到fields_available_in_context然后用jinja2.meta.find_undeclared_variables解析每条模板的全部变量_get_response_template_fields#L498-L501只有模板的全部字段都出现在上下文中该模板才进入候选#L1009-L1016。这是向量检索之外的第二道硬过滤是 strict 模式下引用不存在的交易 ID 就绝不发送这一保证的底层机制。六、从工具直接返回完整响应工具不仅可以提供字段值还可以直接返回完整的 canned response 候选——当你希望基于工具输出生成一条完整回复而非仅提供数据供字段替换时特别有用通常出现在复杂的 QA 检索场景中p.tool def get_answer(context: p.ToolContext, question: str) - p.ToolResult: answer The answer to your question is.... return p.ToolResult( dataanswer, # 将该答案作为完整的 canned response 候选提供 canned_responses[answer], )源码中这类响应以瞬态transient身份参与候选_get_relevant_canned_responses 从暂存工具事件中读取canned_responses列表用CannedResponse.create_transient包装后并入候选集且瞬态响应不受字段依赖过滤与向量相似度阈值约束#L2280-L2284保证本轮工具刚产出的答案一定会参与最终选择。七、优化响应选择控制草稿 Signals选择质量的上限由草稿质量决定。要确保 Agent 选到正确的 canned response可以分两步优化1. 控制草稿消息由于选择过程以草稿为基准第一步是让草稿尽可能贴近你期望的响应。为此可以使用全部标准控制手段guidelines、journeys、tools、glossary 术语、Agent 描述。这意味着你需要密切关注选择前生成的草稿——在 Parlant 集成的 UI 中可以直接检查生成的草稿消息看 Agent本想说什么。2. 用 Signals 确保正确的候选被检索到有时响应本身与草稿足够接近能自然出现在候选列表里但并不总是如此——尤其当模板含有字段替换、语义相似度比较变难时。这时可以使用signals告诉 Agent这些草稿样式适合匹配这条响应。每条 signal 本质上就是一个草稿消息示例。检索候选时引擎会同时参考这些 signals 判断相关性只要某条响应拥有一条与草稿消息非常接近的 signal即使响应本身形态差异很大它也会被检索为候选await agent.create_canned_response( templateYes, weve got this item in stock! Let me know if you need any help finding it., signals[We do have it in stock, We do! Do you need help finding it?], )底层实现印证了这一点CannedResponseVectorStore._insert_canned_response 在入库时会把模板文本与每一条 signal分别嵌入向量_list_canned_response_contents返回[value, *signals]#L535-L536检索时 _list... / filter_relevant_canned_responses 按canned_response_id去重并取最近距离因此模板 signals共同构成该话术的检索面。这是解决模板里{{...}}太多、原文向量与草稿不相似问题的正规手段。八、Jinja2 的灵活性响应模板集成了 Jinja2 模板引擎支持更动态的格式化、替换过滤器filters与列表处理高级语法可参考 Jinja2 官方文档。例如配合工具字段渲染列表p.tool def get_pizza_toppings(context: p.ToolContext) - p.ToolResult: toppings [olives, peppers, onions] return p.ToolResult( data{fToppings are {toppings}}, canned_response_fields{toppings: toppings}, )await agent.create_canned_response( templateWe have the following toppings {% for t in toppings %}\n- {{t}}{% endfor %} )渲染环节由jinja2.Template(response.value).render(**args)完成#L2473字段值由字段抽取器按std → 工具 → 附加字段 → generative的优先级链解析CannedResponseFieldExtractor。九、No-Match ResponsesStrict 模式的兜底在 strict 模式下如果 Agent 无法为草稿找到合适的 canned response就会发送一条 no-match 响应。默认值来自 canned_response_generator.py#L82Not sure I understand. Could you please say that another way?。自定义有两种方式静态 No-Match 响应最简单设置一条静态模板Agent 找不到合适 canned response 时一律使用它async def initialize_func(c: p.Container) - None: no_match_provider c[p.BasicNoMatchResponseProvider] no_match_provider.template My custom no-match response. async with p.Server( initialize_containerinitialize_func, ) as server: ...对应 BasicNoMatchResponseProvider它只是把template属性原样返回因此完全静态、零 LLM 开销。自定义 No-Match Provider需要更多灵活性时可以实现自定义 provider基于对话上下文动态生成 no-match 响应注意p.LoadedContext提供内部引擎状态访问可能在后续版本中变化你的实现将来可能需要相应调整。class CustomNoMatchResponseProvider(p.NoMatchResponseProvider): async def get_template(self, context: p.LoadedContext, draft: str | None) - str: # 基于提供的上下文生成自定义 no-match 响应 # 例如对话历史、草稿消息、guidelines、工具调用等 template ... return template async def configure_func(c: p.Container) - p.Container: c[p.NoMatchResponseProvider] CustomNoMatchResponseProvider() async with p.Server( configure_containerconfigure_func, ) as server: ...抽象基类 NoMatchResponseProvider 的get_response会把get_template的返回值包装为瞬态CannedResponse从调用点看draft参数在无候选可检索时传入None#L2202-L2210在有候选但匹配质量不足时传入实际草稿#L2364-L2366——因此动态 provider 可以据此区分完全没有可用话术与有话术但都不贴切两种场景给出更贴切的引导语。十、要点小结机制canned responses 是草稿 → 向量检索 → Jinja2 渲染 → LLM 选择的四阶段流水线canned_response_generator.py草稿决定选择质量模板决定输出边界模式Fluid 保流畅、Composited 仿风格、Strict 零幻觉可 Agent 级设置也可被 guideline 级模式局部收紧最严格者优先字段std.取上下文标准值源码中还含invalid_params与glossarygenerative.做受控局部生成工具/检索器字段注入真实业务数据并以字段缺失即不入选的依赖门控杜绝后果性幻觉检索模板与 signals 共同嵌入向量库canned_responses.py候选上限 30 条、相似度阈值 0.4兜底strict 模式的 no-match 可用BasicNoMatchResponseProvider.template静态化或继承NoMatchResponseProvider动态生成验证端到端行为有 strict_canned_responses.feature 等 Gherkin 场景与 tests/api/test_canned_responses.py 的 API 测试可作参考便于你本地复现与回归验证。按此路径你可以先以 strict mode 少量高置信模板起步用 UI 中可见的草稿消息持续校准 guidelines 与 signals再视风险逐步放开到 composited 或 fluid——整个过程中对话模型的其他构件始终可复用。【免费下载链接】parlantBuild reliable customer-facing AI agents with Parlant: an interaction control harness optimized for controlled, consistent, and predictable LLM interactions.项目地址: https://gitcode.com/GitHub_Trending/pa/parlant创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表