让客服智能体记住失败方案与处理结果
ResolveIQ 用 PostgreSQL 保存工单、消息和解决尝试的结构化状态,用 Hindsight 管理跨会话的客户记忆,并由模型起草回复。核心设计是记录方案是否奏效,让后续响应避开已经失败的排障步骤。
AI 深度解读
AI 客服最让人恼火的时刻,往往是用户已经说了「试过,没用」,下一轮却又收到同一套步骤。ResolveIQ 想解决的就是这个问题:把建议、执行结果和跨工单记忆分开保存,让下一次回复知道哪些办法已经失败。
作者用 React、TypeScript、FastAPI、PostgreSQL、Groq 和 Hindsight 搭了一套客服流程。标题里的「永不重复」有些满,正文的工程思路更克制,也更值得程序员借鉴:记住聊过什么还不够,系统还得记住试过什么、结果如何。
为什么要单独记录一次解决尝试
聊天记录能保存用户说过的话,却不适合直接承担工单状态管理。同一段对话里,可能同时存在客服建议、用户反馈、补充条件和新的问题。如果每次都让模型重新理解整段历史,就很容易把「建议执行」误当成「已经解决」。
ResolveIQ 把一次建议建模为 resolution attempt,也就是「解决尝试」。模型提出办法后,后端先把它记为 pending;收到后续反馈,再将结果更新为 failed 或 succeeded。人工客服也可以添加、修改这些记录。
这几个状态的区别很关键。用户没回复,只能说明结果未知,不能推断成功。模型生成了一段看起来完整的答复,也只能算提出方案,不能直接关闭问题。
下一次生成回复时,系统会同时提供近期对话和当前工单的尝试列表。前者解释用户正在说什么,后者明确哪些方法已经试过。作者没有把它们混成一段摘要,而是给失败尝试留了专门的位置。
下面这段提示词构造代码,解决的是不同信息在模型上下文里容易混淆的问题:
return (
f"{SYSTEM_INSTRUCTIONS}\n\n"
f"--- CURRENT CONTEXT ---\n"
f"{customer_info}\n\n"
f"Customer Memories:\n{memories_str}\n\n"
f"Prior Attempted Solutions on this Ticket (DO NOT REPEAT FAILED ONES):\n"
f"{attempts_str}\n"
)
要看的是 DO NOT REPEAT FAILED ONES 所在那一行:它把尝试记录单独列出,并明确要求避开失败方案。但它仍是一条模型指令,不是程序层面的禁止规则。
PostgreSQL 和 Hindsight 分别记什么
ResolveIQ 没有让记忆系统接管整个客服流程。
PostgreSQL 保存客户、工单、会话、消息、解决尝试和升级处理记录。工单状态、负责人、尝试结果等信息,需要明确字段和可预测的更新方式,由关系数据库负责更合适。
Hindsight 则负责跨会话的客户记忆,例如反复出现的账户条件,或者过去有效、这次可能用得上的处理办法。作者通过 Python client 使用它的 retain、recall、reflect:分别用于保存交互、检索相关上下文,以及在客服需要时综合客户历史。
大白话解释:数据库负责把这件事办到哪一步记清楚,记忆层负责找出以前哪些经历可能有帮助。
每个客户拥有独立的 Hindsight bank。下面的代码通过配置前缀与客户 ID 生成 bank ID,用来建立客户记忆的归属边界:
def bank_id(self, customer_id: int) -> str:
return f"{self.settings.hindsight_bank_prefix}-customer-{customer_id}"
要看的是返回值里的 customer_id:它让不同客户使用不同 bank。不过,生成不同 ID 只说明有了分区规则,不能据此认定鉴权和访问控制已经完整。
作者给出的例子是银行卡结账失败。第一次建议删除后重新添加银行卡,用户反馈无效,系统便把尝试标为失败,并将交互保存到客户记忆中。后来出现相关工单时,再检索这些历史,避免从头建议同一套动作。
这里有一个实现细节没有交代完整:提示词中的结构化尝试列表属于当前工单,旧工单里的失败记录需要通过客户记忆带过来。原文描述了预期效果,但没有展示跨工单记录如何稳定进入失败尝试区。因此,「当前工单保留失败结果」和「新工单一定找回旧失败方案」,还不能视为同等可靠。
一次回复结束,后端还要做哪些事
这套流程从保存用户消息开始。后端用工单主题和当前消息组成查询,向 Hindsight 执行 recall,再把召回内容、近期对话和相关尝试记录交给模型。回复由服务端 Groq client 生成。
生成之后,后端分离给客户看的正文与 metadata block,更新工单和尝试记录,通过 retain 保存交互,最后评估是否需要升级处理。metadata 中包含建议方案、意图、类别和优先级。
模型可以提出这些字段,最终如何落库仍由后端决定。例如,后端校验类别,只允许优先级上升,并把新方案保存为 pending。回复也不能声称已经执行退款、重置账户等不可逆操作。
流式输出还有一个容易忽略的细节。系统通过 server-sent events(SSE)发送状态和回复增量,同时暂存生成文本末尾的一小段,防止 metadata 分隔符被拆到不同 token 块后,内部内容意外展示给客户。识别出 metadata 起点后,再单独累积这部分文本。原文没有给出分隔符格式和暂存长度,照着做时还需要补齐协议设计。
Hindsight 的同步 SDK 调用则通过 asyncio.to_thread 包装,避免直接阻塞事件循环。调用成功后才更新 last_success_at 并清空 last_error;遇到 MemoryNotConfigured 原样抛出,其他异常记录类型和消息,截取前 300 个字符后包装为 MemoryServiceError。
检索失败和没有相关记忆,是两种必须区分的结果。
否则,系统可能在记忆服务不可用时,仍表现得像已经查过历史。作者还建议生产环境补上超时、适合请求链路的重试策略和可用性告警;这些属于部署建议,原文没有证明已经完成。
多次尝试失败、重复投诉、工单超过配置时长仍未解决,以及高优先级问题,都可以触发升级规则。升级快照包含工单历史、尝试记录、客户信息和相关工单。但作者也承认,创建内部审查记录只是交接的第一步,后续还需要明确负责人、路由集成与监控。
我的判断
我认可这篇文章把「方案结果」放在模型能力前面。相比只拼接聊天记录,显式尝试记录更容易检查,也方便人工接手;相比只依赖语义记忆,关系数据库更适合维护需要审计和约束的状态。
但原文没有给出重复建议率、召回准确率、处理时长或对照实验。它是一份有具体实现片段的设计说明,不能据此判断 Hindsight 比检索旧消息、维护结构化摘要或其他记忆方案效果更好。收益多大,目前仍是经验判断。
还有两处可靠性缺口。第一,后续反馈可以更新尝试结果,但原文没有说明如何处理「用户没有按步骤执行」或「只改善了一部分」等含糊反馈。第二,没有展示新方案与失败方案的语义去重逻辑。模型把同一个操作换种表达,提示词未必拦得住。
作者自己也收回了标题里的绝对承诺:如果某类操作必须禁止重复,就应该由应用逻辑执行约束。对一般团队而言,可以先把尝试及结果建模清楚,再根据跨工单检索需求决定是否引入 Hindsight。多一层记忆服务,也会多一层需要处理的故障。
能拿来做什么
- 做 React 客服工作台时,可以把解决尝试独立展示,列出建议内容及
pending、failed、succeeded状态,让人工客服直接确认结果,不必翻完整段聊天记录。 - 用 Node 或 FastAPI 接模型时,可以将对话、客户记忆和尝试记录分区组织,并由后端校验模型返回的类别、优先级和状态变化,避免生成文本直接成为业务事实。
- 给现有客服系统增加跨工单记忆前,先明确每条记忆的客户归属,并让界面或日志区分召回成功、无相关结果和服务失败,便于定位重复建议的原因。
- 对已经接入工具操作的客服 Agent,应把必须执行的限制放进应用逻辑,同时建立结果回收和人工升级流程,让一次建议最终能够得到可检查的结论。