广告位 · 页头(后台-模块管理-新建模块后替换此区域)

如何解决多轮对话遗忘问题?对话摘要与关键信息提取实战

多轮对话的遗忘问题本质上是上下文窗口有限与信息持续累积之间的矛盾。无论底层是GPT还是Llama,模型能一次性处理的token数量都有上限,当聊天记录超出这个限制,最早的几轮内容就会被截断或丢弃,导致模型丢失用户最初提到的关键约束。解决这个问题的常见做法不是增大上下文窗口,而是在上下文进入模型之前做一层有损压缩,把必须记住的信息保留下来,把冗余的寒暄和重复表述过滤掉。

如何解决多轮对话遗忘问题?对话摘要与关键信息提取实战

压缩手段通常分为两类:对话摘要和关键信息提取。对话摘要要求模型把历史对话改写成一段紧凑的叙述性文本,保留时间线、实体关系和用户偏好;关键信息提取则只抽取离散的结构化字段,例如姓名、地址、订单号、目标价格、是否同意某项条款。两者可以单独使用,也可以组合使用。下面分别拆解它们的适用场景、实现方式和容易踩的坑。

对话摘要:把长聊天记录压成一段话

对话摘要的思路很直观:每隔若干轮,把已经发生的对话交给大模型或者轻量摘要模型,让它输出一段包含核心信息的自然语言总结。之后的新一轮对话不再携带完整的原始历史,而是携带摘要文本和最近几轮未参与摘要的对话。比如设定触发条件为每超过10轮或token数超过阈值时执行一次摘要,摘要完成后清空原始历史,只保留摘要作为前缀。

摘要提示词的质量直接决定记忆效果。一个容易犯的错误是让模型“总结一下对话”,这种指令太模糊,模型可能只概括了情绪或主题,丢掉了具体的数字和人名。更稳妥的做法是明确要求模型保留四类信息:用户明确提出的需求、已经达成的共识、尚未解决的问题、以及任何时间地点等约束条件。下面这段Python代码演示了如何用OpenAI兼容接口触发一次对话摘要,同时把摘要结果写入自定义的上下文管理对象。

import json
from openai import OpenAI
client = OpenAI(api_key="your-api-key")
def summarize_history(history_messages, model="gpt-4o-mini"):
    prompt = (
        "请把下面这段多轮对话压缩成不超过200字的摘要。"
        "必须保留:1) 用户的具体需求;2) 双方已确认的结论;"
        "3) 尚未解决的问题;4) 涉及的日期、金额、地点等约束。"
        "不要添加对话中不存在的信息。\n\n"
        "对话内容:\n" + json.dumps(history_messages, ensure_ascii=False)
    )
    response = client.chat.completions.create(
        model=model,
        messages=[{"role": "user", "content": prompt}],
        temperature=0.2,
    )
    return response.choices[0].message.content
# 假设 history 是 [{"role": "user", "content": "..."}, ...]
# summary = summarize_history(history)

摘要方案的优势是信息密度高,一段200字的摘要可以替代几千字的原始对话,而且对下游模型友好,读起来像是一段背景说明。但它也有明显短板:摘要过程本身会消耗额外的大模型调用,每次摘要都需要等待生成,延迟较高;其次摘要是有损的,模型偶尔会自作主张地“归纳”出原始对话里并不存在的事实,尤其在处理数字时容易出现幻觉。所以在金融、医疗等对精确性要求高的场景,摘要不能单独作为唯一的记忆来源。

为了降低摘要延迟,可以把摘要任务交给更小更快的模型,或者采用异步策略:在用户没有发消息的空闲时间预先执行摘要。另外可以在摘要前后设置校验步骤,例如提取摘要中的数字与原始历史做简单比对,发现不一致就回退到保留更多原始上下文。

关键信息提取:只留下结构化字段

关键信息提取不追求把对话改写成段落,而是直接从原始消息中抽取离散的键值对。比如用户说“我预算5000块,要在国庆前拿到货,收货地址是杭州市西湖区文三路”,提取结果可能是{"budget": 5000, "deadline": "2024-10-01", "address": "杭州市西湖区文三路"}。这类结构化数据占用token极少,而且可以自动更新和覆盖,非常适合与数据库或变量存储结合。

