乔治亚州立大学研究中心基于Amazon Bedrock和Strands Agents SDK构建代理流水线,将生产问题根因分析从15-30分钟人工排查缩短至60秒内自动完成。
这是 TReNDS 中心 Vitaly Omelchenko 的来稿。
在神经影像学与数据科学转化研究中心(TReNDS),这是佐治亚州立大学、佐治亚理工学院和埃默里大学的联合中心,我们开发和应用先进的分析方法与神经信息学工具来推进大脑健康研究。自 2019 年以来,我们的基础设施一直运行在 Amazon Web Services(AWS)上,多年来构建了多样化的应用,包括研究工具和 API,全部运行在 Amazon Elastic Kubernetes Service(Amazon EKS)上,日志通过 FluentBit 输送到 Amazon CloudWatch。
随着应用规模的增长,我们需要调查的错误量也在增加。当我们开始探索 Amazon Bedrock 时,看到了期待已久的机遇:可以将事件响应中最耗时的部分——根本原因调查本身——实现自动化。
在这篇文章中,我们分享在 TReNDS 生产环境中构建和使用的架构。它结合了 Amazon CloudWatch 订阅过滤器、AWS Lambda、Strands Agents SDK 和 Amazon Bedrock,实现实时错误检测、日志上下文和 GitHub 源代码的丰富补充,以及向团队提供 AI 驱动的根本原因分析。
本文中的架构和建议反映了我们 TReNDS 团队的经验,不构成佐治亚州立大学、佐治亚理工学院或埃默里大学的官方指导。
与许多团队一样,我们已有告警和监控机制。我们知道什么时候出问题了。然而,知道某物失败了和理解它为什么失败是两回事。我们的工程师仍然需要打开 Amazon CloudWatch Logs,阅读堆栈跟踪,找到相关的源文件,并在脑海中追踪执行路径。对于简单的错误,这需要 15–30 分钟。对于涉及多个服务的复杂问题,则需要更长时间。
我们意识到,这个调查过程正是具备正确工具的基础模型所做的那种工作。模型不仅仅是总结错误信息,而是通过获取周围的日志上下文、阅读源代码并生成结构化分析来调查错误。这就是我们要构建的东西。
以下是我们最终采用的架构:

