作者两年金融AI智能体实战经验,分享沙盒隔离、数据归一化、技能系统、Temporal工作流等核心技术,强调可靠性优先于速度。
我花了两年时间给金融行业搞AI智能体。这一路踩了无数坑,今天想把学到的东西分享出来。
我要讲的内容包括:
- 沙盒不是可有可无——为啥多步骤智能体工作流必须有隔离的执行环境
- 上下文就是产品——我们怎么把五花八门的金融数据整理成干净、可搜索的上下文
- 解析难题——从"故意刁难人"的SEC文件里提取结构化数据有多难
- 技能才是一切——为啥基于Markdown的技能正在成为产品核心,而不是模型本身
- 模型会吃掉你的脚手架——设计系统时要考虑到模型进步后的淘汰问题
- S3优先架构——为啥S3比数据库更适合存文件和用户数据
- 文件系统工具——ReadFile、WriteFile和Bash怎么搞定复杂的金融工作流
- Temporal改变了一切——可靠的长任务运行和完善的取消处理
- 实时流式传输——用增量更新和交互式智能体工作流打造响应快的用户体验
- 评估不是可选的——领域专用的评测能在错误造成损失前抓住它们
- 生产环境监控——让金融智能体保持可靠的监控体系
为啥金融服务超级难搞
这个领域不原谅错误。数字必须准。营收数字错了、业绩指引理解歪了、DCF假设有问题——专业投资人会根据我们的输出做上百万美元的决策。在1亿美元的仓位上犯一次错,你就永远失去了信任。
用户也很挑剔。专业投资人是世界上最聪明、时间最紧的人之一。他们一眼就能看穿胡说八道。他们需要精确、速度和深度。估值模型不能糊弄,财报电话会议的细微差别不能忽视。
这让我养成了近乎偏执的细节控习惯。每个数字都要复核。每个假设都要验证。每个模型都要压力测试。你会开始质疑LLM输出的所有东西,因为你知道用户也会这么干。DCF模型里一个计算错误,你就永远失去了信誉。
有时候我觉得,"怕出错"反而成了我们最好的功能。
早期的大胆押注
过去几年搞LLM,我们在基础设施上做了不少大胆的早期押注,现在看都押对了。比如Claude Code推出"文件系统优先"的智能体方案时,我们立马就采用了。当时这并不明显,而且要大改架构。我特别幸运,有[某人]跟我Zoom了一下,让我看到了可能性。
当时整个行业,包括Fintool,都在搞复杂的RAG流水线,用向量数据库和嵌入。反思了智能体的信息检索未来后,我写了篇文章,然后Fintool全面转向智能体搜索。我们甚至决定砍掉宝贵的嵌入流水线。心疼,但为了未来嘛!
当时人们觉得我们疯了。那篇文章有人点赞,也有人骂。现在我觉得大部分创业公司都在采用这些最佳实践了。
我觉得我们在其他几个架构选择上也走在前面。分享出来是因为检验想法的最好方式就是公开。
沙盒不是可有可无
2023年刚开始搞Fintool时,我觉得沙盒可能多此一举。"我们就是跑Python脚本而已",我心想,"能出啥事?"
哈哈。啥事都能出。
第一次LLM在我们的服务器上执行rm -rf /时(它想"清理临时文件"),我成了真正的信徒。
问题是:智能体需要执行多步骤操作。专业投资人要个DCF估值,这不是一次API调用就完事的。智能体需要研究公司、收集财务数据、在Excel里建模型、跑敏感性分析、生成复杂图表、迭代假设。这是几十个步骤,每一步都可能修改文件、安装包、运行脚本。
没有代码执行你搞不定这个。但在你的服务器上执行任意代码是疯了。每个聊天应用都需要沙盒。
现在每个用户都有自己的隔离环境。智能体在里面爱干嘛干嘛。全部删掉?行。装奇怪的工具包?随意。这是你的沙盒,随便折腾。
架构长这样:
三个挂载点。Private是你的读写空间。Shared是组织只读的。Public是所有人只读的。
关键在于凭证。我们用AWS ABAC(基于属性的访问控制)生成短期凭证,限定到特定的S3前缀。
用户A根本访问不了用户B的数据。IAM策略用${aws:PrincipalTag/S3Prefix}来限制访问。凭证物理上就不允许。这对企业部署也很好。
我们还做沙盒预热。用户开始打字时,我们在后台启动他们的沙盒。
等他们按回车时,沙盒已经准备好了。600秒超时,每次使用工具延长10分钟。沙盒在对话轮次间保持热状态。
沙盒很棒,但沙盒被低估的魔力在于对文件系统的支持。这就引出了下一个关于上下文的教训。
上下文就是产品
你的智能体好不好,取决于它能访问到什么上下文。真正的工作不是提示工程,而是把几十种来源的 messy 金融数据整理成智能体能用的干净、结构化上下文。这需要工程团队有深厚的领域专业知识。
数据异构性问题
金融数据格式五花八门:
- SEC文件:带嵌套表格、附件、签名的HTML
- 财报电话会议记录:按发言人分段的文本,有问答环节
- 新闻稿:来自PRNewswire的半结构化HTML
- 研究报告:带图表和脚注的PDF
- 市场数据:Snowflake/数据库里的结构化数字
- 新闻:质量和结构各异的文章
- 另类数据:卫星图像、网站流量、信用卡数据
- 券商研究:带目标价和模型的专有PDF
- 基金文件:13F持仓、代理声明、激进投资者信件
每个来源有不同的schema、更新频率、质量等级。
智能体只需要一样东西:能推理的干净上下文。
归一化层
所有东西变成三种格式之一:
- Markdown用于叙事内容(文件、记录、文章)
- CSV/表格用于结构化数据(财务数据、指标、对比)
- JSON元数据用于可搜索性(股票代码、日期、文件类型、财年)
分块策略很重要
不是所有文档都一样的分块方式:
- 10-K文件:按监管结构分块(Item 1、1A、7、8……)
- 财报电话会议:按发言回合分块(CEO发言、CFO发言、分析师问答)
- 新闻稿:通常小到可以整块处理
- 新闻文章:段落级分块
- 13F文件:按持有人和季度持仓变化分块
分块策略决定了智能体能检索到什么上下文。分块不好=答案不好。
表格是特殊的
金融数据充满了表格和CSV。营收细分、部门表现、业绩指引区间。LLM对Markdown表格的推理出奇地好:
但对HTML 元数据实现检索 用户问智能体:"苹果上次财报电话会议怎么说的服务营收?" 要回答这个,Fintool需要: - 股票代码解析(AAPL→正确的公司) 这就是为啥每个文档都要有 谁都能调用LLM API。但不是谁都有几十年的金融数据被归一化成带正确元数据的可搜索、分块Markdown。数据层才是让智能体真正工作的关键。 解析难题 归一化金融数据占80%的工作量。有些事儿没人告诉你。 SEC文件是故意刁难人的 它们不是为机器阅读设计的,是为法律合规设计的: - 表格跨多页,表头重复 我们试过现成的PDF/HTML解析器。它们在以下情况失败了: - 代理声明的多栏布局 Fintool的解析流水线 原始文件(HTML/PDF) 表格提取值得单独说 金融表格信息密度极高。一个营收细分表格可能有: - 合并的表头单元格,跨多列 我们对每个提取的表格评分: - 单元格边界准确度(分对/合对了吗?) 置信度低于90%的表格被标记审核。低置信度提取不进智能体的上下文——垃圾进,垃圾出。 财年归一化至关重要 "2024年Q1"是模糊的: - 日历Q1(2024年1-3月) 我们维护了1万多家公司的财年日历。每个日期引用都归一化成绝对日期范围。当智能体检索"苹果2024年Q1营收"时,它知道要找2023年10-12月的数据。 这对用户不可见,但对正确性至关重要。没有它,你会把苹果10月的营收和微软1月的营收对比,然后说"同一个季度"。 技能才是一切 关于搞AI智能体,有件事没人告诉你:模型不是产品。技能才是产品。 我是硬学会的。我们以前试图通过提示工程让基础模型"更聪明"。调整系统提示、加例子、写详细指令。有点用,但技能才是缺失的部分。 2025年10月,Anthropic正式推出了技能规范,用模块化能力包扩展Claude。技能是一个文件夹,包含带YAML frontmatter(名称和描述)的 我们在公告前几个月就在搞类似的东西。验证感觉很好,但更重要的是,有行业标准意味着我们的技能最终可以移植。 没有技能,模型对领域任务出奇地烂。让前沿模型做个DCF估值。它知道DCF是啥,能解释理论。但实际执行?它会漏关键步骤、用错行业折现率、忘记加回股权激励、跳过敏感性分析。输出看起来合理,但在重要的地方 subtly 错了。 突破来自于我们开始把技能当成一等公民。像产品本身的一部分。 技能是一个Markdown文件,告诉智能体怎么干特定的事。 技能比代码好 这极其重要: 1. 非工程师也能创建技能。我们的分析师写技能。我们的客户写技能。做过500个DCF估值的投资经理,不用写一行Python就能把自己的方法论编码成技能。 2. 无需部署。改个技能文件立即生效。没有CI/CD,没有代码审查,不用等发布周期。领域专家可以自己迭代。 3. 可读且可审计。出问题时,你能读技能文件,准确理解智能体该干嘛。试试对2000行的Python模块做这个。 我们有写时复制的影子系统:优先级:private > shared > public 所以你不喜欢我们的DCF估值方式?自己写一个。丢到 为啥我们不把所有技能挂载到文件系统 这很重要。 天真的做法是把每个技能文件直接挂载进沙盒。智能体可以 错了。这是我们用SQL发现的原因: 1. 懒加载。我们有几十个技能,文档很庞大——光是DCF技能就有10多个行业指南文件。每次对话都加载全部会烧token、搞晕模型。相反,我们先发现技能元数据(名称、描述),只有智能体实际使用时才加载完整文档。 2. 查询时的访问控制。SQL查询实现了我们的三层访问模型:公开技能对所有人可用,组织技能给该组织用户,私有技能给个人用户。数据库强制执行。你不会不小心把客户的专有技能暴露给另一个客户。 3. 影子逻辑。当用户自定义技能时,他们的版本需要覆盖默认的。SQL让这变得 trivial——查三层,应用优先级规则,返回胜者。用文件系统挂载做这个会是符号链接和目录顺序的噩梦。 4. 元数据驱动过滤。 模式是:S3是事实来源,Lambda函数把变更同步到PostgreSQL用于快速查询,智能体在需要时得到需要的东西。 技能是必不可少的。我再强调都不为过。如果你搞AI智能体却没有技能系统,你会很难受。我主张技能的最大理由是:顶级模型(Claude或GPT)在后训练中就学会了使用技能。模型想要获取技能。 模型就是想学习,而它们想学的是我们的技能……直到它们学会了。 模型会吃掉你的脚手架 不舒服的真相:我刚才告诉你的关于技能的一切?我觉得是暂时的。 模型在快速进步。每隔几个月就有新模型让你一半的代码过时。你为处理边缘情况搭的复杂脚手架?模型现在……直接处理了。 我们刚开始时,需要详细的技能,一步步指导一些简单任务。"先做X,再做Y,然后检查Z"。现在?简单任务我们经常只说"做个盈利预览",模型就能搞定(差不多!) 这制造了一种奇怪的紧张。你今天需要技能,因为当前模型不够聪明。但你设计技能时应该知道,未来模型需要更少的手把手指导。这就是我看好Markdown文件而非代码来给模型下指令的原因。更容易更新和删除。 我们给AI实验室发详细反馈。每当我们为绕过模型限制搭复杂脚手架时,我们记录模型具体在哪 struggle,分享给实验室研究团队。这有助于指导下一代模型。目标是让我们自己的脚手架过时。 我的预测:两年后,我们大部分基础技能会是一句话。"生成20个标签页的DCF。"就这些。模型会知道这意味着什么。 但反过来:随着基础任务商品化,我们会推向更复杂的领域。分部门的多步骤估值。投资策略的自动回测。带复杂触发器的实时组合监控。前沿一直在移动。 所以我们写技能。不需要了就删。为出现的新难题建新的。而这些都是文件……在我们的文件系统里。 S3优先架构 有件事让我惊讶:S3存文件比数据库更好。 我们把用户数据(观察列表、组合、偏好、记忆、技能)存成S3里的YAML文件。S3是事实来源。Lambda函数把变更同步到PostgreSQL用于快速查询。 写入→S3(事实来源) 为啥? - 持久性:S3有11个9的持久性。数据库没有。 模式: - 写入直接到S3 同步架构 我们跑两个Lambda函数保持S3和PostgreSQL同步: S3(文件上传/删除) EventBridge(每3小时) 都用带时间戳守卫的upsert——新数据永远赢。reconcile任务抓住漏掉的事件(S3最终一致性、Lambda冷启动、网络闪断)。 用户记忆也在这 每个用户在S3里有个 这出奇地强大。用户写的东西比如"我专注小盘价值股"或"永远跟行业中位数比,不是均值"或"我的组合集中在科技,所以标记集中风险"。智能体每次对话都看到,相应调整。 没有迁移。没有schema变更。就是用户控制的一个Markdown文件。 观察列表同理。S3里的YAML文件,同步到PostgreSQL用于快速查询。当用户问"我的观察列表"时,我们加载相关股票代码,注入为上下文。智能体知道用户关心哪些公司。 文件系统成了用户的个人知识库。技能告诉智能体怎么做事。记忆告诉它用户关心什么。两者都只是文件。 文件系统工具 金融服务的智能体需要读写文件。很多文件。PDF、电子表格、图像、代码。我们是这么处理的。 ReadFile处理复杂性: WriteFile创建链接回UI的产物: Bash给持久的shell访问,180秒超时,10万字符输出限制。所有路径都归一化(LLM喜欢尝试路径遍历攻击, hilarious)。 Bash比你想象的更重要 AI社区越来越有一种共识:文件系统和bash是AI智能体的最佳抽象。 比较了SQL智能体、bash智能体和混合方法查询半结构化数据。 结果有意思:纯SQL达到100%准确度但漏掉边缘情况。纯bash更慢更贵但抓住验证机会。赢家?混合方法——智能体用bash探索和验证,SQL做结构化查询。 这符合我们的经验。金融数据 messy。你需要bash来grep文件、找模式、探索目录结构。但你也需要结构化工具来做重活。智能体两者都需要——以及判断何时用哪个的判断力。 我们大力给智能体沙盒里的完整shell访问。不只是跑Python脚本。是为了探索、验证,以及复杂任务需要的临时数据操作。 但复杂任务意味着长时间运行的智能体。而长时间运行的智能体会搞砸一切。 Temporal改变了一切 用Temporal之前,我们的长任务是个灾难。用户要个全面的公司分析。那要花5分钟。如果服务器重启呢?如果用户关了标签页再回来呢?如果……任何情况? 我们有个自研的任务队列。很烂。重试不一致。状态管理是噩梦。 然后我们换了Temporal,我想喜极而泣! 就这些。Temporal处理worker崩溃、重试、一切。如果Heroku dyno对话中途重启(经常发生lol),Temporal自动在另一个worker上重试。用户永远不知道。 取消处理是棘手的部分。用户点"停止",会发生啥?活动已经在另一台服务器上运行了。我们用每隔几秒发送的心跳。 我们跑两种worker类型: - 聊天worker:面向用户,25个并发活动 它们独立扩展。聊天流量激增?扩展聊天worker。 实时流式传输 在金融,人都没耐心。他们不会盯着加载转圈等30秒。他们需要看到事情在发生。 所以我们建了实时流式传输。智能体工作,你看到进度。 智能体→SSE事件→Redis流→API→前端 关键洞察:增量更新,不是全状态。不是发"这是目前的完整响应"(贵),而是发"追加这50个字符"(便宜)。 用Streamdown流式传输富内容 文本流式传输是基本要求。更难的问题是流式传输富内容:带表格、图表、引用、数学公式的Markdown。我们用Streamdown在内容到达时渲染Markdown,有我们领域特定组件的自定义插件。 图表渐进渲染。引用链接到源文档。数学公式用KaTeX正确显示。用户看到完整、交互式的响应实时构建。 AskUserQuestion:交互式智能体工作流 有时候智能体需要用户在工作流中输入。 "你偏好哪种估值方法?" 我们建了 当智能体调用这个工具时,智能体循环拦截它,保存状态,给用户呈现UI。用户选一个选项(或输入自定义答案),对话用他们的选择继续。 这让智能体从自主黑盒变成协作工具。智能体做重活,但用户掌控关键决策。对高风险金融工作必不可少,用户需要验证假设。 评估不是可选的 "快速上线,后面再修"对大部分创业公司管用。对金融服务不管用。 一个盈利数字错了可能让人亏钱。业绩指引理解错了可能导致糟糕的投资决策。你不能"后面再修",当用户基于你的输出做百万美元决策时。 我们用Braintrust做实验追踪。每个模型变更、每个提示变更、每个技能变更都要在测试集上评估。 通用NLP指标(BLEU、ROUGE)对金融不管用。响应语义相似但数字完全错的情况很多。构建评估数据集比构建智能体还难。我们维护了约2000个测试用例,跨类别: 股票代码消歧 这出奇地难: - "Apple"→AAPL,不是APLE(Appel石油) 真正恶心的是股票代码变更。Facebook 2021年变成META。Google重组为GOOG/GOOGL。Twitter变成X(但法律实体保留)。当用户问"Facebook股票2023年咋了?",你需要知道FB→META,2021年10月前的历史数据在老代码下。 我们维护了股票代码历史表,过去十年每次大改名都有测试用例。 财年地狱 这是大部分金融智能体默默失败的地方: - 苹果Q1是10-12月(财年9月结束) "上个季度"在1月15日意味着: - 日历年公司Q4 2024 我们维护了1万多家公司的财年日历。每个期间引用都归一化成绝对日期范围。期间提取就有200多个测试用例。 数字精度 42亿美元 vs 4200百万美元 vs 42亿美元 vs "四十二亿"。都等价。但单写"4.2"就错了——缺单位。是百万?十亿?每股? 我们测试单位推断、量级归一化、货币处理。说"营收是4.2"没单位的响应评估失败,即使4.2B是对的。 对抗性 grounding 我们在上下文里注入假数字,验证模型引用真实来源,不是植入的那个。 例子:我们包含一份假分析师报告说"苹果营收500亿美元", alongside 显示940亿美元的真实10-K。如果智能体引用500亿,失败。如果它引用940亿并正确标注来源,通过。我们有50个专门测试幻觉抵抗的用例。 评估驱动开发 每个技能有配套的评估。DCF技能有40个测试用例,覆盖WACC边缘情况、终值合理性检查、股权激励加回(模型经常忘这个)。 如果评估分数下降超5%,PR被拦。没有例外。 生产环境监控 我们的生产设置长这样: 我们自动为生产错误提交GitHub issue。错误发生,issue被创建,带完整上下文:对话ID、用户信息、堆栈跟踪、Braintrust跟踪和Temporal工作流的链接。付费客户得 按复杂度路由模型:简单查询用Haiku(便宜),复杂分析用Sonnet(贵)。企业用户永远用最好的模型。 最大的教训 最大的教训不是关于沙盒或技能或流式传输。而是这个: 模型不是你的产品。模型周围的体验才是你的产品。 谁都能调用Claude或GPT。API对每个人都一样。让你的产品不同的是其他一切:你能访问的数据、你构建的技能、你设计的UX、你工程化的可靠性——以及 frankly 你对行业了解多深,这取决于你跟客户花了多少时间。 模型会持续进步。很好!意味着更少的脚手架、更少的提示工程、更少的复杂度。但也意味着模型变得更商品化。 你的护城河不是模型。你的护城河是你围绕它构建的一切。 对我们来说,那是金融数据、领域特定技能、实时流式传输,以及与专业投资人建立的信任。
标签或原始CSV转储的推理就很烂。归一化层把所有东西转成干净的Markdown表格。
- 文件类型过滤(财报记录,不是10-K)
- 时间过滤(最新的,不是2019年的)
- 章节定位(CFO发言或营收讨论,不是法律免责声明)meta.json。没有结构化元数据,你就是在干草堆里做关键词搜索。有了元数据,搜索速度快多了!
- 脚注引用附件,附件又引用其他脚注
- 数字出现在文本、表格、附件中——有时候还不一致
- XBRL标签存在,但经常错或不全
- 格式因申报者而异(每个律所有自己的模板)
- MD&A章节的嵌套表格(表格套表格套表格)
- 水印和页眉渗入内容
- 扫描附件(老文件和附件里还常见)
- Unicode问题(弯引号、破折号、不间断空格)
↓
文档结构检测(标题、章节、附件)
↓
表格提取,保留单元格关系
↓
实体提取(公司、人、日期、金额)
↓
交叉引用解析(附件10.1→实际附件内容)
↓
财年归一化(FY2024→苹果是2023年10月到2024年9月)
↓
质量评分(每个提取字段的置信度)
- 脚注标记(1)、(2)、(a)、(b)引用下方解释
- 负数用括号:$(1,234)表示-1234
- 同一表格混合单位(营收用百万,利润率用百分比)
- 前期重述用斜体或星号标注
- 表头检测(第1行是表头,还是上面还有标题行?)
- 数字解析("$1,234"是解析成1234还是保留文本?)
- 单位推断(百万?十亿?每股?百分比?)
- 苹果财年Q1(2023年10-12月)
- 微软财年Q1(2023年7-9月)
- "在Q1报告的"(Q1提交的,但覆盖前期)SKILL.md文件,加上智能体可能需要的任何支持脚本、引用或数据文件。# 何时使用
用于现金流折现估值。
# 指令
1. 用Task工具深入研究公司(了解所有部门)
2. 确定公司行业,加载行业特定指南
3. 收集财务数据:营收、利润率、资本支出、营运资本
4. 用xlsx技能在Excel里建DCF模型
5. 用行业基准计算WACC
6. 对WACC和终值增长率做敏感性分析
7. 验证:基准年与实际值对账,与市场价格对比
8. 记录你的观点vs市场定价
# 行业指南
- 科技/SaaS:<code>/public/skills/dcf/guidelines/technology-saas.md</code>
- 医疗/制药:<code>/public/skills/dcf/guidelines/healthcare-pharma-biotech.md</code>
- 金融服务:<code>/public/skills/dcf/guidelines/financial-services.md</code>
[……10多个行业,各有特定方法论]
就这些。一个Markdown文件。不改代码。不用部署。就是一个文件告诉智能体该干嘛。/private/skills/dcf/SKILL.md。你的版本优先。cat任何需要的技能。简单,对吧?sql
SELECT user_id, path, metadata
FROM fs_files
WHERE user_id = ANY(:user_ids)
AND path LIKE 'skills/%/SKILL.md'fs_files.metadata列存解析后的YAML frontmatter。我们可以按技能类型过滤、检查技能是否仅限主智能体使用,或查询任何其他结构化属性——都不用读文件本身。
↓
Lambda触发
↓
PostgreSQL(fs_files表)
↓
读取←快速查询
- 版本控制:S3版本控制免费给你审计追踪
- 简单:YAML文件人类可读。你可以用cat调试。
- 成本:S3便宜。数据库存储不便宜。
- 列表查询走数据库(快)
- 单条读取走S3(最新鲜的数据)
↓
SNS主题
↓
fs-sync Lambda→在fs_files表中插入/更新/删除(实时)
↓
fs-reconcile Lambda→全量S3 vs DB扫描,修复差异/private/memories/UserMemories.md文件。就是Markdown——用户可以直接在UI里编辑。每次对话我们加载它,注入为上下文:python
org_memories, user_memories = await fetch_memories(safe_user_id, org_id)
conversation_manager.add_backend_message(
UserMessage(content=f"
)
# /private/artifacts/里的文件变成可点击链接
# computer://user_id/artifacts/chart.png → 在查看器中打开
- 后台worker:异步任务,10个并发活动typescript
enum DeltaOperation {
ADD = "add", // 在索引处插入对象
APPEND = "append", // 追加到字符串/数组
REPLACE = "replace",
PATCH = "patch",
TRUNCATE = "truncate"
}
"我该用一致预期还是管理层指引?"
"你想让我把管线资产纳入估值吗?"AskUserQuestion工具,让智能体暂停、呈现选项、等待用户输入。
- "Meta"→META,不是MSTR(有人叫它"meta")
- "Delta"→DAL(航空公司)还是用户在说delta对冲(期权术语)?
- 微软Q2是10-12月(财年6月结束)
- 大部分公司Q1是1-3月(日历年)
- 苹果Q1 2025(刚发布)
- 微软Q2 2025(季度中)priority:high标签。
简称:沙盒、技能、流式传输!