☰
AI代理可观测性实战:OTel+OpenLit+Elastic全链路追踪
2026/9/26 4:06:57 网站建设 项目流程

1. AI代理可观测性为什么成了绕不开的坎

AI代理和传统后端服务有一个本质区别:它的执行路径不是确定性的。你给它一个输入,它可能调用三次工具、也可能调用十次;可能走检索增强生成(RAG)链路,也可能直接凭模型内部知识作答;同一个问题问两遍,token消耗和响应延迟可能差出一倍。这种不确定性带来的直接后果是——出了问题你根本不知道从哪儿查。

我最早接触AI代理监控是在一个客服问答机器人的项目上。用户反馈“回答变慢了”,但我们翻遍应用日志只看到一条“请求完成,耗时8.2秒”,中间发生了什么完全是黑盒。是模型推理慢?是向量检索慢?还是某个外部工具调用超时重试了三次?没有链路数据,只能靠猜。后来我们接入了OpenTelemetry(简称OTel)做分布式追踪,把代理的每一步——规划、工具调用、模型推理、结果聚合——都打上span,才第一次看清了整条执行链路。

这就是AI代理可观测性要解决的核心问题:让一个非确定性的、多步骤的、涉及外部依赖的执行过程变得可追踪、可度量、可归因。OTel负责采集和标准化遥测数据,OpenLit负责把AI相关的语义约定(比如token数、模型名、prompt内容)自动注入到span里,Elastic负责存储、检索和可视化。三者组合起来,就是一套从采集到分析的完整方案。

这套东西适合谁?如果你正在把AI代理往生产环境推,或者已经被“代理行为不可预测”折磨过,那这套方案值得花时间搭起来。如果你只是本地跑个demo玩玩,那确实用不上,但了解一下思路没坏处。

2. 三个组件各自扮演什么角色

2.1 OTel:遥测数据的“普通话”

OpenTelemetry的核心价值在于标准化。在OTel出现之前,每个监控厂商都有自己的SDK和数据格式,你用了A厂商的APM,想换B厂商就得把所有埋点重写一遍。OTel定义了一套与厂商无关的API、SDK和协议(OTLP),你只需要按它的规范埋点,后端想换谁换谁。

对于AI代理场景,OTel提供三种信号:

  • Traces(链路):记录一次请求从入口到出口的完整调用链,每个步骤是一个span,span之间有父子关系。这是排查“哪一步慢了”的核心武器。
  • Metrics(指标):聚合性的数值,比如请求总数、平均延迟、token消耗总量。适合做告警和趋势分析。
  • Logs(日志):离散的事件记录,适合记录具体的错误信息和调试细节。

三者不是孤立的。OTel的Exemplar机制可以把trace ID关联到metric数据点上,你在看延迟飙升的指标时,能直接跳到对应的慢链路。这个能力在排查AI代理的间歇性性能问题时特别有用。

2.2 OpenLit:给AI代理装上“AI语义”

OTel本身是通用的,它不知道什么是“模型推理”、什么是“token消耗”。OpenLit做的事情就是在OTel的基础上,为AI/LLM场景补充语义约定。它提供了一套自动埋点能力,你只需要初始化OpenLit,它就会自动拦截主流AI框架(LangChain、LlamaIndex、OpenAI SDK等)的调用,生成带有AI专属属性的span。

这些属性包括但不限于:

属性名含义排查时的用途
gen_ai.systemAI系统标识(如openai)区分不同模型提供商
gen_ai.request.model请求的模型名对比不同模型的表现
gen_ai.usage.prompt_tokens输入token数分析成本构成
gen_ai.usage.completion_tokens输出token数发现异常长的输出
gen_ai.response.finish_reasons结束原因判断是否被截断

没有这些属性,你看到的只是一个普通的HTTP调用span,根本不知道这次调用消耗了多少token、用的是哪个模型。OpenLit把这些信息补全了,让链路数据真正对AI场景有意义。

2.3 Elastic:存储、检索与可视化

Elastic在这里承担的是可观测性后端的角色。OTel Collector把数据通过OTLP协议发过来,Elastic的APM Server接收后存入Elasticsearch,Kibana提供可视化界面。

选择Elastic的理由有几个:一是它对OTLP的原生支持已经比较成熟,不需要额外的转换层;二是它的查询语言(KQL、ES|QL)足够灵活,能做复杂的聚合分析;三是它的APM界面开箱即用,服务地图、延迟分布、错误率这些视图不需要自己从零搭。

