1. 项目缘起:当医疗AI智能体“失忆”时,我们遇到了什么?

最近在推进一个医疗领域的AI智能体项目,目标是打造一个能够与患者或医生进行长时间、多轮次有效对话的智能助手。项目代号“132”,寓意着希望它能像“1对1”的私人医生一样,进行“3”段式(问诊、分析、建议)的深度交流,并最终实现“2”方(医患)价值的闭环。听起来很美好,对吧?但在实际开发中,我们团队踩了一个巨大的坑:智能体的“记忆”会乱窜。

想象一下这个场景:一位慢性病患者正在向智能体描述他本周的血糖监测数据,并询问饮食调整建议。聊了五六轮后,患者突然话锋一转,问:“对了,我上次提到的那个新开的降压药,晚上吃可以吗?”这时,一个理想的医疗智能体应该能立刻反应过来,患者指的是三五天前对话中提及的“新换的络活喜(苯磺酸氨氯地平)”,并基于此给出用药时间的建议。但我们早期的版本,很可能一脸“懵圈”地反问:“您指的是哪种降压药?”或者更糟糕,它可能会错误地将当前对话中关于“血糖”的上下文,与“降压药”强行关联,给出“服用降压药可能影响血糖,请监测”这类看似相关实则偏离核心、甚至可能引发误导的回复。

这种“记忆乱窜”或“记忆丢失”的现象,在技术层面被称为 上下文混淆 上下文丢失 。它直接摧毁了医疗对话中最宝贵的资产—— 连续性 准确性 。患者需要的是一个有“长期记忆”的、稳定的对话对象,而不是一个每轮对话都“重启大脑”的健忘机器人。这个问题不解决,所谓的“智能体”就只是一个高级一点的单轮问答工具,根本无法承担起慢病管理、健康咨询、随访跟踪等需要长效交互的医疗任务。

因此,“构筑长效对话链路”成为了我们项目的核心攻坚点。这不仅仅是简单地把历史对话记录一股脑塞给大模型(LLM),而是需要设计一套精密的“记忆机制”,并对“上下文”进行完整、有序、安全地处理。本文将深入拆解我们在“智能体多轮对话记忆机制与上下文完整处理”上的实践与思考,这些经验不仅适用于医疗,对于任何需要深度、多轮交互的AI智能体场景(如客服、教育、陪伴)都有参考价值。

2. 拆解核心概念:记忆、上下文与智能体工作流

在深入技术细节之前,有必要厘清几个关键概念。很多人容易把它们混为一谈,但在系统设计时,区分它们至关重要。

2.1 记忆(Memory) vs. 上下文(Context)

这是最容易混淆的一对概念。在我们的实践中,我们这样定义:

  • 上下文(Context) :指的是 单次请求/响应对 中,直接输入给大模型(LLM)的文本信息总和。它通常有一个固定的长度限制(如 4K, 8K, 16K, 128K tokens)。你可以把它理解为LLM“当前工作台”上的所有材料。在这次推理中,LLM只能“看到”和“使用”工作台上的这些材料。
  • 记忆(Memory) :指的是智能体在 跨越多个对话轮次 后,所需要保留和调用的信息总和。它远远大于单次上下文窗口的限制。记忆是存储在智能体“大脑”(数据库、向量库、缓存等)里的长期或短期信息。

关键关系 记忆是原料库,上下文是加工车间 。智能体的记忆系统负责从庞大的“原料库”(记忆)中,根据当前对话的局势,精准地筛选、提取、组装出最相关的一小部分“原料”,然后放入有限的“加工车间”(上下文)中,供LLM进行本次的推理和生成。处理不当,要么车间里材料不足(记忆提取不全),要么塞进了无关材料(记忆乱窜),都会导致输出结果不佳。

2.2 医疗AI智能体的特殊记忆需求

