Token导航 LogoToken导航TokenDH.com

手把手构建企业级 Agent 框架(九):可观测性与生产化运维 — 让 Agent 在黑夜中也能平稳运行

更新时间 2026-06-06来源 Aike正文 1.1万字阅读约 34分钟1 张图片

经过前面八篇文章的打磨,我们的企业级 Agent 框架已经功能完备:多渠道接入、自主推理、动态技能、多智能体协作、长短期记忆、安全沙箱和全链路权限控制。然而,当它真正部署到生产环境,面对数百个租户、每分钟上千次调用时,如何知道系统是否健康?如何快速定位一次失败的对话?如何预防潜在故障?这正是可观测性(Observability)要回答的问题。今天,我们将为整个框架注入OpenTelemetry 全链路追踪Prometheus 指标监控健康检查智能告警,并最终将所有组件打包为 Docker Compose 一键部署方案。

系列文章:

手把手构建企业级 Agent 框架:从 OpenClaw 架构到自主实现

手把手构建企业级 Agent 框架(二):Gateway 网关与多渠道接入

手把手构建企业级 Agent 框架(三):Pi Agent 运行时与 ReAct 循环

手把手构建企业级 Agent 框架(四):Skill 系统——知识注入与能力扩展

手把手构建企业级 Agent 框架(五):多智能体并行与任务委派,突破单 Agent 的性能与上下文瓶颈

手把手构建企业级 Agent 框架(六):双源记忆系统 — 让 Agent 真正记住你,跨越会话的智能回忆

手把手构建企业级 Agent 框架(七):工具系统与安全执行层 — 让 Agent 的双手安全而有力

手把手构建企业级 Agent 框架(八):安全、权限与企业集成 — 从单用户到企业多租户的信任模型重构

一、可观测性的三大支柱与整体设计

企业级可观测性建立在三大支柱之上:

  • 日志(Logs):
    不可变的带时间戳的事件记录,用于事后排查。我们已经在前文实现了结构化审计日志。
  • 指标(Metrics):
    可聚合的数值数据,反映系统健康状况,如 QPS、延迟、错误率、Token 消耗。
  • 链路追踪(Traces):
    记录一次请求在分布式系统中的完整路径,展示每个步骤的耗时和依赖关系。

OpenClaw 作为单进程应用,可观测性主要依赖日志和简单的健康检查。而我们的企业级部署涉及 Gateway、Agent、Worker、Redis、ChromaDB 等多个服务,必须构建统一的观测平面。下图展示了观测数据的流转架构:

图片

核心监控指标定义:

  • Agent 请求计数
     (agent_requests_total):按租户、渠道、状态(success/fail)统计。
  • LLM 调用延迟
     (llm_call_duration_seconds):P50/P95/P99 分位数。
  • 工具调用计数与延迟
     (tool_call_totaltool_call_duration_seconds)。
  • Token 消耗
     (token_usage_total):实时累计,用于成本追踪。
  • 活跃会话数
     (active_sessions):当前 WebSocket 连接数。
  • 队列深度
     (subagent_queue_depth):等待执行的子任务数量。

二、OpenTelemetry 全链路追踪实现

OpenTelemetry 是 CNCF 的可观测性标准。我们将使用 Python SDK 在代码的关键路径上创建 Span,形成完整的调用链。

2.1 安装依赖

pip install opentelemetry-api opentelemetry-sdk opentelemetry-instrumentation-fastapi
pip install opentelemetry-exporter-otlp opentelemetry-instrumentation-redis
pip install prometheus-client

2.2 初始化 Tracer Provider

在应用启动时全局配置 OpenTelemetry,将追踪数据导出到 Jaeger 或 OTLP Collector。

# telemetry.py
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.instrumentation.redis import RedisInstrumentor

definit_telemetry(service_name:str="eclaw-agent"):
"""初始化 OpenTelemetry,应在应用启动时调用一次"""
    resource = Resource(attributes={SERVICE_NAME: service_name})
    provider = TracerProvider(resource=resource)

# 导出到本地 Jaeger(或通过 Collector)
    exporter = OTLPSpanExporter(endpoint="http://localhost:4317", insecure=True)
    provider.add_span_processor(BatchSpanProcessor(exporter))
    trace.set_tracer_provider(provider)

definstrument_app(app):
"""自动为 FastAPI 应用添加追踪"""
    FastAPIInstrumentor.instrument_app(app)
    RedisInstrumentor().instrument()

