news 2026/10/31 12:04:56

泰迪杯B题财报智能问数助手:RAG全链路实战与调优

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
泰迪杯B题财报智能问数助手:RAG全链路实战与调优

简介:本资源为2026年第十四届泰迪杯数据挖掘挑战赛B题「上市公司财报智能问数助手」的完整题解方案,面向参赛队伍及需要金融NL2SQL与RAG实战案例的学习者。内容覆盖「规则+模型」双擎数据抽取、六维意图分类与高鲁棒NL2SQL引擎、基于DAG自主规划的RAG深度问答框架,并附近两万字Word论文、全链路Python代码及标准结果表,可直接作为核心参考或稍作修改使用。压缩包共281个文件,约551.43MB,以pdf、xlsx、png、py、json、csv、tsx等为主,分别对应论文文档、结果数据、图表、核心代码与前后端源码,另含SQLite数据库与一键运行脚本。目前已有791人学习下载。读者可获得完整赛题方案、模块化代码、独家React+FastAPI全栈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 python=3.10 -y conda activate teddy_b # 安装核心依赖,版本尽量对齐资源包 requirements pip install -r requirements.txt # 如果 requirements 里没有锁版本,手动补几个关键库 pip install pypdf sentence-transformers faiss-cpu jieba rank_bm25

这里有几个参数要留意。python=3.10是因为部分向量库和深度学习框架对 3.11+ 支持还不稳;faiss-cpu是 CPU 版向量检索库,如果机器有 GPU 可以换faiss-gpu,但财报数据量通常几万到几十万 chunk,CPU 版完全够用,别为了炫技上 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, dim=768): """chunks: 预处理后的段落列表""" # 批量编码,batch_size 太大会爆内存,32 是稳妥值 embeddings = model.encode( chunks, batch_size=32, normalize_embeddings=True, # 归一化后可用内积代替余弦相似度 show_progress_bar=True ) embeddings = np.array(embeddings).astype('float32') # 用内积索引,配合归一化等价于余弦相似度 index = faiss.IndexFlatIP(dim) index.add(embeddings) return index, embeddings

逻辑说明:normalize_embeddings=True是关键,向量归一化之后,内积(IndexFlatIP)和余弦相似度等价,省去每次算模长的开销。batch_size=32是内存和速度的平衡点,显存/内存紧张就降到 16。dim=768对应 bge-base 系列的输出维度,如果你换成别的模型,这个维度必须跟着改,否则index.add会直接报维度不匹配——这是新手最常翻的车之一。

索引建好后要持久化,别每次查询都重建:

# 保存索引和对应的 chunk 文本,两者必须一一对应 faiss.write_index(index, "outputs/finance.index") with open("outputs/chunks.txt", "w", encoding="utf-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_k=5): # 向量召回 q_vec = model.encode([query], normalize_embeddings=True).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_k=5是最终喂给生成模型的段落数,太多会稀释关键信息还占上下文,太少可能漏掉答案。融合策略这里用的是简单并集,进阶做法是加权打分(比如向量 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(context=context_text, question=question) return llm.invoke(prompt)

这段提示词里「无法确定」那句是后悔药,能挡掉相当一部分幻觉。context_text用分隔符把多个片段隔开,方便模型区分来源。如果你用的模型支持 system prompt,把约束放 system 里效果更稳。生成完最好再做一个后处理:检查答案里的数字是否真的出现在检索片段中,不在就标记为可疑,这是金融场景的兜底习惯。

4. 避坑与排查:财报问答落地最常见的五个翻车点

4.1 现象:检索到的段落总是差一截,答案缺关键数字

原因:分块时把表格切碎了。财报里「营业收入」和它后面的数字经常在同一行或相邻行,固定长度切分正好切在中间。

解决:改成分块时优先按换行和句号切,遇到疑似表格区域(连续多行含数字和制表符)整块保留。资源里的预处理脚本如果没处理这点,自己加一个表格检测逻辑,宁可 chunk 大一点也别切碎。

4.2 现象:同一个问题问两次,答案数字不一样

原因:生成模型温度参数没设成 0,或者检索结果每次顺序有波动。

