Data Agent 开发实战
前端第一次做 agent 的实战经验。
0. 背景
三个月前,+2 突然单独找我开会,想让我来做一个数据 agent,希望可以解决数据组取数需求慢的问题。当时我也不过是 AI 用的多,对于 agent 也是一知半解,但是我觉得这是一个好机会,可以亲手做出来一个企业级 agent,以后做其他 agent 或者转岗到 agent 相关的开发也可以,于是就接下了这个需求。
因为中间有一些其他的事情,所以 agent 的开发不算太顺利,陆陆续续做了将近两个月才上线,但是上线之后的效果还是很超出我的预期的。在一次真实的取数需求中,找出了数据组的一个 bug,此 bug 会导致最终人数减少 1/3,修正后双方从不同思路排查数据,最终的结果是,在 70w+ 数量级上取数据,误差在两位数,不到 100。
1. 核心思想
整个 agent 的核心思想只有一句话「只有代码才是最准确的」。
产品是有产品文档,开发也确实开发过相关需求,但是线上真实跑的现状和产品文档是否一致,和开发的记忆是否一致,都没人能确定。只有现在运行在线上的代码才是现在最准确的线上逻辑。
根据这个核心思想来设计 agent,就很难采用通用的知识库和 embedding 的方式,因为知识库不过是又多了一份可能是对也可能是错的文档。所以最终采用的方式就是,把一些通用型、而且长期不变的规则写入知识库,其他的逻辑都依赖 agent 当场查代码逻辑得出结论。
2. 开发过程
2.1 知识库构建
最开始的时候,我的想法基本上是参考阿里云的 AI 服务,在 dms 里梳理出相关数据表的结构,构建数据血缘图,根据文档和血缘图直接查数据。
但是在实际整理过程中,发现是有问题的。
如果只依赖数据库,那能看到的东西就只有表名是什么,备注有什么,数据大概是什么样的。就算对应的开发来看,有些表、字段也说不出来具体是什么东西,还需要去查看代码。
这里我就想到有两个解决方案,
- 查询数据之前先去查代码,根据需求查询代码确定数据落在哪里
- 增加置信度,因为有些是 AI 推断出来的,不一定准确
第一点的话有现成的开源项目 sourcebot,同事在公司内网部署了一套,这样的话就不需要把所有的代码都拉到 agent 的目录下,可以直接用工具去搜索代码了。
第二点的置信度,先说说为什么要做这个东西。做之前我想明白了一件事:查出来的数到底对不对,其实没有人能真正知道。产品数据这个东西,没人能保证某个数就是绝对正确的,除非我去一个一个数。我们这边真实的流程是,产品提数据需求,数据组给结论,产品根据经验发现不对劲,提出有疑问的点,数据组再去反查。所以置信度不是在衡量「离真值有多远」——真值根本拿不到——它衡量的是「和大家的共识有多一致」,本质上是把上面那个反查流程搬进了 agent:不确定的地方标出来,产品知道是因为什么不确定,她去找对应的开发确认一下,反馈回来,置信度就提上去了。
方案大概是这样的:
| source | 含义 | 基础分 |
|---|---|---|
code_verified | 后端代码中有明确读写、模型、常量、枚举或业务流程证据 | 0.95 |
db_constraint | 数据库有显式约束、唯一约束或无歧义字段注释 | 0.90 |
data_evidence | 实际数据分布、JOIN 命中率、历史 SQL 佐证 | 0.75 |
bi_sql | 生产中的 BI / DataWorks 作业 SQL 直读 | 0.90 |
product_confirmed | 产品对口径决策或产品语言语义的显式确认 | 0.95 |
naming_semantic | 仅根据命名和业务语义推断 | 0.50 |
guess | 无可靠证据,仅作为线索 | 0.30 |
有两个分数解释一下。bi_sql 给到 0.90 而且不用人工确认,因为数据组写在生产上跑的 SQL 已经是全公司对这个口径的最高共识了,我再去人工确认一遍也不会更准。product_confirmed 给 0.95,因为在口径这件事上产品拍板就是权威——她说买会员不含赠送,那就不含,这不是技术问题。
方案还是比较粗糙的,但是差不多够用,agent 的数据来源也就是这些方面了。
2.2 agent 第一次实战
上面的知识库搭建差不多花费了 3 ~ 4 周,然后我找产品要了一个她常看的问题,用 agent 试着查询一下,看看和目前的看板差距有多少。
然后我发现产品看的看板数据是从 dataworks 里产出的,也就是说有些数据是数据组单独加工、存储过的,这种情况下数据是否准确就更不可知了。因为开发可能出错,数据组可能出错,agent 也可能出错,那这几层错误叠加,误差是几何级上升的。
我又找产品要了她在看的看板地址,找数据组开通了 dataworks 账号,去看产品的数据是哪个 SQL 产出的,去看数据组写的 SQL 逻辑。我是先问了产品一个她每天都在看的数据,先跑已有的数据,这样可以方便校验正确性,然后知识库和方法论构建起来之后,再去跑未知的数据。
第一个问题我记得查出来的数据和产品每天都在看的看板最终差值是 ±1。这给了我很大的鼓励,不止证明了 AI 是可以用来查数据的,也证明了之前的方法论是正确的,不止能跑通,还能查的准。
跑通之后我干的第一件事不是庆祝,是想「这次是不是运气好」。于是就有了后面的盲测。
2.3 盲测:三个模型一起翻车
盲测的思路是这样的:拿数据组生产 SQL 反推出知识写进知识库,然后让不同的模型只读知识库、独立跑同一个问题,最后和看板数据对比。如果几个模型都能跑出一样的数,那说明结果靠的是知识库,不是某个模型碰巧聪明;差在哪,就说明知识库缺了哪块。
第一次跑就翻车了,而且是三个模型各翻各的。一个模型在最后的结论里读到了之前对话的记忆,自己给自己评了分;一个模型试图去读别的任务目录,被我手动停止并拒绝了;还有一个,实验要求不许提交,但是他直接 commit 上去了。
翻完车我想了十来分钟,想出来的办法很土:每次实验新开一个 git workspace,把其他任务目录全删掉,让模型物理上读不到任何不该读的东西。另外看板上的真实数字只在我手里,不写进任何文档、不进任何 commit——防的就是后面哪个模型「偷看答案」。
按这套隔离重跑,三个模型的主口径完全一致,只有一个模型在某一天差了两个,查下来是它自己推理的问题,不是知识库缺东西。到这我才敢说,针对这类问题,知识库、框架、工作流是完整的。
顺便记录了一下成本:一场盲测单个模型跑下来 13 美元,API 时间 38 分钟。当时看了下用量构成,大头是 subagent 和超长上下文。我的处理是把数字记进文档,优化先不做,留着以后看。
2.4 横向扩充知识库
在第一个问题跑通之后,我又找产品要了 5 个问题。最开始的知识库搭建基本上就是冲着产品那一个问题去的,等一个问题跑通了之后,再去尝试扩充到其他同场景的问题上。
这个过程差不多又过了 2 ~ 3 周,中间有别的紧急需求插入,agent 的开发就暂停了。等紧急需求开发的差不多之后,就继续 agent 的开发了。
知识库扩充的足够了,这 5 个问题的准确率差不多在 90% 以上,就要考虑部署的事情了。
3. 部署
3.1 web 部署
最开始我的设想是做一个 web,是参考了腾讯的一个 web AI 服务的。web 上可展示的信息比较多,而且比较可控,我可以随时在对话中插入信息,也可以比较好地观测 agent 使用记录,可以很方便地拉取日志,做对应的优化。
但是后面和产品沟通的时候,产品表示更想要的是飞书机器人的形式,这样的话不用离开飞书就可以直接对话,减少一个使用的工具。和飞书机器人结合的问题也不大,正好省掉一个 web 的开发。
等开发的差不多,准备部署的时候,我去问了一下公司内部的 AI 购买情况,结果只有百炼平台的 api,但是之前我做的误差 ±1 都是基于 claude fable5 做的。最开始的时候我也问过 +2 和产品,如果最终做出来,查一次问题要 5 刀,能不能接受这个成本。两个人给的结论都是先做吧,做出来再说。现在真做出来了,在模型上又发难了。
然后我就开始做不同模型的测试,用了 claude opus 4.8、gpt 5、kimi 2.7、glm 5.2 几个模型来跑。效果和我最开始猜的差不多,模型之间的能力差距还是在的。低质的模型面对现成的 prompt 和知识库都会忽略,根本不按规范去查询,高质模型就遵守得很好,误差也会小一点。这个结论不是感觉,就是拿盲测那套流程跑出来的——评测这个东西搭一次,后面换什么都能拿它量一量。
但是没办法,公司只买了国产模型,那就只能这么用了。
所以还是部署了一个简单的 web 端,可以实现流式对话,代码高亮等功能。
上线前我给 agent 加了条限制:默认排除测试号,但要在结果里说明排除了什么。这个口径知识库里早就有,测试号是固定数字开头的,能系统性识别出来,之前和 BI 看板对过,没问题。规则我自认为写得挺清楚。
跑完一看,数字一点没变,还是 5000+。
翻它的执行记录才发现,它不是没看见这条规则,是看见了然后决定不执行。理由写得还挺像回事:注册类指标按行业惯例是算全量的,所以这次不排除。
这句话把我看笑了,因为它说得也不算全错,注册数确实经常按全量报。但这轮不到它来判断,我加这条规则,本来就是因为我们这边的口径不按行业惯例走。
于是我把措辞收紧,不再只说「排除测试号」,改成覆盖一切以人为单位的指标,并且明确写上不许拿行业惯例当理由跳过。再跑,得到了正确的结果,附了口径说明表和全量对照。
前后就差 3 个人——所有测试号里,那天新注册的只有 3 个。数字变化小到可以忽略,但这两轮是我做下来最干净的一次对照:同一个模型、同一份知识库、同一个问题,我只改了规则的措辞,它的行为就变了。
比跳过规则更值得记的是它跳过的方式。它不是忘了,是给自己找了个理由,还把这个理由写进了结论里。如果我没去翻执行记录,只看那份报告,我大概还会觉得这 agent 考虑得挺周全,连行业惯例都想到了。好在这版 web 的规则还捏在我手里,改改措辞就能纠正。
这版 web 有个我挺满意的设计:agent 拿不准口径的时候会先反问,比如问用户「要不要包含自动续费扣款」,用户答一句「B」或者「1、a」就能继续。从日志看同事真的这么用起来了,反问确实拦住了一批口径歧义。给产品实际使用了一下,产品表示还行,就准备动手部署上线了。
3.2 MCP
后面和 +2 又开了个会,和他反馈了国产模型的问题。他表示现在产品人手一个 chatgpt plus,完全可以做成 MCP 的形式,让产品用 chatgpt 连接 mcp,直接用 gpt 模型去查数据。这个念头其实我之前也冒过——与其我这边扛推理成本和模型能力,不如让大家用自己手里的 AI,我只提供工具和纪律。会上一拍即合,部署方案就改成了 MCP。
MCP 的部署就和之前的 agent 部署完全不一样了。最大的损失就是上面那个反问机制:MCP 这边你控制不了对面的模型和客户端,反问基本上没有模型和 harness 会去遵守,只能砍掉。挺可惜的,但是将来如果还是部署为 agent 的话,还是可以重启的。
MCP 就用了 @modelcontextprotocol/sdk 来部署,整体的迁移过程还是比较简单的,相比于之前的 agent,这种方案反而更简单一点,只需要定义好相关的 tool,部署上线就行了。中间还紧急开发了一个登录的功能,不过这个做的就比较粗糙了,直接用我们现有的飞书机器人做飞书登录,token 设定好过期时间,做一下简单的身份校验就上线了。
4. 观测和测试
先说观测。这块不是设计出来的,是在使用过程中一点一点加上去的。
最开始是零日志。同事用 MCP 查数据,我这边一点痕迹都没有,出了问题都不知道有人踩过。于是第一步,把每次工具调用的输入输出打进阿里云 sls:谁、什么时候、跑了什么 SQL、成功没有、花了多久。因为用了飞书登录,还能直接带上用户在飞书里的名字。
日志有了,我去翻,发现看得见 SQL、看不见问题——同事到底问了 AI 什么,日志里没有。MCP 的架构就是这样,用户的原话在对面的客户端里,根本不经过我的服务端。于是加了个 purpose 字段,让调用方的 AI 在调工具的时候顺手总结一句「我在干什么」,事后回查至少知道这条 SQL 是为了回答什么问题。
然后又发现一次对话会产生一串调用,但日志里行与行之间没有任何关联字段,串不起来。同事提醒可以加个 trace id,于是手搓了一个很简单的方案,initialize 响应带 Mcp-Session-Id 头,三十行代码,同一个会话的调用就都串起来了。
后面又补了数据库连接日志、estTokens 这些。整个过程就是循环:想查一个东西,发现看不见,补一块。补到最后撞到了天花板——用户的完整输入和 AI 的最终输出,我永远拿不到,这不是日志没打好,是选了 MCP 这个形态的那一刻就注定的。
再说测试。
搭建测试平台的初衷是在考虑以后如果更换技术方案或者模型的话,可以有一个测试平台,方便评估修改前后的状态是否正常,防止越改越差的情况。
测试平台我的搭建方案也是从日志里来的,拉取用户真实使用过程中的问题,把这些问题放到评测集中,下次需要测试的时候,就让 AI 去跑一遍测试,用这种方式来评估优化的怎么样。为什么坚持用真实问题?因为量身定做的题大概率能测出效果,那就成了作弊。
eval/
├── AGENTS.md 本文件
├── cases/ 测试集:机器抽取,可重新生成。index.json 是清单
├── labels/ 人工标注:手写,脚本永不覆盖
├── runs/<run_id>/ 每次跑批:manifest.json + results.jsonl + report.md
└── scripts/ extract.mts(已有)/ replay.mts / score.mts(待写)总结
这个 agent,是我作为一个前端首次尝试搭建的 agent,里面很多方案都是我直接在 OpenAI、Claude、GitHub 等平台上,看别人分享的文章或者项目,尝试融入自己的理解,去搭建出来的。其中有很多「一拍脑袋」就做的决定。但是回头看,重要的几个决定都验证过了:置信度体系是因为「没人知道真值」做出来的,实验隔离是被三个模型翻车做出来的,「口径要固化不能指望模型自觉」是被 agent 自己绕规则做出来的。一拍脑袋不可怕,可怕的是拍完不验。
而且这个 agent 给了我一些经验,后续我又尝试搭建了几个其他的 agent,比摸着石头过河快多了。