2.3 在 Agent 运行时中创建自定义 Span

为 ReAct 循环的每个阶段创建独立 Span,清晰地展示思考→工具调用→观察的路径。

# agent.py 中的埋点
from opentelemetry import trace
tracer = trace.get_tracer(__name__)

classAgentRuntime:
asyncdefrun(self, session_id:str, user_input:str, ctx=None)-> AsyncIterator[dict]:
with tracer.start_as_current_span("agent.run")as root_span:
            root_span.set_attribute("session_id", session_id)
            root_span.set_attribute("user_input", user_input[:200])

            step =0
while step < self.max_steps:
                step +=1
# 思考阶段 Span
with tracer.start_as_current_span("agent.think")as think_span:
                    llm_response =await self._call_llm_with_retry(messages, ctx)
                    think_span.set_attribute("step", step)
                    think_span.set_attribute("action", llm_response.get("decision",{}).get("action","unknown"))

                decision = llm_response.get("decision",{})
                action = decision.get("action")

if action =="tool_call":
# 工具调用 Span
with tracer.start_as_current_span("agent.tool_call")as tool_span:
                        tool_span.set_attribute("tool_name", decision.get("tool_name"))
                        tool_span.set_attribute("tool_params",str(decision.get("tool_params",{})))
try:
                            tool_result =await self.executor.execute(
                                decision["tool_name"], decision.get("tool_params",{}), ctx
)
                            tool_span.set_attribute("tool_success",True)
except Exception as e:
                            tool_span.set_attribute("tool_success",False)
                            tool_span.set_attribute("tool_error",str(e))
raise
elif action =="final":
with tracer.start_as_current_span("agent.final_response"):
pass
break

2.4 在 ToolExecutor 中创建更深层 Span

# tool_executor.py 增强
from opentelemetry import trace

classToolExecutor:
asyncdefexecute(self, name:str, params: Dict, context: SecurityContext)-> Any:
with trace.get_tracer(__name__).start_as_current_span(f"tool.{name}")as span:
            span.set_attribute("tool.name", name)
            span.set_attribute("user.id", context.user_id)
            span.set_attribute("tenant.id", context.tenant_id)
# ... 原有执行逻辑

三、Prometheus 指标监控实现

我们需要暴露一个 /metrics 端点供 Prometheus 抓取。

3.1 定义指标

# metrics.py
from prometheus_client import Counter, Histogram, Gauge, generate_latest, CollectorRegistry

registry = CollectorRegistry()

# Agent 请求计数器
agent_requests = Counter(
'agent_requests_total','Total agent requests',
['tenant_id','channel','status'], registry=registry
)

# LLM 调用耗时
llm_call_duration = Histogram(
'llm_call_duration_seconds','LLM call duration',
['model'], buckets=[0.1,0.5,1,2,5,10,30], registry=registry
)

# 工具调用计数器
tool_calls = Counter(
'tool_call_total','Total tool calls',
['tool_name','status'], registry=registry
)

# 活跃 WebSocket 连接数
active_connections = Gauge(
'active_ws_connections','Active WebSocket connections',
    registry=registry
)

# Token 消耗累计
token_usage = Counter(
'token_usage_total','Total token usage',
['model','type'], registry=registry  # type: prompt/completion
)

defget_metrics():
return generate_latest(registry)

3.2 在代码中记录指标

# 在 Agent 的 _call_llm_with_retry 中
asyncdef_call_llm_with_retry(self, messages, ctx):
    start = time.time()
try:
        result =await self._actual_llm_call(messages)
        duration = time.time()- start
        llm_call_duration.labels(model=self.model_name).observe(duration)
# 模拟 token 计数(实际应从 API 响应中解析)
        token_usage.labels(model=self.model_name,type="prompt").inc(100)
        token_usage.labels(model=self.model_name,type="completion").inc(50)
return result
except Exception:
        agent_requests.labels(tenant_id=ctx.tenant_id if ctx else"unknown",
                             channel="internal", status="error").inc()
raise

3.3 暴露 /metrics 端点

# main.py 中添加
from fastapi import FastAPI, Response
from metrics import get_metrics, active_connections

app = FastAPI()

@app.get("/metrics")
asyncdefmetrics():
return Response(content=get_metrics(), media_type="text/plain")