通用聊天机器人和医疗AI智能体对记忆的需求有本质区别:

  1. 结构化与非结构化并存 :患者描述“头晕、三天了、下午加重”,这是非结构化文本。但智能体需要将其结构化存储为“症状:头晕;持续时间:3天;加重时间:下午”。同时,化验单上的“HDL-C: 1.8 mmol/L”是高度结构化的数据。记忆系统需要能兼容处理这两种形态。
  2. 长期记忆与短期记忆分离 :患者的过敏史(青霉素过敏)、基础疾病(高血压III级)、植入物(心脏支架)是需要永久或长期记忆的 长期记忆 。本次就诊的主诉、现病史、当次的检查结果,属于本次会话的 短期记忆 。两者必须隔离,避免本次的胃炎问诊错误地引用了三年前的阑尾炎手术记录。
  3. 事实性记忆与推论性记忆 :患者说“我血压150/100”,这是 事实性记忆 。智能体根据此推断“患者目前血压处于2级高血压水平”,这是 推论性记忆 。系统需要能区分并记录这两种记忆,并在后续对话中正确引用。例如,当患者说“我吃了那个药”,智能体需要能追溯到“那个药”指的是它之前推论推荐的“某某地平片”,而不是患者最初提到的“医生开的药”这个模糊事实。
  4. 隐私与安全隔离 :不同患者之间的记忆必须绝对隔离。这在技术上称为 租户隔离 会话隔离 。A患者的糖尿病病史绝不能泄露到B患者的对话中。这不仅是功能要求,更是法律和伦理红线。

2.3 智能体的核心工作流与记忆的接入点

一个典型的智能体工作流(以处理用户一轮消息为例)如下:

  1. 接收输入 :用户发送消息:“我昨晚膝盖又疼了,和上周爬山有关系吗?”
  2. 记忆检索 :智能体解析当前输入,从记忆库中检索相关记忆。这里的关键是“相关”如何定义?系统需要检索:
    • 短期记忆 :本次对话中是否提过“膝盖疼”?什么时间开始的?怎么个疼法?
    • 长期记忆 :该用户的病史中是否有“关节炎”、“痛风”、“旧伤”等记录?
    • 上周的对话记忆 :关于“爬山”的具体细节(时间、强度、感受)。
  3. 上下文组装 :将检索到的记忆片段、系统指令(角色设定)、当前用户输入,按照预设的模板组装成一个完整的上下文提示(Prompt),送入LLM。
  4. LLM推理与生成 :LLM基于组装好的上下文,生成回复:“根据您之前的描述和病史,您有轻度膝骨关节炎病史。上周的爬山活动可能增加了关节负荷,导致炎症反应暂时加重。建议近期减少负重活动,可继续观察,如果出现红肿热痛或加剧,请及时就医。”
  5. 记忆更新 :将本轮对话中有价值的新信息写入记忆库。例如,将“昨晚膝盖又疼了”作为新的事实性记忆,与“爬山”事件关联存储。LLM生成的“可能原因”可以作为推论性记忆存储。

整个流程的瓶颈和核心挑战就在第2步(记忆检索)和第3步(上下文组装)。下面我们就重点拆解这两部分。

3. 记忆机制的设计与实践:从粗放到精细

我们迭代了三个版本的记忆机制,最终形成了一个分层、分类的混合记忆系统。

3.1 版本一:原始对话日志(The Naive Approach)

最初,我们采用最简单粗暴的方式:将整个对话历史(纯文本)作为上下文。每轮新对话,就把之前所有的QA记录拼接起来,发给LLM。

  • 优点 :实现简单,信息理论上最全。
  • 致命缺点
    • Token爆炸 :对话超过10轮,就可能超出上下文窗口,导致最早的关键信息(如主诉)被“遗忘”。
    • 噪音干扰 :LLM需要从冗长的历史中自己寻找相关性,效率低下,且容易受到无关对话(如寒暄、重复提问)的干扰。
    • 无法处理长程依赖 :即使上下文窗口足够大(如128K),LLM对处于上下文中间位置的信息的注意力也会衰减,依然可能“视而不见”。

我们很快意识到,这不是“记忆”,这只是“录音带”。

3.2 版本二:向量检索记忆(The RAG Approach)

为了解决信息检索效率问题,我们引入了向量检索(RAG)技术。

  • 做法 :将每一轮对话的“用户输入”和“AI回复”分别或共同编码成向量(Embedding),存入向量数据库(如Chroma, Weaviate)。当新用户输入到来时,将其也编码成向量,在向量数据库中进行相似度搜索(如余弦相似度),召回最相关的K条历史对话片段。
  • 改进 :检索效率大幅提升,可以快速从海量历史中找到语义上最相关的记忆,不受对话轮次限制。
  • 新问题
    • “记忆乱窜”的根源 :向量检索基于 语义相似度 。当用户当前输入“降压药晚上吃行吗?”时,它可能召回历史上所有关于“药”、“晚上”、“吃”的片段,包括之前讨论的“降糖药饭后吃”。这就导致了跨话题的“记忆乱窜”。
    • 缺乏时序性和逻辑性 :向量检索召回的记忆片段是孤立的,丢失了对话发生的先后顺序和逻辑链条。它知道“降压药”和“晚上吃”相关,但不知道“这个降压药是三天前换的络活喜”。
    • 结构化信息损失 :纯文本向量化难以很好地捕捉“血压 150/100 -> 2级高血压”这样的结构化推理关系。