实现方式可以从简单的正则表达式开始,逐步升级到NER模型或大模型函数调用。对于地址、手机号、身份证号这类有固定模式的信息,正则的准确率足够高且零延迟;对于用户意图、情感倾向等模糊信息,则更适合用大模型配合JSON输出约束。下面这段代码展示了正则与规则字典的配合:用一个字段配置表定义每种信息对应的关键词和正则表达式,扫描每轮消息时实时更新状态。

import re
FIELD_RULES = {
    "budget": {
        "keywords": ["预算", "多少钱", "价格"],
        "pattern": r"(\d{3,6})\s*(?:元|块|人民币)"
    },
    "deadline": {
        "keywords": ["之前", "截止", "最晚", "国庆前"],
        "pattern": r"(\d{4}-\d{1,2}-\d{1,2})"
    },
    "phone": {
        "keywords": ["电话", "手机"],
        "pattern": r"1[3-9]\d{9}"
    }
}
def extract_fields(user_text, current_fields):
    for field, rule in FIELD_RULES.items():
        if any(kw in user_text for kw in rule["keywords"]):
            match = re.search(rule["pattern"], user_text)
            if match:
                current_fields[field] = match.group(1)
    return current_fields
# 每次用户输入后调用,current_fields 会逐步积累和覆盖

规则提取的缺陷也很明显:无法理解语义变化。例如用户说“我不打算买5000块那个了,改要3000的”,如果规则只匹配第一个数字,就会错误地保留5000。此时需要加入否定判断和上下文覆盖逻辑,或者直接交给大模型做增量抽取。大模型函数调用可以把用户本轮消息和当前已有字段一起作为输入,输出需要更新的字段集合,有效处理修改和撤销操作。不过大模型提取的延迟比规则高,但通常低于全文摘要,因为它只输出少量结构化字段。

在实践中,可以把关键信息提取做成一个独立模块,每次用户消息进来时先执行规则快速提取,如果检测到否定词、转折词或规则覆盖冲突,再触发大模型提取作为兜底。这样既保证了常规输入的响应速度,又兜住了复杂语义。

回填上下文:让记忆真正生效

不管是摘要还是关键信息,提取出来的内容必须重新注入到发给大模型的上下文中,否则记忆模块形同虚设。一个常见的做法是在每次调用大模型前,把当前记忆内容格式化成一段前缀,拼接到系统提示词或消息列表里。对于摘要,可以直接把摘要文本作为一条历史消息放在最近对话之前;对于关键信息,则写成类似“当前已知信息如下:预算=5000,截止日期=2024-10-01”的固定句式。

这里有一个细节:摘要文本和关键信息字段不能无脑拼接,否则会造成信息重复或矛盾。比如原始历史中已经包含“预算5000”,而摘要里也写了“预算5000”,模型就可能困惑于哪个才是最新值。解决方式是在拼接前做一次去重和优先级排序:关键信息字段的优先级高于摘要文本,因为结构化字段是明确值;摘要文本中如果出现同一字段,应删除或标记为历史参考。下面这段代码展示了一个简单的上下文组装函数,将结构化记忆和摘要记忆分别放入系统提示词的不同区域。

def build_context(fields, summary, recent_messages):
    field_block = "\n".join(
        f"{key} = {value}" for key, value in fields.items()
    )
    system_prompt = (
        "你是购物助手。以下是已提取的用户需求信息,优先遵守:\n"
        f"{field_block}\n\n"
        "以下是历史对话摘要,仅作背景参考,若与上方字段冲突以字段为准:\n"
        f"{summary}\n\n"
        "请基于以上信息和最近对话回答用户。"
    )
    messages = [{"role": "system", "content": system_prompt}]
    messages.extend(recent_messages)
    return messages

回填时机也值得注意。如果每次请求都重新拼接全套记忆,当记忆内容很长时会增加首token延迟。可以采用增量更新:只在字段或摘要发生变化时重建系统提示词,其他时间直接用缓存。另外对于需要严格审计的对话,建议保留一份完整的原始历史在日志中,供事后回溯,而不是只保留压缩后的记忆。

最后要提醒的是,任何记忆机制都会带来新的失败模式。摘要可能歪曲事实,关键词提取可能漏掉重要约束,上下文回填可能引入矛盾信息。上线前应该针对自己的业务场景构造一组包含长对话、修改需求、否定表达、数字敏感信息的测试集,对比开启记忆前后的应答准确率和用户满意度,再决定摘要与提取的混合比例。

广告位 · 文章底部(后台-模块管理替换此区域)