# 在 WebSocket 连接建立和断开时更新 Gauge
@app.websocket("/ws/chat")
asyncdefws_endpoint(ws: WebSocket):
await ws.accept()
    active_connections.inc()
try:
# ... 处理消息
pass
finally:
        active_connections.dec()

四、健康检查与自愈设计

生产环境需要两个级别的健康检查:

  • Liveness(存活探针):
    进程是否存活,Kubernetes 据此决定是否重启 Pod。
  • Readiness(就绪探针):
    依赖服务(Redis、ChromaDB、LLM API)是否可用,决定是否接入流量。
# health.py
from fastapi import APIRouter
import redis.asyncio as aioredis

router = APIRouter()

@router.get("/health/live")
asyncdefliveness():
return{"status":"alive"}

@router.get("/health/ready")
asyncdefreadiness():
    checks ={}
# 检查 Redis
try:
        r = aioredis.from_url("redis://localhost:6379")
await r.ping()
        checks["redis"]="ok"
except Exception as e:
        checks["redis"]=f"error: {e}"
# 检查 ChromaDB(简单示例)
try:
import chromadb
        client = chromadb.Client()
        client.list_collections()
        checks["chromadb"]="ok"
except Exception as e:
        checks["chromadb"]=f"error: {e}"
# 如果有任何一个不健康,返回 503
    all_ok =all(v =="ok"for v in checks.values())
return{"status":"ready"if all_ok else"not_ready","checks": checks}

🔧 自愈策略建议:

  • 模型调用失败:
    自动切换备用模型(如 GPT-5 → DeepSeek),重试 3 次后返回降级回复。
  • 工具连续失败:
    触发熔断器,暂时禁用该工具并通知运维。
  • 消息队列积压:
    动态增加 Worker 实例(Kubernetes HPA)。
  • 内存泄漏:
    设置 Pod 资源限制,配合 liveness probe 自动重启。

五、告警规则配置

基于 Prometheus + AlertManager,定义关键告警规则:

# alerts.yml
groups:
-name: eclaw-agent
rules:
-alert: HighErrorRate
expr: rate(agent_requests_total{status="error"}[5m]) > 0.05
for: 2m
labels:
severity: critical
annotations:
summary:"Agent 请求错误率超过 5%"

-alert: HighLLMLatency
expr: histogram_quantile(0.95, rate(llm_call_duration_seconds_bucket[5m])) > 10
for: 5m
labels:
severity: warning
annotations:
summary:"LLM 调用 P95 延迟超过 10 秒"

-alert: ToolFailureSpike
expr: rate(tool_call_total{status="error"}[10m]) > 0.1
for: 5m
labels:
severity: warning
annotations:
summary:"工具调用失败率超过 10%"

-alert: HighTokenUsage
expr: rate(token_usage_total[1h]) > 100000
for: 10m
labels:
severity: info
annotations:
summary:"每小时 Token 消耗超过 10 万,请关注成本"

六、Docker Compose 生产级部署

将所有组件整合为一个编排文件,实现一键启动完整的可观测 Agent 平台。

6.1 项目结构

eclaw-deploy/
├── docker-compose.yml
├── gateway/
│   ├── Dockerfile
│   └── ... (Gateway 代码)
├── agent/
│   ├── Dockerfile
│   └── ... (Agent 代码)
├── worker/
│   ├── Dockerfile
│   └── ... (Worker 代码)
├── prometheus/
│   └── prometheus.yml
├── grafana/
│   └── dashboards/
│       └── agent-dashboard.json
└── alerts.yml

6.2 Docker Compose 编排

# docker-compose.yml
version:'3.8'

services:
gateway:
build: ./gateway
ports:
-"8000:8000"
environment:
- REDIS_URL=redis://redis:6379
- OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4317
depends_on:
- redis
- jaeger
healthcheck:
test:["CMD","curl","-f","http://localhost:8000/health/live"]
interval: 15s
retries:3

agent:
build: ./agent
environment:
- REDIS_URL=redis://redis:6379
- CHROMA_HOST=chroma
- OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4317
depends_on:
- redis
- chroma
deploy:
replicas:2

worker:
build: ./worker
environment:
- REDIS_URL=redis://redis:6379
- OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4317
depends_on:
- redis
deploy:
replicas:3

redis:
image: redis:7-alpine
ports:
-"6379:6379"
volumes:
- redis_data:/data

chroma:
image: chromadb/chroma:latest
ports:
-"8001:8000"
volumes:
- chroma_data:/chroma/data