图 1 — 自动根本原因分析架构
我们在 EKS 上的应用通过 FluentBit 向 CloudWatch 发送日志。CloudWatch 订阅过滤器监视错误级别的模式(ERROR、Exception、FATAL、CRITICAL),当匹配发生时调用 Lambda 函数。Lambda 运行由 Amazon Bedrock 驱动的 Strands Agent 来调查错误,然后将分析发布到 Amazon Simple Notification Service(Amazon SNS)主题以交付给我们的团队。
系统的核心是 Amazon Bedrock。基础模型(FM)负责对错误、代码和根本原因进行实际推理。我们在 Amazon Bedrock 之上使用 Strands Agents SDK 来处理工具调用编排。我们定义可用的工具,模型决定何时以及如何调用它们。面对堆栈跟踪,Agent 可能会获取相关源文件,意识到需要更多上下文,搜索相关的错误处理,并生成结构化分析——而无需我们硬编码调查路径。
由于 TReNDS 处理与健康相关的研究数据,数据驻留和合规性是重要考虑因素。Amazon Bedrock 在我们的 AWS 账户内处理请求,因此日志数据和源代码保持在与应用其余部分相同的环境中。AI 分析不需要将数据发送到外部端点。这使数据流保持在我们已经管理的边界内。这对我们的工作尤为重要,因为 TReNDS 处理可能属于 HIPAA 要求范围内的健康相关研究数据。有关符合 HIPAA 条件的 AWS 服务的更多信息,请参阅 AWS HIPAA 符合性服务参考。
虽然我们的设置使用 EKS 和 FluentBit,但这种模式也适用于向 CloudWatch 发送日志的其他应用,包括 ECS、Lambda、EC2 或使用 CloudWatch Agent 的本地工作负载。
要实现此解决方案,您需要以下条件:
Agent 的能力来自于我们赋予它的工具。在我们构建的所有工具中,源代码检索是最关键的。堆栈跟踪引用文件路径和行号,但没有对实际实现代码的访问权限,Agent 将仅限于日志模式匹配。通过赋予 Agent 读取源文件的能力,它可以追踪执行路径并识别导致失败的特定代码。使用 Strands Agents SDK,通过用 @tool 装饰器装饰 Python 函数来定义自定义工具。以下是我们构建的用于从 GitHub 仓库获取源代码的工具:
import base64
import boto3
import requests
from strands import Agent, tool
# 从 AWS Secrets Manager 获取 GitHub token
secrets_client = boto3.client("secretsmanager")
github_token = secrets_client.get_secret_value(
SecretId="trends/github-token"
)["SecretString"]
@tool
def fetch_source_code(file_path: str, repo: str) -> str:
"""Fetch a source file from a GitHub repository.
Args:
file_path: Path to the file in the repository
repo: Repository in 'owner/repo' format
"""
response = requests.get(
f"https://api.github.com/repos/{repo}/contents/{file_path}",
headers={"Authorization": f"token {github_token}"}
)
if response.status_code != 200:
return f"Could not fetch {file_path} from {repo}: HTTP {response.status_code}"
content = base64.b64decode(response.json()["content"])
return content.decode("utf-8")
文档字符串和类型提示很重要。Strands 使用它们来告诉模型工具的作用以及它期望什么参数。然后模型根据在错误中发现的内容决定何时调用此工具。有关更多模式,请参阅自定义工具文档。
对于部署,我们使用 Strands Agents 官方 Lambda layer。无需手动打包 SDK。
当我们的某个应用发生错误时,管道会自动经过四个阶段。首先,CloudWatch 检测到错误模式并用压缩的日志数据调用我们的 Lambda 函数。Lambda 解码事件,Strands Agent 从此接管。Agent 从同一容器获取额外的日志上下文,从 GitHub 检索相关源代码,并推理根本原因。最后,它将结构化分析发布到 SNS 以交付给我们的团队。以下各节将详细介绍每个阶段。
CloudWatch 订阅过滤器向 Lambda 发送 base64 编码的 gzip 压缩日志事件。每次调用包含在短时间窗口内与过滤器模式匹配的一个或多个日志事件。Lambda handler 解码信息,提取日志组名称和匹配事件,并将它们传递给 Agent 进行分析。有关标准解码模式,请参阅 CloudWatch Logs 订阅过滤器文档。
订阅过滤器传递匹配的日志行,但单行通常是不够的。CloudWatch 事件信息包含 logStream,它标识了产生错误的特定容器。我们构建了第二个 @tool 来从同一流获取周围的日志。这为 Agent 提供了完整的堆栈跟踪和导致失败请求上下文,而不受其他并发请求的干扰:
@tool
def fetch_log_context(log_group: str, log_stream: str, timestamp: int,
window_seconds: int = 30) -> str:
"""Fetch log lines from the same log stream surrounding an error.
Args:
log_group: CloudWatch Log Group 名称
log_stream: 产生该错误的日志流
timestamp: 错误事件时间戳(毫秒)
window_seconds: 错误前后时间窗口(秒)
"""
response = logs_client.filter_log_events(
logGroupName=log_group,
logStreamNames=[log_stream],
startTime=timestamp - (window_seconds * 1000),
endTime=timestamp + (window_seconds * 1000),
)
events = response.get("events", [])
if not events:
return f"No log events found in {log_stream} within {window_seconds}s of the error."
return "\n".join(e["message"] for e in events)
通过限定在日志流范围内,我们得到了来自同一容器的清晰、按时间顺序排列的事件序列。这包括触发错误的请求、前置的警告信息,以及完整的异常堆栈。
AI 智能体收到错误及其上下文后,自主决定要调查什么。与遵循预定义决策树的基于规则的系统不同,AI 智能体会解释错误消息、识别堆栈跟踪中的文件路径和类名,并确定要获取哪些源文件。如果初步代码审查发现错误源自某个依赖或共享工具,AI 智能体会沿着这条链路继续追查,而无需我们额外提示。我们通过系统提示词来塑造输出格式:
SYSTEM_PROMPT = """You are a senior Site Reliability Engineer analyzing production errors.
Given an error and its surrounding log context:
1. Identify the root cause by analyzing the stacktrace
2. Use fetch_source_code to read the relevant source files
3. Provide a structured analysis with:
- Severity (CRITICAL/HIGH/MEDIUM/LOW)
- Root cause explanation
- Relevant source code context
- Suggested fix
- Related areas that may be affected
"""
系统提示词定义了结构化的输出格式,但将调查策略留给了模型。AI 智能体根据在错误中发现的内容决定调用哪些工具。带有清晰文件路径的堆栈跟踪会触发 fetch_source_code 调用。没有堆栈跟踪的错误可能会引导 AI 智能体在代码库中搜索错误消息字符串。这种灵活性是 AI 智能体方法的核心价值。我们不需要预先预测我们的应用程序可能产生的每一种错误类型。
Lambda 处理程序将所有内容串联在一起:
agent = Agent(
model=BedrockModel(model_id="us.anthropic.claude-sonnet-4-20250514"),
system_prompt=SYSTEM_PROMPT,
tools=[fetch_source_code, search_github_code, fetch_log_context]
)
result = agent(
f"Analyze this error from {log_group}:\n\n{error_message}"
)
处理程序创建了一个 Agent 实例,指定了我们选择的 Amazon Bedrock 模型、定义输出格式的系统提示词,以及可用的工具列表。然后它将错误消息连同日志组名称一起传递给 AI 智能体,触发自主调查循环。
AI 智能体完成分析后,我们将结果发布到一个 Amazon SNS 主题,并分流到电子邮件和 Slack。以下是一个典型通知的样子:
Error Analysis — order-service
Severity: HIGH
Error: NullPointerException at OrderService.java:142
Root Cause: The method processPayment() calls paymentGateway.charge()
which can return null on gateway timeout, but line 142 accesses
response.getTransactionId() without a null check.
Source Context (OrderService.java:138-148):
[relevant code shown]
Suggested Fix: Add null check for gateway response before accessing
fields. Consider retry mechanism for gateway timeouts.
Related: Similar pattern in RefundService.java:89
AI 智能体自主调查了这个错误,没有人工指导。它阅读了相关源代码,找到了空检查的缺失,甚至标记了另一个文件中的类似模式。这展示了 AI 智能体方法的价值。与遵循固定检查清单不同,AI 智能体根据在每一步发现的内容调整其调查策略,就像一位资深工程师会做的那样。
自部署该系统以来,我们看到了团队处理生产错误方式的明显变化。最直接的变化是速度。调查时间从 15 到 30 分钟缩短到了 60 秒以内。因为 AI 智能体的分析包含了建议的修复方案,我们的工程师通常会在收件箱中收到一个现成的解决方案。他们可以直接着手实现修复,而无需花费时间进行诊断。
运行该系统的成本可以忽略不计。每次分析仅产生极少的 Amazon Bedrock 推理费用,通常每个错误涉及两到三轮工具调用。就我们的工作负载而言,这远低于同等工程师时间的成本。
我们的开发人员通过电子邮件收到 AI 智能体的分析,反馈一直很正面。分析为解决问题提供了清晰的起点,即使对于工程师之前未曾遇到的错误也是如此。工程师可以快速了解发生了什么以及如何处理,而无需额外调查。
发布后,同一代码路径可能产生重复错误。我们的去重机制使用 Amazon DynamoDB,确保只有第一次出现才触发分析。其余的会被静默过滤,保持收件箱清洁并降低 Amazon Bedrock 成本。
选择合适的 Amazon Bedrock 模型
Amazon Bedrock 使我们可以通过单一 API 访问一系列基础模型。我们测试了多个模型以找到最适合错误分析用例的模型,评估了推理质量(理解代码和错误)、工具使用可靠性(调用我们的 GitHub 和 CloudWatch 工具)、延迟以及每次分析的成本。
我们选择 Claude Sonnet 作为主要模型。在我们的测试中,它始终产生最准确的根因分析。它能够追踪多文件调用链,识别细微问题(如缺失的空检查),并推理并发问题。对于有不同成本或延迟要求的团队,表中其他模型是处理更简单错误模式的可靠替代方案。
使用 Strands 切换模型只需修改一行代码,这使我们的评估变得简单:
# We tested multiple models by changing this single line.
# Check Amazon Bedrock documentation for the latest available model IDs.
model = BedrockModel(model_id="us.anthropic.claude-sonnet-4-20250514")
注意:模型 ID 会定期更新。有关当前模型 ID,请参阅 Amazon Bedrock 支持的模型文档。
我们正在探索该系统的几个扩展。首先是通过检索增强生成(RAG)连接我们的内部运行手册。
通过将 Amazon Bedrock Knowledge Bases 与我们的内部运行手册和过去的事件报告集成,AI 智能体将能够在分析中参考 TReNDS 特定的处理程序。
我们还计划实施分层模型策略。简单的、已知的错误模式将路由到 Haiku 进行快速、低成本的分类,而复杂或新颖的错误将升级到 Sonnet 进行深入分析。这将优化我们错误总量上的成本和响应时间。
最后,我们正在努力实现自动创建 GitHub Issue 和 Pull Request。当 AI 智能体识别出潜在修复方案时,它将自动创建一个带有分析的 GitHub Issue,并打开一个包含建议代码更改的 Pull Request,减少诊断和解决之间的人工步骤。
随着进一步扩展,我们还在研究 Amazon Bedrock AgentCore,用于托管 AI 智能体运行时、可观测性和身份管理。有关多 AI 智能体和部署模式,请参阅 Strands Agents 示例。
我们在 TReNDS 构建这个系统,是因为我们希望应用程序中的每个错误都能得到即时的、结构化的调查,而不仅仅是告警。Amazon Bedrock 和 Strands Agents SDK 使其实现起来非常简单。我们定义了几个工具并编写了系统提示词。现在,我们有了一个像资深工程师一样推理生产错误的 AI 智能体。它在几秒钟内交付结果。
添加新能力意味着再写一个 @tool 函数和几行 Python 代码。无论是 Jira 集成、带建议修复的 GitHub PR,还是连接到运行中 Pod 以进行更深入调查的工具,模式都是相同的。基础模型处理推理和编排,我们将其连接到它需要的系统。