Agent 安全 #05:审计与可观测性 —— 你的 Agent 每小时在做 300 个决策,你一个都不知道

人工智能Agent 2026-07-23 49
预计阅读时间:8 分钟

系列:Agent 安全专题 | 编号:05 | 专栏:人工智能 Agent 从部署到生产

TL;DR

Agent 不是黑盒——它是你可以拆开的灰盒。每一次 LLM 调用、每一次工具执行、每一次记忆写入,都可以被记录、标记、回溯。 本文给出一个生产可用的审计架构:结构化日志 → 链路追踪 → 指标聚合,三层递进。不是概念框架——每层都有可直接套用的 JSON schema 和代码示例。


1. 为什么 Agent 的审计和传统 API 审计不同?

传统 API 审计:

请求 → [服务] → 响应

一条日志就能说清楚:谁、什么时候、调了什么、返回了什么。 Agent 的调用链:

用户输入 → LLM 思考 → 工具 A → LLM 再思考 → 工具 B(出错)
→ 错误处理 → LLM 重试 → 工具 B(成功) → LLM 合成回复

一条请求产生了 7+ 次 LLM 调用和 3+ 次工具调用。 单条日志完全不够。 更关键的是:Agent 的决策过程不是黑盒——它在思考链(chain-of-thought)里完整暴露了自己的推理逻辑。你不记录它,它就白白消失了。


2. 三层审计架构

