Agent 可观测概述3:采集与还原的现实选择
这是 Agent 可观测性系列的第三篇文章。前两篇我们聊了”为什么难”和”观测什么”,这一篇来聊最落地的问题:数据怎么采集?
理想很丰满,现实很骨感
如果不考虑任何约束,理想的 Agent 可观测性应该是这样的:
- 完整记录每一次 LLM 调用的 Prompt 和 Response
- 完整记录每一次工具调用的输入、输出、耗时
- 完整记录 Agent 的思维链(Chain of Thought)
- 实时关联所有数据,形成完整的执行图景
但现实中,我们面对的是:
- Agent 可能是第三方框架,代码改不动
- LLM 流量是 HTTPS 加密的,抓包看不到
- 工具调用分散在各种协议里(HTTP、gRPC、SQL、管道)
- 不同数据源的时间戳和 ID 体系不统一
这篇文章,就来聊聊在这些约束下,我们能做出什么样的现实选择。
一、三种采集范式的权衡
从技术实现角度,Agent 数据采集大致有三种范式:
范式 1:代码埋点 (SDK Instrumentation)
原理:在 Agent 代码中引入 SDK,SDK 在关键位置(LLM 调用、工具调用)自动上报数据。
典型场景:
from agent_observability import trace
@trace
def call_llm(prompt):
response = openai.chat.completions.create(...)
return response
@trace
def call_tool(tool_name, params):
result = tools[tool_name].execute(params)
return result
优点:
- 能获取最丰富的上下文(变量值、调用栈、自定义标签)
- 对应用层语义理解最深
- 社区生态成熟(LangSmith、Langfuse 等)
缺点:
- 侵入性强:需要修改业务代码
- 覆盖有限:只能埋你改得动的代码
- 信任问题:Agent 可以选择性上报,绕过监控
适用场景:自研 Agent 的开发调试阶段
范式 2:网络代理 (Network Proxy)
原理:在 Agent 与外部服务之间部署代理,拦截并解析流量。
典型场景:
- 在 Agent Pod 前面加一层 Envoy Sidecar
- 部署一个透明代理解密 HTTPS 流量
- 使用 API Gateway 拦截 LLM 调用
优点:
- 无需改代码:对业务透明
- 集中管控:一个代理点可以覆盖所有 Agent
- 策略可控:可以在代理层做限流、审计、拦截
缺点:
- 性能开销:额外的网络跳转 + TLS 终结
- 单点风险:代理挂了,Agent 就断了
- 协议局限:只能处理网络流量,进程内调用看不到
适用场景:需要集中管控 LLM 出口流量的场景
范式 3:运行时观测 (Runtime Observation)
原理:在操作系统层面(内核或运行时)采集数据,无需修改任何应用代码。
典型技术:
- eBPF:在内核层面挂载探针,捕获系统调用
- uprobes:在用户态函数入口/出口采集数据
- ptrace:跟踪进程行为
优点:
- 零侵入:完全不需要改业务代码
- 全覆盖:所有行为都能观测到,包括”不想被观测的”
- 绕不过去:从内核层面采集,应用无法规避
缺点:
- 实现复杂:需要深入了解内核机制
- 性能敏感:不当使用可能影响系统稳定性
- 语义丢失:内核只能看到字节流,应用层语义需要重建
适用场景:生产环境的安全审计、异常检测
我的选择:分层组合
实际工作中,我发现没有银弹,最佳实践是分层组合:
| 层次 | 采集方式 | 采集内容 |
|---|---|---|
| 应用层 | SDK 埋点(可选) | Prompt/Response 语义、自定义标签 |
| 网络层 | 流量解析 | A2L、A2T 的 HTTP/gRPC/SQL 流量 |
| 系统层 | eBPF | 进程创建、文件读写、网络连接 |
三层数据通过 Trace ID 或时间窗口关联,形成完整视图。
二、审计日志与运行时行为的错位
在 Kubernetes 环境中,有一个特别棘手的问题:审计日志和运行时行为是割裂的。
典型场景:kubectl exec
假设一个 Agent 通过 kubectl exec 进入 Pod 执行命令:
kubectl exec -it my-pod -- bash
# 进入容器后
rm -rf /data/important/*
从 K8s Audit Log 的视角,你能看到:
{
"verb": "create",
"resource": "pods/exec",
"user": "system:serviceaccount:default:agent-sa",
"objectRef": {
"name": "my-pod",
"namespace": "production"
},
"requestReceivedTimestamp": "2024-01-15T10:30:00Z"
}
审计日志告诉你:谁在什么时候对哪个 Pod 发起了 exec 请求。
但审计日志不会告诉你容器内执行了什么命令——因为 exec 建立的是一个交互式会话,后续的命令通过 SPDY/WebSocket 流式传输,不在审计日志的记录范围内。
另一边,如果你在节点上用 eBPF 监控 execve 系统调用,你能看到:
{
"syscall": "execve",
"pid": 12345,
"ppid": 12340,
"comm": "rm",
"args": ["-rf", "/data/important/*"],
"container_id": "abc123",
"timestamp": "2024-01-15T10:30:05Z"
}
运行时监控告诉你:哪个进程在什么时候执行了什么命令。
但运行时监控不知道这个命令是谁发起的——Linux 内核不感知 Kubernetes 的身份体系。
这个错位的代价
两边的数据都是对的,但关联不起来就等于废了一半:
- 安全团队看到 Audit Log 说”有人 exec 进了 Pod”,但不知道具体干了什么,无法评估风险
- 运维团队看到 eBPF 说”有人执行了 rm -rf”,但不知道是谁,无法追责
如何修复这个错位?
这是我花了很多时间研究的问题。核心思路是建立关联桥梁:
方案 A:时间窗口 + 命名空间匹配
如果:
- Audit Log 记录了 Pod P 在时间 T1 收到 exec 请求
- eBPF 记录了容器 C(属于 Pod P)在时间 T2 有新进程创建
- T2 - T1 < 阈值(如 2 秒)
- 新进程是 shim 进程的直接子进程(exec 特征)
那么:
- 关联成功,给 eBPF 事件打上 User 标签
这是一种启发式关联,在大多数场景下有效。
方案 B:Admission Webhook 注入 Trace ID
通过 K8s Admission Webhook 拦截 exec 请求,自动修改命令:
# 原命令
kubectl exec -it my-pod -- bash
# 被 Webhook 修改后
kubectl exec -it my-pod -- /bin/sh -c "export TRACE_ID=xxx; bash"
这样,后续在容器内启动的所有进程都能继承 TRACE_ID 环境变量,eBPF 采集时可以一并捕获。
这种方案更精确,但有Shell 依赖——如果容器里没有 /bin/sh,就会失败。
三、为什么”可还原性”比”全量采集”更重要
在设计采集方案时,我走过一个弯路:一开始追求”全量采集”——尽可能多地采集数据,越全越好。
后来发现这是个坑。
全量采集的问题
问题 1:数据量爆炸
一个活跃的 Agent 每分钟可能产生:
- 10+ 次 LLM 调用,每次 Prompt + Response 可能有几千 Token
- 50+ 次工具调用,每次有输入输出
- 数百次系统调用
全部存储的话,一个 Agent 每天可能产生 GB 级别的数据。几十个 Agent 就是几十 GB。
问题 2:噪音太多
不是所有数据都有价值。大量的”正常”数据淹没了”异常”数据。当你真正需要排查问题时,反而找不到关键信息。
问题 3:隐私和合规
Prompt 和 Response 里可能包含敏感信息(用户数据、API Key、内部系统信息)。全量存储会带来隐私合规风险。
“可还原性”是更好的目标
我现在的设计原则是:不追求存储所有数据,而是确保在需要时能还原关键场景。
具体来说:
原则 1:分层采样
- 元数据全量采集:每次调用的时间、类型、耗时、状态码——这些数据量小,可以全量
- 内容智能采样:Prompt/Response 内容只采集摘要或首尾部分
- 异常时全量:当检测到异常(错误、超时、敏感操作)时,触发全量采集
原则 2:关联 > 完整
与其存储每次调用的完整内容,不如确保:
- 每次调用都有唯一 ID
- ID 能跨系统(K8s Audit、eBPF、网络流量)关联
- 关联关系持久化
这样,当需要深入分析时,可以通过 ID 追溯到原始系统获取详情(比如重放请求、查询原始日志)。
原则 3:能重建 > 能记录
很多时候,与其记录”Agent 做了什么”,不如确保能重建 Agent 的决策上下文:
- 当时的 System Prompt 是什么?
- 当时的历史对话是什么?
- 当时可用的工具列表是什么?
有了这些,即使没有记录每一步的详细过程,也能通过重放来理解 Agent 的行为。
小结
采集是可观测性的基础,但采集方案的选择要基于现实约束:
- 三种范式各有适用场景:SDK 埋点适合开发调试,网络代理适合集中管控,运行时观测适合安全审计
- 审计与运行时的错位是真实痛点:需要设计关联机制来弥合
- 可还原性 > 全量采集:目标不是存储所有数据,而是确保关键场景能被还原
下一篇是这个系列的最后一篇,我们来聊聊:有了数据之后,如何从数据走向判断?成本归因、异常检测、行为基线,这些”可观测性”如何走向”可运营”。
评论