1. 项目缘起与整体架构设计
1.1 为什么选择RAG而不是微调
做A股智能选股这个方向,最开始团队内部争论了很久:到底是走大模型微调路线,还是走RAG检索增强路线。我们最终选了RAG为主、轻量微调为辅的混合方案,原因很实际。
A股市场的特点决定了知识更新频率极高。财报按季度发布、政策随时出台、行业事件每天都在发生,如果走全量微调路线,每次新数据进来都要重新训练一轮,成本高不说,还容易出现灾难性遗忘——模型学了新财报,把老的行业逻辑忘了。而RAG的核心优势就是知识外挂:把最新的研报、财报、公告、行情数据切块存进向量库,检索时动态拼进上下文,模型本身不需要频繁变动。
另一个关键考量是可解释性。选股这件事,用户天然会问“为什么选这只”。纯微调模型的输出是黑盒,而RAG可以明确告诉你:这条结论来自哪份研报的第几段、哪份财报的哪个指标。这在合规和用户信任层面是刚需。
当然,RAG也不是银弹。我们踩过的坑是:纯RAG在需要深度推理的场景(比如多期财务数据交叉验证)表现不如微调模型。所以最终方案是——RAG负责事实召回,微调负责推理风格对齐,两者配合。
1.2 智能体的角色分工设计
这个项目不是单一模型,而是一个多智能体协作系统。我们参考了Agentic RAG的思路,把整个选股流程拆成几个专职智能体:
- 数据采集智能体:负责从行情接口、公告接口、研报库拉取原始数据,做清洗和结构化。
- 检索智能体:接收用户query,改写检索意图,去向量库和多路数据源召回候选信息。
- 分析智能体:对召回内容做财务指标计算、行业对比、估值判断。
- 风控智能体:检查推荐标的是否触碰ST、退市风险、行业禁投等红线。
- 报告生成智能体:把前面所有结果整合成一份可读的选股分析报告。
这种拆分的好处是每个智能体职责单一,prompt可以针对性优化,出问题也容易定位。坏处是链路长,延迟高,需要做并行化和缓存。
1.3 技术栈选型与理由
| 组件 | 选型 | 理由 |
|---|---|---|
| 大模型 | DeepSeek系列 + 本地部署Qwen | 中文金融语料表现好,本地部署保证数据不出域 |
| 向量库 | Milvus | 支持混合检索,亿级向量性能稳定 |
| 编排框架 | 自研轻量编排 + AgentScope参考 | 避免框架黑盒,方便调试 |
| 数据源 | 行情API + 公告解析 + 研报PDF解析 | 多源交叉验证 |
| 微调 | LoRA轻量微调 | 成本可控,风格对齐够用 |
选Milvus而不是FAISS,是因为我们需要标量过滤+向量检索的混合查询。比如“在新能源行业、市值50亿到200亿之间、最近一个月有机构调研的股票里,找和‘固态电池量产’语义最相关的”。FAISS做不了这种带条件的检索,Milvus可以。
2. 核心细节解析与实操要点
2.1 知识库构建:切块策略决定检索上限
RAG项目里,切块策略是地基。切得不好,后面检索再优化也救不回来。我们试过三种方案:
第一种是固定长度切块,比如每512个token一刀切。问题是财报里的表格和段落经常被切断,检索出来的片段语义不完整。
第二种是按段落切,遇到空行就切。比固定长度好,但研报里经常有长段落,一个段落讲三个逻辑,检索时全被召回,噪声大。
最终采用的是语义切块+重叠窗口:先用规则识别文档结构(标题、表格、段落),再在段落内部按语义相似度做二次切分,相邻块之间保留15%的重叠。重叠的作用是防止关键信息刚好落在切分边界上被割裂。
具体参数上,我们最终定的是:单块目标长度300到500字,重叠50到80字。这个范围是实测出来的——太短召回信息不足,太长噪声多且浪费上下文窗口。
注意:财报表格一定要单独处理,不能当普通文本切。我们的做法是把表格转成Markdown格式,每个表格作为一个独立块,并在块头部加上表名和单位说明。
2.2 向量化模型的选择与踩坑
embedding模型我们对比了三个:通用中文embedding、金融领域embedding、以及大模型自带的embedding接口。
通用模型在金融术语上表现一般,比如“市盈率”和“PE”它可能认为是两个东西。金融领域embedding明显更好,但对新出现的概念(比如“低空经济”)覆盖不足。大模型embedding接口效果好但成本高、延迟大。
最终方案是金融领域embedding为主,配合关键词检索做兜底。也就是混合检索:向量召回Top50,BM25关键词召回Top50,然后做RRF融合排序。这样既保证了语义理解,又不会漏掉精确匹配的关键词。
实测下来,混合检索比纯向量检索的hit rate提升了大概18个百分点。这个提升在选股场景里很关键,因为漏掉一份关键研报可能就错过一个逻辑。
2.3 检索意图改写:让query更懂行
用户输入的query往往很口语化,比如“帮我找找最近有啥好股票”。这种query直接拿去检索,效果很差。我们加了一个检索意图改写智能体,做三件事:
第一,把口语query转成结构化检索条件。比如上面那句会被改写成:时间范围=最近一周,类型=机构推荐/评级上调,行业=不限,附加条件=有明确上涨逻辑。
第二,做query扩展。比如“固态电池”会扩展出“半固态电池”“全固态电池”“硫化物电解质”等相关术语,提高召回率。
第三,做query分解。复杂query拆成多个子query分别检索,再合并结果。比如“新能源里估值低且机构在调研的”会拆成“新能源 低估值”和“新能源 机构调研”两路检索。
这个改写环节看起来简单,但对最终效果影响巨大。我们做过消融实验,去掉改写环节,检索准确率下降超过25%。
3. 实操过程与核心环节实现
3.1 数据管道的搭建
数据管道是整个项目最脏最累的部分。A股数据源质量参差不齐,公告格式五花八门,研报PDF解析经常出错。
我们的管道分四层:
第一层:采集。行情数据走接口定时拉取,公告走交易所公开接口,研报走PDF解析。这里的关键是去重和版本管理——同一份研报可能有多个版本,同一份公告可能被多个源重复抓取。
第二层:清洗。PDF解析出来的文本经常有乱码、页眉页脚、表格错位。我们写了一套规则清洗,重点处理:去除页眉页脚、修复断行、表格转Markdown、识别并保留章节结构。
第三层:结构化。把非结构化文本里的关键信息抽出来,比如财报里的营收、净利润、毛利率,研报里的评级、目标价、核心逻辑。这一步用大模型做信息抽取,配合规则校验。
第四层:入库。结构化数据进关系库,文本块进向量库,两边通过doc_id关联。
实操心得:数据管道一定要做幂等设计。同一份数据重复跑不能产生重复记录。我们早期没注意这点,导致向量库里同一份研报存了七八遍,检索结果全是重复的。
3.2 多智能体编排的实现
编排层我们没用现成框架,而是自己写了一个轻量的DAG调度器。核心逻辑是:每个智能体是一个节点,节点之间通过消息队列传递数据,支持串行和并行。
举个例子,一次完整的选股请求流程是这样的:
- 用户query进入,先过意图改写智能体,输出结构化检索条件。
- 检索条件分发给检索智能体,同时去向量库、关系库、行情接口拉数据。
- 召回结果送给分析智能体,做财务计算和逻辑梳理。
- 分析结果送给风控智能体做红线检查。
- 通过检查的结果送给报告生成智能体,输出最终报告。
整个链路里,检索和分析可以并行做部分工作,我们做了异步优化,把平均响应时间从12秒压到了4秒左右。
3.3 提示词工程与上下文管理
多智能体系统里,prompt管理是个大工程。我们的做法是prompt模板化+版本管理。每个智能体的prompt存在配置文件里,支持热更新和A/B测试。
上下文管理上,最大的挑战是上下文窗口有限但召回内容很多。我们的策略是:
- 检索阶段召回Top100,粗排到Top20。
- 精排阶段用一个小模型做相关性打分,选出Top5到Top8。
- 最终拼进上下文的,除了这5到8个块,还有结构化的财务指标摘要和风控结论。
这样既保证了信息量,又不会撑爆窗口。实测下来,精排环节能过滤掉大概70%的噪声。
4. 常见问题与排查技巧实录
4.1 检索命中率低的排查思路
检索命中率低是RAG项目最常见的问题。我们的排查顺序是:
| 排查项 | 检查方法 | 常见问题 |
|---|---|---|
| 切块质量 | 随机抽100个块人工看 | 块太碎或太长,语义不完整 |
| embedding质量 | 拿已知query测相似度 | 模型不匹配领域 |
| 检索策略 | 对比纯向量vs混合检索 | 漏掉关键词匹配 |
| query改写 | 看改写后的query是否合理 | 改写过度或不足 |
| 索引更新 | 检查最新数据是否入库 | 增量更新失败 |
我们遇到过一次典型问题:某天开始检索结果突然变差。排查发现是数据管道的一个增量任务挂了,导致最新三天的研报没入库。所以监控索引新鲜度很重要,我们后来加了一个告警,超过24小时没更新就报警。
4.2 大模型幻觉的抑制
选股场景里,模型胡说八道是要出事的。我们用了三层抑制:
第一层,检索约束。prompt里明确要求“只基于提供的资料回答,资料里没有的信息不要编”。
第二层,引用校验。模型输出的每个结论都要标注来源,后处理环节校验来源是否真实存在。
第三层,数值校验。财务数据这类硬信息,从结构化库里直接取,不让模型自己算。
即便如此,还是会有漏网之鱼。我们的兜底方案是风控智能体做最终检查,发现明显不合理的输出直接拦截。
4.3 性能优化的几个关键点
多智能体+RAG的链路很长,性能优化是必须的。我们做了这几件事:
- 缓存:相同query的检索结果缓存,相同股票的财务指标缓存。
- 并行:检索和分析里能并行的步骤全部并行。
- 流式输出:报告生成用流式,用户不用等全部生成完。
- 模型分级:简单任务用小模型,复杂任务才调大模型。
优化前后,P99延迟从20多秒降到了6秒以内。这个体验差距是巨大的。
4.4 常见问题速查表
| 问题现象 | 可能原因 | 解决方向 |
|---|---|---|
| 检索结果不相关 | query改写失败/embedding不匹配 | 检查改写日志,换embedding模型 |
| 回答内容空洞 | 召回块质量差/上下文太短 | 优化切块,增加召回数量 |
| 数值错误 | 模型自己算数 | 改为从结构化库取值 |
| 响应超时 | 链路太长/模型太慢 | 加缓存,并行化,模型分级 |
| 重复推荐 | 去重逻辑缺失 | 在检索和输出层都加去重 |
| 风控漏检 | 规则覆盖不全 | 定期review风控规则库 |
5. 项目复盘与后续扩展方向
5.1 做对了什么
回头看,这个项目有几个决策是对的。混合检索是其中之一,纯向量检索在金融场景真的不够用。多智能体拆分也是对的,虽然链路长了,但每个环节可优化、可监控、可替换。数据管道幂等设计虽然前期麻烦,但后期省了无数事。
还有一个隐性决策:没有过度依赖现成框架。我们参考了AgentScope等框架的设计思路,但核心编排自己写。好处是出了问题能定位到代码行,坏处是开发工作量大。对于要长期维护的项目,这个取舍是值得的。
5.2 踩过的坑
最大的坑是早期低估了数据清洗的工作量。我们原以为数据管道两周能搞定,实际花了两个月。PDF解析、表格识别、公告去重,每一项都比想象中复杂。
第二个坑是prompt版本管理混乱。早期prompt直接写在代码里,改一次要重新部署。后来抽成配置文件,支持热更新,效率才上来。
第三个坑是没有做检索质量监控。上线初期全靠人工抽查,后来加了自动化的hit rate监控和告警,才能及时发现退化。
5.3 后续可以扩展的方向
这个项目后续还能往几个方向走。一是加入GraphRAG,把股票、行业、产业链、事件之间的关系建成图谱,检索时不仅召回文本,还能召回关系路径。二是引入更多模态,比如把K线图、财报图表也纳入检索。三是做个性化,根据用户的历史偏好调整检索和排序策略。
我个人在实际操作中的体会是,RAG项目里检索质量决定上限,工程实现决定下限。模型选型固然重要,但切块、检索、排序这些“脏活”才是真正拉开差距的地方。很多团队花大量时间调模型,却忽略了知识库本身的质量,这是本末倒置。
最后分享一个小技巧:定期做检索质量的回归测试。我们维护了一个包含200个query的测试集,每次改动后跑一遍,看hit rate有没有下降。这个习惯帮我们避免了好几次线上事故。