3.3 版本三:分层分类混合记忆系统(Our Solution)

基于上述教训,我们设计了一个更精细的系统。核心思想是: 不同的记忆,用不同的方式存储和检索

3.3.1 记忆分层:会话记忆 vs. 实体记忆

我们将记忆分为两层:

  1. 会话记忆(Conversation Memory) :绑定到一次具体的对话会话(Session)。主要用于维护对话的连贯性和短期焦点。我们采用了一种改进的 滑动窗口缓冲池

    • 实现 :维护一个固定长度的FIFO(先进先出)队列,例如保存最近10轮对话的原始文本。
    • 关键增强 :不仅仅是存储,我们还为每一轮对话计算一个简单的“主题标签”(通过提取关键词或调用小模型分类),当进行检索时,除了向量相似度,还加入“主题匹配度”作为权重。这在一定程度上缓解了跨话题乱窜。
    • 作用 :保证LLM始终对“刚刚在聊什么”有清晰的感知,处理代词(这个、那个)、省略句等依赖近期上下文的表达。
  2. 实体记忆(Entity Memory) :绑定到用户(或患者)这个实体。存储需要长期保留的事实、推论和用户画像。这里我们引入了 图数据库 (如Neo4j)。

    • 为什么用图数据库? 因为医疗信息本质上是关系型的。
    • 举例 :我们可以创建如下节点和关系:
      • 节点(患者:张三),属性{年龄:55, 性别:男}
      • 节点(疾病:高血压),属性{等级:2级, 诊断时间:2022-01}
      • 节点(药品:络活喜),属性{规格:5mg, 用法:口服}
      • 关系:(张三)- [患有] -> (高血压)
      • 关系:(张三)- [服用] -> (络活喜), 属性{开始时间:2023-10, 频次:每日一次}
      • 关系:(络活喜)- [治疗] -> (高血压)
    • 检索方式 :当用户提到“我的降压药”时,系统首先通过NER识别出“降压药”是一个药品实体,然后在图数据库中查询该用户(张三)服用的、用于治疗“高血压”的药品节点,很容易就能定位到“络活喜”。这种方式是基于 实体关系 的检索,比纯语义检索精准得多,彻底解决了“降压药”和“降糖药”的混淆。
3.3.2 记忆分类:事实性记忆 vs. 摘要性记忆

在会话记忆层,我们进一步对记忆内容进行分类存储:

  1. 事实性记忆 :直接来自用户或权威来源的陈述。如用户说“我血压150/100”。我们原样存储。
  2. 摘要性记忆 :由LLM生成的、对一段对话或一个话题的总结。例如,每对话5轮,或当检测到话题明显转换时,触发一个摘要任务:“请用一句话总结过去几轮对话中关于患者膝盖疼痛的核心信息。” 然后将这个摘要作为一条新的记忆存入缓冲池,并可能替代它所概括的那些细节轮次。这相当于 记忆的压缩和提炼 ,是应对有限上下文窗口的关键策略。

注意 :摘要的生成本身需要成本,且可能有信息损失。我们的策略是“惰性摘要”,即只在必要时(如缓冲区快满或话题转换时)才进行,并且保留被摘要的原始对话的索引,以备需要回溯细节时查询。

4. 上下文完整处理:组装、管理与优化

有了高质量的记忆原料,下一步就是如何将它们高效、有序地组装成LLM的“工作台”(上下文)。

4.1 上下文组装模板(Prompt Engineering)

这是将技术逻辑转化为LLM可理解指令的关键一步。一个糟糕的模板会让所有精密的记忆检索功亏一篑。我们的模板遵循以下结构:

你是一位专业的全科医生助理。请根据以下的患者信息和对话历史,专业、友善地回答患者的最新问题。

