基于load_qa_chain的QA聊天机器人对话历史超token限制求解
解决LangChain QA机器人对话历史触发Token超限的方案(不修改完整记忆)
方案1:动态按Token截断对话历史(保留完整记忆存储)
核心思路:内存中仍保留完整对话历史,但每次构建Prompt时,仅传入最近的、总Token数不超限制的对话片段,确保Prompt整体Token数在模型上限内。
实现步骤:
- 引入
tiktoken库计算Token数 - 自定义对话历史截断逻辑,在生成Prompt前对
chat_history进行处理
代码修改示例:
import tiktoken # 定义计算Token数的函数 def count_tokens(text, model_name="gpt-3.5-turbo"): encoding = tiktoken.encoding_for_model(model_name) return len(encoding.encode(text)) # 截断对话历史的函数 def truncate_chat_history(chat_history, max_available_tokens, model_name="gpt-3.5-turbo"): # 拆分对话历史为单个轮次 history_lines = chat_history.split("\n") truncated_history = [] current_token_count = 0 # 从后往前遍历,保留最近的对话 for line in reversed(history_lines): line_tokens = count_tokens(line, model_name) if current_token_count + line_tokens <= max_available_tokens: truncated_history.append(line) current_token_count += line_tokens else: break # 反转回原顺序 return "\n".join(reversed(truncated_history)) # 修改generate_response函数 def generate_response(query): docs = docsearch.similarity_search(query) # 获取完整对话历史 full_chat_history = memory.load_memory_variables({})["chat_history"] # 计算context的Token数 context_text = "\n".join([doc.page_content for doc in docs]) context_tokens = count_tokens(context_text) query_tokens = count_tokens(query) # 模型最大Token数减去context、query和预留的完成Token数 model_max_tokens = 4097 completion_reserve = 1000 # 对应chain里的max_tokens=1000 max_history_tokens = model_max_tokens - context_tokens - query_tokens - completion_reserve if max_history_tokens > 0: truncated_history = truncate_chat_history(full_chat_history, max_history_tokens) else: truncated_history = "" # 若空间不足,暂时清空历史 # 手动构建输入,传入截断后的历史 return chain({ "input_documents": docs, "human_input": query, "chat_history": truncated_history }, return_only_outputs=True)['output_text']
说明:此方法不会修改memory中存储的完整对话历史,仅在每次调用时动态截取合适长度的历史传入Prompt,既保留了完整记忆,又避免超限。
方案2:优化上下文文档检索(减少Context占用的Token)
核心思路:通过减少Prompt中context部分的Token消耗,为对话历史腾出更多空间,无需修改记忆存储。
具体操作:
- 调小文档拆分的
chunk_size:比如从1000改为500,减少单段文档的Token数 - 减少检索返回的文档数量:将
similarity_search(query)改为similarity_search(query, k=1),只返回最相关的1段文档 - 改用更精准的检索策略:比如使用
MMR检索(最大边际相关性),优先返回与query最相关且冗余度最低的文档
代码修改示例(减少返回文档数量):
def generate_response(query): # 只返回最相关的1个文档,减少context的Token数 docs = docsearch.similarity_search(query, k=1) return chain({"input_documents": docs, "human_input": query}, return_only_outputs=True)['output_text']
方案3:更换大上下文窗口模型
直接更换为支持更大上下文的模型(如gpt-3.5-turbo-16k、gpt-4等),从根本上提升Token上限,无需修改记忆或Prompt逻辑。
代码修改示例:
chain = load_qa_chain( # 更换为16k上下文的模型 OpenAI(model_name="gpt-3.5-turbo-16k", temperature=1, max_tokens=1000), chain_type="stuff", memory=memory, prompt=prompt )
方案4:使用对话摘要Buffer内存(保留完整历史,Prompt用摘要)
使用ConversationSummaryBufferMemory,它会同时存储完整对话历史和历史摘要:当对话历史Token数超过阈值时,自动将早期对话转换为摘要传入Prompt,而完整历史仍保存在内存中。
代码修改示例:
from langchain.memory import ConversationSummaryBufferMemory from langchain.chat_models import ChatOpenAI # 初始化摘要Buffer内存,设置Token阈值 memory = ConversationSummaryBufferMemory( memory_key="chat_history", input_key="human_input", llm=ChatOpenAI(temperature=0), # 用于生成摘要的模型 max_token_limit=1500 # 当历史超过1500Token时,自动生成摘要 ) # 后续chain初始化和生成逻辑不变 chain = load_qa_chain( OpenAI(temperature=1, max_tokens=1000), chain_type="stuff", memory=memory, prompt=prompt )
说明:此方法内存中始终保留完整对话历史,仅在Prompt中根据阈值自动替换为摘要,既满足“不修改记忆”的要求,又避免Token超限。
内容的提问来源于stack exchange,提问作者prime
相关产品推荐
相关产品推荐