jaeger:
image: jaegertracing/all-in-one:latest
ports:
-"16686:16686"# Jaeger UI
-"4317:4317"# OTLP gRPC

prometheus:
image: prom/prometheus:latest
ports:
-"9090:9090"
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
- ./alerts.yml:/etc/prometheus/alerts.yml
command:
-'--config.file=/etc/prometheus/prometheus.yml'

grafana:
image: grafana/grafana:latest
ports:
-"3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- ./grafana/dashboards:/etc/grafana/provisioning/dashboards

volumes:
redis_data:
chroma_data:

6.3 Prometheus 配置

# prometheus/prometheus.yml
global:
scrape_interval: 15s

alerting:
alertmanagers:
-static_configs:
-targets:['alertmanager:9093']

rule_files:
-'alerts.yml'

scrape_configs:
-job_name:'gateway'
static_configs:
-targets:['gateway:8000']
metrics_path:'/metrics'
-job_name:'agent'
static_configs:
-targets:['agent:8001']
metrics_path:'/metrics'

七、运行与验证

  1. 在 eclaw-deploy 目录下运行:
    docker-compose up -d
  2. 访问服务:
    • Gateway API: http://localhost:8000
    • Jaeger UI: http://localhost:16686(查看调用链)
    • Prometheus: http://localhost:9090(查看指标)
    • Grafana: http://localhost:3000(默认账号 admin/admin)
  3. 发送测试请求后,在 Jaeger 中搜索 Trace,你将看到完整的调用瀑布图:Gateway → Agent.run → Agent.think → Tool.query_order → ToolExecutor.execute,每一步的耗时一目了然。

📊 Grafana 看板建议:

  • 概览面板:
    总请求量、成功率、P95 延迟。
  • LLM 监控:
    各模型调用次数、延迟分布、Token 消耗趋势。
  • 工具调用面板:
    各工具 QPS、失败率 Top 10。
  • 基础设施:
    Redis 内存、ChromaDB 查询延迟、队列深度。

八、与 OpenClaw 的对标思考

🔍 “我们做了什么” vs “OpenClaw 为什么这样做”?

可观测性: OpenClaw 作为单进程应用,主要依赖控制台日志和文件日志来排查问题,这是个人工具的合理选择。我们的企业版引入了 OpenTelemetry 全链路追踪和 Prometheus 指标,能够在分布式、多实例环境下快速定位瓶颈和故障,是生产级系统的必备能力。

部署方式: OpenClaw 推崇“本地优先”,通常通过 npm 全局安装或 clone 源码运行,非常轻量。我们选择 Docker Compose 编排多服务,虽然增加了复杂度,但保证了环境一致性、服务隔离和弹性伸缩能力,适合团队协作和企业 IT 管理。

自愈能力: OpenClaw 依赖用户手动重启或简单的进程守护。我们的健康检查配合容器编排平台(如 Docker Compose 或 Kubernetes),可以实现自动重启、滚动更新和资源限制,大幅降低运维负担。

监控粒度: 我们在代码层面埋点了 LLM 调用、工具执行、会话管理等关键路径,可以实时追踪 Token 成本和调用链延迟。这种细粒度的可观测性是优化 Agent 性能和用户体验的基础。

本文的可观测性方案将 OpenClaw 式的 Agent 框架真正推向了生产就绪,使其具备企业级运维所要求的可见性、可控性和自愈能力。

九、总结与下一步

本文我们:

  1. 基于 OpenTelemetry 实现了从 Gateway 到 Agent 再到工具执行的全链路追踪。
  2. 使用 Prometheus 定义了核心业务指标,并暴露了 /metrics 端点。
  3. 添加了健康检查端点(Liveness + Readiness)和自愈策略建议。
  4. 配置了智能告警规则,覆盖错误率、延迟和成本异常。
  5. 将所有组件编排为 Docker Compose,实现一键启动可观测的生产环境。

下一篇文章预告:《第10篇:总结、对比与展望》——我们将回顾整个系列从架构到生产落地的完整历程,与 LangGraph 等主流框架进行横向对比,并展望多模态、本地模型推理等前沿扩展方向。

文章标签智能体
资讯来源:由AI资讯编辑整理自互联网公开内容,版权归原作者所有,未经许可,不得转载。

继续浏览更多资讯

返回资讯目录

相关资讯

更多