一、 初识赛题------从迷茫到清晰
刚看到赛题时,坦白说有些不知所措。"多模态"、"RAG"、"图文混排PDF",这些词汇组合在一起,听起来就像一个庞大而复杂的工程。但当我强迫自己静下心来,从"终点"(提交格式和评审规则)倒推整个流程后,任务的核心瞬间清晰了。
我的顿悟:
-
这不是一个聊天机器人,而是一个"引经据典"的"学术助理"。 我们的最终目标不是生成一段华丽但空洞的回答,而是要为
test.json
里的每一个question
,精准地提供三样东西:answer
(答案)、filename
(来源文件)、page
(来源页码)。 -
"可溯源"是王道。 评审规则里,
filename
和page
的匹配度占了总分的50%!这意味着,一个内容完美的答案如果来源错误,得分会大打折扣。反之,一个内容尚可但来源精准的答案,也能拿到一半的基础分。这个发现直接决定了我的优化方向:溯源的准确性与答案内容的质量同等重要。 -
核心矛盾: 机器(特别是LLM)天生只懂文本,而我们的知识库(PDF)却是图文并茂的。如何将视觉信息(图表)转化成机器能理解的格式,并与文本信息建立联系,是这个赛题最核心的挑战。
基于以上理解,我将赛题的四大难点,用我自己的话重新梳理了一遍:
-
难点一(信息转化): 怎么让AI"看懂"图片和表格?尤其是财报里的各种业绩图、流程图,信息密度极高。
-
难点二(精准定位): 怎么在成堆的PDF中,像侦探一样快速找到包含答案线索的那一页、那一段?大海捞针,还不能捞错。
-
难点三(忠实回答): LLM很能"脑补",怎么给它戴上"紧箍咒",让它只根据我们找到的资料说话,别自由发挥?
-
难点四(协同优化): 如何平衡"答得对"和"找得准"这两个目标?我的整个系统设计必须同时为这两个指标服务。
二、 解剖Baseline------一个完整的起点
通读了Baseline方案后,我对其设计思路有了清晰的认识。它是一套经典的文本RAG流程,巧妙地绕过了最棘手的多模态问题。
Baseline流程的两大阶段:
-
离线预处理(构建知识库):
-
解析(Parse): 使用
fitz_parse_pdf.py
脚本,将所有PDF的纯文本内容按"页"为单位提取出来,保存成一个大的JSON文件 (all_pdf_page_chunks.json
)。 -
向量化与索引(Index): 在
rag_from_page_chunks.py
的setup()
函数中,加载上述JSON,调用Embedding模型(如BGE-M3)将每一页的文本内容转换成向量,然后存入一个简单的内存向量库(SimpleVectorStore
)。
-
-
在线推理(回答问题):
-
检索(Retrieve): 接收一个问题,同样将其向量化,然后在向量库中搜索最相似的Top-K个页面文本作为上下文。
-
生成(Generate): 将"问题"和"检索到的上下文"打包成一个精心设计的Prompt,喂给大语言模型(LLM,如Qwen),并指令它以JSON格式输出答案、来源文件和页码。
-
我对Baseline的评价:
-
优点: 逻辑清晰,端到端完整。它是一个"麻雀虽小,五脏俱全"的系统,让我能够快速跑通整个流程,建立对RAG的基本认知。模块化设计也为后续的优化提供了便利。
-
核心不足:
-
信息丢失严重:
PyMuPDF
(fitz
) 只提取文本,完全丢弃了所有图表、表格信息。这是它最大的短板,直接导致所有基于视觉信息的问题都无法回答。 -
分块策略粗糙: 按"页"分块,一页可能包含多个不相关的主题,也可能一个主题被分页符切断,这会严重影响检索的精度。
-
检索过于简单: 单纯的向量相似度搜索,容易引入噪音,干扰LLM的判断。
-
这个Baseline是一个绝佳的起点,它的不足之处,恰恰就是我们上分的突破口。
三、 我的上分之路------从Baseline到更高分的进阶方案
针对Baseline的不足,我设计了一个三步走的优化路线图,从易到难,逐步提升系统性能。
第一步:低成本快速优化 (Low-hanging Fruits)
这些改动不涉及复杂的模型训练,但能快速见效。
-
精调Prompt (Prompt Engineering):
-
目标: 降低幻觉,提高溯源准确性。
-
方案: 在
rag_from_page_chunks.py
中修改PROMPT_TEMPLATE
。-
增加严格指令: 明确要求LLM:"你必须严格依据提供的'上下文'作答。如果'上下文'中没有答案,请明确回答'根据现有信息无法回答'。你的答案必须从'上下文'中提供的某一个'chunk'中总结得出,并使用该'chunk'的
file_name
和page
作为来源。" -
引入Few-shot示例: 在Prompt中加入1-2个高质量的问答示例,向LLM展示理想的输入输出格式,特别是如何从多个上下文中选择最相关的一个作为来源。
-
-
-
优化分块策略 (Smarter Chunking):
-
目标: 提高检索内容的信噪比和完整性。
-
方案: 放弃按"页"分块。改用更智能的分块方法。
-
策略: 使用
langchain.text_splitter
中的RecursiveCharacterTextSplitter
,按段落、标题等递归地切分文本。 -
实现: 设置一个合理的块大小(
chunk_size
,例如512个字符)和重叠大小(chunk_overlap
,例如50个字符)。重叠可以保证语义的连续性,避免关键信息被切断。这将生成更小、更聚焦的知识块,大大提升检索精度。
-
-
第二步:攻克核心难点 (Tackling the Core Challenges)
这是拉开分数差距的关键一步,直接面对多模态和数据质量问题。
-
更换PDF解析引擎:从
PyMuPDF
到MinerU
-
目标: 实现真正的图文信息提取,这是整个方案从量变到质变的一步。
-
方案:
-
弃用
fitz_parse_pdf.py
,改用mineru_pipeline_all.py
(或自己基于MinerU编写脚本)。 -
效果: MinerU能进行版面分析,将PDF解析为包含标题、段落、表格(转为Markdown格式)、图片等多元素的结构化数据。现在,我的知识库里不仅有文本,还有了结构化的表格 和被识别出的图片。
-
-
-
"图片文本化":引入VLM生成图像描述 (Image-to-Text)
-
目标: 让系统理解图片的内容。
-
方案: 这是对
MinerU
提取出的图片信息的二次处理。-
工具: 调用一个强大的多模态大模型(VLM),如通义千问Qwen-VL。
-
流程: 遍历所有被
MinerU
提取出的图片,用Qwen-VL为其生成详细的文本描述。对于图表,Prompt可以更具引导性:"请详细描述这张图表。它的标题是什么?横轴和纵轴代表什么?数据的主要趋势和关键节点是什么?" -
融合: 将这些生成的"图片描述"作为新的文本块,与原始文本块一样,附上它们的元数据(文件名、页码),一起存入向量数据库。
-
-
成果: 经过这一步,我的RAG系统虽然底层仍然是文本检索,但知识库已经融合了图像信息,实现了"伪多模态",能够回答基于图表内容的问题了。
-
第三步:向高分榜冲刺 (Advanced Optimizations)
当基础框架搭建完毕,这些高级技巧可以进一步挖掘系统潜力。
-
智能检索:引入重排模型 (Re-ranking)
-
目标: 解决Top-K检索结果中噪音过多的问题,优中选优。
-
方案:
-
两阶段检索:
-
召回(Recall): 先用快速的向量搜索,召回一个较大的候选集,比如Top 20个知识块。
-
重排(Re-rank): 然后使用一个更强大的Cross-Encoder模型(如BGE-ReRanker),对这20个知识块与问题的相关性进行更精细的计算和排序,最终选出Top 3或Top 5个最相关的知识块交给LLM。
-
-
效果: 极大地提升了喂给LLM的上下文质量,是提升答案准确率的利器。
-
-
-
真·多模态:端到端多模态模型方案
-
目标: 让模型直接看图说话,避免"图片->文字"过程中的信息损失。
-
方案:
-
多路召回: 当问题输入后,不仅在文本向量库中搜索,如果问题可能与图像相关,也同时在图像向量库(用CLIP等模型构建)中搜索。
-
多模态生成: 将检索到的文本块 和原始图片文件一起作为上下文,提交给一个强大的多模态生成模型(如Qwen-VL-Max)。Prompt会变成:"请根据以下文本和图片,回答问题..."。
-
挑战: 这个方案对模型的选择和系统流程的设计要求更高,但它是解决此类问题的最前沿、最彻底的方案。
-
-
四、 总结与反思
这次比赛的学习过程,对我来说是一次非常宝贵的经历。我最大的收获是:
-
从终局思考: 永远先理解任务目标和评价标准,这会指引你所有技术选型的方向。
-
数据质量是生命线: Garbage in, garbage out。在RAG系统中,PDF解析的质量直接决定了知识库的天花板。在
MinerU
上的投入是性价比最高的。 -
迭代式开发: 不要妄想一步到位构建一个完美的系统。从一个能跑通的Baseline开始,通过分步优化,不断给它"打补丁"、"升级零件",才是最稳健的路径。
-
拥抱开源工具:
Xinference
,MinerU
,HuggingFace Transformers
... 无数强大的开源工具极大地降低了我们实现复杂系统的门槛。