
把一份五十页的年度财报丢给大模型让它准确说出“去年第四季度毛利率是多少比第三季度变化了多少”你会得到什么如果直接问通用大模型它大概率会给你一个看似合理、其实是编出来的数字。这正是我想通过 Financial RAG Demo 解决的核心问题用一条完整的RAG服务链路把金融文档变成可问答、可溯源、可验证的“本地知识库”。这篇内容会完整拆解这个Demo从文档解析、切片、向量检索到生成回答的整个设计与实操过程适合正在做知识库问答、金融NLP场景或者刚接触RAG应用开发的同学参考。金融场景对RAG的要求和通用文档问答不太一样数据密度高、术语密集、表格多、对数字准确性要求极高。整个Demo做完我最大的感受是RAG服务的难点不在“接入大模型”而在前面的数据准备和检索质量。链路中任何一个环节偷懒最后都会在回答质量上暴露出来。下面直接进入正题按我实际开发的顺序把所有关键技术决策和踩坑经验逐一讲清楚。1. 为什么做Financial RAG Demo金融场景对RAG服务的特殊要求1.1 金融文档问答难在哪里金融领域的文档管理是个典型的海量数据场景。上市公司的年报、季报、临时公告、研报、监管政策文件动辄几十上百页PDF格式居多内部还塞满了财务报表、管理层讨论、脚注注释。过去这些文档的利用方式基本就是“存起来、搜出来、人肉看”想要跨文档得到一个综合性的答案效率非常低。我把这个项目定位在“金融文档问答”而不是泛泛的“聊天机器人”就是因为金融问答有几个很难绕开的痛点。第一个痛点是术语密集。像“毛利率”“净资产收益率”“经营活动产生的现金流量净额”这种表述一个概念有多种叫法缩写和全称混用。通用问答系统很难理解这些术语之间的关联检索时容易出现“关键词命中了但拿到的段落根本不是用户想问的那个指标”。第二个痛点是数字敏感。金融问答最怕胡说一个人问“去年研发投入多少钱”如果系统回答里数字跟真实财报对不上这个系统基本就失去信任了。数字对错的根源往往不在大模型而在检索阶段有没有把正确的表格和上下文段落召回来。RAG服务如果不针对表格做专门处理检索到的内容经常是残缺的。第三个痛点是来源必须可溯。“根据哪份报告、哪一页得出这个结论”在金融场景几乎是刚需。基金经理、风控人员、研究员拿到一个AI回答第一反应就是去看原始依据。没有引用来源的答案在金融场景等于无效答案。第四个痛点是时效性。财报是定期发布的公告是实时增加的模型固有的知识永远滞后。RAG天然适合这种“知识库持续变化、需要即时更新”的场景换数据源就能换知识不需要重新训练。1.2 为什么是RAG而不是微调大模型在做这个Demo之前一个绕不开的问题是为什么不用微调的方式把财报内容直接喂给大模型训练我当时把两种方案放在一起对比过结论非常明确。对比维度RAG方案全量微调方案知识更新成本换索引、加文档即可分钟级生效需要重新训练小时到天级成本高输出可信度回答基于检索到的原文片段可溯源知识固化在参数里无法定位证据幻觉控制能力可在提示词层强约束“只能依据上下文”模型容易把训练知识与上下文混杂实施成本无需训练GPU开发成本集中在检索链路需要训练环境和高质量标注数据适合场景动态知识库、专有文档、持续更新的资料固定领域语料的指令遵循能力提升微调解决的是“让模型更懂这个领域的说话方式”而不是“让模型记住这份文档里写了什么”。你把一份三千行的财报PDF塞进微调数据集训练完之后你问它其中某个具体数字它依然可能编造。因为大模型的学习本质是压缩和泛化不是精确存储。RAG的思路完全不同我不要求模型记住文档内容我只要求它学会“基于给定的材料回答问题”。知识存储在检索系统里模型负责组织和表达。这一层解耦是金融场景格外看重的因为文档是流动的、权限是分级的、答案是必须能翻到原始页面的。1.3 Demo项目的目标与边界一个Demo不能什么都做否则会变成“什么都很糙的半成品”。我在动手之前先划定了边界明确这个Financial RAG Demo要证明三件事一是能对结构化程度低的金融PDF完成高质量解析与切片二是能准确检索到包含关键数字和对比信息的片段三是能基于检索内容生成带来源引用的可读回答。演示数据源我选的是公开的模拟财报PDF、模拟公司公告和模拟行业研报一共准备了十几份。为什么不直接用真实财务数据一是合规风险二是不想在Demo阶段花大量时间处理数据授权问题。模拟数据已经足够把链路跑通、把问题暴露出来。这个项目的定位是“最小可用的金融问答参考实现”不是生产级系统。在这个定位下我要的是快速验证方案、沉淀可复用的工程代码而不是一开始就去追求过高的准确率。2. RAG链路逐个拆从金融PDF到可信答案2.1 文档解析金融PDF结构化处理不能只做文本抽取RAG服务的上游是文档解析。很多人一开始觉得解析很简单不就是把PDF里的文字提取出来嘛。金融PDF分分钟教你做人。我遇到的第一个坑是表格。财报的利润表、资产负债表、现金流量表全是表格。如果用最简单的文本抽取工具直接提取表格会变成一行行没有任何结构逻辑的文字堆叠数字和项目名称的对应关系全部丢失。比如“营业收入 1023456789”和“营业成本 987654321”在一张表里提取成纯文本后你根本不知道哪个数字对应哪个指标。要解决这个问题必须使用能识别表格结构边界、能按单元格关系输出的解析流程。我实际用的方案是两层处理先用布局分析方式识别页面中的文本块和表格区域再对表格区域单独走表格结构识别逻辑尝试还原出行列关系。解析完成后每条内容都带上坐标信息、所在页码和所属章节标题这一步为后面的切片和引用打基础。页眉页脚也是必须去掉的不然每页重复的“某某公司2023年年度报告”字样会被切进多个切片检索时严重干扰相关性排序。这里有一个容易被忽视的点解析结果不要直接扔给切片器而是要转成统一的中间结构。我给每个文档块设计了一个简单的元数据模型包括doc_id、page、section、content、type文本/表格/图表标题字段。后面所有环节都依赖这套元数据作用类似给每块内容贴上了身份证检索到之后能回答“这段出自哪里”。2.2 切片策略保留语义、保住上下文、还要留得住表格切片是RAG里看似简单、实际影响非常大的环节。切片太粗检索粒度大相关性漂移严重一个大片段里只含一小段有效信息浪费向量空间切片太细语义不完整一个财务指标的定义被切断或者表格的表头和数据被拆到两个切片里检索到了也没法用。我测试过三种切片方式结果差异非常明显切片方式优点缺点适合场景固定长度切片如每512字一刀实现简单索引稳定切断段落语义表格极易拆散纯文本资料按章节/标题切片语义完整边界自然章节长度差异大长章节片段过大结构化良好的文档自定义语义切片标题段落表格保护兼顾语义与检索精度需要额外开发规则金融财报、公告我最终用的是第三种核心逻辑很简单先把文档按标题层级切成大的区块再对超过长度阈值的区块按段落边界二次切分同时加了一个保护规则——表格区域凡是检测到表头和首行数据的就整个作为一个独立切片不允许切散。切片之间加少量重叠窗口避免把一个概念的结尾和下一个概念的开头隔开。重叠窗口大小我设了64个字符左右。这个数值不是拍脑袋定的是通过检索召回率对比得出来的。太小起不到衔接作用太大会让相邻切片高度相似检索结果里出现四五条高度重复的内容白白占用上下文窗口。2.3 Embedding与检索混合检索加排序为什么是标配这个Demo的检索模块没有只依赖向量相似度而是用了“稀疏检索稠密检索”的混合方案。原因很现实向量检索擅长处理语义相近但字面不同的表达比如“公司赚了多少钱”和“净利润”的语义关联但在金融场景关键词精确匹配依然非常重要尤其是年份、股票代码、专有指标名这种字符特征强烈的查询稀疏检索的精确命中率往往高于向量召回。我使用的Embedding特征不是自己训练的而是选择了在通用语料上预训练好的模型来生成向量。选择模型时主要看两方面一是对中文长文档的支持二是金融术语的理解能力。实测下来通用Embedding模型对“ROE”“经营性现金流”这类术语的字面匹配还行但有时会抓不住“股东回报能力”这种同义表达。当时时间有限没有自训练模型但我在检索层做了补偿加入BM25稀疏召回再用重排序模型把两路结果合并打分这样即使向量语义召回没抓住精确关键词BM25也能补位反之亦然。混合检索的合并我用的是RRF算法对两路候选按排名融合而不是简单加权平均分数。因为不同检索算法产出的分数区间差异很大直接加权容易被某一方带偏。排序阶段再通过一个轻量级的重排序模型对召回的Top 20段重新打分取Top 5作为上下文。这一步的效果立竿见影Top 5命中率明显提升冗余内容也被滤掉不少。2.4 生成增强提示词约束、来源引用与拒答机制检索做得再好生成环节设计不到位回答质量同样拉跨。我在提示词层面做了三个重要设计。第一个是上下文约束。原原本本在系统提示词里写清楚“只根据下面提供的资料回答问题。如果资料未提及直接回答‘未在现有资料中找到相关信息’严禁编造。”这是对抗幻觉的第一道防线虽然不能完全消除幻觉但能大幅降低模型自由发挥的概率。第二个是来源标注。我要求模型在回答的每个关键结论后用中括号标注资料编号例如“[资料1][资料3]”。这些编号对应检索结果的元数据最终在展示层映射回“报告名称页码”。用户看到数字想核实只要照着页码翻原文就行。这是金融场景必须有的能力。第三个是拒答机制。对于检索相关度不够高的提问我设置了一个门槛逻辑如果重排序后的最高分数低于设定阈值或者上下文与问题的关键词重叠度太低服务端就直接返回“当前资料库中没有覆盖到您的问题”。宁可说不知道也不硬答。这个思路在可靠性要求高的场景非常值得推广。3. 实操记录我如何搭起这个RAG服务3.1 技术选型与环境准备技术选型只有一个原则能用最简方案搞定的就不引入重型组件。整套RAG服务验证下来我的核心组成是一个PDF解析模块负责布局分析和文本抽取、一个向量化模块负责将切片转为向量、一个向量数据库负责存储和召回、一个搜索引擎做BM25稀疏检索、一个重排序模型负责精排以及一个大模型服务负责生成最终答案。为什么向量数据库我选了本地部署而不是云服务因为这个Demo涉及金融资料处理数据不出本地是更稳妥的姿态。实际选型时看重的点也不复杂是否支持过滤条件、是否有稳定易用的客户端、查询延迟能不能控制在几百毫秒以内。开源方案完全够用。大模型这一层我同时测试了本地部署的开源轻量模型和商业API两种方案。Demo阶段为了效果展示更稳定最终选择了API方式因为显卡资源有限。但代码层面我把模型服务封装成了可替换的接口后期如果要在纯内网环境部署只需要把接口实现换成本地模型其他逻辑完全不用动。这一点对金融项目尤其重要因为生产环境很可能不允许调用外部服务。3.2 离线索引构建的完整流程索引构建是RAG服务的离线环节我刚才梳理了一下完整流程大概是这样的读取PDF文件逐页做布局分析识别正文、表格区域和页眉页脚对表格区域走表格结构还原逻辑整理成带有行列信息的Markdown格式文本将每页内容按“章节切片区块切分表格保护”的规则切分同时记录元数据对每个切片做清洗去掉空白字符修正编码问题调用Embedding接口生成向量将切片文本、元数据、向量全部写入向量数据库同时将文本写入稀疏检索引擎构建完成后做一个抽样自检把几个典型问题对应的切片捞出来确认内容是否完整。清洗这一步在代码里必须做好否则后面检索会出现各种奇怪问题金融文本里常见的全角半角混用、制表符残留、编号列表的缩进符号都需要在这一步抹平。索引构建完成后我建议加个自动统计脚本输出切片总数、平均长度、表格切片数量等指标。这一步能帮你迅速发现解析环节有没有出问题。我第一次跑完发现表格切片数量异常少回去排查才发现表格识别逻辑对某些样式的表格失效了这个脚本直接帮我定位了问题非常实用。3.3 在线问答服务的关键代码实现在线问答服务我拆成了三个模块Query理解、检索召回、生成回答。以下是我核心链路简化后的实现逻辑# 检索模块混合召回 RRF融合 重排序 def retrieve_documents(query: str, top_k: int 20, final_k: int 5): # 1. 稀疏召回BM25 sparse_results sparse_searcher.search(query, top_k) # 2. 稠密召回向量 query_vector embedding_service.embed(query) dense_results vector_db.search(query_vector, top_k) # 3. RRF融合两路结果 fused rrf_fusion(sparse_results, dense_results) # 4. 用重排序模型做精排取Top K reranked rerank_service.rerank(query, fused) return reranked[:final_k]# RRF融合按排名倒数累加分数 def rrf_fusion(sparse_results, dense_results, k: int 60): score_dict {} for rank, doc in enumerate(sparse_results): doc_id doc[id] score_dict[doc_id] score_dict.get(doc_id, 0) 1 / (k rank) for rank, doc in enumerate(dense_results): doc_id doc[id] score_dict[doc_id] score_dict.get(doc_id, 0) 1 / (k rank) return sorted(score_dict.items(), keylambda x: x[1], reverseTrue)生成模块的提示词我构建成了一段结构化的文本把检索到的片段按编号排列并要求大模型严格按照编号引用请回答用户问题。只允许参考下面列出的资料严禁使用资料以外的知识作答。 资料 [1] 来源模拟公司2023年度报告 第12页 内容报告期内公司实现营业收入142.8亿元同比增长18.5%... [2] 来源模拟公司2023年三季度报告 第5页 ... 要求 - 回答每一点关键信息后标注来源编号例如[1] - 若资料中没有相关信息直接回复“现有资料中未找到相关信息” - 语言简洁使用金融行业常用表述。有个接口细节必须注意问答接口的响应时间几乎全花在大模型生成上检索阶段一般耗时不大。用户侧体验上建议加流式输出一边生成一边推送而不是等全部生成完再一次性返回。金融场景里用户看到的是逐段出现的答案加引用标注体验会好很多。3.4 演示界面与效果评估Demo要给别人看一个丑陋的命令行界面肯定不行。我花了一下午用轻量级Web框架搭了个极简的聊天界面左侧是文档目录和索引状态右侧是对话窗口回答内容下面的“查看来源”按钮可以直接定位到原始PDF的页码。界面本身只做展示重点是把检索链路的所有中间信息暴露出来。展示中间信息是我特别坚持的一点。每次问答界面右侧会显示命中的Top 5片段、各片段的重排序分数、用了哪些资料。别小看这个设计它能极大增强使用者的信任感也让这个Demo更适合用于内部技术评审。别人看到的不只是一个“漂亮的回答”而是一条可解释的处理路径。效果评估我建了一个小规模的人工评测集三十个问题覆盖三类事实查询型“去年营收是多少”、对比分析型“四季度毛利率环比变化”、跨文档综合型“两份报告中关于研发投入的描述有什么差异”。我按四个维度打分检索命中率、回答准确率、来源引用准确率、拒答合理率。初版全量跑完回答准确率大概在七成左右后来经过切片策略和重排序调优后有明显提升。这里说一句构建评测集不需要很多条但要保证问题的典型性和多样性比盲目堆数量有用得多。4. 踩坑实录金融RAG的常见问题与排查4.1 表格数据解析后“散架”了我踩过无数次的坑表格数据解析成Markdown后行错位、列错位。原因很隐蔽PDF里某些单元格的文字过长会自动换行如果解析器没有正确处理“单元格内换行”就会把下一列的内容顶到下一行整个表的结构直接崩掉。排查方式是把解析后的表格与原文并排对比逐列检查。修复方式有两种一是选用对单元格边界更敏感的解析配置二是在解析后增加一条校验逻辑对项目名与数字的对应关系做简单规则检查发现明显异常就触发重新解析。这个教训让我明白一件事金融PDF解析永远需要人工抽检不能只看一两个页面的效果就默认全部没问题。4.2 财报数字检索错位指标和数值对不上有一次测试问“经营活动现金流净额”系统返回的是“投资活动现金流”那个表的片段。乍一看两个表结构非常像但指标名差了几个字数字完全不是用户想要的。原因在于Embedding模型对“经营活动”和“投资活动”这两个高度相似短语的区分能力不足向量距离太近导致错误召回。解决办法是引入字段级元数据的硬约束检索时如果问题中识别到“经营”“投资”“筹资”这类关键词就在向量检索的过滤条件中限定对应的表头区域不让其他表的数据参与候选。混合检索加规则化是金融RAG必须有的组合纯粹靠语义理解来处理财务术语的精确对应目前还不可靠。4.3 金融缩写与专业术语让向量检索失效“ROE”这个词通用Embedding模型大概率能关联到“净资产收益率”但“EBITDA”这种更专业的缩写向量召回效果就明显差了。测试问“公司EBITDA情况”召回到的片段经常是正文里顺带提到这个缩写的那句话而不是真正披露指标的表格页。我之前一直强调混合检索和重排序就是在这里发挥作用的。BM25对“EBITDA”这种精确词命中很好重排序模型也能根据上下文重新识别真正包含指标的表格段落。此外我还手动维护了一小份金融术语同义词词典查询进来先做一遍术语扩展把“ROE”映射成“净资产收益率”这样一来向量检索的命中率有明显提高。4.4 答案仍然幻觉提示词约束并不万能就算提示词写了“严禁编造”模型在上下文中找不到明确答案时依然可能把相邻年份的数据张冠李戴或者把上下文没有的因果关系编出来。我见过最典型的情况是上下文中只有2022年和2023年的营收数据问“2021年营收”的时候模型硬是从两句数据的表述里推测了一个数字还说得很笃定。应对幻觉靠两道防线。第一道是检索质量只要正确的切片被召回到Top位置幻觉概率就大降第二道是“拒答门槛”我在生成前置逻辑里加了相关性闸门如果检索结果综合分数过低就不再调用生成接口而是直接返回提示让用户换种问法。前面提到过宁可答不上来也不要给一个貌似严谨的错误答案这在金融领域是一条底线。4.5 检索性能与索引更新问题文本量放大之后检索延迟会明显上升。我曾在索引里塞了几千份文档发现一次查询到了秒级赶紧查了原因——向量索引的索引参数没有调优导致暴力检索。解决方式是把索引类型切换成更高效的近邻检索并调整构建参数延迟立刻降到了百毫秒以内。建议在压测阶段就关注这个参数不要等线上慢了再来排查。索引更新是另一个容易被忽略的环节。业务上新增了一份财报需要让新知识立刻被检索到。我采用的是全量重建与增量写入结合的方式常规更新走增量接口文档结构大改或数据清洗规则变化时触发重建。增量更新时务必维护好文档ID与切片ID的映射表否则旧切片无法定位更新后容易出现新老数据混答的问题。最后再分享一个小技巧整套Financial RAG Demo跑下来如果要我说最值得关注的优化点不是换更大的模型而是把数据准备和检索调优做扎实。数据准备阶段的每个决策都会在最终的回答质量上加分或减分。我建议每个做类似项目的人在第一次跑通端到端之后不要急着加强大模型能力而是建立一个“检索失败案例台账”把问答中检索不到正确片段的案例一条条记录下来分析是切片问题、解析问题还是Embedding问题。我自己就是这样连续记录和修复了三周数据准确率才真正稳定下来。这个台账方法免费、实用、任何场景都能用强烈推荐。