Google 开源 AQuA(Ambient Quality Agent),部署在生产 Agent 旁,自动聚类失败对话并定位到对应代码版本,解决 HTTP 200 掩盖的静默质量下降问题。
你的智能体返回 HTTP 200,延迟预算内没有任何工具调用报错。然而它依然把 3A 座位记录为已确认,却从未向座位选择子智能体确认 3A 是否空闲;或者向一位资料标注为纯素食主义者的旅客推荐了一家佛罗伦萨牛排馆。
前 80% 是相对简单的部分
我们合作过的每一个在生产环境运行智能体的团队,都在试图回答同样的三个问题:我的智能体现在表现如何?它的损失模式是什么——那些反复出现的失败方式是什么?我该如何提升:改变点什么,知道它起了作用,并且防止它回退?
让智能体达到前 80%,即处理你预期到的测试用例,在今天可能相对直接和快速。一份评估数据集、一个编码智能体,以及一个紧密的内部循环——运行、评分、修复、比较——通常能在几天内让你在已知场景下达到任务成功。然后你上线了,质量曲线开始趋平甚至下降。
让剩下的 20% 更难的原因在于,上线后地壳在移动。用户发现智能体实际能做什么后,使用模式会发生变化,线上流量不再像你最初的评估集那样。与此同时,底层系统也在变化:当你升级模型、更新 harness、或更换工具或技能时,一切仍然在运行,健康检查依然绿灯(图 2),但对话质量和任务成功率可能在标准部署流水线从未预警的方式下发生偏移。
而当一个会话确实出错时,找出原因意味着分离多层因素,从外部看它们几乎一模一样。例如:
这些失败模式可能有不同的责任方和不同的修复方式。原始轨迹记录的是"接下来发生了什么",而不是"是什么导致的"。要区分这些层级,需要将失败的轨迹与产生它们的精确代码版本一起阅读。在线评估仪表盘只能告诉你分数移动了,编码智能体可以在你知道了往哪看之后检查一条 trace,但在生产量级下,没有人能手动阅读每一个对话。
在我们六月的帖子中,我们在 travel-concierge 上从头到尾走了一遍上线前的内部循环。本文涵盖外部循环,作为可组合的构建块开源发布,你可以在自己的项目中运行它,适配到自己的技术栈,并帮助我们一起探索团队如何在生产环境中运行持续的智能体质量。
AQuA 是什么:一个 7×24 小时的质量智能体,在你笔记本合上时观察、聚类和诊断生产环境中的智能体故障
AQuA(Ambient Quality Agent,氛围质量智能体)在你的 Google Cloud 项目中无人值守地运行在智能体旁边,按计划从 Cloud Trace、Cloud Logging 或 BigQuery 扫取生产轨迹,也可以在每次部署后或按需扫取——而你的笔记本是合上的。原始会话记录、源码快照和 BigQuery 表单留在你的项目边界内,AQuA 永远不处于请求路径中,也不会回写任何内容到你的智能体。
每次运行读取一批最近的会话样本,通过五阶段流水线:
采样(Sampling)。从你的遥测(Cloud Trace / Cloud Logging 或 BigQuery)中随机抽取最多 1,000 个最近会话的样本(设置上限以保持运行成本可预测)。
评审(Review)。对照九点常见故障清单(流程、工具选择与参数、落地、任务完成等)为每个会话评分,同时让智能体查看其指令、工具和可选的 goal.md。当会话失败时,它写出一条结构化的"实际结果 / 预期结果"发现。
聚类(Clustering)。将共享相同失败机制的会话发现分组为候选问题聚类。
验证(Verify)。另一个模型对照最多三个完整会话记录检查每个候选聚类,丢弃证据不支持的聚类。
追踪(Tracking)。将存活的聚类与 BigQuery 中的开放洞察(跨运行追踪的已验证问题)进行匹配,标记为 NEW(新发现)、RECURRING(反复出现)或自动 RESOLVED(已解决——14 天未再现)。
把它想象成一个初级质量工程师做第一轮筛选:阅读对话、过滤噪音、为轮值人员准备好带有证据会话的案件档案。
为了将第二阶段引导向你的领域,你需要编写一个纯英文的开发者目标(goal.md),它会被追加到每个评审 prompt 中;确定性 Python 自定义指标(eval_config.yaml)与评判器并行运行以追踪通过率。默认情况下,AQuA 使用单轮会话评审器(session_review judge),它在一轮模型调用中对照清单评估对话(保持计划内扫取的经济性,并产生推动聚类的实际/预期差异)。你也可以选择加入 Gemini 平台托管的轨迹自动评分器(task_success、tool_use_quality、trajectory_quality),它们运行专用的逐指标评估器(例如,如果你想用开箱即用的评分标准对会话评分,或将指标与离线评估对齐)。
当一条洞察值得调查时,你可以从仪表盘 Chat 或 agents-cli aqua run 触发根因分析。AQuA 将失败的轨迹与部署时捕获的不可变源码快照一起阅读:当缺陷在你的代码仓库中时,它引用 <path>:<start>-<end> 并针对快照行提出修改建议;当故障源于你的代码之外(上游依赖、交接环节或检索到的 payload)时,它将失败归因于轨迹中的该步骤,而不提出代码差异。它不会自行应用修改或打开拉取请求。
它位于你已有的两个循环之间:离线评估用已知测试用例对候选构建评分,在线评估监控生产中的通过率趋势,编码智能体或专用优化器修改 prompt 和代码。AQuA 将原始生产流量转化为已诊断的、锚定到代码的洞察,以及归档的失败会话记录,为你的内部循环提供养料。
在 travel-concierge 上走一遍多智能体扫取
让我们在 travel-concierge(google/adk-recipes)上走一遍运行过程,它将旅客路由到各个子智能体(inspiration_agent、place_agent、poi_agent、planning_agent、flight_search_agent、flight_seat_selection_agent、booking_agent 以及 pre_trip / in_trip / post_trip),并通过 memorize(key, value)(travel_concierge/tools/memory.py)将行程保存在会话状态中。
1. 部署、捕获源码快照,并设置开发者目标
将 AQuA 附加到 travel-concierge 项目需要三条 agents-cli 命令:
agents-cli extension add "${AQUA_CHECKOUT}"
agents-cli infra single-project --project="${GOOGLE_CLOUD_PROJECT}" --apply
agents-cli deploy --project="${GOOGLE_CLOUD_PROJECT}" --region us-east1
除了 AQuA 的运行器、BigQuery 数据集和 Cloud Run 仪表盘(在 Identity-Aware Proxy 后面),agents-cli deploy 还会将 travel-concierge 源码树的不可变快照写入 Cloud Storage,以部署版本(Revision 1)为 key。
在仪表盘的 Configuration 页面上,我们保存了一个开发者目标(goal.md)来引导评审聚焦于高层产品不变量,并压制风格层面的噪音(同时仍要求每条发现引用智能体或子智能体违反其指令或工具状态的具体对话轮次):
Goal: Help travelers move from trip inspiration to a concrete itinerary and confirmed bookings across our sub-agents, with every confirmed flight, hotel, seat, and recommendation grounded in tool results and the traveler's profile.
Failure modes to make sure we cover:
- Mid-conversation changes (a weak spot in pre-launch testing): if a user updates destination, dates, or flight/hotel choices after an initial plan, make sure subsequent sub-agent tool calls and state updates reflect the change.
- Dropped context when handing off or delegating across inspiration_agent, planning_agent, and booking_agent (such as traveler profile preferences or prior selections).
Ignore: tone, greetings, small talk, or sessions where the user browses options and leaves without booking.
如果你还想在 eval_config.yaml 中加入确定性 Python 评分标准来追踪通过率随时间的变化,可以用 agents-cli aqua metrics publish 与扫取任务一起发布。
2. 扫取并验证聚类
在一次 32 会话的多智能体扫取中(四个脚本化旅客旅程各重放 8 次,产生了 1,583 个跨 travel_concierge 及其子智能体的 OpenTelemetry span),5 个会话干净通过,27 个产生 42 条结构化发现,每条都按 span 限定到具体子智能体的独立指令和工具声明。以下是关于 planning_agent 的一条发现:
实际:当用户在同一轮对话中选择了美联航 204 航班出发航班并要求 3A 和 3B 座位时,planning_agent 直接将座位号保存到会话状态,而没有调用 flight_seat_selection_agent 检查 3A 和 3B 是否可用。
预期:planning_agent 应该在将出发或返程座位号保存到会话状态之前,调用 flight_seat_selection_agent 验证座位可用性和价格。
聚类将 42 条发现结果归入 9 个候选簇。验证器(Gemini 3.7 Flash)针对最多 3 个完整会话记录和每个子智能体的定义检查每个簇,并拒绝了 3 个误报簇:其中两个是旅客明确要求选择第一个返程航班或直接跳到预订;第三个是聚类将两个不同子智能体(flight_search_agent 和 inspiration_agent)的不相关提示-工具不匹配错误合并了。这留下 6 个已验证问题,其中最突出的包括:
当用户主动提供座位时绕过座位可用性检查(15 个会话,planning_agent):当旅客在选择航班时同时指定座位(或在行程中途切换目的地后立即指定座位),planning_agent 会直接将座位写入会话状态(返回 HTTP 200,零工具错误),而不调用 flight_seat_selection_agent 检查该座位是否存在或是否开放。
膳食约束在子智能体边界丢失(7 个会话,inspiration_agent → place_agent):inspiration agent 的提示词包含旅客画像(food_preference: vegan),但 place_agent 被包装为隔离工具,其提示词从未收到该画像。当 inspiration_agent 请求 place_agent 提供美食旅行建议而未在请求中传递"vegan"时,place_agent 推荐的是 Carbonara 和 Bistecca alla Fiorentina(牛排)。
poi_agent 中提示词与工具集矛盾(5 个会话,poi_agent):兴趣点子智能体的提示词要求来自 Google Maps Grounding Lite 的已验证图像、地图和地点 ID 字段,但该智能体声明时工具列表为空(tools=[]),因此它伪造了占位符 https://example.com/... URL。
在仪表板中打开最上面的洞察,会显示该簇的完整分解:匹配的会话数(15 条追踪记录)、验证摘要,以及 15 条关联的会话追踪记录及其触发的具体发现:
从洞察卡片点击 Investigate 会针对 Revision 1 的 33 文件源代码快照启动根因诊断智能体(Gemini 3.8 Flash)。通过将 15 条失败追踪记录与代码库树交叉比对,智能体将 bug 追溯到 travel_concierge/sub_agents/planning/prompt.py 第 93 行:航班搜索指令只告诉 planning_agent 在向用户展示座位图供其选择时调用 flight_seat_selection_agent,而遗漏了用户直接提供座位号的情况。它在对话中提出了一个行级别的修复方案(同样追溯了 vegan 画像传递 bug 到 travel_concierge/sub_agents/inspiration/prompt.py 第 23 行):
同一份洞察负载,包括其锚定的 edits[] 和 occurrences[].rubrics[].trace,可通过 agents-cli aqua get-insight 获取。编码智能体(使用 agents-cli-aqua 和 google-agents-cli-eval 技能)可以无头触发诊断、在分支上应用编辑、提取失败会话的用户输入到本地重放文件,并在打开 Pull Request 之前验证修复:
# 1. 拉取影响 >= 10 个会话且无根因的新洞察
agents-cli aqua list-insights --status NEW --root-cause false \
| jq -r '.insights | sort_by(-.trace_count) | .[] | select(.trace_count >= 10) | "\(.insight_id) \(.label)"'
# 2. 无头触发根因分析并获取锚定编辑 + 证据追踪
agents-cli aqua run 'Diagnose insight 220d9209e27d4e16a73b4ad4741caa81. What is the root cause, and how would you fix it?'
agents-cli aqua get-insight 220d9209e27d4e16a73b4ad4741caa81 > insight.json
# 3. 从 insight.json 中附带的追踪提取用户轮次用于本地重放(或添加到你的评估集)
jq '{state: {}, queries: [.occurrences[0].rubrics[0].trace[] | select(.role == "user") | .content]}' \
insight.json > ./b4b38471-inputs.json
adk run --replay ./b4b38471-inputs.json travel_concierge
应用两个一行提示词修复(planning/prompt.py:93 和 inspiration/prompt.py:23)并部署 Revision 2 后,在相同开发者目标下针对 Revision 2 重放相同 32 个会话,结果显示:
座位选择绕过下降 87%,从 15 个会话降至 2 个边缘情况会话。
Vegan 画像遗漏从 7 个会话降至 0。
完整会话通过数翻倍以上,从 5/32 增至 13/32。
未触及的 5 会话 poi_agent 缺陷(tools=[])仍在队列中跟踪。
诊断你智能体的智能体有时会出错。这是在使用模型作为评判者的固有局限。当我们设计 AQuA 时,核心工程要求是确保未经核实的假设永远不会看起来与已验证的发现相同:
簇在被完整会话记录验证之前只是声明。 每个簇抽检最多 3 个完整会话记录进行验证,确保失败模式是真实存在的,才会进入你的队列(在 87 条追踪记录的内部基准测试上,验证器拒绝了 24 个候选簇中的 4 个),而簇的追踪记录计数给你一个粗略的优先级排序,而不是验证每个会话成员。
洞察即其引用,而非自报的置信分数。 Schema 中没有置信度字段:每个事件都链接到 Cloud Trace 中的会话 ID,每个根因都引用服务器针对该修订版快照验证过的 <path>:<start>-<end> 范围。任何指向不存在文件或行的引用都会被拒绝。
跳过或失败的工作会显示在运行记录上。 被拒绝的簇、超出 50 簇验证上限的簇、评分标准错误以及空或未捕获的追踪窗口都被明确记录在运行记录上,而不是算作干净会话。
按设计的行为可以被永久忽略。 一旦你在某个发现上点击 Dismiss,未来的扫描不会将其重新标记为 NEW。
此参考实现中的几个边界是我们在生产中看到的实际问题之间的权衡:
大规模选择要读取的会话。 深度评估多轮轨迹的成本远高于检查 HTTP 状态码,因此这个 MVP 每次运行最多随机采样 1,000 个会话(ORDER BY RAND())。添加廉价的结构预过滤器(重试、延迟峰值、高轮次计数、用户差评)是 straightforward 的下一步,但仅基于异常进行筛选往往只会拉取同一超时的数百个副本。更难的问题是捕捉静默回归——这种回归破坏了关键工作流的 1%,而每个结构信号看起来都正常——而无需在所有 50,000 个每日会话上运行深度评判器。
通用清单与领域目标和 SME 校准。 goal.md 和自定义指标将审查导向领域规则,但让模型评判者与领域专家达成一致仍是真实的工程工作。
有界验证和可预测的运行成本。 每次运行验证最多 50 个簇、每个簇最多 3 个会话记录,使运行成本在轨迹深度扩展时保持可预测。在 96 会话单智能体扫描(约 5-6 个 span/会话)上,审查、聚类和验证总成本为 $0.70(~$0.007/会话;Gemini 3.1 Pro 和 Gemini 3.7 Flash 共计 220,841 输入 / 48,736 输出 token)。在上述 32 会话 travel-concierge 扫描(1,583 个 span 跨 travel_concierge 及其子智能体,约 50 个 span/会话)上,审查和聚类(Gemini 3.1 Pro,1,635,197 输入 / 128,740 输出 token)加上 9 个簇验证(Gemini 3.7 Flash,1,131,937 输入 / 36,976 输出 token)在标准 Gemini 平台定价下总成本为 $3.76(~$0.12/会话)。根因诊断(Gemini 3.8 Flash)仅按需运行,每个被调查的洞察成本 $0.33 至 $2.47,取决于它拉取多少完整轨迹。
长时域轨迹和上下文压缩。 传递完整会话记录适用于十轮对话,但在运行数百轮且工具输出达到兆字节级别的编码或研究智能体上会崩溃。这些场景需要语义轨迹压缩,将 500 轮追踪折叠成子任务里程碑,并隔离其脱轨位置。
从归档会话记录到可复现测试用例。 adk run --replay 将记录的用户轮次重新发送到本地代码,这在工具是幂等的或被 mock 时有效,但静态重放不会重建外部环境状态(如果数据库行发生变化、API 超时,或者第 3 轮依赖于智能体在第 2 轮说的话,重新发送静态轮次会产生分歧)。
在 Google 自研的第一方 Agent 中,低级原语(轨迹与反馈摄取、核心评估器、数据集管理)复用良好,而外层环工作流往往因产品工具、领域不变量、数据管道和编排 harness 的不同而各有定制。随着基础模型和 Agent 在读取轨迹和导航代码方面越来越强,单独评分和根因推理能力会自然提升——这也正是我们将 AQuA 的提示词视为模块化配方、将其洞察视为原始轨迹和源代码快照索引的原因:更强的模型或编码 Agent 始终可以将完整的示例对话直接拉入上下文中。更强的模型和 Agent 本身无法带来的是周围的平台。以下是我们在接下来思考的一些方向:
将轨迹扫描与在线评估分数、延迟/成本突增、SME 校准评分,以及通过 Feedback 服务收集的最终用户反馈相结合,使得可以将用户明确投诉的会话与普通流量进行对比,然后在与用户从未点击差评但悄然放弃工作流的会话中找到同样的缺陷。
在另一个领域,CodeMender 扫描代码漏洞并提出经过静态分析、动态分析、差分测试、模糊测试和 SMT 求解器验证的补丁。其"扫描-验证"形态与 AQuA 的"扫描-验证"直接对应。 remediation 出现分歧:一旦外部状态已推进,实时对话没有确定性预言机(没有崩溃输入或失败测试),而且修复可能位于工具契约、编排交接点或上游依赖中,而非可以单独 hill-climb 的提示词。将已验证的失败聚类转化为具有模拟工具状态和模拟用户的密封沙箱测试用例,使编码 Agent 或优化器能够跨越代码、工具和提示词进行 climb——这是超越对话回放的必然下一步。
当前的参考实现是在仓库中 1:1 紧邻单个 ADK Agent 搭建的;对于在生产环境中运行数十个 Agent 的团队,我们正在探索如何从现有项目遥测直接跨 Agent 集群(N)附加环境质量扫描,而无需为每个 Agent 部署 sidecar。
当被观察的 Agent 本身是声明式或托管的而非任意应用代码时,"源代码"收缩为系统指令、工具模式和技能。这将根因搜索空间收窄为一组有界的结构化制品,使平台能够自动连接轨迹捕获和修订快照,并将已验证的通过和失败轨迹转化为可靠的学习信号——对于优化器或 Agent 自身的记忆和自学习环来说,生产流量本身没有真实标签。
要在没有云项目、没有凭证、没有模型调用的情况下,在本地探索合成一个月的运行和洞察的仪表盘:
git clone https://github.com/google/adk-recipes.git
cd adk-recipes/core/python/ambient-quality-agent
make demo # 在合成数据上本地提供服务
要将 AQuA 附加到你自己的、部署在 Google Cloud 上的 Agent,并使用交钥匙 ADK 脚手架(所有遥测、源代码快照和 BigQuery 表在你的服务账号下保留在你的项目中,位于 IAP 之后):
agents-cli extension add "${AQUA_CHECKOUT}"
agents-cli infra single-project --project="${GOOGLE_CLOUD_PROJECT}" --apply
agents-cli deploy --project="${GOOGLE_CLOUD_PROJECT}"
要使用你的编码 Agent(Antigravity、Gemini CLI、Claude Code 或 Cursor)驱动双环,请在内部环评估技能(npx skills add https://github.com/google/agents-cli --skill google-agents-cli-eval)旁安装 agents-cli-aqua 技能(skills/agents-cli-aqua/SKILL.md)。
我们开源分享 AQuA,是希望与在生产环境中运行 Agent 的团队合作,共同塑造方向。当你将它应用到你的技术栈时,我们很想听听你的想法:
AQuA 由 Ákos Frohner、Aleksandra Grzegorczyk、Alessandro Grassi、Andrzej Kiewicz、Angelica Bilanenko、Dima Melnyk、Elia Secchi、Iwo Naglik、Lucas Matuszkowiak、Ludwik Trammer、Maciej Pawłowski、Max Gasztych、Pavel Sirotkin、Saksham Singhal、Xi Liu、Yaroslav Polyakov 以及更广泛的 Gemini 平台团队构建。