LangChain中ChatOpenAI结合RetrievalQA多提问触发Token超限问题
问题
我正在用LangChain构建一个基于给定文档回答问题的简单应用,以下是可正常运行的概念验证代码:
from langchain_openai import ChatOpenAI from langchain.vectorstores.chroma import Chroma from langchain_openai import OpenAIEmbeddings from langchain.chains import RetrievalQA embedding_client = OpenAIEmbeddings(openai_api_key) embeddings = Chroma.from_texts([document_text], embedding_client) llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0, max_tokens=4096) retriever = RetrievalQA.from_chain_type(llm, retriever=embeddings.as_retriever()) answer = retriever({"query": question})
但循环提问时,第二次或第三次后会收到OpenAI的context_length_exceeded错误,哪怕每次循环都重新实例化对象、提问相同问题:
openai.BadRequestError: Error code: 400 - {'error': {'message': "This model's maximum context length is 16385 tokens. However, your messages resulted in 17725 tokens. Please reduce the length of the messages.", 'type': 'invalid_request_error', 'param': 'messages', 'code': 'context_length_exceeded'}
我怀疑ChatOpenAI底层调用复用了聊天历史或文档内容导致问题,调整token限制解决不了核心问题。本以为每次提问新建ChatOpenAI实例就能解决,希望有人解释原因,并提供无需复杂修改的解决方案(无需保留聊天历史)。
原因分析
核心问题并非ChatOpenAI实例复用聊天历史,而是RetrievalQA默认的stuff链类型逻辑:该类型会将检索到的所有文档内容直接拼接进prompt,若document_text本身token量接近gpt-3.5-turbo的16385上限,即使每次新建ChatOpenAI实例,单次请求中prompt(含文档内容+问题+模板固定内容)的总token量也会超过限制。
另外,若循环内未正确管理Chroma向量存储(比如重复添加相同文档),会导致检索返回重复内容,进一步叠加token数量,加速触发超限错误。
解决方案
切换链类型:改用
map_reduce或refine链类型,这类链会分批次处理文档内容,避免一次性将所有文档塞进prompt:# 循环内创建RetrievalQA时指定链类型 retriever = RetrievalQA.from_chain_type( llm=llm, retriever=embeddings.as_retriever(), chain_type="map_reduce" # 可选"refine" )限制检索文档数量:通过
search_kwargs减少每次检索返回的文档数,降低prompt内容长度:# 只返回最相关的2条文档 retrieval_retriever = embeddings.as_retriever(search_kwargs={"k": 2}) retriever = RetrievalQA.from_chain_type(llm=llm, retriever=retrieval_retriever)自定义无历史prompt模板:明确使用仅包含当前问题和检索文档的模板,彻底杜绝历史内容残留:
from langchain.prompts import PromptTemplate # 自定义prompt,仅保留必要内容 prompt = PromptTemplate( template="基于以下文档回答问题:\n{context}\n\n问题:{question}\n回答:", input_variables=["context", "question"] ) retriever = RetrievalQA.from_chain_type( llm=llm, retriever=embeddings.as_retriever(), chain_type_kwargs={"prompt": prompt} )
内容的提问来源于stack exchange,提问作者Topazoo
相关产品推荐
相关产品推荐