┌─────────────────────────────────────────────┐
 第三层:指标(Metrics                       
 Prometheus + Grafana                        
 LLM 调用量/成本/token 用量/工具错误率          
├─────────────────────────────────────────────┤
 第二层:链路追踪(Traces                     
 OpenTelemetry Span                          
 一次 Agent Run = 一个 Trace                  
 每个 tool_call / LLM call = 一个 Span        
├─────────────────────────────────────────────┤
 第一层:结构化日志(Structured Logs          
 JSON Lines,每行一个事件                      
 包含 session_idtrace_idevent_type        
└─────────────────────────────────────────────┘

2.1 第一层:结构化日志

每一条日志是一个 JSON 对象,一行。字段:

{
  "timestamp": "2026-07-23T10:15:32.123Z",
  "session_id": "sess_abc123",
  "trace_id": "trace_xyz789",
  "span_id": "span_001",
  "event_type": "tool_call",
  "tool_name": "terminal",
  "tool_input": {"command": "ls /tmp"},
  "tool_output_truncated": "file1
file2
Err...",
  "tool_output_sha256": "e3b0c442...",
  "duration_ms": 234,
  "error": null
}

event_type 枚举: | event_type | 含义 | 记录时机 | |-----------|------|---------| | session_start | Agent 会话开始 | 用户输入接收时 | | llm_call | LLM API 调用 | 每次 /chat/completions 请求 | | tool_call | 工具执行 | 每次工具函数返回时 | | memory_write | 记忆写入 | 每次 hindsight_retain | | decision | Agent 决策点 | 关键分支(如 safety check 拒绝) | | session_end | 会话结束 | 回复发送后 |

2.2 第二层:链路追踪

用 OpenTelemetry 构建 Span 树:

from opentelemetry import trace
tracer = trace.get_tracer("hermes-agent")
# 每个 Agent Run 是一个 Trace
with tracer.start_as_current_span("agent_run") as run_span:
    run_span.set_attribute("session_id", session_id)
    run_span.set_attribute("user_input_hash", sha256(user_input))
    # 每次 LLM 调用是一个子 Span
    with tracer.start_as_current_span("llm_call") as llm_span:
        llm_span.set_attribute("model", "deepseek-v4-pro")
        llm_span.set_attribute("input_tokens", 3421)
        llm_span.set_attribute("output_tokens", 156)
        llm_span.set_attribute("duration_ms", 1832)
        # 实际 API 调用...
    # 每次工具调用是一个子 Span
    with tracer.start_as_current_span("tool_call") as tool_span:
        tool_span.set_attribute("tool.name", "terminal")
        tool_span.set_attribute("tool.duration_ms", 234)
        # 实际工具调用...

导出到 Jaeger/Zipkin,你就能看到一次 Agent Run 的完整调用瀑布图——哪次 LLM 调用最慢、哪个工具报错、重试了几次。

2.3 第三层:指标

Prometheus metrics 示例:

from prometheus_client import Counter, Histogram, Gauge
llm_calls_total = Counter('agent_llm_calls_total', 'Total LLM calls', ['model', 'status'])
llm_tokens_total = Counter('agent_llm_tokens_total', 'Total tokens', ['model', 'type'])
tool_calls_total = Counter('agent_tool_calls_total', 'Total tool calls', ['tool', 'status'])
agent_cost_dollars = Counter('agent_cost_dollars_total', 'Total API cost in USD', ['model'])
agent_session_duration = Histogram('agent_session_duration_seconds', 'Session duration')
# 每次 LLM 调用后
llm_calls_total.labels(model='deepseek-v4-pro', status='success').inc()
llm_tokens_total.labels(model='deepseek-v4-pro', type='input').inc(3421)
agent_cost_dollars.labels(model='deepseek-v4-pro').inc(0.0042)

3. 一个完整的审计事件流

假设用户说「帮我查一下今天的天气,然后发邮件给老板请假」。 审计日志记录的事件序列:

10:15:30  session_start   user_id=xxx, input_hash=abc
10:15:30  llm_call        model=deepseek, tokens_in=210, tokens_out=45
10:15:31  tool_call       tool=web_search, query="北京天气 2026-07-23"
10:15:32  tool_call       tool=web_search, output="晴 35°C"
10:15:32  llm_call        model=deepseek, tokens_in=320, tokens_out=78
10:15:33  decision        type=safety_check, action=allowed
10:15:33  tool_call       tool=agently_mail, to=manager@company.com
10:15:35  tool_call       tool=agently_mail, status=sent
10:15:35  llm_call        model=deepseek, tokens_in=150, tokens_out=60
10:15:36  session_end     duration_ms=6234, total_cost_usd=0.012

如果有问题:回溯到 10:15:33safety_check,看当时 Agent 为什么决定允许发邮件;或者查 10:15:33agently_mail 调用,看收件人是不是正确的。

4. 异常检测

有了完整的审计日志 + 指标,可以做自动异常检测: | 异常模式 | 检测方法 | 示例 | |---------|---------|------| | LLM 调用激增 | llm_calls_total rate 5min > 基线 × 3 | 陷入重试循环 | | 工具调用失败率飙升 | tool_calls_total{status="error"} / total > 20% | 外部 API 宕机 | | Token 用量异常 | 单次 llm_call input_tokens > 10K | 上下文爆炸 | | 成本异常 | agent_cost_dollars hourly > $5 | 配置错误导致反复调用 | | 关键操作频率异常 | decision{type="safety_denied"} rate > 0 | 攻击者正在探测边界 |


5. 落地建议

不要等 Agent 出事了再补审计。 审计日志是你出事后唯一的回溯能力。没有它,你连「Agent 到底做了什么」都说不清楚。 最小可落地版本(第一周就能上): 1. 每个 Agent Run 生成一个 session_id + trace_id 2. 每次 LLM 调用和工具调用写一行 JSON 日志 3. 用 jq 就能查:cat audit.log | jq 'select(.event_type=="tool_call" and .error!=null)' 从 Week 2 开始加 OpenTelemetry + Prometheus。不要一开始就追求「完美架构」——先保证完整性,再追求可视化


6. 与前四篇的衔接

安全专题 本文关联
#01 Prompt Injection 防御 审计日志能发现异常 prompt 模式
#02 Tool Sandbox 安全边界 审计记录了每次 sandbox 决策
#03 多 Agent 协作 Kanban 架构 跨 Agent 调用链需要全局 trace_id
#04 Credential Vault 凭据设计 凭据访问必须写入审计日志(不可篡改)
---
下一篇预告:#06 Agent 灰度发布与回滚 —— 如何在不炸生产的前提下上线新 Agent 版本

本文由 admin 原创,转载请注明出处。

相关推荐

评论

0
暂无评论,来发表第一条评论吧

发表评论

登录 后发表评论

发现更多