开发者将失败测试交给编程Agent时,Agent的完整执行轨迹本质上成为一种有价值的应用数据,可用于改进调试和工作流。
假设一个测试开始失败,开发者把它交给一个 coding agent。Agent 会深入相关文件、运行测试套件、改动两个文件,然后运行有针对性的验证。任务视图会显示它访问过的文件、运行的命令、这些命令返回的结果,以及它最终提交的 diff。
在接受这个 patch 之前,开发者会审查这些活动。团队成员之后可能会重新打开同一次运行记录,查看代码为什么被改了。构建该 agent 的团队可以比较成千上万次运行记录,观察模型或 prompt 的更新是提高了测试成功率,还是仅仅增加了工具调用次数和成本。
这些记录需要一个存放的地方。开发者和审查者需要在任何需要的时候都能获取到这些运行的持久化版本。工程团队则需要对多次运行进行聚合分析,因为 agent 的行为是非确定性的,会随时间变化。
"对于许多 agentic 产品,这些记录最终会成为具有 telemetry 形态工作负载的应用数据。"
对于许多 agentic 产品,这些记录最终会成为具有 telemetry 形态工作负载的应用数据。正是这种组合改变了存储决策。
并非所有 agent trace 都属于应用数据。可以通过采样、过期或丢弃的内部诊断 trace 仍然是 telemetry。同一个 trace 内部可以存在这条边界——原始诊断字段保留在内部,而用于重建用户任务的字段则进入产品状态。
一旦产品需要检索、展示或保留一份持久化的执行记录,这条边界就会移动。例如,开发者可能需要查看 agent 检查过哪些文件,或者审查者需要检查运行了哪些命令以及测试是否通过。
这不需要暴露模型的私有思维链。产品可以渲染出可观测执行的一个投影,展示模型调用、工具调用、文件读取、命令结果、错误、时序和状态转换。这些记录让用户验证结果,并决定在多大程度上信任它。在某些工作流中,它还成为审计记录,这改变了其访问和保留需求。
"内部诊断字段不会仅仅因为它们来自同一次运行就自动进入产品视图。"
一旦执行记录成为产品状态,就必须遵循应用的访问模型。代码、prompt、检索到的文档、工具参数和命令输出可能包含租户或用户数据,因此应用在存储和检索时必须强制执行相同的授权边界。内部诊断字段不会仅仅因为它们来自同一次运行就自动进入产品视图。
Pull request 量随提交审查的补丁数量而增长,issue 量随开发任务数量而增长。两者都是在工作流边界处计数工作单元。
Agent trace 的扩展方式不同,取决于每个任务内部的执行图。一次修复失败测试的请求可能会触发多次模型调用、文件读取、搜索、命令执行、测试重试和分支,然后 agent 才会提出一个补丁。其中每个步骤都可能产生自己的 span 或 event。
仍在开发中的 OpenTelemetry GenAI 语义约定为模型推理、工具执行和检索定义了独立的 span 类型。捕获什么取决于 span 类型和内容捕获策略。推理 span 可能包含模型标识符、token 使用量,以及可选的输入和输出消息;工具执行 span 可能包含可选的参数和结果。
Span 数量追踪的是 agent 在每个工作单元内部实际做了什么。如果你添加了一个新工具、重试策略或分支,数据量可能增加,即使完成的任务数量没有变化。
"如果你添加了一个新工具、重试策略或分支,数据量可能增加,即使完成的任务数量没有变化。"
这种扇出也出现在 coding agent 之外。在一个案例研究中,Laminar 报告每天超过 50 万个浏览器事件。一个浏览器 agent 会话可能运行超过 30 分钟,并产生数十万个 DOM diff 事件。Laminar 使用这些事件重建了 agent 所见内容的类视频回放。
无论 agent 是写代码还是浏览网页,同一组数据服务于两个不同目的。一个人加载一条 trace 来理解单次运行,而工程团队扫描多条 trace 来发现规律。这种点检索和队列分析的组合赋予了这个数据不同寻常的形态。
Agent trace 中的大多数记录是一次写入而非更新。模型调用或工具结果描述的是已经发生的事件。分数、注释和运行状态可能稍后更改,但团队可以将这些可变字段分开存储,或者将更改记录为新事件。
这些记录还携带高基数维度,如模型版本、prompt 模板、工具名称、会话 ID、用户 ID 和结果。它们的价值只在上下文中体现。孤立的工具调用 span 本身说明不了什么,但完整的轨迹可以解释一次失败的运行,而队列分析可以揭示一次回归。
因此,同一个数据集服务于多个不同的读者:
| 模式 | 示例 | 描述 |
|---|---|---|
| ConsumerRead | 产品界面 | 点查询 |
| Evaluation pipeline | 队列扫描 | 跨 agent 版本比较测试成功率、延迟和成本 |
| Platform team | 时间窗口聚合 | 按模型、工具或部署分组错误和延迟 |
在适度规模下,主应用数据库可以服务于所有三种模式,但当广泛扫描和高基数聚合开始与产品读写竞争关键路径时,情况就变了。
将 trace 移出主数据库没有通用的事件数量阈值。决策取决于工作负载本身。
第一个信号是争用——当摄入或保留工作开始消耗足够的 I/O 和 CPU,影响到事务操作时。接下来是分析摩擦——当评估和调试查询需要扫描长时间范围或连接大型 trace 表,无法再满足团队的延迟目标。最终团队被迫采样,丢弃 trace 以保护应用数据库,尽管产品或审计流程需要完整记录。
Langfuse 在扩展其开源平台用于 LLM 可观测性、评估和 prompt 管理时记录了争用和分析摩擦两种情况。它在摄入期间经历了 Postgres IOPS 耗尽,在重负载下 prompt API 延迟达到七秒。Langfuse 将其追踪数据从 Postgres 迁移到 ClickHouse,同时保持事务和延迟敏感路径隔离。
数据库迁移解决了一类问题,但原始数据模型造成了另一类问题。Langfuse 最初将独立的 trace、observation 和 score 表带入其分析架构。更新需要去重和跨表分析,增加了连接成本。
"存储只是决策的一半。分析存储引擎解决了工作负载问题,而数据模型减少了跨表工作。"
后来,Langfuse 将这些记录折叠成一个宽的、几乎不可变的 observations 表,每次模型调用、工具执行或 agent 步骤对应一行。大型数据集的初始表加载从秒级降到毫秒级,大型项目的仪表板加载时间在更长的时间范围内至少提升了 10 倍。
Langfuse 需要同时做这两项改变。分析存储引擎解决了工作负载问题,而数据模型减少了跨表工作。
从产品必须支持的读取开始,然后选择满足这些要求的最简单架构。
在适度规模下,将 trace 保存在主数据库中可以避免另一个操作边界。随着分析争用增长,应用可以将 trace 事件发送到专用分析存储,同时将可变业务记录保留在事务数据库中。
有些应用需要两个系统共享数据。一个以 Postgres 为后端的产品可能将用户、权限和工作流状态保留在 Postgres,同时将 agent 事件发送到 ClickHouse 进行分析查询。如果相关应用数据已经存在于 Postgres 中,变更数据捕获可以将其复制到分析路径。
谁依赖这些记录以及他们如何查询,比 trace 看起来像日志还是 pull request 更重要。
如果你的产品需要重建单次运行的持久化记录,而工程团队需要跨数千次运行比较行为,那么 trace 就成了具有类似 telemetry 存储工作负载的应用数据。
在选择存储之前,先绘制点查询和跨运行扫描的地图。如果两者都是产品需求,从保留的第一条 trace 开始就为两者进行设计。