我前阵子刚把团队里那个投资决策agent跑通到“持续稳定压准风险指标”的阶段,憋了很久想把这个项目彻底拆开聊聊。因为市面上聊投资agent的文章不少,但你真去看,大部分要么停在调API的玩具层,要么把系统说得神乎其神却给不出工程细节。尤其是我这次把语料规模直接干到了百亿token的量级,过程中踩过的坑、推翻过好几次的设计决策,我觉得很值得沉淀成一篇能给同行参考的实战笔记。这篇文章会完整讲清楚它的数据管线、检索增强方式、多智能体协作机制、回测与上线保护设计,内容涉及大量真实操作层面的取舍和教训,适合已经有一定NLP、大数据或量化基础、想认真做LLM+金融落地的朋友。
先说清楚这个agent的定位:它不是一个替你一键下单的自动炒股机器人,而是一个“投研决策增强系统”。它帮助研究员和基金经理完成信息收集、标的初筛、事件解读、风险预警这些环节,最终给出结构化的分析和建议,由人来拍板。我觉得这个定位很重要——如果一上来就追求全自动交易,那风险和工程复杂度都不可控,而且责任归属也很麻烦。下面我从架构聊起,把整个系统的运转逻辑掰开揉碎地讲。
1. 为什么我会选择“手搓”一个投资agent
别误会,这里说的“手搓”不是连分词器都自己写,而是说整个应用层、数据层和调度框架都是基于自己的业务需求搭起来的,没有直接套某个现成的“大模型应用开发框架”。做这个选择是在对比了好几条路线之后才定的,我来说说当时的思考过程。
第一,通用RAG框架解决不了金融场景的时间敏感问题。普通问答型的RAG,用户问一个问题,系统检索相关文档,拼进上下文,然后吐一个答案,这个链路对“今天涨了为什么”这种问题还行。但投资场景里的真实需求是跨时间窗口、跨多数据源的信息整合,比如“最近三个交易日该标的基本面有没有发生边际变化”“这条新闻对哪些关联标的影响最大”,这需要数据按时间片对齐、事件级别关联,而不是简单地把一堆文档塞进Prompt。
第二,我们要面对的是百亿token级别的语料。这个量级意味着你不可能一次全塞进上下文,哪怕模型上下文窗口做到几百万token也不现实。你真正需要的是把语料做好索引、切好分块、设计好召回策略,让每次调用模型时只取其中最有价值的几十条片段。这其实是经典的“索引+检索+生成”架构问题,而不是单纯靠模型能力就能绕过去的。
第三,也是最重要的一点:金融场景必须可解释、可审计。模型根据什么数据得出这个结论,这条结论对应的原文出处是什么,在时间线上是否属于未来信息(前视偏差),这些都需要整个链路能回溯。用现成框架就像用黑盒,出了问题你根本不知道是检索丢了数据还是模型自己编了个理由,排查成本极高。自己控制链路,每一步都能加日志、加校验、加人工检查点。
所以最终决定:大模型本身还是用成熟的开源底座,但围绕它的数据加工、检索、多智能体调度、回测风控全部自己写。这样做的成本确实高,但换来的是对系统每一环的绝对掌控,后面排错和迭代都会轻松不少。
2. 整体架构设计与关键取舍
整个系统可以拆成六个核心模块:数据采集与血缘管理、清洗与时间对齐、索引与存储、检索与重排、多智能体推理调度、回测与风控。这六个模块的依赖关系是单向递进的,理解起来比较清晰。
2.1 模块总览与核心依赖
| 模块 | 职责 | 关键依赖 |
|---|---|---|
| 数据采集与血缘管理 | 对接行情、财报、公告、新闻、研报等数据流,记录数据来源与版本 | 数据提供商、抓取调度 |
| 清洗与时间对齐 | 去重、去噪、实体归一化、统一时区对齐到交易日历 | 上一级原始库 |
| 索引与存储 | 分区存储原始数据,同时产出倒排索引、向量索引、时序索引 | 清洗结果 |
| 检索与重排 | 按检索条件从多索引中召回候选片段,并用排序模型压缩数量 | 索引层 |
| 多智能体推理调度 | 多个角色共享检索结果与中间结论,最终生成决策建议 | 检索层、大模型 |
| 回测与风控 | 检验决策逻辑的历史表现,上线后持续监控,异常时熔断 | 全链路数据 |
这里面最容易被人忽视的其实是血缘管理。你可能觉得“数据从哪来的有什么好管的”,但在百亿token的工程里,数据源升级、字段调整、质量修复都会导致你重新灌一遍语料,没有版本管理你根本不知道当前线上跑的是哪一批数据,回测结果也无法复现。
2.2 多智能体的角色划分依据
为什么这个系统不直接用一个Agent大包办?我试过,效果很差。因为投资决策链路里每个环节对能力的要求不一样:有人需要严格的计算纪律,有人需要广泛的语言理解,有人需要保守的风险判断。把这些诉求塞进一个System Prompt里,模型会在不同任务间互相干扰,表现极不稳定。
我最终把链路拆成了五个角色:
- 数据采集Agent:负责监听新数据源事件,发现新入库的公告、新闻、行情快照,触发下游任务。
- 因子计算Agent:负责计算技术面、基本面相关指标,对数据做严格数学处理,输出标准化因子表。
- 新闻解读Agent:负责把一条新闻放到更长的时间上下文里解读,判断情绪倾向、事件类型和对相关标的的潜在影响。
- 风控Agent:负责检查信号是否触碰了回撤限制、持仓集中度限制、波动率限制,输出拒绝或警告。
- 决策Agent:负责汇总以上所有输出,按权重和规则生成最终结构化决策建议,供人类审核。
每个角色用不同的System Prompt,甚至可以用不同的模型规模和推理参数。因子计算Agent我甚至不用大模型,直接用规则脚本替代,因为数学计算用大模型属于自找麻烦。所以“多智能体”不等于“多个大模型聊天”,准确说是“多个专业模块共享推理语境”,其中有些模块是大模型,有些则是传统程序。这是很多人误解的地方。
2.3 为什么非要“检索增强”不可
有朋友问我:既然模型已经训练了那么多知识,为什么不直接让它基于自己的记忆分析,非要加检索?我反问他:你敢让一个研究员凭记忆报三年前的财报数据吗?大概率数字是编的。大模型也存在同样的问题,尤其是对细粒度的结构化数据,它记不准,也容易混淆不同年份的数据。
检索增强的本质是把事实存储从模型参数中剥离出来,放到一个可更新、可审计的外部数据库里。模型负责理解和推理,数据库负责记忆事实。这样我们每次分析某个标的时,可以直接拉取该标的最新财报、近期新闻、公告原文,再让模型基于这些拉取到的内容做判断,而不是让它去回忆“大概是这样”。
我在实现上用的是“混合检索+引用约束”的方案,后面第四章会详细讲。这里先记住一个结论:没有检索增强的投资agent就是空中楼阁,它无法处理实时信息,也无法为你提供可验证的推理依据。
3. 数据基建:百亿token语料是怎么从0落地的
做投资agent,模型选型、Prompt调优都是后话,真正决定系统上限的是数据。我这次把语料推到百亿token这个量级,过程中最痛苦的不是模型推理慢,而是数据管道动不动就崩,或者数据对不齐导致回测结果虚假得离谱。下面按环节说。
3.1 多源数据接入与时间对齐
系统涉及的原始数据主要分为五大类:行情数据、财务报表、上市公司公告、新闻资讯、券商研报。每一类的更新频率、数据格式、时区体系都完全不同。比如行情数据是秒级甚至毫秒级,公告和新闻是事件驱动发布,财报则是季度级别的定期数据。
最折磨人的就是时间对齐。新闻的“发布时间”和它影响的“业务时间”经常不一致。比如某公司晚上八点发公告,直接影响第二天的开盘情绪,那这条公告到底应该归属到哪一天?如果按发布时间归属,当天的行情已经收盘,根本无法反映这个信息;如果按下一个交易日归属,那回测时就要把“信息产生时间”与“可交易时间”严格区分开。
我们的做法是引入统一交易日历,把所有事件都映射到一个“最早可知情时间”字段。这个字段用于回测时判定信息是否在当时已经被市场获得,从而避免前视偏差。原始数据的原始时间戳也会完整保留,绝不覆盖。这里我强烈建议所有做金融数据工程的朋友,永远不要试图“修改”原始数据,只能“新增”派生字段。因为一旦原始时间戳被污染,整个数据集的审计价值归零。
3.2 清洗、去重与分块策略
原始数据里充斥着重复新闻、转载内容、模板化公告、无效字符。如果不清洗,你检索的时候会召回几十条同一个事件的重复报道,浪费宝贵的上下文窗口,也让模型抓不住重点。
新闻去重我用的是MinHash+LSH方案,先计算文本的SimHash指纹,再通过局部敏感哈希把相似文本聚到一起,保留最早、最完整的一篇作为主文档。这个方案对“同一条快讯被几十家媒体转载”的场景效果很好。
分块策略也做了很长时间的调优。我最终的方案是按时间桶再按实体桶两级切分,块大小控制在256到512 token之间,相邻块之间重叠约32 token。为什么不直接按段落切?因为金融文本里很多关键信息是分散在多个段落里的,比如一个公告里“交易概述”在第一段,“风险提示”在最后一段,但两者在同一语境下。按固定窗口分块并加上少量重叠,能比较好地保住局部语义的连续性。
3.3 百亿token落地的分布式处理
百亿token是什么概念?按中文平均每token约1.5个汉字估算,大概对应15亿汉字左右的语料量,压缩成纯文本也有几十GB。这个量级单机处理会很尴尬,分布式是必须的。
我把处理流程抽成了三个Stage:原始清洗、分块标准化、向量化入库。每个Stage都是独立的计算任务,可以横向扩展。向量化阶段我用了一个开源的嵌入模型,在8卡GPU集群上跑,吞吐量大概能到每秒几百条文本。算个账你就知道为什么这个阶段要开集群:如果一条文本约500 token,百亿token就是200万条文档,即使每秒处理300条,单批也要跑接近两个小时。这还只是向量化,前面还有分词、清洗、校验等多个阶段。所以整个pipeline必须支持增量处理和断点续跑,否则每次新增数据都要全量重跑,会崩溃的。
这里有个小技巧:每个文档入库时写入哈希指纹,新增任务先比对指纹,相同的直接跳过。这样如果某一批数据重复推送,系统能自动幂等,不会重复占存储。整套增量流程上线后,数据延迟从原来的“全量更新需要两三天”压到了“新数据进入库到可被检索小于五分钟”。
3.4 存储层选型
存储层我拆成了三种索引协同工作:倒排索引负责关键词精确匹配,向量索引负责语义相似检索,时序索引负责按时间范围聚合。你可能会问为什么不用一个全能的向量库搞定一切,因为投资场景的查询经常是结构化的,比如“某标的最近三天的所有公告”,这种查询是纯粹的KV型过滤,用向量索引去做完全是用牛刀杀鸡,效率和准确率都不行。
我的存储方案是:原始文档放对象存储,按日期和标的类型分区;倒排索引单独建,用于精确词匹配;向量索引单独建,用于语义召回;元数据全部进宽表数据库,用于结构化过滤。四者通过统一的文档ID关联。方案看起来笨,但胜在每一层都能独立扩展,且任何一个索引挂了都不影响其他模块运转。
4. RAG检索系统:让agent学会“有据可查”
如果说数据基建是地基,那检索系统就是整个agent的信息入口。检索系统做得不好,模型再聪明也没用,因为喂进去的上下文是脏的、偏的、过时的。这个模块我迭代的版本最多,从最开始的纯关键词检索到后来的混合检索+重排,效果有质的提升。
4.1 混合检索:为什么单靠向量不够
刚开始图省事,只用了向量检索。测下来发现两个问题:一是金融领域专有名词和缩写很多,比如某个公司简称、某个行业术语,在语义向量空间里经常匹配不到;二是有些需要精确匹配的内容,比如股票代码、报告期编号、公告编号,用语义检索会召回一堆不相关的内容。
后来改成“BM25倒排检索+稠密向量检索”双通道,两种召回结果合并后去重。为什么这个组合对金融有效?因为倒排索引能确保“代码、名称、术语”这类精确信息一定被召回,向量检索能兜住“语义改写、同义表达”这类模糊场景。比如用户写“盈利下滑”,倒排可能搜不到什么,但向量能把它和“净利润减少”“业绩不佳”等表述关联起来。
双通道合并后召回数量还是太多,需要做重排压缩。我用的方案是对候选片段打一个相关度分,综合考量文本语义相似度、时间衰减、实体匹配度三个维度。时间衰减很重要,否则一条两年前的旧新闻会跟一条昨天的快讯排在一起,模型很容易被旧信息带偏。
4.2 向量化模型选型与领域适配
嵌入模型我对比过多个开源方案,最终并没有选择参数最大的那个,而是选了在中文金融语料上综合表现最稳的一个通用模型,因为金融领域对“数字敏感性”要求很高,纯通用模型在涉及数字比较、时间关系、否定语义的场景下容易出错的概率明显更大。
测试中我专门构造了一个领域测试集,包含三类问题:数字比较类(“某公司去年利润是否为正增长”)、否定语义类(“某公告是否构成业绩承诺”“某声明是否实质构成减持”)、事件时序类(“该增持计划是在回购之前还是之后”)。前两类问题最能暴露模型和检索的缺陷。结果显示,在加入检索后准确率明显提高,但在否定语义上仍然有稳定比例的错误,这个只能靠后续微调或者规则修正去补。
百亿token语料本身就是做领域适配的好原料。我建议如果条件允许,用这部分语料对嵌入模型做继续预训练或者对比学习微调,效果会更好。但我们当时为了控成本,选择先以通用模型垫底,微调放到二期,这个决定回溯来看是可以接受的——先把链路跑通,再逐步优化瓶颈。
4.3 引用约束与防幻觉设计
防幻觉不能靠“叮嘱模型不要编”,要靠约束机制。我的做法是为每一条检索结果附带一个引用ID,并在Prompt里强约束:模型的每一句事实性描述都必须附上引用ID,没有引用支撑的内容必须明确标注为“推测”。同时生成的结果会经过一个引用校验器,去核实每个引用ID是否真的存在于检索结果中,如果模型用了不存在的引用ID,该回复会被直接打回重写。
金丝雀测试集是另一道防线。我每个季度会人工挑一批有明确答案的问题,送入系统走全链路,核对模型的输出是否引用了正确的数据源、是否给出了正确的结论。这项工作是纯人工的,很费时间,但它是整个系统质量的生命线。没有它,你根本不知道系统上线后是变好了还是悄悄变坏了。
5. 多智能体协作:从情报到投资决策的完整链路
系统最终产出的是一个结构化的决策建议,这个建议不是一步到位的,而是五个智能体接力协作的结果。让我具体讲讲它们之间是怎么配合的。
5.1 智能体之间到底怎么沟通
首先明确一点:我没有让智能体之间用自然语言互相聊天。很多人一提到多智能体就想到A说一句B接一句,这在投资场景里既慢又不可控。我采用的是“结构化消息+共享黑板”机制。每个智能体完成后把输出写成一个JSON消息,包含结构化字段、结论摘要、引用片段列表、置信度。决策Agent读取这些汇总消息,而不是去读其他Agent的冗长分析原文。
这样做的好处有三个:第一是上下文占用低,决策Agent不需要把所有分析原文都塞进来,只需要读摘要;第二是可调试,每个Agent输出都是结构化的,中间逻辑出了问题能快速定位到具体环节;第三是可审计,谁在什么时候得出了什么结论,全部留痕。
共享黑板机制则用于记录全局状态,比如当前正在分析哪些标的、哪些结论已经被确认、哪些还在待复核。这样不同Agent不会重复检索相同的内容,大幅节省了推理和数据查询的开销。
5.2 事件驱动的主循环与任务调度
系统不是“用户问一句答一句”的同步模式,而是事件驱动的异步模式。核心事件循环可以简化为以下伪代码:
while True: event = event_bus.next_event() if event.type == "NEW_DATA": tasks = parse_data_event(event) for task in tasks: dispatcher.submit(task, role="FACTOR_WORKER") elif event.type == "FACTOR_DONE": res = event.result if res.has_anomaly(): dispatcher.submit(res, role="NEWS_ANALYST") elif event.type == "ANALYSIS_DONE": dispatcher.submit(event.result, role="RISK_GUARD") elif event.type == "RISK_PASSED": dispatcher.submit(event.result, role="DECISION_MAKER", require_approval=True) elif event.type == "DECISION_PROPOSED": notify_human_approver(event.result)所有任务通过消息队列异步调度,各角色可以并发执行。比如同时来了十条公告,因子计算Agent可以并行处理多条,新闻解读Agent也不必等前一条完全结束才启动。这里有个工程细节:模型推理是最耗时的部分,所以凡是不需要大模型的环节(比如因子计算)尽量走普通计算,只有到新闻解读、决策汇总等真正需要语义理解的地方才调用大模型,这样能省很大一笔推理成本。
5.3 人工审批与权限设计
再说说人工审批这个落地层面的关键设计。当决策Agent输出一个带有方向性操作信号的建议时,系统并不会直接执行,而是进入一个人工审批队列。审批界面展示的内容包括:信号摘要、置信度、检索引用的原文列表、相关历史相似信号的回测统计结果。审批人只需要做三个动作:同意、拒绝、退回补充分析。
权限设计上也做了区分:不同级别的信号需要不同权限审批。比如“信息提示”级别可以自动推送到研究员的仪表盘,“交易建议”级别必须由具备相应权限的投资经理审批,“涉及集中度突破”这样的高风险信号则需要风控负责人二次审批。这种分级机制保证了系统在高效率的同时不会因为无人把关而失控。
6. 回测体系、上线监控与那些踩过的坑
系统光能跑通还远远不够,你必须有一个可靠的回测体系去验证它的历史表现,同时有一套上线后的监控与熔断机制来保证它不会在实盘中惹出大祸。这一块可以说是整个项目中“含金量”最高的部分,也是我踩坑最密集的地方。
6.1 基于事件驱动回测的投资场景
传统的回测框架大多以交易日为单位,按收盘价/开盘价计算交易结果,这在量价策略里够用。但要验证一个Agent的决策链路,这种框架远远不够,因为Agent的决策是事件驱动的,数据什么时候到达、什么时候被处理、什么时候模型做判断,这些都要精确模拟。
我最终搭建了一个事件驱动回测引擎,按时间顺序回放历史事件流,每个事件进入系统后走和线上完全相同的处理链路,然后记录决策信号及其之后一段时间的表现。回测过程中最核心的校验就是前视偏差:某个信号在回测中被触发时,用到的所有数据必须在该信号触发时间之前就已公开。
我在这里吃过一次大亏。早期回测数据显示系统信号准确率高得惊人,后来排查发现是公告数据在入库时被打上了“财报报告期”的时间戳,而不是“公告发布日期”的时间戳。结果就是2024年的一季报被当成2024年一季度初就已知晓的数据参与计算,相当于提前几个月“预知”了财报。那次教训让我彻底确立了“原始时间戳永久保留,其他字段只能新增派生”的数据铁律。
6.2 上线后的监控与熔断机制
系统上线后我设置了四个关键监控指标:推理延迟、检索命中率、空结果率、人工审批拒绝率。这四个指标各有意义:
- 推理延迟反映系统负载和模型推理性能;
- 检索命中率反映数据管线和检索质量,命中率突降通常意味着索引挂了或者数据断更;
- 空结果率则反映检索召回量是否合理,长期为0说明检索策略过于严格;
- 人工审批拒绝率是最重要的指标,它直接反映模型输出与人类专家判断的偏离程度,拒绝率突然飙高说明系统某个环节在恶化。
熔断机制分三档:软件熔断、策略熔断、人工熔断。软件熔断是当推理延迟或错误率超过阈值时,系统自动减少任务并发量;策略熔断是当模拟盘或小资金实盘的当日回撤超过设定阈值时,系统自动进入只读模式,只输出分析不再更新交易建议;人工熔断则是任何一个具备权限的成员都可以一键停掉整个Agent的自动决策链路。我强烈建议任何做AI决策系统的团队都要把人工熔断放在最高优先级,因为再完备的自动化也会遇到意想不到的情况,最终兜底的必须是人。
6.3 关于数据质量、评估指标的几点血泪经验
最后再分享几个我反复踩过的坑,希望后来者能一次避开。
第一,评估一个投资agent不要只看收益率。收益率只是结果,而且受市场环境影响极大。我后来构建了一套多维评估体系:信号准确率、决策稳定度、最大回撤、可解释性评分、引用正确率。这五个维度放在一起看,才能比较全面地判断Agent是不是真的在“正确地工作”。
第二,不要试图用一个全能的评估集去覆盖所有场景。我早期把几千条历史样本混合做评测,结果模型在某些类上表现好、某些类上很差,但混合分数看起来还凑合。后来改成按事件类型分层评估,比如“财报类”“公告类”“新闻类”“宏观类”各自单独出报告,问题才暴露出来。分层评估的额外好处是你可以针对最差的部分单独做数据补充和Prompt优化。
第三,也是我觉得最值得强调的一点:不要一头扎进全自动。我第一次搭建这个系统的目标曾经是全流程无人化,结果在开发周期上付出了巨大代价。后来把目标改成“人机协同”——机器负责信息收集和初步分析,人负责最终判断,整个项目才真正跑通。与其追求一个完美但迟迟无法落地的“终极形态”,不如先把“辅助工具”做得足够好用、足够可靠。
这个投资agent做到现在,我最大的体会是:最重的不是模型能力,而是数据质量和评估体系。大模型技术在飞速迭代,但如果没有干净、可信、可追溯的数据底座,再强的模型也会在真实场景里摔跤回测和上线监控不是可选的加分项,而是整个系统的安全底线。如果你也想做类似的东西,我的建议是别想着一步到位,把单个任务助手先跑通,比如只做新闻情绪分析、只做财报异常扫描,再逐步扩展成多角色协同。投资决策的最终责任始终要由人来承担,agent的角色是帮你把信息处理得更快、更准、更完整。这个定位想清楚了,后面所有的技术取舍都会顺很多。