当然Elastic不是唯一选择,Jaeger、Grafana Tempo、Datadog都能接OTel数据。但如果你已经在用Elastic Stack做日志分析,那把它扩展成可观测性后端是最省事的路径。

3. 从零搭建:环境准备与部署实操

3.1 部署Elastic Stack

我推荐用Docker Compose来搭本地环境,比手动安装省心得多。Elastic官方提供了docker-compose.yml模板,但默认配置对可观测性场景不够用,需要做几处调整。

首先创建一个docker-compose.yml:

version: '3.8' services: elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:8.13.0 environment: - discovery.type=single-node - xpack.security.enabled=false - ES_JAVA_OPTS=-Xms2g -Xmx2g ports: - "9200:9200" volumes: - es-data:/usr/share/elasticsearch/data kibana: image: docker.elastic.co/kibana/kibana:8.13.0 environment: - ELASTICSEARCH_HOSTS=http://elasticsearch:9200 ports: - "5601:5601" depends_on: - elasticsearch apm-server: image: docker.elastic.co/apm/apm-server:8.13.0 command: > apm-server -e -E output.elasticsearch.hosts=["elasticsearch:9200"] -E apm-server.auth.anonymous.enabled=true -E apm-server.rum.enabled=false ports: - "8200:8200" depends_on: - elasticsearch volumes: es-data:

几个关键点说明一下。xpack.security.enabled=false是为了本地调试方便,生产环境必须开启。ES_JAVA_OPTS设置堆内存为2GB,低于这个值在数据量上来后容易OOM。APM Server的auth.anonymous.enabled=true允许匿名上报,同样只适合本地环境。

启动后访问http://localhost:5601确认Kibana正常,访问http://localhost:8200看到APM Server的JSON响应就算通了。

注意:Elastic 8.x默认开启安全认证,如果不想关安全,需要生成 enrollment token 并配置证书,步骤会多不少。本地开发建议先关掉,跑通流程后再补安全配置。

3.2 配置OTel Collector

OTel Collector是数据管道的中枢,它接收应用发来的遥测数据,经过处理后转发给Elastic。创建一个otel-collector-config.yaml:

receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 http: endpoint: 0.0.0.0:4318 processors: batch: timeout: 5s send_batch_size: 1024 memory_limiter: check_interval: 1s limit_mib: 512 exporters: elasticsearch: endpoints: ["http://apm-server:8200"] tls: insecure: true service: pipelines: traces: receivers: [otlp] processors: [memory_limiter, batch] exporters: [elasticsearch] metrics: receivers: [otlp] processors: [memory_limiter, batch] exporters: [elasticsearch]

memory_limiter这个processor建议一定要加。AI代理的span数据量可能很大,特别是prompt内容被完整记录时,没有内存限制Collector可能被撑爆。batch的timeout设为5秒是个折中值,太短会增加网络请求数,太长会导致数据延迟可见。

3.3 应用侧接入OpenLit

Python环境下接入最简单:

pip install openlit opentelemetry-exporter-otlp

然后在应用入口处初始化:

import openlit openlit.init( otlp_endpoint="http://localhost:4318", application_name="my-ai-agent", environment="development", capture_message_content=True, )

capture_message_content=True会把prompt和completion的完整内容记录到span里。这个选项在调试阶段非常有用,但生产环境要慎重——一是数据量大,二是可能包含敏感信息。建议生产环境关掉,或者只记录摘要。

初始化完成后,OpenLit会自动拦截LangChain、OpenAI SDK等库的调用。你不需要改任何业务代码,原有的代理逻辑照常跑,遥测数据会自动上报。

4. 核心细节:埋点策略与数据采集要点

4.1 手动埋点补充自动埋点的盲区

OpenLit的自动埋点覆盖了主流AI框架的调用,但代理的“规划”和“决策”环节往往是自己写的逻辑,自动埋点覆盖不到。这时候需要手动加span:

from opentelemetry import trace tracer = trace.get_tracer("agent.planner") def plan_task(user_input): with tracer.start_as_current_span("agent.plan") as span: span.set_attribute("agent.input_length", len(user_input)) plan = llm_plan(user_input) span.set_attribute("agent.step_count", len(plan.steps)) span.set_attribute("agent.plan_type", plan.type) return plan