解决:生成时把temperature设为 0(或接近 0),检索结果固定排序。金融问答要的是确定性,不是创造性,温度必须压死。

4.3 现象:向量检索报维度不匹配错误

原因:换了向量模型但没改索引维度,或者加载了旧索引配新模型。

解决:换模型必须重建索引,dim参数跟着模型输出维度走。bge-base 是 768,bge-large 是 1024,m3e 系列又不一样,别想当然。

4.4 现象:中文分词把公司名和数字切散,BM25 召回失效

原因:jieba 默认词典不认识财报里的专有名词和股票代码。

解决:加载自定义词典,把公司名、行业术语、股票代码加进去:

jieba.load_userdict("dict/finance_terms.txt") # 词典格式:每行一个词,可带词频和词性 # 贵州茅台 100 n # 研发费用 100 n

4.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 命中率:

指标含义目标参考
Recall@5前 5 个片段含正确答案的比例越高越好,优先优化
MRR正确答案排名的倒数均值反映排序质量
答案准确率生成答案与标准答案一致比例人工抽检

评测脚本自己写一个循环就行,把每个问题跑一遍检索,比对标注的 chunk id 是否在召回列表里。这个评测集建一次能反复用,调分块、换模型、加重排,每次改动都跑一遍,才知道是真优化还是自我感觉良好。

5.3 一个容易被忽略的技巧:问题改写

用户问「它去年赚了多少」,这里的「它」指代不明,直接检索必然翻车。做法是在检索前加一步问题改写,用模型把口语化、带指代的问题补全成「XX 公司 2023 年净利润是多少」这种自包含的查询。改写后再走检索,召回质量提升立竿见影。这一步在资源里如果有对应模块就直接用,没有的话加一个轻量 prompt 就能实现,成本很低。

从那以后我每次搭 RAG 系统,都强制先建评测集再调参,不然改了半天全是玄学。这套泰迪杯 B 题的资源把链路铺得很完整,拿它当练手项目,把上面这些坑挨个踩一遍,比看十篇综述都管用。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/31 12:04:56

从零搭建大模型Agent:Function Calling与ReAct模式实战指南

这两年做Agent开发的越来越越多。你去任何技术社区搜“大模型 Agent”,扑面而来的都是框架、论文、路线图,但真正能把一个Agent从零搭起来、跑通、还能稳定工作的资料,其实没有想象中那么多。很多人卡在同一个地方:教程看了不少&a…

作者头像 李华
网站建设 2026/10/31 12:04:55

Agent Skills实战指南:从提示词模块化到可复用技能封装

1. 项目概述:当提示词变成了可复用的“技能包”我第一次被“Skills”这个概念打动,是在一个实际项目里。当时在做一个多步骤的文档处理工作流,需要让大模型定期整理批量上传的报表,做格式清洗、字段抽取、异常标记,然后…

作者头像 李华
网站建设 2026/10/28 4:26:44

Agent-Reach:轻量级CLI代理层原理与工程实践

1. “Agent-Reach”不是新框架,而是一把被误读的CLI手术刀你点开GitHub搜索“Agent-Reach”,第一眼看到的很可能是一个叫shihabal3amri/diplay的仓库——它在热词中反复出现,标题写着“diplay github”,URL里还夹着空格和拼写变形…

作者头像 李华
网站建设 2026/10/19 17:19:57

让Claude记住你:claude-mem持久记忆工具原理与实战

1. 先聊聊那个让人头疼的"金鱼记忆"问题如果你经常用 Claude 做正经工作,一定有过这种体验:第一天上午让它帮忙分析了一堆业务日志,理清了客户投诉的几种模式,顺手还定了个数据清洗的方案。第二天你打开一个新的对话窗口…

作者头像 李华
网站建设 2026/10/30 8:53:01

Agent-Reach:命令行驱动的跨平台代理调度中枢

1. 项目概述:Agent-Reach 是什么,它解决的是哪类真实问题?Agent-Reach 不是一个泛泛而谈的“智能体框架”或“AI工具集合”,而是一个面向开发者与技术型内容创作者的命令行驱动型多平台代理调度中枢。它的核心定位非常清晰&#x…

作者头像 李华