代码审查是工程团队最耗时又最不可省略的环节。Baz 团队面对的问题是:Spec Review(规格审查)需要逐条比对需求文档与代码实现,人工做这件事既慢又容易漏项。他们用 Amazon Bedrock 搭配新发布的 AgentCore 框架,构建了一个 Spec Review Agent,把审查准确率拉到了可投产的水平。这篇文章拆解他们的架构选择、实现细节和踩坑经验。
问题本质:不是"读代码",而是"比对意图"
普通 LLM 代码审查容易陷入表面风格检查——变量命名、缺少注释之类。Baz 的核心需求不同:他们要验证代码是否完整覆盖了 spec 中每一条要求。这意味着 Agent 必须:
- 解析 spec 文档,提取可验证的条目清单;
- 逐条在代码中寻找对应实现或缺失;
- 输出结构化审查报告,而非泛泛的"建议改进"。
这天然是一个多步骤、有状态、需要工具调用的任务——正好落在 AgentCore 的能力范围内。
架构选择:为什么是 AgentCore 而不是裸 Bedrock Invoke
Baz 最初尝试直接调用 Bedrock 的 Converse API,把整个 spec 和代码塞进一个 prompt。结果有两个硬伤:
- 长上下文丢细节:spec + 代码动辄上万 token,模型对尾部条目的召回率明显下降。
- 无法分步验证:一次性输出让模型跳过了"逐条比对"的严谨过程,容易给出笼统结论。
切换到 AgentCore 后,他们获得了三个关键能力:
| 能力 | 解决的问题 |
|---|---|
| 多步行动规划(ReAct loop) | 拆解"提取条目→逐条比对→汇总报告"为独立步骤 |
| 工具调用(Code Access) | Agent 可按需拉取特定文件、函数,而非一次性喂入全部代码 |
| 会话状态管理 | 每条 spec 条目的验证结果持久化,避免重复计算 |
架构简图:
Spec Document ──► AgentCore Orchestrator
│
├── Step 1: SpecParser Tool → 提取条目清单
├── Step 2: CodeFetcher Tool → 按条目拉取相关代码片段
├── Step 3: ClauseValidator Tool → 逐条比对并记录结果
└── Step 4: ReportGenerator → 汇总结构化报告
│
Amazon Bedrock (Claude 3.5 Sonnet)
核心设计原则:让 Agent 每一步只处理一小块信息,但通过状态串联保证全局一致性。
实现细节:Agent 与工具的定义
Baz 用 AgentCore 的 Python SDK 定义了 Agent 和三个自定义工具。下面是一个可改造运行的简化示例,展示核心结构:
# agent_spec_review.py
# 前置依赖:pip install boto3 agentcore-sdk
# 需要 AWS 凭证配置(~/.aws/credentials 或环境变量)
# 需要 Amazon Bedrock 模型访问权限(Claude 3.5 Sonnet)
import boto3
from agentcore_sdk import Agent, Tool, ToolInput
# ---- 工具定义 ----
class SpecParserInput(ToolInput):
spec_text: str # 需求文档原文
@Tool(name="SpecParser", description="从需求文档中提取可验证的条目清单")
def parse_spec(input: SpecParserInput) -> dict:
"""
实际生产中这里会调用 Bedrock 做结构化提取,
示例用简化逻辑演示流程。
"""
# 将 spec 拆分为条目列表(生产环境用 LLM 提取)
clauses = [
{"id": "C1", "text": "用户登录必须使用 OAuth2.0"},
{"id": "C2", "text": "密码长度不少于 12 位"},
{"id": "C3", "text": "登录失败需记录审计日志"},
]
return {"clauses": clauses, "total": len(clauses)}
class CodeFetcherInput(ToolInput):
clause_id: str # 要验证的条目 ID
repo_path: str # 代码仓库本地路径
@Tool(name="CodeFetcher", description="根据条目关键词拉取相关代码片段")
def fetch_code(input: CodeFetcherInput) -> dict:
"""
生产环境会做语义搜索(如向量检索),
示例用关键词匹配简化。
"""
# 模拟:根据 clause_id 返回相关代码片段
code_snippets = {
"C1": "auth/oauth_login.py: def oauth2_login(client_id, redirect_uri): ...",
"C2": "auth/password_policy.py: MIN_PASSWORD_LENGTH = 12",
"C3": "auth/audit_logger.py: def log_failed_attempt(user_id, timestamp): ...",
}
return {
"clause_id": input.clause_id,
"snippet": code_snippets.get(input.clause_id, "未找到相关代码"),
}
class ClauseValidatorInput(ToolInput):
clause: dict # 条目详情
code_snippet: str # 对应代码片段
@Tool(name="ClauseValidator", description="比对单条需求与代码实现,判定覆盖状态")
def validate_clause(input: ClauseValidatorInput) -> dict:
"""
生产环境调用 Bedrock 做语义比对,
示例用关键词包含判断简化。
"""
clause_text = input.clause["text"]
snippet = input.code_snippet
# 简化判定逻辑(实际应由 LLM 语义判断)
keywords = clause_text.lower().split()
matched = any(kw in snippet.lower() for kw in keywords if len(kw) > 3)
status = "COVERED" if matched else "MISSING"
return {
"clause_id": input.clause["id"],
"clause_text": clause_text,
"status": status,
"evidence": snippet,
}
# ---- Agent 定义 ----
bedrock_client = boto3.client("bedrock-runtime", region_name="us-east-1")
spec_review_agent = Agent(
name="SpecReviewAgent",
model_id="anthropic.claude-3-5-sonnet-20241022-v2:0",
instruction=(
"你是一个代码规格审查 Agent。工作流程:\n"
"1. 调用 SpecParser 从需求文档提取条目清单\n"
"2. 对每条条目,调用 CodeFetcher 拉取相关代码\n"
"3. 对每条条目,调用 ClauseValidator 比对验证\n"
"4. 汇总所有条目结果,输出结构化审查报告\n"
"报告格式:每条列出 id、状态(COVERED/MISSING/PARTIAL)、证据代码位置。\n"
"最后给出覆盖率百分比和缺失条目清单。"
),
tools=[parse_spec, fetch_code, validate_clause],
bedrock_client=bedrock_client,
)
# ---- 运行 ----
if __name__ == "__main__":
spec_text = """
需求文档 v2.3:
- 用户登录必须使用 OAuth2.0
- 密码长度不少于 12 位
- 登录失败需记录审计日志
"""
result = spec_review_agent.run(
input_text=f"请审查以下需求文档与代码的覆盖情况:\n{spec_text}",
# 传入代码仓库路径供 CodeFetcher 使用
context={"repo_path": "/path/to/your/repo"},
)
print(result.output)
运行前需要修改的地方:
repo_path改为你的实际代码目录;- 确保 AWS 账号已开通 Bedrock Claude 3.5 Sonnet 模型访问;
- 生产环境中三个 Tool 的内部逻辑应替换为真实的 LLM 调用或向量检索。
关键优化:准确率是怎么拉上去的
Baz 从初版到最终版做了几轮迭代,准确率提升的核心动作:
分步比对替代一次性判断。 这是最根本的改变。AgentCore 的 ReAct 循环让模型先提取条目、再逐条验证,每步的 prompt 短且聚焦,大幅减少了遗漏。Baz 的数据:条目召回率从单次调用的 68% 提升到分步的 94%。
工具粒度控制信息量。 CodeFetcher 不是把整个仓库喂给模型,而是根据条目关键词只拉取相关文件片段。这把每步的上下文控制在 2000 token 以内,模型对细节的注意力显著增强。
结果持久化防重复与遗漏。 AgentCore 的会话状态让每条条目的验证结果被记录。即使某步出错重试,已完成条目不会丢失,也不会被跳过。
模型选择:Claude 3.5 Sonnet。 Baz 测试了多个模型,Sonnet 在"严格比对而非自由发挥"这类任务上表现最稳定——它更倾向于说"未找到对应实现",而非编造一个看似合理的覆盖判断。
部署与运维考量
Baz 的生产部署架构:
# cloudformation-snippet.yaml
# AgentCore Agent 的基础设施定义(简化版)
Resources:
SpecReviewAgent:
Type: AWS::Bedrock::Agent
Properties:
AgentName: spec-review-agent
FoundationModelId: anthropic.claude-3-5-sonnet-20241022-v2:0
Instruction: !Ref AgentInstructionText # 上述 instruction 内容
AgentResourceRoleArn: !GetAtt AgentRole.Arn
SpecParserAction:
Type: AWS::Bedrock::AgentActionGroup
Properties:
AgentId: !Ref SpecReviewAgent
ActionGroupName: SpecParser
ActionGroupExecutor:
Lambda: !GetAtt SpecParserLambda.Arn
FunctionSchema:
Functions:
- Name: parseSpec
Description: 从需求文档提取条目清单
Parameters:
specText:
Type: string
Required: true
Description: 需求文档原文
AgentRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: bedrock.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonBedrockFullAccess
几个运维要点:
- 成本控制:每次审查涉及 N 次工具调用 + N 次 Bedrock 调用(N = 条目数)。Baz 用 Prompt Caching 减少了重复 spec 文本的 token 计费,单次审查成本控制在 $0.3–0.8。
- 超时处理:大型仓库审查可能超过 AgentCore 默认超时。Baz 设置了 15 分钟上限,并对超时条目标记为
REVIEW_INCOMPLETE,由人工补审。 - 幻觉兜底:ClauseValidator 的结果经过一轮"反查"——用条目 ID 回查代码位置,确认模型声称的证据确实存在。这步用简单的字符串匹配即可,成本极低但有效拦截了约 5% 的幻觉输出。
落地建议与取舍
如果你也在考虑用 Agent 搞代码审查,几个判断标准:
适合用 Agent 的场景: - 审查对象是结构化 spec 与代码的覆盖关系; - 仓库规模大,人工逐条比对不可行; - 需要可追溯的审查记录(每条有证据、有状态)。
不适合的场景: - 纯风格/命名审查——单次 LLM 调用就够了,Agent 的多步开销不值得; - spec 本身模糊不可拆条——Agent 的分步优势建立在条目可结构化提取的前提上; - 极小仓库(< 10 文件)——直接把全量代码塞进一次调用更简单。
上手路径: 1. 先用裸 Bedrock Converse API 做一次端到端审查,记录遗漏率; 2. 把遗漏最多的条目类型抽出来,用 AgentCore 拆成独立验证步骤; 3. 逐步把剩余条目也迁入 Agent 流程,对比准确率变化; 4. 加上反查兜底和 Prompt Caching 后再投产。
Baz 的经验说明:AI 代码审查的准确率瓶颈往往不是模型能力,而是任务拆解方式和信息供给粒度。AgentCore 提供的正是这个拆解框架——让模型每步只做一件小事,但串联起来完成大任务。