手动span的价值在于把代理的决策过程显式化。当用户反馈“代理选错了工具”时,你能直接看到规划阶段的输入和输出,而不是只能看到工具调用的结果。

4.2 Span的粒度控制

粒度太粗,排查时定位不到具体步骤;粒度太细,数据量爆炸且链路图难以阅读。我的经验是遵循“一个可独立失败的步骤一个span”原则。

具体来说:

  • 每次模型推理调用 → 一个span
  • 每次外部工具调用 → 一个span
  • 每次向量检索 → 一个span
  • 代理的整体规划 → 一个span(父span)
  • 整个请求入口 → 一个span(根span)

不要为每个token生成事件,也不要把整个代理循环塞进一个span。前者数据量不可控,后者失去了追踪的意义。

4.3 采样策略的选择

生产环境不可能记录100%的链路,采样是必须的。OTel支持两种采样:

  • 头部采样(Head-based):在链路开始时决定是否采样,实现简单但可能漏掉慢请求。
  • 尾部采样(Tail-based):等链路结束后根据条件决定,能保证慢请求和错误请求被采集,但需要Collector缓存完整链路。

对于AI代理场景,我强烈建议用尾部采样。因为AI代理的慢请求往往是间歇性的,头部采样很容易漏掉。在Collector里配置:

processors: tail_sampling: decision_wait: 10s policies: - name: errors type: status_code status_code: {status_codes: [ERROR]} - name: slow-requests type: latency latency: {threshold_ms: 5000} - name: sample-10-percent type: probabilistic probabilistic: {sampling_percentage: 10}

这个配置保证所有错误链路和超过5秒的慢链路都被采集,其余链路按10%采样。decision_wait设为10秒是因为AI代理的链路可能很长,需要等足够时间让所有span到达。

提示:尾部采样会显著增加Collector的内存消耗,因为要缓存等待中的链路。如果内存紧张,可以适当降低decision_wait或减少采样策略。

5. 实操过程:从数据采集到问题定位

5.1 验证数据链路是否通畅

搭好环境后第一步是确认数据能到Elastic。在Kibana里进入Observability → APM,如果看到my-ai-agent这个服务出现,说明链路通了。点进去应该能看到:

  • 服务地图:展示服务间的调用关系
  • 延迟分布:P50、P95、P99延迟曲线
  • 事务列表:每次请求的详细链路

如果服务没出现,按这个顺序排查:应用是否成功初始化OpenLit → OTel Collector是否收到数据(看Collector日志)→ APM Server是否正常接收(看APM Server日志)→ Elasticsearch是否有索引写入。

5.2 用KQL定位慢请求

假设用户反馈“某些问题回答特别慢”,在Kibana的APM界面用KQL过滤:

service.name: "my-ai-agent" and transaction.duration.us > 5000000

这会筛出所有超过5秒的请求。点进任意一条,能看到完整的span瀑布图。我实际排查过的一个案例中,瀑布图显示:

  • 根span:8.2秒
  • 规划span:0.3秒
  • 向量检索span:0.5秒
  • 模型推理span:7.1秒
  • 结果聚合span:0.3秒

问题一目了然——模型推理占了87%的时间。进一步看span属性,发现gen_ai.usage.completion_tokens高达2000,而正常请求只有200左右。原因是某个用户的提问触发了模型的长篇输出,而我们的max_tokens设置得太宽松。

5.3 用ES|QL做聚合分析

Kibana的ES|QL(Elasticsearch Query Language)适合做跨链路的聚合分析。比如统计不同模型的平均token消耗:

FROM traces-apm* | WHERE service.name == "my-ai-agent" | STATS avg_prompt = AVG(gen_ai.usage.prompt_tokens), avg_completion = AVG(gen_ai.usage.completion_tokens) BY gen_ai.request.model

这个查询能帮你判断哪个模型的性价比最高。我们当时对比了三个模型,发现其中一个模型虽然单价低,但completion token数平均是其他模型的两倍,实际成本反而更高。没有这个聚合数据,光看单价很容易做出错误决策。

5.4 建立告警规则

可观测性的最终目的是提前发现问题,而不是等用户投诉。在Kibana的Alerting里可以基于APM指标建告警:

告警项条件严重级别
高延迟P95延迟 > 10秒持续5分钟Critical
错误率错误率 > 5%持续3分钟Critical
Token异常单次请求completion token > 4000Warning
工具调用失败工具span错误率 > 10%Warning

