Elastic Agent Builder 中能够引用证据的 AI 根因分析

作者:来自 Elastic Jeffrey Rengifo

新版本发布失败率为 27.2%,旧版本为 28.2%,因此deployment从来都不是导致故障的原因;agent 在 72 秒内找出了这一点,并返回了导致故障的那条 trace 的 ID。

根因分析的工作,是将引发故障事件的最初失败,与由它导致的其他故障,或者只是与它同时发生的故障区分开来。级联故障让这件事变得很困难:一个恰好在同一分钟发布的部署,以及一个同时发生故障的第二服务,看起来都可能是原因。

三个 ES|QL 工具和大约二十几行 query 文本,就可以让 AI 根因分析变得可验证。本文将在 Elastic Agent Builder 中构建这些工具,并将它们连接到一个 agent,使其生成的报告能够指出每个数字背后的工具,以及每个根因判断背后的 trace.id 和文档 _id

复现此根因分析所需的环境

  • Elasticsearch 和 Kibana 9.5,可以是自管理部署,也可以运行在 Elastic Cloud 上。

  • 一个用于 Agent Builder 的生成式 AI connector。我们使用的是 Anthropic Claude Sonnet 4.6。

  • Python 3.12,以及 opentelemetry-distro[otlp]、Flask instrumentation 和 requests instrumentation。

  • 一个具有 OTLP 写入权限以及 Agent Builder API 所需 Kibana 访问权限的 Elasticsearch API key。

如果你想复现本文中的用例,可以使用配套 notebook

Demo 环境:运行 OpenTelemetry 的四个 Python 服务

四个 Python 服务运行在同一台主机上。checkout-api 负责处理客户请求,并调用 pricing-apipricing-api 再调用 fx-rates 获取汇率报价。第四个服务 search-api 提供商品搜索,并不属于这条调用链。

每个服务都直接通过 OTLP 将数据导出到 Elasticsearch 原生 OTLP endpoint,中间没有 collector。日志存储在 logs-generic.otel-default 中,span 存储在 traces-generic.otel-default 中。同时运行两个不同版本的 checkout-api 进程,而下面的其中一个工具依赖于这一点。

启动一个服务只需一条命令,其中OTLP endpoint和服务身份信息通过环境变量提供:

