1. 金融场景下的智能体工程化落地思路
1.1 为什么金融行业对智能体有真实需求
金融行业每天要处理的东西,说白了就是三件事:数据、规则、沟通。数据来自行情、财报、交易流水、风控日志;规则来自监管口径、内部合规、产品条款;沟通则是客户经理、研究员、运营、客服之间的信息流转。这三件事有一个共同点——重复度高、容错率低、对时效敏感。
我最早接触金融类项目是在做数据中台的时候,当时最头疼的不是数据量,而是业务方总在问“这个指标为什么变了”“这条规则到底怎么算的”。后来大模型能力起来之后,我第一反应就是:能不能把这种“解释型、查询型、汇总型”的工作交给智能体去做。financial-services这个项目标题看起来很大,但落到工程上,其实就是把金融业务里的高频动作拆成可编排的智能体能力,再用 API 和插件把它们串起来。
这里要先把一个概念说清楚:智能体不是聊天机器人。聊天机器人是“你问我答”,智能体是“你给目标,它自己拆步骤、调工具、拿结果、做校验”。金融场景里真正有价值的是后者,因为金融业务很少是一问一答就能闭环的,往往需要查数据、比对规则、生成报告、再走审批流。
1.2 整体架构为什么这样选
我在设计这类项目时,习惯先把系统分成四层,这个分层不是拍脑袋,而是踩过坑之后总结出来的:
| 层级 | 职责 | 常见选型 | 选型理由 |
|---|---|---|---|
| 接入层 | 接收请求、鉴权、限流 | API 网关 + 密钥管理 | 金融场景必须可审计,所有调用要留痕 |
| 编排层 | 任务拆解、工具调度 | Agent 框架 + Skills 注册表 | 把“能力”和“流程”解耦,方便替换 |
| 能力层 | 具体工具、插件、模型调用 | 插件体系 + 多模型适配 | 不同任务对模型要求不同,不能绑死一家 |
| 数据层 | 行情、文档、向量库、日志 | 关系库 + 向量库 + 对象存储 | 结构化与非结构化数据要分开存 |
为什么强调“编排层”和“能力层”分离?因为金融业务变化太快。今天监管要求变了一个口径,明天产品上线一个新规则,如果你把逻辑写死在 Agent 里,改一次就要重新测一遍全链路。把能力做成插件、把流程做成配置,改起来才不至于伤筋动骨。
还有一个现实问题:模型不能只用一家。我在实际项目里试过,同一个金融问答任务,不同模型在数字准确性、长文本理解、工具调用稳定性上差异非常大。所以架构上必须留出多模型适配的口子,通过统一的 API 层去路由,而不是在每个 Agent 里硬编码模型名。
1.3 关键词背后的技术栈映射
把热搜词和这个项目对上号,其实能看出一条很清晰的技术路线:
Claude、claude code、claude cli:说明大家在用 Claude 系列做代码生成和 Agent 编排,尤其是 CLI 形态适合本地开发调试。API、openrouter api key、deepseek api如何调用、智谱api:多模型接入是刚需,OpenRouter 这类聚合层能省掉大量适配工作。插件、vscode插件、pycharm ai插件、webstorm插件:开发工具链的智能化,本质是把 Agent 能力嵌进 IDE。agents、agent skills、skills开发、opencode skills:Skills 正在成为 Agent 能力复用的标准单元。api error: 400 this model's maximum context length is 1048576 tokens:长上下文处理是金融文档场景的硬骨头。代码诊断插件、前端开发skills:说明 Skills 不只用于后端,前端和诊断类场景也在铺开。
这些词放在一起,指向一个结论:金融智能体项目的核心不是模型本身,而是围绕模型构建的工程体系。模型是发动机,插件是零件,Skills 是装配说明书,API 是油路,缺一个都跑不起来。
2. 核心模块拆解与实操要点
2.1 Skills 体系怎么设计才不乱
Skills 这个概念现在被说得有点玄,我把它翻译成人话:Skills 就是给 Agent 看的“操作手册 + 工具包”。一个 Skill 通常包含三部分——触发条件、执行步骤、输出格式。金融场景里,我建议按业务域来切 Skills,而不是按技术类型切。
比如不要建“数据库查询 Skill”“HTTP 请求 Skill”这种太底层的东西,而是建“财报指标提取 Skill”“合规条款比对 Skill”“客户风险等级计算 Skill”。原因很简单:Agent 在编排时是按业务目标找能力的,不是按技术手段找能力的。你给它一个“数据库查询 Skill”,它不知道什么时候该用;你给它一个“财报指标提取 Skill”,它一看名字就知道该在什么场景调用。
一个 Skill 的目录结构我通常这样组织:
skills/ financial-report-extract/ skill.md # 技能说明,给模型看的 schema.json # 输入输出结构定义 handler.py # 实际执行逻辑 examples/ # 少样本示例 input-01.json output-01.jsonskill.md是核心,它要写清楚:这个技能解决什么问题、什么时候触发、输入需要哪些字段、输出是什么格式、有哪些边界情况。我见过太多项目把 Skill 写成一段模糊的自然语言描述,结果 Agent 调用时全靠猜,稳定性极差。
注意:Skill 的输入输出一定要用 JSON Schema 约束死。金融场景里数字精度、日期格式、币种单位错一个,后面全盘皆输。别指望模型自己“理解”,要用结构去限制它。
2.2 多模型 API 接入的工程细节
金融项目里模型调用有几个绕不开的问题:成本、延迟、准确性、可用性。我的做法是建一个统一的模型路由层,对外暴露一个标准接口,内部根据任务类型路由到不同模型。
路由策略我一般这样配:
| 任务类型 | 推荐模型特征 | 路由理由 |
|---|---|---|
| 数字计算与校验 | 推理强、输出稳定 | 金融数字不能幻觉,宁可慢一点 |
| 长文档摘要 | 上下文窗口大 | 财报、合同动辄几十万字 |
| 工具调用编排 | 函数调用能力强 | Agent 核心链路,稳定性优先 |
| 客服话术生成 | 响应快、成本低 | 高频低价值任务,控制成本 |
接入时有个细节特别容易踩坑:不同模型的 API 错误码和重试语义不一样。有的模型限流返回 429,有的返回 400 带特定 message;有的支持流式,有的流式格式还不统一。所以路由层必须做错误归一化,把各家错误映射成统一的内部分类,再决定是重试、降级还是直接失败。
# 错误归一化示意 ERROR_MAP = { "rate_limit": ["429", "rate limit", "too many requests"], "context_overflow": ["maximum context length", "token limit exceeded"], "auth_failed": ["api key is required", "invalid api key", "401"], "model_unavailable": ["model not found", "503", "overloaded"], } def normalize_error(raw_msg: str) -> str: low = raw_msg.lower() for category, keywords in ERROR_MAP.items(): if any(k in low for k in keywords): return category return "unknown"这个映射表看着简单,但能省掉大量排查时间。尤其是context_overflow这类错误,如果不单独识别,重试多少次都没用,必须走截断或分块策略。
2.3 长上下文处理的实战方案
热搜里那条maximum context length is 1048576 tokens的错误,做金融文档处理的人几乎都遇到过。1048576 听起来很大,但一份年报加上附注、审计报告、关联交易说明,很容易就超了。而且就算没超,把整份文档塞进去,成本和延迟也受不了。
我的处理策略是三层:
第一层是结构化预处理。财报、合同这类文档,先用解析工具把表格、标题、段落结构抽出来,不要直接扔原始文本。结构化之后,很多信息可以用规则直接提取,根本不需要模型。
第二层是分块加索引。把文档切成语义完整的块,每块打上元数据(章节、页码、类型),存进向量库。Agent 需要时先检索再生成,而不是全文塞入。
第三层是摘要加回溯。对超长文档先生成层级摘要,Agent 先看摘要定位,再回查原文块。这样既控制上下文长度,又保证信息可追溯。
实操心得:分块时不要按固定字数切,要按语义边界切。我试过按 512 token 硬切,结果把一张财务报表从中间切开,模型读出来的数字全是错的。后来改成按标题层级和表格边界切,准确率明显提升。
2.4 插件与 IDE 集成的价值
vscode插件、pycharm ai插件、代码诊断插件这些词反映了一个趋势:开发者希望 Agent 能力直接出现在写代码的地方,而不是切到另一个网页。金融项目里,这一点尤其重要,因为很多逻辑需要边写边验证。
我自己的做法是把核心 Skills 通过 CLI 暴露出来,再在 IDE 里做轻量封装。这样同一套能力,既能在终端里跑批处理,也能在编辑器里做单点调用。CLI 的好处是可脚本化、可进 CI,IDE 插件的好处是交互快、反馈即时。
集成时要注意权限隔离。金融代码库往往有敏感信息,IDE 插件不能无差别读取整个工作区。我的做法是显式声明插件可访问的目录和文件类型,默认拒绝,按需授权。
3. 从零搭建金融智能体的完整流程
3.1 环境准备与依赖安装
先把基础环境搭起来。我习惯用 Ubuntu 做开发环境,Windows 下如果用 WSL 也可以,但要注意虚拟化平台相关组件要提前启用,否则容器和虚拟机相关功能会报错。
# 基础依赖 sudo apt update sudo apt install -y python3.11 python3.11-venv git curl # 创建虚拟环境 python3.11 -m venv venv source venv/bin/activate # 安装核心库 pip install fastapi uvicorn pydantic httpx pip install openai anthropic # 多模型 SDK pip install chromadb # 向量库,本地开发够用如果你用 Docker 做依赖隔离,注意 Docker Desktop 在部分环境下会报failed to connect to the docker api at npipe这类错误,通常是 Docker 服务没启动或者管道配置不对。先确认服务状态,再检查环境变量里的 socket 路径。
# 检查 Docker 服务 docker info # 如果报管道错误,确认服务已启动 sudo systemctl status docker3.2 模型接入配置
多模型接入我建议用环境变量管理密钥,绝对不要写进代码。下面是一个统一配置的示例:
import os from dataclasses import dataclass @dataclass class ModelConfig: name: str base_url: str api_key: str max_context: int supports_tools: bool def load_models(): return { "reasoning": ModelConfig( name=os.getenv("REASONING_MODEL", "claude-sonnet"), base_url=os.getenv("REASONING_BASE_URL"), api_key=os.getenv("REASONING_API_KEY"), max_context=200000, supports_tools=True, ), "long_context": ModelConfig( name=os.getenv("LONG_CONTEXT_MODEL", "deepseek-chat"), base_url=os.getenv("LONG_CONTEXT_BASE_URL"), api_key=os.getenv("LONG_CONTEXT_API_KEY"), max_context=128000, supports_tools=False, ), }这里有个经验:max_context 不要照抄官方文档的最大值。官方说支持 128K,不代表你就能稳定用满 128K。实际可用上下文往往要打七到八折,因为输出也要占额度,而且接近上限时模型质量会下降。我一般按官方值的 70% 来规划。
3.3 Skill 注册与调用链路
Skill 注册我做成一个注册表,启动时扫描目录,把每个 Skill 的元信息加载进来。
import json from pathlib import Path class SkillRegistry: def __init__(self, root: str): self.root = Path(root) self.skills = {} def load_all(self): for skill_dir in self.root.iterdir(): if not skill_dir.is_dir(): continue meta_file = skill_dir / "schema.json" if not meta_file.exists(): continue meta = json.loads(meta_file.read_text()) self.skills[meta["name"]] = { "meta": meta, "path": skill_dir, } def get_tool_specs(self): """输出给模型看的工具描述""" specs = [] for name, item in self.skills.items(): specs.append({ "name": name, "description": item["meta"]["description"], "parameters": item["meta"]["input_schema"], }) return specs调用链路是这样的:用户请求进来 → 编排 Agent 拿到工具列表 → 模型决定调用哪个 Skill → 路由层执行 Skill → 结果回传模型 → 模型生成最终回复。这个链路里最容易出问题的是工具描述写得不清楚,导致模型该调不调、不该调乱调。
注意:工具描述要写“什么时候用”,而不只是“这是什么”。比如不要写“查询数据库”,要写“当需要获取某公司近三年营收数据时使用”。前者模型不知道触发时机,后者一目了然。
3.4 参数计算与上下文预算
上下文预算是金融智能体必须算清楚的一笔账。假设一次请求包含:系统提示 2000 token、工具描述 3000 token、历史对话 5000 token、检索到的文档块 20000 token、用户问题 500 token,合计约 30500 token。如果模型输出预留 4000 token,那么总需求约 34500 token。
如果模型上限是 128000 token,看起来绰绰有余。但实际场景里,历史对话和文档块会随轮次增长。我的做法是设一个阈值,比如用到上限的 60% 时就开始压缩历史对话,用到 75% 时强制只保留最近三轮加摘要。
def estimate_tokens(text: str) -> int: # 粗略估算,中文约 1.5 字/token,英文约 4 字符/token chinese = sum(1 for c in text if '\u4e00' <= c <= '\u9fff') other = len(text) - chinese return int(chinese / 1.5 + other / 4) def check_budget(messages, docs, model_max, reserve=4000): total = sum(estimate_tokens(m["content"]) for m in messages) total += sum(estimate_tokens(d) for d in docs) usable = model_max * 0.7 - reserve return total <= usable, total, usable这个估算不精确,但足够用来做预警。真要精确计数,得用对应模型的 tokenizer,但那个开销也不小,日常预警用粗估就够了。
4. 常见问题与排查技巧实录
4.1 模型调用类问题速查
| 现象 | 可能原因 | 排查方向 | 解决方式 |
|---|---|---|---|
| 400 上下文超限 | 输入超过模型上限 | 打印 token 估算值 | 分块、摘要、压缩历史 |
| 401 鉴权失败 | 密钥缺失或过期 | 检查环境变量 | 重新配置密钥,确认请求头格式 |
| 429 限流 | 调用频率过高 | 看调用日志时间分布 | 加退避重试,做请求队列 |
| 工具调用不触发 | 工具描述不清 | 检查 description 字段 | 补充触发场景说明 |
| 输出数字错误 | 模型幻觉 | 对比原始数据 | 关键数字用规则校验,不信任模型直出 |
这张表是我从实际项目里攒出来的,每一条都对应过真实故障。尤其是最后一条,金融场景里模型把 1.2 亿写成 12 亿这种事,出一次就够喝一壶的。所以我的原则是:模型可以参与计算,但最终数字必须过规则校验。
4.2 Skill 加载失败的排查
Skill 加载失败通常有几个原因:目录结构不对、schema.json 格式错误、handler 导入报错。我一般按这个顺序查:
- 确认 skill 目录下有
skill.md和schema.json。 - 用
json.loads单独验证 schema 文件能否解析。 - 单独导入 handler 模块,看有没有依赖缺失。
- 检查 skill 名称是否重复,重复会导致注册表覆盖。
# 快速验证所有 schema 文件 find skills -name "schema.json" -exec python -c "import json,sys; json.load(open(sys.argv[1]))" {} \;这个命令能一次性把所有格式错误的 schema 揪出来,比一个个点开看快得多。
4.3 长文档处理的坑
长文档处理我踩过最大的坑是表格跨页。一份 PDF 财报,表格从第 5 页跨到第 6 页,解析工具把它当成两个独立表格,结果表头和表体对不上,模型读出来的数据完全错位。后来我的做法是:解析后先做表格合并检测,如果相邻两页的表格列数一致、且第一页表格没有表尾,就尝试合并。
另一个坑是数字单位。有的文档写“万元”,有的写“元”,有的用“百万”。如果不统一单位,Agent 汇总时就会出错。我的做法是在预处理阶段就做单位归一化,全部转成基础单位,并在元数据里记录原始单位。
实操心得:金融文档预处理阶段多花一小时,后面能省十小时排查。别急着把原始文本喂给模型,先把结构理清楚。
4.4 多模型切换的稳定性问题
多模型切换最大的问题是行为不一致。同一个 Skill,模型 A 调用时参数格式对,模型 B 调用时可能少传一个字段。我的应对方式是:在 Skill 的 handler 里做参数校验和默认值填充,不依赖模型每次都传全。
def validate_and_fill(params: dict, schema: dict) -> dict: result = {} for field, spec in schema["properties"].items(): if field in params: result[field] = params[field] elif "default" in spec: result[field] = spec["default"] elif spec.get("required"): raise ValueError(f"缺少必填字段: {field}") return result这样即使模型漏传,只要字段有默认值就能兜住。必填字段缺失就直接报错,让编排层决定是重试还是换模型。
4.5 成本控制的几个手段
金融项目调用量大,成本很容易失控。我常用的手段有四个:
- 缓存:相同查询结果缓存,尤其是行情、汇率这类高频低变数据。
- 分级路由:简单任务走小模型,复杂任务才走大模型。
- 批量合并:多个小请求合并成一次调用,减少往返开销。
- 输出限制:给模型设 max_tokens,避免它长篇大论。
缓存这块要注意失效策略。金融数据时效性强,行情类缓存可能只保留几秒,财报类可以保留几天。缓存键要把所有影响结果的参数都包含进去,否则会串数据。
5. 工程化落地的经验与边界
5.1 什么该交给智能体,什么不该
这是我在金融项目里被问最多的问题。我的判断标准很简单:可验证、可回滚、低实时性要求的任务可以交给智能体;不可验证、不可回滚、高实时性要求的任务必须走确定性系统。
举个例子,生成一份客户持仓分析报告,可以交给智能体,因为报告出来有人复核,错了能改。但执行一笔交易,绝对不能交给智能体,因为错了没法回滚,而且实时性要求极高。
再比如,从合同里提取关键条款,可以交给智能体,因为提取结果可以人工抽检。但根据条款自动冻结账户,就必须走规则引擎,因为这是不可逆操作。
这个边界划清楚,项目才不会跑偏。我见过一些团队一上来就想让智能体做全流程自动化,结果在关键节点上出了几次事故,整个项目就被叫停了。
5.2 可观测性怎么建
金融智能体必须可观测,否则出了问题根本不知道是哪一步错的。我一般埋三类日志:
- 调用日志:每次模型调用记录模型名、输入 token 数、输出 token 数、耗时、错误码。
- Skill 日志:每次 Skill 执行记录入参、出参、耗时、是否命中缓存。
- 决策日志:Agent 每次选择调用哪个 Skill、为什么这么选,记录模型的推理过程。
第三类最容易被忽略,但排查时最有用。当 Agent 该调 A 却调了 B,你看决策日志就能知道是工具描述的问题还是模型理解的问题。
import logging import time def traced_skill_call(skill_name, params, handler): start = time.time() try: result = handler(params) logging.info({ "event": "skill_call", "skill": skill_name, "params": params, "result_summary": str(result)[:200], "duration_ms": int((time.time() - start) * 1000), "status": "ok", }) return result except Exception as e: logging.error({ "event": "skill_call", "skill": skill_name, "params": params, "error": str(e), "duration_ms": int((time.time() - start) * 1000), "status": "error", }) raise日志里参数和结果要脱敏,金融数据不能明文落盘。我一般对账号、金额、身份证号做掩码处理,只保留用于排查的结构信息。
5.3 团队协作与 Skills 复用
Skills 最大的价值是复用。一个团队里,A 写的“财报指标提取 Skill”,B 的项目也能用。但复用的前提是接口稳定、文档清楚。
我的做法是建一个内部 Skills 仓库,每个 Skill 有版本号、维护人、变更记录。用的时候按版本引用,不要直接拷贝代码。这样 Skill 升级时,下游能感知到,不会突然被破坏性变更搞崩。
版本管理我建议用语义化版本:主版本号变更表示不兼容,次版本号表示新增功能,修订号表示修复。Skill 的 schema 一旦发布,就不要改字段含义,要改就发新版本。
5.4 安全与合规的底线
金融项目对安全的要求不用多说。我在工程上坚持几条底线:
- 密钥不落代码、不落日志、不进版本库。
- 模型输入输出全程加密传输。
- 敏感数据在进入模型前做脱敏,能本地算的不要传给模型。
- 所有智能体操作留审计日志,可追溯。
- 关键操作设人工确认环节,不搞全自动。
这几条不是建议,是底线。尤其是最后一条,金融场景里任何涉及资金、权限、客户信息的操作,都必须有人工确认或者确定性规则兜底。智能体可以提建议、可以做预处理,但最终决策权不能完全交出去。
5.5 后续可以扩展的方向
这套架构搭起来之后,扩展性其实很好。往深了做,可以接更多数据源,比如把行情、舆情、公告都纳入检索范围;往广了做,可以把 Skills 开放给更多业务线,让不同团队按需组合。
我最近在试的一个方向是多智能体协作。单个 Agent 处理复杂金融任务时容易顾此失彼,拆成几个专职 Agent 分工协作,比如一个负责数据检索、一个负责规则比对、一个负责报告生成,最后由一个协调 Agent 汇总。这样每个 Agent 的职责更清晰,调试也更容易。
不过多智能体也有代价,就是链路变长、延迟增加、故障点变多。所以要不要上多智能体,得看任务复杂度。简单任务单 Agent 就够了,硬上多智能体反而是过度设计。
我在实际项目里的体会是,金融智能体这件事,技术只占三成,剩下七成是对业务的理解和对边界的把握。模型能力再强,如果你不清楚哪些环节能容错、哪些不能,项目就很难真正落地。先把业务拆明白,再谈技术选型,顺序反了就要返工。