告警触发后可以对接邮件、Slack或PagerDuty。关键是阈值要结合业务实际调整,不要照搬默认值。我们一开始把延迟阈值设为3秒,结果告警天天响,后来发现AI代理的正常延迟就在2-4秒之间,调到10秒后才变得有意义。

6. 常见问题与排查技巧实录

6.1 数据不上报的排查路径

这是最常见的问题,按以下顺序检查:

  1. OpenLit是否初始化成功:在应用启动日志里搜索“openlit”,确认没有报错。
  2. OTLP端点是否可达:用curl http://localhost:4318/v1/traces测试,返回405说明端点活着。
  3. Collector日志是否有错误:docker logs otel-collector看有没有导出失败的信息。
  4. APM Server是否接收:docker logs apm-server看有没有publish相关的日志。
  5. Elasticsearch索引是否存在:curl http://localhost:9200/_cat/indices?v看有没有traces-apm*索引。

我踩过的一个坑是:OpenLit初始化时otlp_endpoint写成了http://localhost:4317(gRPC端口),但OpenLit默认用HTTP协议,导致数据发不出去。改成4318就好了。gRPC和HTTP的端口别搞混。

6.2 Span丢失与链路断裂

有时候能看到部分span,但链路不完整。常见原因:

  • 上下文传播失败:跨进程调用时trace context没有正确传递。检查是否在HTTP header里带了traceparent。
  • 异步任务未关联:Python的asyncio任务如果没正确传递context,span会变成孤儿span。用trace.use_span()手动关联。
  • 采样决策不一致:如果多个服务各自采样,可能出现上游采了下游没采的情况。统一在Collector做尾部采样可以避免。

6.3 数据量过大导致Elasticsearch压力

AI代理的span属性多、内容长,数据量比普通APM大得多。几个优化手段:

  • 关闭prompt内容记录:生产环境设capture_message_content=False。
  • 设置索引生命周期(ILM):trace数据保留7天,之后自动删除或归档到冷存储。
  • 限制属性长度:在Collector里用attributesprocessor截断过长的属性值。
  • 调整刷新间隔:Elasticsearch默认1秒刷新一次,对trace数据可以调到30秒,减少写入压力。

6.4 常见问题速查表

现象可能原因解决方法
服务不出现在APMOpenLit未初始化或端点错误检查初始化代码和端点端口
链路只有根span自动埋点未生效确认AI框架版本被OpenLit支持
延迟数据偏高采样包含了大量慢请求检查采样策略是否偏向慢请求
token数为0模型响应未包含usage信息确认模型API返回了usage字段
Kibana查询超时索引过大或查询范围太广缩小时间范围或优化查询条件

7. 生产环境部署的几点经验

本地跑通和上生产是两回事。几个我在实际部署中总结的要点:

安全配置不能省。本地关掉的安全认证,生产必须开。APM Server的secret token、Elasticsearch的TLS、Kibana的认证,一个都不能少。我见过因为APM端点暴露导致trace数据被篡改的案例,虽然不常见但后果严重。

资源规划要留余量。Elasticsearch对内存很敏感,trace数据又是写入密集型的。建议至少给Elasticsearch分配4GB堆内存,并且用SSD存储。如果数据量特别大,考虑用Elastic Cloud的Serverless模式,省去运维成本。

采样率要动态调整。业务低峰期可以调高采样率获取更多细节,高峰期调低减少压力。这个可以通过Collector的配置热更新实现,不需要重启服务。

和现有监控体系打通。如果团队已经在用Prometheus做指标监控,可以把OTel的metric数据同时导出到Prometheus和Elastic,避免形成数据孤岛。OTel Collector支持配置多个exporter,一份数据多处消费。

定期review span属性。随着代理逻辑的迭代,span属性可能会变得冗余或缺失。建议每个季度review一次埋点方案,删掉不再使用的属性,补充新业务需要的属性。我们有一次发现某个关键的工具调用参数没有被记录,导致排查问题时缺少关键信息,后来补上才解决。

这套方案我从最初搭建到稳定运行大概花了两周时间,其中大部分时间花在调采样策略和优化Elasticsearch性能上。一旦跑顺了,排查AI代理问题的效率提升是数量级的——以前靠猜,现在靠数据。如果你也在被AI代理的“黑盒”问题困扰,建议尽早把这套可观测性体系搭起来,越早搭收益越大。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询