问题与目标
用户先问“AX-3 何时离线”,随后追问“它什么时候恢复”。直接检索第二句时,“它”没有明确对象;把全部历史原样塞进检索又会引入旧答案和无关聊天。
本篇把当前追问与必要历史改写成独立检索查询,再复用两步式 RAG。目标是不同会话互不串线,历史有长度上限,旧的模型回答不会被当作知识库事实。
核心概念
多轮 RAG 至少维护三类数据:
- 原始消息:用户与助手看见的对话,用于界面和审计;
- 检索查询:只为召回而生成的独立表达;
- 知识证据:本轮检索返回的 Document,是回答事实的依据。
问题改写可以使用模型,但输出只能改变表达,不能生成答案或加入历史中不存在的实体。会话存储必须用已认证的 user_id + thread_id 隔离,不能只信任客户端传来的线程编号。
可运行实现
先用确定性代码演示最小改写和会话窗口:
from collections import defaultdict, deque
histories = defaultdict(lambda: deque(maxlen=6))
def rewrite(question: str, history: list[dict]) -> str:
pronouns = {"它", "这个设备", "该设备"}
if not any(word in question for word in pronouns):
return question
entities = [
token.strip(",。?!")
for message in reversed(history)
if message["role"] == "user"
for token in message["content"].split()
if token.startswith("AX-")
]
if not entities:
return question
return question.replace("它", entities[0]).replace("该设备", entities[0])
def independent_query(user_id: str, thread_id: str, question: str) -> str:
key = (user_id, thread_id)
query = rewrite(question, list(histories[key]))
histories[key].append({"role": "user", "content": question})
return query
print(independent_query("u-17", "t-1", "AX-3 何时离线"))
print(independent_query("u-17", "t-1", "它什么时候恢复"))
print(independent_query("u-18", "t-1", "它什么时候恢复"))
真实模型改写 Prompt 应要求只返回独立查询,并允许在指代无法确定时返回 NEED_CLARIFICATION。随后流程固定为:
standalone = rewrite_chain.invoke({
"history": selected_history,
"question": current_question,
})
docs = retriever.invoke(standalone)
answer = answer_chain.invoke({
"question": current_question,
"context": build_context(docs),
})
历史过长时,优先保留最近消息和结构化实体状态。摘要只能帮助理解会话,不能替代知识库证据;摘要本身也要带版本并可重新生成。
常见问题与排查
把助手上一轮答案放进本轮证据
助手答案可能有误。它只能用于理解指代,回答事实仍必须来自本轮检索到的授权知识片段。
不同用户共用同一个 thread_id
服务端按认证主体构造复合键,并在读写时校验所有权。缓存、数据库和追踪系统都要使用相同隔离规则。
每轮都把完整历史发给模型
设置消息数或 Token 上限,选择与当前问题相关的轮次,再对更早内容做结构化摘要。记录被裁剪范围,避免无法复盘。
改写结果变成了答案
校验输出长度、是否包含问句意图和已知实体;固定测试“它、那里、那个错误”以及歧义问题。无法确定时向用户澄清,不要猜实体。
小结
多轮 RAG 不是简单追加聊天记录,而是把会话理解和事实检索分开:历史帮助消解指代,本轮知识片段负责支撑答案。会话隔离、历史裁剪和不可确定时澄清,是多轮能力可靠运行的前提。
License: CC BY-NC 4.0
Updated 2 hours ago
Was this article helpful? Give it a like.
0 comments