ini 复制代码
`

1.  export OTEL_EXPORTER_OTLP_ENDPOINT="${ES_URL}/_otlp"
2.  export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
3.  export OTEL_EXPORTER_OTLP_HEADERS="Authorization=ApiKey ${ES_API_KEY}"
4.  export OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true
5.  OTEL_SERVICE_NAME=checkout-api 

7.  OTEL_RESOURCE_ATTRIBUTES="service.version=2026.07.26.2,deployment.environment=production" 

9.  opentelemetry-instrument python services/checkout_api.py

`Lobster AI![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

为什么最 "吵闹" 的服务并不是根因

在 09:55:33Z,fx-rates 开始拒绝大约三分之一的请求,因为其缓存的 FX 快照已经超过了它所强制执行的 max_agepricing-api 将每次拒绝转换为 500 错误,而 checkout-api 则向客户返回 500 错误。

在同一时间窗口内,还发生了两件互不相关的事情。checkout-api 的 2026.07.26.2 版本于 09:55 开始发布,恰好就是错误开始出现的那一分钟;而在 fx-rates 发生故障 120 毫秒后,search-api 在对其目录执行计划内重新索引时开始出现超时。

一个故障发生了传播,而另外两个信号与它唯一的共同点就是时间:

值班人员看到的情况是:四个服务都处于不健康状态,并且刚刚完成了一次部署。以下查询给出了该时间窗口内每个服务的故障率:

ini 复制代码
`

1.  FROM traces-generic.otel-default
2.  | WHERE kind == "Server"
3.  | EVAL failed = CASE(attributes.http.status_code >= 500, 1, 0)
4.  | STATS failures = SUM(failed), requests = COUNT(*)
5.      BY service = resource.attributes.service.name
6.  | EVAL failure_rate_pct = ROUND(100.0 * failures / requests, 1)
7.  | SORT failure_rate_pct DESC

`Lobster AI

结果中没有任何内容对候选根因进行排序。search-api 的故障率最高,达到 36.4%,但它恰恰是唯一一个没有参与发生故障的结账请求的服务。

使用默认 Elastic AI Agent 自动执行根因分析

在编写任何工具之前,我们先将该故障事件交给默认的 Elastic AI Agent,看看仅凭内置的可观测性技能,它能够做到什么程度:

sql 复制代码
`

1.  checkout-api started returning HTTP 500s to customers today. Investigate the
2.  window 2026-07-26T09:50:00.000Z to 2026-07-26T10:02:00.000Z and tell me the root
3.  cause. Context you have from the deploy log: checkout-api release 2026.07.26.2
4.  rolled out at 09:55 UTC, and the on-call channel also reported search-api
5.  timeouts starting at 09:55 UTC.

`Lobster AI

它正确识别出了故障级联。它指出 fx-rates 是故障源头,引用了缓存快照过期的消息,并将 search-api 的超时作为另一个独立问题进行了区分。然后,它对自己的假设进行了排序:

假设 2 声称此次发布重启了 fx-rates,但没有刷新其快照,或者降低了其 max_age。但遥测数据中没有任何迹象支持这一说法:发布针对的是 checkout-api,而 fx-rates 的全部 7,374 个 span 中,service.version 都是 2026.07.19.1。

agent 掌握了一个时间戳:部署和第一次错误发生在同一分钟内。在三次运行中,它每次都通过不同的臆测机制将两者联系起来:

  • 一次重启导致快照丢失

  • 发布前没有执行过的一条新调用路径

  • 原本能够容忍过期数据的代码路径不再容忍

数字也存在同样的问题。同一回答中的影响摘要将故障率报告为"约 38%",它用 2,043 次失败除以 5,331 次成功,而不是除以 7,374 次请求,而且这个数字旁边没有对应的查询,读者无法验证。

本文的其余部分将通过工具来弥补这一缺口。

Agent Builder 中内置的 16 个可观测性工具

Agent Builder 自带了一些工具,可以覆盖大部分探索工作,因此在编写任何自定义工具之前,请先查看内置工具参考。工具目录位于 Agent Builder > Manage components > Tools

16 个内置工具属于可观测性工具,它们围绕调查步骤进行组织,而不是围绕索引操作:

工具 它能回答什么问题
observability.get_logs 对于这个过滤条件,日志量和日志形态如何?包括样本和消息类别
observability.get_traces 这些 trace 包含哪些文档?按 trace.id 分组
observability.get_service_topology 该服务有哪些依赖?每条连接的错误率是多少?
observability.get_apm_correlations 在缓慢或失败的事务中,哪些属性出现得更多?
observability.run_log_rate_analysis 哪些字段或模式与日志吞吐量的变化相关?
observability.get_log_change_points 哪些消息类别出现激增、下降或变化?发生在什么时候?

这里还有一个平台工具也很重要。platform.streams.investigation_progress_report 是 agent 在调查过程中用于发布假设列表的工具,其中包含每个假设的状态、结论,以及数据无法确定的内容的明确列表:

bash 复制代码
`

1.  {
2.    "summary": "string",
3.    "hypotheses": [
4.      {
5.        "candidate": "string",
6.        "confidence": 0.0,
7.        "status": "investigating | dismissed | confirmed",
8.        "reason": "string"
9.      }
10.    ],
11.    "conclusion": "string",
12.    "gaps_found": ["string"]
13.  }

`Lobster AI![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

用于 AI 根因分析的三个 ES|QL 工具

Agent Builder 的内置可观测性工具旨在展示整体情况。要排除一个候选根因,需要一个数字来回答一个具体问题,而这次故障事件中有两个问题没有对应的内置工具。第三个工具则返回文档 ID,让相关判断可以被验证。

如何区分一个故障事件与两个巧合同时发生的故障(rca_failure_shapes

两个服务只有在出现在相同的请求中时,即使它们在同一分钟内发生故障,也才属于同一个故障事件。这个工具按 trace 对错误日志进行分组,将每个 trace 压缩为其中发生错误的服务 集合 ,然后统计每种故障组合的数量:

sql 复制代码
`

1.  FROM logs-generic.otel-default
2.  | WHERE @timestamp >= TO_DATETIME(?start) AND @timestamp <= TO_DATETIME(?end)
3.    AND severity_text == "ERROR" AND trace_id IS NOT NULL
4.  | STATS services = VALUES(resource.attributes.service.name) BY trace_id
5.  | EVAL failure_shape = MV_CONCAT(MV_SORT(services), " + ")
6.  | STATS traces = COUNT(*) BY failure_shape
7.  | SORT traces DESC
8.  | LIMIT 20

`Lobster AI

MV_CONCAT(MV_SORT(...)) 在这里很重要。直接按多值字段进行分组时,ES|QL 会将其展开,每个服务生成一行,从而丢失服务组合关系。先将这个集合压缩成一个字符串,可以保持故障组合的完整性。

两个集群返回了结果,而且它们之间没有共享任何 trace。三个 checkout 服务在 2,043 个 trace 中同时出现,search-api 则独自在 1,324 个 trace 中报错,而这种零重叠关系在一行结果中就确定了这只是巧合。

如何排除部署是根因(rca_version_split

如果某个版本发布导致了故障,那么承载该版本的服务,其故障率应该明显高于它所替代的版本。服务端 span 同时包含 service.version 和响应状态,因此一个查询就可以给出答案:

ini 复制代码
`

1.  FROM traces-generic.otel-default
2.  | WHERE @timestamp >= TO_DATETIME(?start) AND @timestamp <= TO_DATETIME(?end)
3.    AND resource.attributes.service.name == ?service AND kind == "Server"
4.  | EVAL failed = CASE(attributes.http.status_code >= 500, 1, 0)
5.  | STATS failures = SUM(failed), requests = COUNT(*)
6.      BY version = resource.attributes.service.version
7.  | EVAL failure_rate_pct = ROUND(100.0 * failures / requests, 1)
8.  | SORT version

`Lobster AI

27.2% 对比 28.2%,在每个版本大约 3,700 次请求的情况下,这一差异处于噪声范围内。已经运行的版本与刚发布的版本故障率同样高,这就排除了此次发布是根因的可能性。

这个工具依赖两个版本同时提供服务。如果一次发布是瞬时且全面完成的,那么没有任何查询能够将一个损坏的新版本与恰好在同一时间发生的其他故障区分开来。

如何返回 agent 可以引用的文档 ID(rca_evidence_sample

ES|QL 源命令中的 METADATA _id 会返回 Elasticsearch 文档 ID,因此一个判断可以指向一条具体的记录:

sql 复制代码
`

1.  FROM logs-generic.otel-default METADATA _id, _index
2.  | WHERE @timestamp >= TO_DATETIME(?start) AND @timestamp <= TO_DATETIME(?end)
3.    AND severity_text == "ERROR" AND resource.attributes.service.name == ?service
4.  | KEEP @timestamp, _id, _index, trace_id,
5.         attributes.error.kind, attributes.upstream.service, body.text
6.  | SORT @timestamp DESC
7.  | LIMIT 5

`Lobster AI

每个工具都通过一次 POST kbn:/api/agent_builder/tools 调用进行注册,其中包含查询语句及其类型化参数。注册完成后,rca_failure_shapes 看起来如下;agent 会在调用时填充 startend

将工具描述写成它所支持的决策

模型会读取工具描述,以决定何时调用该工具,因此工具描述所发挥的作用比工具名称更大。

我们将 rca_failure_shapes 描述为一种用于判断两个同时发生故障的服务究竟属于同一个故障还是两个故障的方法。这种表述使它在问题提到第二个发生故障的服务时,就会被调用。

工具描述的权重也高于 agent 指令。即使只有这三个工具,并且 agent 只有一行指令:"你是一名 SRE 助手,帮助用户找出生产环境故障事件的根因",它仍然每次都会排除部署和巧合这两个可能性。

将工具连接到故障事件根因分析 agent

该 agent 获得三个自定义工具、四个内置可观测性工具,以及 platform.streams.investigation_progress_report。它的指令分为五个编号步骤:

sql 复制代码
`

1.  1. Scope. Establish the affected service, the failure window, and the size of the
2.     symptom before you name any cause. State the window as an explicit ISO 8601
3.     range and reuse that same range in every tool call.
4.  Enumerate. Write down at least three candidate causes before you test any of
5.  them. Include the candidate a human on call would reach for first, such as a
6.  recent deploy or another service that started failing at the same minute.
7.  Refute. For each candidate, state the observation that would prove it wrong,
8.  then run the query that produces that observation. A candidate is dismissed
9.  when the refuting observation is present, not when a different candidate
10.  looks better.
11.  Cite. Every number you report must name the tool that returned it. Every claim
12.  about a root cause must carry at least one trace_id and at least one document
13.  _id. A claim with no citation is not a finding, it is a guess.
14.  Report. Put anything the available data cannot settle in gaps_found rather
15.  than resolving it with reasoning.

`Lobster AI![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

两个规则紧随这些步骤之后。每条规则都用于避免我们在默认 agent 的回答中看到的一种错误:

  • 不要根据故障量对服务进行排序,然后将最 "吵闹" 的服务认定为根因。

  • 不要将时间相关性视为因果关系。两个服务在同一分钟开始发生故障,只有当它们出现在相同的 trace 中时,才属于同一个故障事件。

最终完成的 agent 关闭了 Elastic 功能,因此它生成的所有内容都来自这八个工具:

运行调查:11 次 工具调用 ,耗时 72 秒

我们向新 agent 提出了完全相同的问题,连措辞都没有改变。它首先打开了一份进度报告,列出了候选根因,然后以并行批次运行用于证伪的查询:

11 次工具调用、72 秒后,报告以一张引用表开头:

三层、三条逐字消息、三个文档 ID,以及一个由它们共享的 trace ID。源头消息说明了发生故障的机制:StaleQuoteError: fx snapshot age 1215s exceeds max_age 900s (provider=ecb-eod)

接下来是对各个候选原因的排除,每一项都附带了用于排除它的观测结果:

候选原因 用于排除的观测结果 工具
checkout-api 发布 2026.07.26.2 两个版本的故障率相同,分别为 27.2% 和 28.2%,每个版本约 3,700 次请求 rca_version_split
search-api 超时 没有共享 trace。2,043 个 trace 包含三个 checkout 服务,1,324 个 trace 仅包含 search-api rca_failure_shapes
数据库故障 checkout-api 日志中没有数据库错误 rca_evidence_sample

报告最后以一个差距部分收尾:

第一个差距涵盖了默认 agent 曾经用一个虚构的机制回答的问题。第一次错误发生时,快照已经有 1,127 秒没有更新,因此数据源在这个时间窗口开始之前就停止刷新了,而这个系统中的日志没有记录具体停止时间。下一步应该检查数据源摄取任务。

在 ES|QL 中手动验证 agent 的引用

报告给出了一个 trace ID,因此打开它:

三条记录之间相差 1 毫秒,顺序与报告所声称的完全一致。fx-rates 最先报错,并且没有指明上游服务,因为它就是源头。pricing-api 指明 fx-rates,而 checkout-api 指明 pricing-api

将 upstream.service 添加到错误日志中

upstream.service 是一种 OpenTelemetry 不提供的日志记录约定:每个服务在依赖项发生故障并因此产生错误时,将该字段写入错误记录。有了这个字段,错误记录就会按照调用链自动排列,而这种排序能够经受住时钟偏差以及导致基于时间戳排序失效的亚毫秒级跳转。

另外两个字段承担了其余的重要工作:只要日志是在活动 span 内生成的,trace.id 就可以通过自动 instrumentation 免费获得;结构化的 error.kind 则让你无需匹配消息文本,就可以统计不同的故障模式。

从你的数据流中提取知识指标

知识指标是 Elastic 使用 LLM 模型从你的原始数据中提取的事实。它会识别底层基础设施以及服务之间的依赖关系等信息。

你可以在 Streams > {your_stream} > Significant events 下找到这个选项:

点击生成,系统就会为你创建这些指标:

三个 ES|QL 工具如何改变了 agent 的回答

三个 ES|QL 工具和大约二十几行查询文本,将 agent 的回答变成了一份报告:列出候选原因,展示排除每个候选原因的观测结果,并为每个数字提供对应的记录 ID。

下一步

  • 运行配套 notebook,在你自己的集群中重现此次故障事件、这些工具以及该 agent。

  • 编写一个能够排除你们团队最常见错误答案的查询,例如最近的部署或最"吵闹"的服务,然后将其注册为工具,并在工具描述中说明它所支持的决策。

  • 审查一个服务的错误日志,确认其中包含 trace.id、结构化的 error.kind 和明确声明的 upstream.service。后两个字段只需要对日志记录做一行修改。

  • 将你们团队已经了解的故障模式注册为重大事件,这样下一次调查就可以从已有历史开始,而不是面对一个空白时间窗口。

  • 如果想了解另一种 Agent Builder 调查方式,请阅读从五个仪表板到一个提示,该文章使用五个 ES|QL 工具对 APM 服务健康状况进行评分。

常见问题

如何判断两个同时发生故障的服务是否属于同一个故障事件?

按照 trace.id 对错误日志进行分组,并比较每个 trace 中出现的服务集合。属于同一个级联故障的服务会共享 trace ID,而两个无关的故障则不会。在这个演示中,2,043 个 trace 同时包含 checkout-apipricing-apifx-rates 的错误,而 1,324 个 trace 仅包含 search-api 的错误,两者没有任何重叠。

如何排除部署是故障事件的根因?

在相同的时间窗口内,比较新服务 service.version 与其替代版本的请求故障率。如果两个版本的故障率相近,那么此次发布就不是根因。这要求两个版本曾经同时承载流量。

Agent Builder 工具如何返回用于引用的文档 ID?

在 ES|QL 源命令中添加 METADATA _id, _index。这样,工具就会在每一行旁边返回 Elasticsearch 文档 ID,使 agent 能够引用具体记录,而读者也可以在 Discover 中打开该记录。

原文:AI root cause analysis with ES|QL tools in Agent Builder | Elastic Observability Labs

相关推荐
考虑考虑3 小时前
elasticSearch中的element_type
运维·后端·elasticsearch
CHANCE V7 小时前
Elasticsearch 进阶
大数据·elasticsearch·搜索引擎
Elasticsearch9 小时前
Elasticsearch 向量数据库:几分钟内完成部署,以经济高效的方式扩展至数千亿规模
elasticsearch
vx-Biye_Design16 小时前
SSM伴侣动物伴护星小程序06330-计算机课程设计、毕业设计
spring boot·后端·elasticsearch·小程序·架构·课程设计·idea
云泽80819 小时前
Git 版本控制系统(下):从 .git 目录结构到冲突解决机制详解
大数据·git·elasticsearch
Elastic 中国社区官方博客19 小时前
教程:使用 ES|QL 进行威胁狩猎
大数据·运维·数据库·elasticsearch·搜索引擎·全文检索·安全威胁分析
Elasticsearch1 天前
jina-reranker-v3.5:通过混合注意力与自蒸馏实现更快的列表式重排序
elasticsearch
Elasticsearch1 天前
Elasticsearch Python DSL 客户端开发
elasticsearch
麻辣布丁1 天前
Git冲突原因与解决方法全解
大数据·git·elasticsearch