
一、项目动机从手动翻公告到想做会查数的智能助手做这个项目之前我每天的固定流程是这样的早上打开财经数据终端把持仓和自选股相关的公告刷一遍遇到重大事项披露再去翻财报原文对照历史数据看变化最后自己在表格里记一堆备注。这套流程熟练之后效率也不算太低但问题很明显——真正耗时间的不是看而是找。一份几十页的年报要找某个业务线的毛利率变化得先定位章节再翻附注碰上跨年份对比就更头疼。当时我就在想如果有一个东西能帮我把找数据、查上下文、做对比这摊事干了我只负责下结论和做决策该多省心。这就是基于RAG的A股智能选股分析智能体的最初动机。出于合规考虑项目定位从头到尾都是辅助投研分析不是自动荐股更不涉及自动交易。它要做的事情很聚焦让用户用自然语言提问系统从海量公告、财报、研报和行情数据中检索出相关内容再把结果交给大模型组织成带引用来源的分析回答。项目从立项到跑通前后花了大概两个半月。整体复盘下来最深的体会是RAG的本质不是检索生成两个模块的简单拼接而是数据工程、检索策略、模型调度三条线的协同调优。这篇文章把整条链路拆开讲包括技术选型、数据管道、检索优化、智能体编排、评测迭代这几个核心环节以及那些靠报错和踩坑换来的经验。适合正在做或有打算做RAG相关应用、同时对金融文本处理有需求的开发者参考。二、技术选型复盘为什么是RAG智能体而不是微调或大模型直出2.1 三条路线的对比分析立项第一周团队讨论最激烈的就是技术路线。当时摆在桌上有三个选择大模型直出把问题直接丢给大模型靠模型自身参数量里的常识来回答。微调用历史研报、公告数据微调一个垂直领域模型。RAG智能体先检索后生成再用智能体编排多步分析流程。三条路线的对比我整理成了下面这个表格维度大模型直出微调RAG智能体时效性训练数据截止时间之后的事完全不知道微调后会更新一部分知识但成本高、周期长新公告入库即生效秒级可用幻觉控制最弱不懂就编比直出好一些但编造细节仍常见检索结果约束生成范围可追溯、可查证可解释性无弱强答案可溯源到具体文档段落开发成本最低高需要标注数据和训练资源中等主要是检索链路和工程搭建更新维护等模型升级需要重新训练数据入库/更新即可核心结论很清晰选股分析这个场景对时效性和可解释性的要求排在第一位。财报数据每个季度都在更新重大公告更是随时可能出模型训练数据截止之后的信息直出和微调都覆盖不了。而A股投研最怕的就是信息来源不明——你提供一个观点必须能指出依据是什么否则没法用。这两条刚需恰好都是RAG的强项。微调也不是完全没用后面我们在生成环节确实用了微调后的模型来提升输出格式的稳定性但那是锦上添花不是地基。2.2 RAG在小样本、强时效场景下的优势再说一个反直觉的发现A股数据看起来很多但真正针对某一只股票、某一段时期、某一个业务问题的高质量数据样本量其实很小。比如分析某公司某个新业务的收入占比变化可能只有两三份公告里各有一段描述算下来不到几千字。这种场景下做微调数据量根本不够容易过拟合效果反而差。而RAG天然适合这种小样本、强上下文关联的检索任务——它不要求模型记住所有知识只要求标记好每份文档的位置和内容查询时精确取回。后来我们在评测时也验证了这一点在覆盖近一年公告、准确回答某季度营收变化原因这类问题上RAG方案的答案准确率比直出方案高出将近三成。原因不复杂直出模型很可能压根没见过那条新公告而RAG是真的把公告原文找出来让模型读了再回答。2.3 整体架构设计定下RAG智能体方向后我们把整体架构划分成五层数据层负责公告、财报、新闻、行情数据的获取、清洗、结构化解析、增量入库。索引层将文档切片后做向量化构建混合索引稠密向量稀疏关键词。检索层接收问题后进行意图识别、查询改写、混合检索、重排序输出高相关性的文档段落。生成层大模型基于检索结果组织回答附带引用来源标记。智能体编排层调度以上所有能力支持多轮对话、工具调用如查行情、算指标、做时间范围过滤并按用户意图拆解复杂任务成多步执行流程。这里要特别说明RAG和智能体不是一回事但在这个项目里它们必须结合。纯RAG能回答某公司2024年营收是多少但回答不了对比一下A公司和B公司过去三年毛利率变化并分析可能的原因——后者需要先去查A的三年财报、再去查B的三年财报、然后做指标计算和对比最后结合行业背景信息生成分析。这中间的规划下一步查什么、调用什么工具、什么时候该结束就是智能体编排层要干的活。三、数据层构建A股数据管道是最脏最累的活3.1 数据源选型与合规边界数据是整个系统的地基但也是最容易让人崩溃的环节。我们在数据源上做了三个层面的组合结构化财务数据用财务数据接口拿三大报表的历史数值用于指标计算、公告和财报全文用公开的公告披露渠道抓取PDF和HTML原文用于RAG的文档语料、新闻和研报抓取公开资讯但注意版权合规只保留链接和摘要不整篇入库。这里必须提醒一点也是最容易被忽略的一点金融数据合规的边界远比你想象的要严。我们项目从一开始就限定基于公开信息做分析辅助所有入库数据只来源于官方披露渠道和已获授权的数据接口不碰内幕信息、不提供投资建议、不做收益预测。开发者在做同类项目时建议优先和数据服务商签正规授权协议同时在产品层面明显标注内容仅供参考不构成投资建议。这不是套话是底线。3.2 清洗与结构化非结构化公告如何切分原始公告拿到手基本都是一堆格式混乱的文本有PDF扫描件、有HTML页面、有带表格的Word文档。我们踩过的第一个大坑就是直接把整份公告丢给向量模型。一份动辄上百页的年报直接向量化之后检索出来的相关段落往往是整页的长度里面包含了大量的无关信息导致生成环节被噪声带跑偏。后来我们重新设计了文档处理管线核心步骤如下格式归一化PDF、HTML、Word统一转成纯文本保留段落结构和表格数据。标题层级识别把文档的章节结构解析出来例如第三节 经营情况讨论与分析底下又分主营业务分析、资产与负债状况等这个结构后续会作为检索的元数据。语义切分按章节和语义段落做切分而不是机械地按固定字数切。切分时尽量保证每个切片的语义完整——这一步非常关键我后面单独说。表格内容单独处理财报里的表格是关键信息密度最高的地方但表格转文本后会严重乱序。我们采用的是表格按行提取每行附带表头信息的保留方式例如营业收入 | 2023年 | 1,234,567,890 | 同比增长12.5%。元数据标注每一条切片都带上报收日期、股票代码、公告类型、标题、章节路径这些字段为后续检索的过滤和重排序做准备。3.3 切分策略的实测对比切分是RAG项目里最容易被低估的环节。字数是100还是500好按句子切还是按段落切测试下来会发现根本没有一个万能参数只看你的文档类型和检索深度需求。我们在公告语料上做了几组对比切分方式每片长度字符检索命中率Top5包含正确答案答案完整度固定长度、无重叠20061%低经常截断关键数字固定长度、50字重叠30072%中部分表格错位按标题层级段落智能切分不等长81%高上下文完整结论很直接优先生成有语义边界的切片而不是固定窗口。按标题和段落切虽然每片长度不固定但模型拿到手的是一个完整的意思单元生成时明显更不容易断章取义。固定窗口一旦恰好把净利润和1,234,567,890这两个关键信息切成两片后面做检索和生成就全废了。后来我们搞了一个折中方案先按标题层级切大块大块超过1500字再按段落二次切分每个切片之间保留一句话的上下文重叠。这套规则上线后检索阶段的召回率明显提升后续的优化压力小了很多。3.4 增量更新策略与幂等性设计A股数据是强时效的今天入库的公告明天可能就有补充更正。之前一度图省事用全量重建索引的方式每天跑一次结果数据量上来之后重建一次要将近两个小时而且期间查询走的是旧索引容易和刚更新的数据不一致。改造成增量更新之后核心思路是两条幂等入库每条公告用唯一ID做主键重复推送同一份公告不会产生重复数据只会覆盖更新。分钟级增量同步定时任务每5分钟检查一次新公告和更正公告发现有更新就只重新处理这一篇同时把旧的向量切片标为过期新切片写入向量库。增量更新的坑在于删除逻辑。向量库如果只进不出过期内容会越积越多时间一长检索结果里可能混入已经被更正过的旧数据。我们后来加了一个版本号字段每次检索只命中当前版本号的切片彻底解决这个问题。四、检索层优化召回率从43%到81%的实测记录4.1 第一版纯向量检索的问题第一版系统用的是单纯向量检索——问句直接embedding然后用余弦相似度在向量库里找Top-10。提测之后召回率只有43%也就是将近六成的正确答案根本检索不出来。快速翻了一下badcase问题集中在三类专业术语变体比如用户问ROE公告里写的是净资产收益率问扣非净利公告里写的是归属于上市公司股东的扣除非经常性损益的净利润。语义相近但embedding的向量方向并不完全重合。数值条件检索失效问毛利率超过30%的公司这里30%是关键约束但向量检索对这种数值范围的语义几乎没有感知能力。符号和缩写干扰一些股票名和公司全称的缩写形式向量表达到的关键信息会被稀释。纯靠向量模型解决不了这些问题因为embedding本质上是把文本的整体语义压成一个向量它对描述性语义的捕捉能力优秀但对精确词匹配和数值条件非常弱。4.2 混合检索BM25与稠密向量的互补针对上面的问题我们把检索链路改成BM25稀疏关键词检索 稠密向量检索双路并行再用RRFReciprocal Rank Fusion算法融合两个结果集。BM25负责精确匹配它擅长搞定术语变体、代码、数值条件。问ROE就能命中净资产收益率是因为BM25对词的形态变化和缩写索引敏感只要建索引时做了同义词扩展或缩写映射。稠密向量负责语义理解用户问这家公司赚钱能力怎么样向量检索能找到公司毛利率与净利率处于行业领先水平这类语义上相关但没有完全关键词重合的句子。融合排序上RRF算法的公式很简单每个文档在某个列表中的排名分 1 / (k rank)k一般取60把两个列表的排名分相加取Top-N。实测下来同样的评测集混合检索把召回率从43%拉到了68%。4.3 查询改写智能体要会翻译用户的问题在混合检索的基础上再加一层查询改写召回率从68%提升到了81%。这层工作直接体现了智能体的意义——用户的问题本来就不用是检索友好的。用户问茅台最近怎么样系统不直接拿这句话去检索而是先做两步改写实体识别与标准化识别出茅台→贵州茅台酒股份有限公司股票代码600519补充为查询条件。时间范围补充最近改写为2025年8月1日至2025年10月1日基于当前日期动态生成。问题目标识别是找公告、找财务数据、还是找新闻不同的目标走不同的检索子链路。这一步改写的效果特别明显。原始问句包含的检索噪声非常大去掉口语化表达、补上结构化条件之后检索质量立刻上了一个台阶。4.4 重排序模型模型精排而不是只看向量相似度混合检索给出的Top-N精度仍然不够——有一段和问题所在章节相似但实际在讨论战术层内容的段落可能也混进来了。我们在生成之前加了一步重排序Rerank用专门的排序模型对检索出来的段落根据问题和段落的相关性精排。排序模型我们试了两种思路交叉编码器cross-encoder排序和基于LLM的逐对比较排序。前者效果稳定但耗时后者慢但准确性更高。最终落地用的是交叉编码器跑前50个候选再从排序结果里取Top-5送入生成。这一步让最终回答的忠实度指标提高了约11%因为只有最相关的内容才进得了大模型的眼睛。五、智能体编排层从多次问答到多步分析流程的执行引擎5.1 工具集设计与格式约定智能体编排层是整个系统最有智能感的部分。它做的事本质上是把一个复杂的分析需求拆成多个可执行步骤每一部调一个工具最后汇总结果。项目里一共设计了四类工具工具类型功能调用示例文档检索工具从公告/财报/新闻库中检索相关片段search_documents(query, date_range, stock_code)数据指标工具从结构化财务库中拉指标并计算get_financial_indicator(stock_code, indicator, period)行情查询工具获取行情数据用于上下文背景get_market_data(stock_code, date_range)对比分析工具接收多组数据、按条件生成对比分析compare_financials(stock_list, indicator, periods)每个工具都定义了严格的输入参数schema和输出格式并且有一层异常返回机制。这层设计非常有价值——模型调用工具时往往会出现参数幻觉比如把股票简称当代码传进来没有schema约束和异常兜底整个流程很容易崩。5.2 Agent循环的决策逻辑何时检索、何时算数、何时收尾常见的基础RAG只是一次检索生成不够支撑对比分析这类任务。我们在智能体编排层实现了一个带循环的决策流核心逻辑可以理解为七步意图识别把用户的自然语言问题分类单点查询、数据对比、综合报告、开放咨询。任务规划如果需要多步完成把用户问题拆解成一个执行序列并标注每一步要用什么工具。工具调用执行每一步把返回结果加入上下文记忆区。中间验证每一步执行后检查结果是否满足约束条件数据是否为空、日期范围是否合理等。结果汇总从上下文记忆区抽取关键信息组合成一个完整的、带引用的回答框架。生成大模型按照框架生成最终文本。自检循环生成结果后让同一个模型检查一遍回答中的每个观点是否有检索源支持如果没有就标注未找到明确来源避免编造。这七步不是每轮都要走全。比如用户只问某公司最近一期营收是多少意图识别直接判定为单点查询第2步的任务规划就退化为一步检索不会有额外的工具调用开销。智能体编排要解决的核心矛盾是多步能力和响应速度的平衡不是每轮都演一遍大循环否则用户等得没耐心。5.3 一个完整的分析案例拆解用项目上线前测试的一个场景来拆解整条链路。用户问题分析一下宁德时代最近一年的毛利率变化趋势并和行业平均水平做个对比。意图识别综合报告数据趋势对比。任务规划拆成三步——第一步查宁德时代最近四个季度的毛利率第二步查行业平均毛利率第三步对比分析。步骤1调用数据指标工具参数传入股票代码300750、指标毛利率、区间最近一年。返回值是四个季度的毛利率序列。步骤2调用数据指标工具查询行业平均毛利率。结果返回时顺带有一段来自行业研报的文字说明。步骤3调用文档检索工具搜宁德时代 毛利率 变化 原因取回几段相关公告和分析内容。生成模型把三部分数据融合生成一份几十行的分析报告每一段关键结论后面都用[1][2]标注引用来源对应到具体的公告或研报文本。自检模型检查到第四季度毛利率1.65%这个数据没有检索源支撑实际是四舍五入后和原文不一致自动改成原文的精确数值并补充引用。这个例子很能说明问题智能体编排不是为了让流程变得复杂而是让多源数据在结构化调度下有序汇合。单靠一次RAG这种需要数文对比的请求根本答不全而靠人手工查十几分钟的时间成本实实在在。六、评测与迭代离线指标、模拟盘和真实使用中的差距6.1 RAGAS离线评测忠实度和答案相关性是两大指挥棒项目做到后半程我们发现好不好用不能只靠拍脑袋得有一个可量化的评测体系。RAG领域有一套公开的评测框架RAGAS我们直接拿来做了基础重点盯两个指标忠实度Faithfulness回答中的每个观点是否能被检索到的上下文支持。我们要求这个指标不低于85%。答案相关性Answer Relevance回答是否直接回应了用户的问题不被无关信息带跑。要求不低于80%。每一轮迭代之后都跑一遍评测集里的100个典型问题线上测试指标不达标就不允许发布新版本。6.2 线下评测和真实使用的差距三个教训评测指标好看不代表真实用户体验一定好。我们总结出三个教训检索片段列太多反而降低可信度早期版本Top-5检索结果里可能只有1条是有用的后面全是从相似文档里捞出来的噪声。虽然排序分数不低但用户看着一长串引用来源反而觉得系统不靠谱。优化后收紧为只保留高置信度关联的Top-3引用来源少而精用户反馈反而更好。用户真正关心的是数字对不对评测中我们试着生成大段分析文字但测试用户更在意的是里面的关键财务数字是否有原文出处。所以产品形态最终做了调整——每个关键数字旁边都放上查看原文的跳转链接比一大段精心写的分析文字有用得多。冷启动阶段不能直接强上全量数据最初把所有数据一股脑灌进去查询又慢又乱。后来改成先支持重点覆盖的30只股票等检索质量稳定后再逐步扩容。有时候是把事情做小才能做透。6.3 监控模块上线之后才发现靠抠指标远远不够复盘时发现最该一开始就做但最后才补上的是一套详细到链路各环节的监控模块。之前只能看到最终回答质量指标一旦哪个环节出问题比如某天公告抓取服务挂了线上用户感知到的就是检索结果变差而你完全不知道是因为索引没更新、还是源数据断了、还是模型调度出错了。后来的做法是在每个关键环节埋点数据层记录每日入库量、更新失败率、延迟时间。索引层记录索引构建/更新耗时、增量任务异常数。检索层记录每个查询的BM25和向量检索的召回数量、重排后Top-N的相关性打分分布。生成层记录生成请求延迟、引用来源命中率、忠实度自检通过率。这套监控上线之后有两次帮我快速定位了问题一次是公告源的数据抓取延迟导致当天新公告没有入库另一次是向量模型升级后索引库版本不匹配导致相似度计算结果异常。没有先做监控是这次项目里排得上号的遗憾后续做类似项目的朋友建议把监控埋点和主流程一起设计。七、复盘总结做得对的、踩过的坑和后续规划7.1 做得对的几件事技术路线选稳不选新。当时有同事提议直接上纯Agent自主规划全部流程但在金融领域自主规划的失控风险太高最终采用确定性工具可控Agent循环的折中方案每一步都对用户透明、可回溯。这个选择在之后的测试中被一再证明是对的。数据层的结构化深耕。很多人做RAG把精力放在模型和检索上忽略了数据进得来、找得到才是地基。我们把公告解析、表格保留、元数据标注这些苦活累活做扎实了后面的检索和生成才有的放矢。评测先行的迭代方式。每一次改动都拿同一套评测集测用数据说话而不是靠主观感受调参数这一条保证了项目后期迭代没有走偏。7.2 踩过的大坑第一版切分方案导致答案被截断。这是调起来最费时间的问题一度换了三个版本的切分逻辑最后在语义边界优先重叠上下文这套方案上稳定下来。查询改写过度。有一次为了追求检索精度把贵州茅台最近怎么样改写成了贵州茅台酒股份有限公司6005192025年第三季度经营情况分析报告结果向量检索反而找不到原始问答对。教训是改写不能把口语变书面语而是要保留问题语义、补全结构化条件而不是替换表达方式。模型上下文长度幻觉。早期想省成本把Top-5检索结果全部塞给大模型发现它不仅处理得慢而且经常被中间某段低相关度内容带偏。后来压到Top-3并结合重排序只保留高置信度片段生成质量和响应速度同时提升。7.3 这个项目后续还能怎么扩展路径上已经想清楚的有三条一是接得更细把财务数据里的数据项与公告中的对应段落做强关联做数值级溯源二是把智能体规划部分做得更灵活支持用户自定义分析模板例如每次问答自动附带行业比较三是引入用户反馈回路让用户对每条回答的引用来源做有用/无用标记反馈进重排序环节做在线学习。我在这个项目里最大的体会还是那句经常被提起但容易只停留在口头的话RAG项目的成败大半藏在数据的处理细节里。模型和检索的调优是有清晰手段的而数据管道、切分质量、监控体系这些脏活累活才真正决定了系统能不能从Demo变成可用、可维护的产品。如果你也在做RAG相关的智能体项目建议把精力往这上面多倾斜一些。