# 患者长期健康档案(实体记忆)
[此处插入从图数据库中检索到的、与当前问题相关的实体及关系,例如:患者张三,55岁,男性。患有2级高血压(2022年诊断),目前服用络活喜(5mg,每日一次)。无药物过敏史。]

# 近期对话摘要(摘要性记忆)
[此处插入最近一个对话片段的摘要,例如:患者在过去几分钟内主要咨询了爬山后膝盖疼痛加重的问题,你已建议减少负重活动并观察。]

# 最近几轮对话(会话记忆-滑动窗口)
用户(2分钟前):我上周六去爬了香山,感觉有点累。
AI(2分钟前):登山是一项很好的运动,但对于关节有一定压力。您爬完后膝盖有不适吗?
用户(刚刚):我昨晚膝盖又疼了,和上周爬山有关系吗?

# 当前问题
用户:对了,我上次提到的那个新开的降压药,晚上吃可以吗?

# 请根据以上信息,思考并回答:
1. 首先,明确“新开的降压药”具体指什么?(请引用长期健康档案)
2. 然后,评估晚上服用的可行性,给出理由。
3. 最后,将回答聚焦于当前问题,语言简洁专业。

这个模板的 设计逻辑 是:

  1. 角色与背景前置 :固定系统指令,强化AI的角色认知。
  2. 记忆分层呈现 :按信息的重要性和时效性降序排列。长期档案(最稳定)在前,近期摘要(次稳定)居中,具体对话(最动态)在后。这符合人类的认知习惯,也帮助LLM分配注意力。
  3. 结构化指引 :通过“请根据以上信息,思考并回答:”后面的子问题,引导LLM的思考链(Chain-of-Thought),确保它按步骤处理信息,特别是先进行“实体解析”(第一步),避免张冠李戴。
  4. 明确指令 :“将回答聚焦于当前问题”,防止LLM过度发散,重新讨论膝盖疼痛。

4.2 上下文窗口管理与优化策略

即使采用了摘要和精准检索,上下文窗口仍然是稀缺资源。我们采用了以下组合策略:

  1. 动态上下文窗口 :不是固定使用最大窗口。系统会根据检索到的记忆总量和当前对话的复杂度,预估一个合理的token数量,并留出一定的安全余量。这可以降低推理成本和提高速度。
  2. 优先级淘汰 :当组装的内容接近窗口上限时,启动淘汰机制。淘汰的优先级是: 寒暄内容 > 重复信息 > 最早期的会话记忆(除非被标记为关键)> 相关性最低的实体记忆 。摘要性记忆通常具有高优先级,因为它信息密度高。
  3. 关键信息锚定 :对于每次对话的“核心诉求”(如本次就诊的主诉),会被打上“关键”标签,在上下文组装中优先保留,避免被淘汰。例如,即使用户聊了20轮饮食,当再次问回膝盖时,最初“膝盖疼痛3天”的主诉仍需能被快速唤起。

4.3 处理“未能获取有效的上下文”类错误

在开发中,我们遇到过类似 InvalidContextException: 未能获取有效的上下文 的错误。这通常发生在以下情况:

  • 记忆检索为空 :用户的输入过于模糊或新颖,导致向量检索和图查询都返回空结果。此时上下文模板中记忆部分是空的。
  • 会话ID丢失或错误 :在微服务架构下,请求的会话ID(Session ID)在传递过程中丢失或无法在内存/缓存中找到对应的会话状态。
  • 上下文组装失败 :在组装过程中,由于编码问题、模板渲染错误等,产生了格式非法或为空的内容。

我们的处理策略

  1. 降级策略 :当记忆检索为空时,不直接抛出错误,而是降级为“无记忆上下文”模式。在模板中明确告知LLM:“未找到相关的历史信息,请基于通用医学知识进行回答,并主动询问更多细节以帮助定位问题。” 这保证了对话的流畅性。
  2. 状态检查与恢复 :在智能体工作流的入口处,加强会话状态的校验。如果发现无效会话,尝试根据用户ID等身份信息恢复最近一个活跃会话,或创建一个干净的新会话,并友好提示用户:“我们似乎开始了新对话,您可以继续之前的话题吗?”
  3. 上下文验证 :在调用LLM API前,对组装好的上下文字符串进行基础验证(非空、长度、关键标记符是否存在等),将格式错误扼杀在摇篮里。

