泰迪杯B题财报智能问数助手:RAG全链路实战与调优
简介本资源为2026年第十四届泰迪杯数据挖掘挑战赛B题「上市公司财报智能问数助手」的完整题解方案面向参赛队伍及需要金融NL2SQL与RAG实战案例的学习者。内容覆盖「规则模型」双擎数据抽取、六维意图分类与高鲁棒NL2SQL引擎、基于DAG自主规划的RAG深度问答框架并附近两万字Word论文、全链路Python代码及标准结果表可直接作为核心参考或稍作修改使用。压缩包共281个文件约551.43MB以pdf、xlsx、png、py、json、csv、tsx等为主分别对应论文文档、结果数据、图表、核心代码与前后端源码另含SQLite数据库与一键运行脚本。目前已有791人学习下载。读者可获得完整赛题方案、模块化代码、独家ReactFastAPI全栈Web系统源码、经五维校验的财报数据及排错思路助力高效完赛、冲刺国奖。1. 从一份财报问答赛题说起这套资源到底能跑出什么去年带新人做金融文本问答时最头疼的不是模型选型而是找不到一份「数据、代码、评测、论文」四件套齐全的公开案例。多数开源项目要么只给模型权重要么只给一份清洗过的 CSV中间的数据构造、检索链路、评测口径全靠自己猜。2026 第 14 届泰迪杯数据挖掘挑战赛 B 题「上市公司财报智能问数助手」这套资源恰好补上了这个缺口——它把赛题原文、财报数据集、检索与生成全链路代码、结果图、论文模板打包在一起属于典型的「拿来就能复现」型赛题方案。它解决的问题很具体给定一批上市公司年报/季报文本用户用自然语言提问比如「XX 公司 2023 年研发费用同比变化多少」系统要定位到正确财报段落并给出带数字的答案。适合三类人想入门 RAG检索增强生成但缺真实语料的工程师、准备打数据挖掘类竞赛的学生、以及需要给内部做财报问答原型的技术负责人。下面按「资源是什么 → 怎么搭 → 坑在哪 → 怎么调优」的顺序拆开讲中间所有步骤都能直接抄。2. 财报智能问数的技术底座为什么是 RAG 而不是纯大模型2.1 财报问答的三个硬约束决定了技术选型先想清楚这类任务难在哪。第一财报数字必须精确模型不能靠「记忆」编一个营收数字出来一旦幻觉就是事故第二财报文本动辄几百页单份年报 PDF 转文本后轻松超过 10 万 token塞不进任何主流模型的上下文窗口第三问题形式高度多样既有「净利润是多少」这种抽取式也有「相比去年增长原因」这种需要跨段落归纳的。纯大模型微调把财报喂进去做 SFT在这三点上都吃亏微调成本高、更新财报要重训、数字幻觉无法根治。所以这套方案走的是 RAG 路线——先用检索把相关段落捞出来再让模型基于检索到的原文作答。检索负责「找对地方」生成负责「组织语言」数字直接从原文抄幻觉概率大幅下降。这也是当前金融问答落地的主流做法常见做法是「向量检索 关键词检索」双路召回再重排。2.2 资源包里的模块划分与数据流这套资源的核心链路可以拆成四段理解了这个数据流后面看代码就不会迷路模块输入输出关键文件数据预处理原始财报 PDF/TXT分块后的段落 元数据preprocess 相关脚本索引构建段落文本向量库 关键词索引build_index 脚本检索召回用户问题Top-K 相关段落retriever 模块答案生成问题 段落带引用的答案generator 模块数据预处理阶段最关键的是分块策略。财报有天然的层级结构章节 → 小节 → 段落如果按固定 512 字符硬切很容易把一张表格切成两半导致「营业收入」和它对应的数字分离。资源里采用的是「按标题层级 段落长度」的混合切分我一般也会这么做先按章节标题切大块再对超长块按句号二次切分保证每个 chunk 语义完整。2.3 环境搭建与依赖安装拿到资源包后第一步是把环境跑通。财报处理会用到 PDF 解析和向量库依赖不算轻建议单独建虚拟环境# 创建独立环境避免和已有项目冲突 conda create -n teddy_b python3.10 -y conda activate teddy_b # 安装核心依赖版本尽量对齐资源包 requirements pip install -r requirements.txt # 如果 requirements 里没有锁版本手动补几个关键库 pip install pypdf sentence-transformers faiss-cpu jieba rank_bm25这里有几个参数要留意。python3.10是因为部分向量库和深度学习框架对 3.11 支持还不稳faiss-cpu是 CPU 版向量检索库如果机器有 GPU 可以换faiss-gpu但财报数据量通常几万到几十万 chunkCPU 版完全够用别为了炫技上 GPU 增加部署复杂度。sentence-transformers负责把文本转向量jieba和rank_bm25是关键词检索那一路要用的。装完后先跑一遍资源自带的冒烟测试脚本一般叫test_pipeline.py或类似名字确认从「读一条财报 → 建索引 → 问一个问题 → 出答案」整条链路能通再动自己的数据。这一步别省我见过太多人直接上全量数据结果卡在某一步报错排查半天发现是依赖版本问题。3. 检索链路实操向量召回与关键词召回怎么配3.1 文本向量化与索引构建检索质量决定答案质量这一步是整个系统的命门。先看索引构建的核心代码逻辑import faiss import numpy as np from sentence_transformers import SentenceTransformer # 加载中文语义向量模型财报文本用中文模型效果更稳 model SentenceTransformer(BAAI/bge-base-zh-v1.5) def build_vector_index(chunks, dim768): chunks: 预处理后的段落列表 # 批量编码batch_size 太大会爆内存32 是稳妥值 embeddings model.encode( chunks, batch_size32, normalize_embeddingsTrue, # 归一化后可用内积代替余弦相似度 show_progress_barTrue ) embeddings np.array(embeddings).astype(float32) # 用内积索引配合归一化等价于余弦相似度 index faiss.IndexFlatIP(dim) index.add(embeddings) return index, embeddings逻辑说明normalize_embeddingsTrue是关键向量归一化之后内积IndexFlatIP和余弦相似度等价省去每次算模长的开销。batch_size32是内存和速度的平衡点显存/内存紧张就降到 16。dim768对应 bge-base 系列的输出维度如果你换成别的模型这个维度必须跟着改否则index.add会直接报维度不匹配——这是新手最常翻的车之一。索引建好后要持久化别每次查询都重建# 保存索引和对应的 chunk 文本两者必须一一对应 faiss.write_index(index, outputs/finance.index) with open(outputs/chunks.txt, w, encodingutf-8) as f: for c in chunks: f.write(c.replace(\n, ) \n)注意 chunk 文本的保存顺序必须和向量写入顺序完全一致否则检索出来的是「A 的向量配 B 的文本」答案会张冠李戴。我一般会在保存时额外存一份 id 映射表方便排查。3.2 双路召回与重排策略只用向量召回有个明显短板财报里的专有名词、股票代码、精确数字向量模型经常抓不准。比如问「600519 的毛利率」向量检索可能召回一堆讲毛利率的段落但未必是这家公司的。所以资源里配了关键词召回那一路用 BM25 兜底。from rank_bm25 import BM25Okapi import jieba # 对 chunk 分词建 BM25 索引 tokenized [list(jieba.cut(c)) for c in chunks] bm25 BM25Okapi(tokenized) def hybrid_retrieve(query, vec_index, chunks, top_k5): # 向量召回 q_vec model.encode([query], normalize_embeddingsTrue).astype(float32) _, vec_ids vec_index.search(q_vec, top_k * 2) # 关键词召回 q_tokens list(jieba.cut(query)) bm25_scores bm25.get_scores(q_tokens) bm25_ids np.argsort(bm25_scores)[::-1][:top_k * 2] # 简单融合两路结果取并集后按向量得分排序 merged list(dict.fromkeys(list(vec_ids[0]) list(bm25_ids))) return [chunks[i] for i in merged[:top_k]]参数说明top_k * 2是每路先多召回一些给融合留余量top_k5是最终喂给生成模型的段落数太多会稀释关键信息还占上下文太少可能漏掉答案。融合策略这里用的是简单并集进阶做法是加权打分比如向量 0.7 BM25 0.3或上 Cross-Encoder 重排资源里如果带了重排模型优先用重排效果提升明显。3.3 生成阶段的提示词与引用约束检索到段落之后生成阶段的核心是「约束模型只依据给定段落作答」。提示词写不好模型照样自由发挥PROMPT_TEMPLATE 你是一个财报问答助手。请严格根据下面提供的财报片段回答问题。 如果片段中没有相关信息直接回答「根据提供的资料无法确定」不要编造。 回答涉及数字时必须原样引用片段中的数字。 财报片段 {context} 问题{question} 答案 def generate_answer(question, contexts, llm): context_text \n---\n.join(contexts) prompt PROMPT_TEMPLATE.format(contextcontext_text, questionquestion) return llm.invoke(prompt)这段提示词里「无法确定」那句是后悔药能挡掉相当一部分幻觉。context_text用分隔符把多个片段隔开方便模型区分来源。如果你用的模型支持 system prompt把约束放 system 里效果更稳。生成完最好再做一个后处理检查答案里的数字是否真的出现在检索片段中不在就标记为可疑这是金融场景的兜底习惯。4. 避坑与排查财报问答落地最常见的五个翻车点4.1 现象检索到的段落总是差一截答案缺关键数字原因分块时把表格切碎了。财报里「营业收入」和它后面的数字经常在同一行或相邻行固定长度切分正好切在中间。解决改成分块时优先按换行和句号切遇到疑似表格区域连续多行含数字和制表符整块保留。资源里的预处理脚本如果没处理这点自己加一个表格检测逻辑宁可 chunk 大一点也别切碎。4.2 现象同一个问题问两次答案数字不一样原因生成模型温度参数没设成 0或者检索结果每次顺序有波动。解决生成时把temperature设为 0或接近 0检索结果固定排序。金融问答要的是确定性不是创造性温度必须压死。4.3 现象向量检索报维度不匹配错误原因换了向量模型但没改索引维度或者加载了旧索引配新模型。解决换模型必须重建索引dim参数跟着模型输出维度走。bge-base 是 768bge-large 是 1024m3e 系列又不一样别想当然。4.4 现象中文分词把公司名和数字切散BM25 召回失效原因jieba 默认词典不认识财报里的专有名词和股票代码。解决加载自定义词典把公司名、行业术语、股票代码加进去jieba.load_userdict(dict/finance_terms.txt) # 词典格式每行一个词可带词频和词性 # 贵州茅台 100 n # 研发费用 100 n4.5 现象全量数据建索引跑到一半内存溢出原因一次性把所有 chunk 编码进内存数据量大时扛不住。解决分批编码、分批写入索引或者用IndexIVFFlat这类需要训练的索引降低内存占用。数据量超过 50 万 chunk 时考虑上专门的向量数据库而不是 faiss 本地索引。5. 进阶调优与效果验证把答案准确率再抬一档5.1 用重排模型做二次精排双路召回解决了「找得到」重排解决「排得准」。做法是把召回的前 20 个片段用 Cross-Encoder 逐对打分问题-片段取分数最高的 3 到 5 个喂给生成模型。Cross-Encoder 比向量模型慢但只对少量候选打分开销可接受。常见做法是用 bge-reranker 系列中文财报场景表现稳定。加了重排之后Top-3 命中率通常能比纯向量召回高十几个百分点这是性价比最高的一步优化。5.2 建立自己的评测集验证效果没有评测就没有优化方向。建议从赛题数据里抽 100 到 200 个问答对人工标注正确答案所在的 chunk然后跑检索看 Top-K 命中率指标含义目标参考Recall5前 5 个片段含正确答案的比例越高越好优先优化MRR正确答案排名的倒数均值反映排序质量答案准确率生成答案与标准答案一致比例人工抽检评测脚本自己写一个循环就行把每个问题跑一遍检索比对标注的 chunk id 是否在召回列表里。这个评测集建一次能反复用调分块、换模型、加重排每次改动都跑一遍才知道是真优化还是自我感觉良好。5.3 一个容易被忽略的技巧问题改写用户问「它去年赚了多少」这里的「它」指代不明直接检索必然翻车。做法是在检索前加一步问题改写用模型把口语化、带指代的问题补全成「XX 公司 2023 年净利润是多少」这种自包含的查询。改写后再走检索召回质量提升立竿见影。这一步在资源里如果有对应模块就直接用没有的话加一个轻量 prompt 就能实现成本很低。从那以后我每次搭 RAG 系统都强制先建评测集再调参不然改了半天全是玄学。这套泰迪杯 B 题的资源把链路铺得很完整拿它当练手项目把上面这些坑挨个踩一遍比看十篇综述都管用。希望帮到你。本文还有配套的精品资源点击获取