
本文介绍在 LangChain 中通过预处理文档分块(而非运行时截断)来规避大模型上下文超限问题,重点解决 create_retrieval_chain 场景下因未切分原始文档导致 context_length_exceeded 错误的典型实践。
本文介绍在 langchain 中通过预处理文档分块(而非运行时截断)来规避大模型上下文超限问题,重点解决 `create_retrieval_chain` 场景下因未切分原始文档导致 `context_length_exceeded` 错误的典型实践。
在使用 LangChain 构建检索增强对话系统(RAG Chatbot)时,一个常见但易被忽视的关键问题是:文档入库方式直接影响检索链的上下文安全边界。如您所遇,即使使用了 ChatOpenAI 和 create_retrieval_chain 等现代链式 API,若原始文档以整篇形式(如数万字符的长文本)直接存入向量数据库(如 Qdrant),则 retriever.invoke() 返回的单个 Document 可能包含远超模型最大上下文(如 gpt-3.5-turbo 的 8192 tokens)的内容——这会导致后续 document_chain 渲染 prompt 时直接触发 OpenAI 的 context_length_exceeded 报错。
根本原因在于:LangChain 的 create_stuff_documents_chain 默认将所有检索到的 Document 的 page_content 拼接进
✅ 正确解法:在数据写入阶段即完成语义合理、长度可控的文本分块(Chunking),而非试图在检索或生成阶段“事后补救”。
以下是推荐的端到端实践方案:
1. 使用 RecursiveCharacterTextSplitter 替代基础 CharacterTextSplitter
虽然问题中使用了 CharacterTextSplitter,但生产环境更推荐 RecursiveCharacterTextSplitter——它能按优先级顺序(\n\n, \n, " ", "")智能切分,保留段落结构,避免生硬截断:
from langchain.text_splitter import RecursiveCharacterTextSplitter
def create_chunks(text: str, chunk_size: int = 800, chunk_overlap: int = 100) -> list[str]:
text_splitter = RecursiveCharacterTextSplitter(
separators=["\n\n", "\n", " ", ""],
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
length_function=len,
keep_separator=False,
strip_whitespace=True,
)
return text_splitter.split_text(text)
# 存储前分块
chunks = create_chunks(file_contents)
vector_ids = qdrant_collection.add_texts(chunks) # ✅ 不再 add_documents([Document(...)])
⚠️ 注意:add_texts() 接收字符串列表,内部自动创建 Document(page_content=chunk);避免手动构造含超长 page_content 的 Document 对象。
2. 配置检索器返回数量与相似度阈值
分块后,还需控制检索结果数量,防止过多 chunk 拼接仍超限:
retriever = existing_vector_store.as_retriever(
search_kwargs={
"k": 3, # 最多返回3个chunk(通常足够)
"score_threshold": 0.4, # 过滤低相关性结果(需Qdrant支持)
}
)
3. (可选)在 Prompt 中显式约束上下文长度
虽非强制,但可在 system prompt 中加入轻量提示,辅助 LLM 聚焦关键信息:
SYSTEM_TEMPLATE = """
你是一个精准、简洁的助手。请严格基于以下上下文回答问题,忽略无关内容。
若上下文未提供答案,请回答“我无法确定”。
<context>
{context}
</context>
"""
总结与最佳实践
- 核心原则:Token 限制是端到端工程问题,必须在 数据摄入(Ingestion)阶段解决,而非 推理(Inference)阶段修补。
- 分块策略建议:chunk_size=500–1000(对应约 150–300 tokens),overlap=100–200,平衡语义完整性与冗余度。
-
避免陷阱:
- ❌ 不要尝试在 RunnablePassthrough.assign(context=...) 中对 docs 做 truncate_by_token() —— LangChain 无内置 token 计数器,且 page_content 是字符串,需额外依赖 tiktoken 才能精确计算,易引入性能瓶颈与逻辑错误;
- ❌ 不要降级使用已废弃的 ConversationalRetrievalChain 或 ReduceDocumentsChain,它们与 ChatOpenAI 不兼容且维护成本高;
- ✅ 优先升级 langchain 至 >=0.1.16 后,可探索 ContextualCompressionRetriever + EmbeddingsFilter 等更高级压缩方案,但当前版本下分块是最稳定、最可控的方案。
通过前置分块,您不仅解决了 token 超限问题,还显著提升了检索精度(细粒度 chunk 更易匹配用户 query)和系统鲁棒性——这才是 RAG 架构设计的正确起点。











