先说结论:这工具就两个文件,一个 Go 二进制,一个 SQLite。跑起来监听 4318,收 OpenTelemetry 的 trace,代理那边配一个环境变量就接进来了。没有数据库,没有队列,更没有三个微服务才敢撑起来的那套"可观测性平台"。它就是个跑在 localhost 上的小录音机。
我确实这么干过——把二进制和那个 .db 文件扔到一台 6 美元的 VPS 上,跑起来,完事。备份?把文件复制一份,没了。
那为什么不直接用托管服务?因为用户消息会原样出现在别人的服务器上。提示词里装的是真实用户发出去的内容,这玩意儿离开我的机器,我过不去这道坎。你可以说它们有加密、有合规、有隔离。加密防的不是服务商自己。
自托管?随便翻开一个自托管观测项目的 README,PostgreSQL、ClickHouse、Redis,再配一个叫不上名字的 collector。我就想看看几次 trace,不想从此伺候五六个服务。这哪里是自托管,这是自找一份运维工作。
所以最后我写了一个自己的。它把代理的一次执行叫一个"运行",下面挂着步骤——LLM 调用、工具调用这些。每条调用都能看到实际的消息、token 消耗和费用。价格表内置了主流几家最新的,遇到不认识的模型就只显示 token 数、不估费用。不知道就是不知道,拍脑袋给个数才是骗人。
整堆代码里最花心思的是翻译层。现在各家 agent 框架吐 trace 的格式,说方言都是客气的:OpenTelemetry 有自己的 GenAI 约定,OpenInference 是另一套,Vercel AI SDK 更直接,在 span 上挂一堆 ai.* 开头的属性。你要是只认一种格式,等于把自己绑死在某个生态上。我的做法是:进来什么格式先归一化成内部统一模型,同时把原始负载原封不动存着。画外音:这是兜底设计——万一哪天翻译逻辑出了偏差,原始的东西还在,想重放那次执行,细节也不会缺。
这里我还真纠结过要不要省掉这步,直接存 OTel 原始事件、展示的时候再转换。后来想明白了:转换是有损的,等你真正需要细节的那天,它早没了。先存原始、展示层再翻译,这两件事的代价差一个数量级,收益不成比例。
现在回头看,这个纠结白纠结了——后面好多功能都是长在这一层上面:在 trace 界面直接写评估断言,或者让 LLM 当裁判;把两次不同 prompt 的运行摆一起对比。要是没有这个统一模型,每接一个新的 SDK 就得写一套 UI,那才叫灾难。
它现在还糙得很。单用户,没有登录,默认监听 localhost。这个阶段上多用户和权限管理,等于在猜需求。我甚至不太确定下一步该做什么——先磨界面,还是等真用户长出来再说。这个我暂时想不清楚,干脆不做决定。
给自己立条规矩:以后每个 agent 项目开工前,先回答两个问题——数据存在哪,谁能看见。答不上来,就先别开工。
