目录
前面的 RAG 示例都是单轮问答。
但真实使用时,用户经常会追问:
Python 函数式编程讲了什么?
上一轮提到的高阶函数,适合初学者先理解哪些?
第二个问题里的“上一轮”就是上下文引用。如果系统没有记忆,就很难知道用户在问什么。
这一篇就做一个最小多轮 RAG。
本文对应代码位于 code/langchain-demo/chapter09。
本篇目标
第九篇要做两件事:
- 保存历史对话消息。
- 每一轮都根据当前问题检索资料,并把历史和上下文一起交给模型。
完整流程如下:
第 1 轮问题
-> 检索上下文
-> 生成回答
-> 保存 HumanMessage / AIMessage
第 2 轮问题
-> 读取历史消息
-> 检索当前问题上下文
-> 生成回答
-> 继续保存历史
这一篇先用内存里的 list 保存历史,不引入数据库。
MessagesPlaceholder
LangChain 里可以用 MessagesPlaceholder 把历史消息插入 Prompt:
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"你是一个本地博客问答助手。"
"回答时要结合历史对话和当前检索上下文。"
"如果上下文不足,就明确说明。",
),
MessagesPlaceholder("history"),
(
"human",
"当前问题:{question}\n\n"
"当前检索上下文:\n{context}\n\n"
"请用中文回答,并给出参考来源。",
),
]
)
这里的 history 不是普通字符串,而是一组消息:
history: list[BaseMessage] = []
每轮回答后,把用户问题和模型回答都追加进去:
history.append(HumanMessage(content=question))
history.append(AIMessage(content=answer))
这样下一轮调用时,模型就能看到前面的对话内容。
每轮都要检索
多轮对话里有一个容易忽略的问题:有了历史,不代表就不需要检索。
历史消息解决的是“用户上下文”。
RAG 检索解决的是“知识上下文”。
所以每一轮仍然会执行:
relevant_chunks = retrieve(question, chunks, top_k=4)
然后把检索结果放进 prompt:
answer = chain.invoke(
{
"history": history,
"question": question,
"context": format_context(relevant_chunks),
}
)
这样模型既能知道用户上一轮问了什么,也能看到当前问题对应的文章片段。
默认对话
第九篇写了两个默认问题:
DEFAULT_QUESTIONS = [
"Python 函数式编程讲了什么?",
"上一轮提到的高阶函数,适合初学者先理解哪些?",
]
第一轮是完整问题。
第二轮则故意写成追问,依赖上一轮回答里的“高阶函数”。
这能直观看到 memory 的作用。
运行 demo
在 code/langchain-demo 目录下运行:
uv run python -m chapter09.memory_rag
输出会按轮次打印:
Turn 1
Question:
Python 函数式编程讲了什么?
Sources:
...
Answer:
...
Turn 2
Question:
上一轮提到的高阶函数,适合初学者先理解哪些?
Sources:
...
Answer:
...
第二轮回答时,模型会结合第一轮对话,知道“上一轮提到的高阶函数”指的是 Python 函数式编程里的 map、reduce、filter、sorted 这些内容。
也可以传入自己的第一轮问题:
uv run python -m chapter09.memory_rag "FFmpeg 硬解码相关内容有哪些?"
程序会自动追加一个第二轮追问。
这种 memory 有什么限制
这一篇的 memory 很简单,只是一个 Python list。
它有几个限制:
- 程序结束后历史就没了。
- 对话很长时,历史消息会占用越来越多上下文。
- 没有摘要压缩。
- 没有按用户或会话保存。
不过作为入门学习,它已经足够说明一个重点:
多轮对话 = 历史消息 + 当前问题 + 当前检索上下文
后面做真实应用时,可以把 history 保存到数据库,也可以在历史太长时做摘要。
小结
这一篇补上了多轮对话能力。
到这里,本地博客问答已经可以处理追问,不再是每次都从零开始。
下一篇会把前面这些能力收束成一个简单 CLI,让它更像一个可以日常使用的小工具。
参考
code/langchain-demo/chapter09- LangChain
MessagesPlaceholder - LangChain
HumanMessage - LangChain
AIMessage
原创文章,转载请注明来源: LangChain 入门学习第九篇-多轮对话和上下文记忆
相关文章
- LangChain 入门学习第八篇-带工具的博客问答 Agent
- LangChain 入门学习第七篇-RAG 检索质量优化
- LangChain 入门学习第六篇-持久化索引和本地知识库问答
- LangChain 入门学习第五篇-使用本地 Embeddings 实现向量检索 RAG
- LangChain 入门学习第四篇-基于本地 Markdown 的 RAG 问答
- LangChain 入门学习第三篇-工具调用和 Agent
- LangChain 入门学习第二篇-PromptTemplate 和结构化输出
- LangChain 入门学习第一篇-环境准备和模型调用
- 使用 Cursor 进行 AI 编程的年度总结
- 真的,AI 可能就是新时代的信息差