如何解决RAG应用中ChatGPT LLM仅返回PDF表格顶部数据的问题
问题解决:RAG查询PDF表格仅返回顶部记录的优化方案
问题背景
基于LangChain和ChatGPT开发的RAG应用,在查询PDF内表格结构数据时,LLM仅返回表格顶部的部分记录(如100多行表格仅返回前20行),调整超参数后无改善,需优化响应生成流程,提升表格查询的相关性与准确性。
核心问题分析
当前实现存在以下关键缺陷:
- PDF加载方式不当:使用
TextLoader加载PDF文本,会将表格转为无结构的纯文本,丢失表格的行列结构信息,导致后续拆分和检索无法精准定位表格行。 - 文本拆分策略不匹配表格:
RecursiveCharacterTextSplitter按固定字符长度拆分,容易将完整表格行拆分为多个chunk,检索时仅召回包含表格顶部的chunk,无法覆盖全部目标记录。 - 检索召回范围不足:默认
search_kwargs={"k":3}仅召回3个chunk,对于大表格来说,无法覆盖所有相关行的chunk。 - LLM上下文与提示限制:未明确提示LLM需返回全部匹配记录,且基础模型的上下文窗口可能无法容纳完整表格内容,导致截断输出。
技术改进建议与代码优化
1. 替换为表格友好的PDF加载器
使用支持表格识别的加载器,保留表格的结构化信息(部分加载器可将表格转为Markdown格式):
from langchain.document_loaders import UnstructuredPDFLoader def load_docs(self, file_path): # 替换TextLoader为UnstructuredPDFLoader,支持表格元素识别 loader = UnstructuredPDFLoader(file_path, mode="elements") docs = loader.load() return docs
2. 调整文本拆分策略适配表格
针对表格类文档,使用MarkdownHeaderTextSplitter(若表格转为Markdown)或按段落/表格元素拆分,避免破坏表格结构:
from langchain.text_splitter import MarkdownHeaderTextSplitter def create_chain(self, chunked_dir): # ... 省略文件加载部分 ... # 按Markdown表头拆分,确保表格作为完整单元或按行拆分 headers_to_split_on = [ ("#", "Header 1"), ("##", "Header 2"), ("###", "Header 3"), ] text_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on) chunks = [] for doc in list_of_all_docs: splits = text_splitter.split_text(doc.page_content) chunks.extend(splits)
3. 增强检索召回能力
- 提高检索召回数量(
k值),确保覆盖更多表格相关chunk - 使用MMR检索策略,平衡相关性与多样性,避免仅召回顶部相似chunk
# 创建向量数据库后,调整retriever参数 db = Chroma.from_documents(chunks, embeddings) # 使用MMR检索,k值调整为8-10,扩大召回范围 retriever = db.as_retriever( search_type="mmr", search_kwargs={"k": 8, "fetch_k": 15} )
4. 优化LLM提示与模型选择
- 自定义提示词,明确要求LLM返回所有匹配的表格记录,禁止截断
- 使用大上下文窗口模型(如
gpt-4-turbo)容纳更多表格内容
from langchain.prompts import PromptTemplate # 自定义检索问答提示词,强制返回完整匹配记录 prompt_template = """基于以下提供的文档内容,回答用户的问题。 注意:如果问题涉及表格数据,请返回**所有匹配的完整记录**,不要截断任何行。 文档内容: {context} 用户问题:{question} 回答:""" PROMPT = PromptTemplate( template=prompt_template, input_variables=["context", "question"] ) # 使用大上下文窗口模型,避免内容截断 chain = ConversationalRetrievalChain.from_llm( ChatOpenAI(model_name="gpt-4-turbo", temperature=0.3), retriever=retriever, return_source_documents=True, combine_docs_chain_kwargs={"prompt": PROMPT} )
5. 优化链的复用性
避免每次查询都重新创建链,将create_chain改为初始化时执行或缓存:
class PdfQA(): def __init__(self, chunked_dir): self.open_api_key = os.getenv('OPENAI_API_KEY') # 初始化时创建链,避免重复构建向量库与链 self.chain = self.create_chain(chunked_dir) # ... 省略其他方法 ... def get_response_from_query(self, query, chat_history): # 直接使用已创建的链处理查询 result = self.chain({"question": query, "chat_history":chat_history}, return_only_outputs=True) result['question']= query result['chat_history']= chat_history return result
额外优化点
- 若表格数据量极大,可将表格提取为结构化数据(如CSV/JSON)单独存储,查询时直接检索结构化数据,再让LLM格式化输出
- 添加元数据过滤:加载文档时为表格内容添加元数据(如表格标题、页码),检索时可通过元数据过滤特定表格,提升精准性
内容的提问来源于stack exchange,提问作者Sk Raina
相关产品推荐
相关产品推荐

