如何在LangChain中使StuffDocumentsChain具备ConversationChain的对话记忆能力?
问题与解决方案:实现带对话记忆的StuffDocumentsChain行为
问题背景
想要使用StuffDocumentsChain并实现ConversationChain的对话记忆功能,但现有基于ConversationalRetrievalQAChain的代码无法满足需求。
当前代码
import fs from 'fs'; import path from 'path'; import { OpenAI } from "langchain/llms/openai"; import { RecursiveCharacterTextSplitter } from "langchain/text_splitter"; import { HNSWLib } from "langchain/vectorstores/hnswlib"; import { OpenAIEmbeddings } from "langchain/embeddings/openai"; import { ConversationalRetrievalQAChain } from "langchain/chains"; const model = new OpenAI({openAIApiKey: 'sk-...', modelName: 'gpt-3.5-turbo'}); const text = fs.readFileSync(path.resolve(__dirname, './data.txt'), 'utf-8'); const textSplitter = new RecursiveCharacterTextSplitter({ chunkSize: 1000 }); const docs = await textSplitter.createDocuments([text]); const vectorStore = await HNSWLib.fromDocuments(docs, new OpenAIEmbeddings( {openAIApiKey: 'sk-...', modelName: 'text-embedding-ada-002'} )); const chain = ConversationalRetrievalQAChain.fromLLM( model, vectorStore.asRetriever() ); const question = 'Hello my name is Archie'; const res = [] res.push(await chain.call({ question, chat_history: [] })) console.log(res[0]) res.push(await chain.call({ question: "What is my name?", chat_history: [question, res[0].text] })) console.log(res[1]);
当前输出
{ text: 'That is not a question, but nice to meet you Archie!' } { text: "I'm sorry, but I cannot provide you with an answer as the given context does not contain any relevant information to your question." }
期望输出
{ text: 'Hi Archie, nice to meet you! How can I assist you today?' } { text: "Your name is Archie, as you mentioned earlier. Is there anything else you would like to know or discuss?" }
解决方案
核心问题分析
当前代码失效的原因有两个:
- 对话历史格式错误:
ConversationalRetrievalQAChain要求chat_history是对话对数组(每一项是[用户提问, AI回答]的数组),而非扁平的文本列表。 - 默认提示模板限制:默认提示会优先从检索到的文档中找答案,忽略对话历史里的信息,导致模型无法识别用户之前提到的名字。
修改后的代码
import fs from 'fs'; import path from 'path'; import { OpenAI } from "langchain/llms/openai"; import { RecursiveCharacterTextSplitter } from "langchain/text_splitter"; import { HNSWLib } from "langchain/vectorstores/hnswlib"; import { OpenAIEmbeddings } from "langchain/embeddings/openai"; import { ConversationalRetrievalQAChain } from "langchain/chains"; import { PromptTemplate } from "langchain/prompts"; const model = new OpenAI({openAIApiKey: 'sk-...', modelName: 'gpt-3.5-turbo', temperature: 0.7}); const text = fs.readFileSync(path.resolve(__dirname, './data.txt'), 'utf-8'); const textSplitter = new RecursiveCharacterTextSplitter({ chunkSize: 1000 }); const docs = await textSplitter.createDocuments([text]); const vectorStore = await HNSWLib.fromDocuments(docs, new OpenAIEmbeddings( {openAIApiKey: 'sk-...', modelName: 'text-embedding-ada-002'} )); // 自定义提示模板:优先参考对话历史,再结合文档内容 const customPrompt = PromptTemplate.fromTemplate(` 给定以下对话历史和一个新的问题,先从对话历史中寻找相关信息回答问题;如果对话历史没有相关内容,再结合提供的文档上下文回答。 对话历史:{chat_history} 文档上下文:{context} 新问题:{question} 请给出友好、自然的回答: `); const chain = ConversationalRetrievalQAChain.fromLLM( model, vectorStore.asRetriever(), { qaChainOptions: { prompt: customPrompt, }, returnSourceDocuments: false } ); const question = 'Hello my name is Archie'; const res = [] // 第一次调用:对话历史为空 res.push(await chain.call({ question, chat_history: [] })) console.log(res[0]) // 第二次调用:使用对话对格式的历史记录 res.push(await chain.call({ question: "What is my name?", chat_history: [[question, res[0].text]] })) console.log(res[1]);
关键修改点
- 修正对话历史格式:将
chat_history改为[[用户提问, AI回答]]的二维数组格式,让模型正确识别对话上下文。 - 自定义提示模板:明确告知模型优先从对话历史中提取信息,再使用文档内容,避免模型忽略对话记忆。
- 调整模型温度:设置
temperature: 0.7让回答更自然友好,接近期望输出的语气。
若需明确使用StuffDocumentsChain
如果要直接使用StuffDocumentsChain结合对话记忆,可以手动组合链结构:
// 额外导入需要的类 import { StuffDocumentsChain } from "langchain/chains"; import { ConversationChain } from "langchain/chains"; import { BufferMemory } from "langchain/memory"; // 创建StuffDocumentsChain用于文档处理 const docChain = new StuffDocumentsChain({ llm: model, documentPrompt: PromptTemplate.fromTemplate("文档内容:{pageContent}"), documentSeparator: "\n\n", // 自定义文档处理的提示 combineDocumentsPrompt: PromptTemplate.fromTemplate(` 结合以下文档内容和对话历史回答问题: 对话历史:{chat_history} 文档:{context} 问题:{question} 回答: `), }); // 创建带对话记忆的ConversationChain const conversationChain = new ConversationChain({ llm: model, memory: new BufferMemory({ memoryKey: "chat_history" }), }); // 组合使用:先通过对话记忆获取历史,再传入StuffDocumentsChain const answer1 = await conversationChain.call({ input: question }); console.log(answer1); // 处理第二个问题时,先获取对话历史,再结合文档 const chatHistory = conversationChain.memory.chatHistory.messages.map(msg => `${msg.type === 'human' ? '用户' : 'AI'}: ${msg.text}`).join('\n'); const answer2 = await docChain.call({ question: "What is my name?", chat_history: chatHistory, input_documents: docs, // 传入处理后的文档 }); console.log(answer2);
内容的提问来源于stack exchange,提问作者Arsenius
相关产品推荐
相关产品推荐