5. 实战中的挑战与精细化调优

将上述架构落地,我们经历了大量的调试和优化。以下是几个印象深刻的“坑”和解决方案。

5.1 挑战一:摘要的“信息扭曲”问题

我们让LLM对一段关于“头痛”的对话做摘要,原始描述是“左侧颞部阵发性胀痛,持续数秒,每天发作数次”。摘要可能变成“患者有头痛”。信息损失巨大。

解决方案 :设计 结构化摘要指令 。不再简单地说“请摘要”,而是给出模板: “请提取以下对话中的关键医疗信息:

  • 症状部位:[ ]
  • 症状性质:[ ]
  • 发作频率:[ ]
  • 持续时间:[ ]
  • 已提及的可能原因或关联事件:[ ]” 这样得到的摘要既是自然语言,又保留了关键的结构化信息,便于后续检索和使用。

5.2 挑战二:实体链接的歧义消除

用户说:“我吃那个药胃不舒服。” “那个药”可能指代本次对话中刚提到的“阿司匹林”,也可能指代长期服用的“二甲双胍”。如何确定?

解决方案 :实现一个 多阶段指代消解(Coreference Resolution)流程

  1. 会话内优先 :首先在本次会话的滑动窗口记忆中进行查找,寻找最近被提及的药品实体。
  2. 实体关系过滤 :如果会话内找到多个候选(例如既提到了阿司匹林也提到了二甲双胍),则利用图数据库的关系进行过滤。查询当前用户“胃不舒服”可能与哪种药物的常见副作用更相关。二甲双胍更易引起胃肠道反应,因此优先级提高。
  3. LLM仲裁 :如果仍无法确定,将候选实体和对话片段放入上下文,让LLM进行判断:“用户所说的‘那个药’,在以下上下文中,最可能指代阿司匹林还是二甲双胍?请给出理由。” 将LLM的判断结果作为推论性记忆存储,并用于本次回复。

5.3 挑战三:记忆更新的时机与冲突

何时更新记忆?如果用户说“我血压好像正常了”,是立刻更新长期档案中的“高血压”状态吗?这太危险了。

解决方案 :制定 记忆更新置信度规则

  • 高置信度,直接更新 :用户提供明确的、结构化的数据(“我刚量的血压是118/75”)。系统可以自动更新“最近一次血压值”这个事实节点。
  • 低置信度,标记待确认 :用户提供模糊的、主观的陈述(“我感觉好多了”、“血压好像正常了”)。系统不直接修改核心健康档案,而是创建一个“待确认主张”节点,关联到用户和相应疾病。在后续对话中,智能体可以主动求证:“您说感觉血压正常了,最近有测量过具体的数值吗?”
  • LLM辅助决策 :对于复杂陈述,由LLM判断信息类型和置信度。例如,用户说“医生说我可以停药了”,LLM可解析出这是一个“医嘱”类高置信度信息,触发对“用药”关系的更新。

6. 效果评估与未来展望

经过上述改造,我们的智能体“132”在测试中表现出了质的飞跃。在模拟的30轮长对话中,其对早期关键信息的召回准确率从不足40%提升至85%以上,跨话题混淆率从约25%降至5%以下。医生和体验用户最直观的反馈是:“它更像一个‘记得事’的助手了。”

当然,这套系统依然有进化空间:

  • 更智能的记忆摘要 :探索在对话流中实时、增量式摘要,而非定时触发。
  • 记忆的主动应用 :当前记忆主要是“被动检索”,未来可以让智能体更主动地基于记忆发起提问或提醒。例如,在对话一段时间后主动询问:“您之前提到的膝盖疼痛,这一周有变化吗?”
  • 多模态记忆扩展 :支持将用户上传的检查报告图片、语音描述等内容,通过多模态模型理解后,转化为结构化和非结构化的记忆存储。

构筑长效对话链路,本质是赋予AI时间感和历史感。记忆机制是骨架,上下文处理是血肉。在医疗这样高严谨性的领域,这套系统的每个细节都关乎信任与安全。希望我们“132”项目的这些实践与思考,能为更多同行在构建可靠、可信、可长期交互的AI智能体时,提供一些有价值的参考。这条路很长,但每一次让AI“记住”并“理解”的进步,都让我们离那个理想的、无处不在的智能健康伙伴更近一步。

更多推荐