Product Docs

多轮上下文管理

返回作品总览

多轮上下文管理

设计目标

多轮上下文的目标是让 AI 记住本次问题链路,而不是记住客户的所有历史。外卖客服的会话特征非常明确:单次问题短、闭环快、跨会话相关性低。客户晚上点单咨询配送,三天后再来咨询活动,两次会话之间没有强关联。系统不需要、也不应该把所有历史都喂给模型。

过长上下文带来三类问题:

  • 成本上升:token 数量直接影响每次会话的 LLM 成本
  • 过期引用:模型可能引用早期已被纠正的信息
  • 注意力稀释:关键事实被淹没在闲聊里,模型反而抓不住重点

因此系统采用「窗口 + 结构化插槽」的双轨策略:消息窗口保留最近上下文,结构化插槽保留必须传递的业务字段。

消息窗口策略

场景保留消息数额外保留
普通 FAQ(营业时间、招牌菜)6 条
订单查询8 条订单号、工具调用结果摘要
售后投诉12 条投诉类别、订单关联、补充描述、情绪等级
转人工后人工回复全量转人工原因、上下文摘要
跨日重新进入0 条 + 摘要上次会话 summary、未关闭工单

普通咨询窗口故意做得很小(6 条),因为外卖 FAQ 几乎不需要长上下文。窗口越小,模型越聚焦于本轮意图,回复也更短。

结构化插槽

除了消息窗口,系统在 conversationsmessages 表上维护一组结构化字段,作为 AI 跨轮可见的"工作记忆":

字段用途谁写入
conversation.intent_category当前会话主意图意图分类器
conversation.summary会话摘要(每 N 轮更新)LLM 摘要任务
conversation.assigned_to当前接管人人工接管接口
messages.cited_article_ids已引用知识RAG 检索
messages.tool_calls已执行工具 + 结果工具调用框架

工具结果以结构化形式(订单号、ETA、骑手信息)写入 tool_calls,后续回答优先引用这些字段,而不是让模型重新解析自然语言。这样能保证「订单号 MT-001 → ETA 18 分钟」这条信息在后续 5 轮内仍准确,不会被模型改写。

多意图处理

客户在一轮中提出多个问题非常常见。例:

你们家几点关门?有什么招牌菜?现在有满减吗?

系统的处理流程是:

  1. 意图分类器识别主意图 + 次意图(最多 2 个,按出现顺序)
  2. 高风险意图优先级覆盖:若任一意图属于 allergy / complaint_quality / handoff_request / 健康风险,立即按高风险处理,其他意图延后
  3. 主意图决定动作(如调用工具、发起转人工)
  4. 次意图作为补充内容,附加在回答末尾
  5. 三个以上意图时,回复主意图 + 次意图,明确告知"其他问题麻烦您再发一次"

这套规则在 02-conversation-flow 里有完整决策树。多意图处理的核心思路是「不漏答,但不被低风险带偏」——客户问"过敏能吃吗顺便问下满减",主意图必须是过敏,满减只能在转人工后由人工补充。

工具结果回填

每次工具调用结束后,系统把结构化结果回填到 messages.tool_calls,并生成一条人类可读的简述写入下一条 AI 消息的引用区。后续轮次:

  • AI 不再重新调用相同工具(除非客户明确要求刷新)
  • AI 直接引用已有结果("刚才查到您的订单...")
  • 工具结果有时效性(订单 ETA、活动有效期)时,超过窗口需要重新调用

工具结果的"重新调用阈值"按场景设定:订单状态 5 分钟、活动信息 1 小时、营业时间 1 天。超过阈值后再次询问相同问题,系统会主动重查而不是引用旧结果。

会话结束判定

会话结束不能只看客户是否回复"谢谢"。系统结合下列信号综合判断:

信号权重说明
是否还有未关闭工单有未关闭工单视为未结束
是否触发同类重复追问重复追问视为未真正解决
质检综合分 ≥ 4.0质检不通过视为未解决
7 天窗口内同客户同类咨询再次咨询视为未真正解决
客户主动结束语"谢谢"、"好的"作为辅助信号

最终的 ai_resolved 标记由质检 cron 任务在每日 1:00 回填,不是会话结束当下决定的。这个延迟判定是反作弊设计的核心(参见 06-data/02-anti-cheat)。

跨会话连续性

外卖客户跨日重新进入聊天 widget 时,系统不还原上次完整历史,而是用一句摘要恢复上下文:

上次您咨询了订单 MT-001 的配送状态,已正常送达。今天有什么需要帮忙的吗?

如果上次会话有未关闭工单,开场会主动提示:

您 5 月 6 日提交的"虾不新鲜"工单,老板已联系处理。需要我帮您查看处理结果吗?

跨会话恢复用的是 conversation.summary + 关联 tickets,不是消息历史回灌。这样既不浪费 token,也避免老对话的旧规则引用造成混乱。

设计取舍

多轮上下文设计的几个明确取舍:

  • 不做客户长期画像:外卖场景里"客户偏好辣度"这类长期偏好价值有限,做了反而增加合规风险
  • 不做跨商家上下文:每个 merchant_id 是隔离的,多店扩展时也保持隔离
  • 不做动态窗口大小:固定窗口比基于 token 数的动态窗口更可预测、更易调试
  • 不让模型决定记多少:上下文管理是产品规则,不是模型自由裁量

这些取舍让系统在中小商家场景里足够用,又不会因"想记得更多"而引入复杂度。后续如果扩展到连锁店或会员场景,可以再增加跨会话记忆层。