You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解决RAG应用中ChatGPT LLM仅返回PDF表格顶部数据的问题

问题解决:RAG查询PDF表格仅返回顶部记录的优化方案

问题背景

基于LangChain和ChatGPT开发的RAG应用,在查询PDF内表格结构数据时,LLM仅返回表格顶部的部分记录(如100多行表格仅返回前20行),调整超参数后无改善,需优化响应生成流程,提升表格查询的相关性与准确性。

核心问题分析

当前实现存在以下关键缺陷:

  1. PDF加载方式不当:使用TextLoader加载PDF文本,会将表格转为无结构的纯文本,丢失表格的行列结构信息,导致后续拆分和检索无法精准定位表格行。
  2. 文本拆分策略不匹配表格:RecursiveCharacterTextSplitter按固定字符长度拆分,容易将完整表格行拆分为多个chunk,检索时仅召回包含表格顶部的chunk,无法覆盖全部目标记录。
  3. 检索召回范围不足:默认search_kwargs={"k":3}仅召回3个chunk,对于大表格来说,无法覆盖所有相关行的chunk。
  4. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 15:23:21