艺术品评估群
基于Google ADK、A2A协议和MCP构建的多智能体艺术评价系统-- 运行于 亚马逊新星三位独立的专业代理对一件艺术品进行评估 同时,合成剂应用多数投票产生最终结果 建议。
作者 Mani Khanuja — 子堆栈
框架(Google ADK)、代理间协议(A2A)和工具层(MCP) 与模型提供者完全解耦。整个蜂群在亚马逊上奔跑 Nova对代理逻辑、提示或协议没有任何更改。 它展示了你可以使用 亚马逊基岩新星 具有任何框架的模型及其与MCP和A2A等标准协议的兼容性。
先决条件
- Python 3.12+
- Docker 桌面版
- Nova API密钥来自 nova.amazon.com/dev/api --登录
使用您的亚马逊帐户,不需要AWS帐户。
设置
git clone --branch part-2 --depth 1
cd nova_adk_a2a_mcp_swarm
# Configure credentials
cp .env.example .env
# Edit .env and set your NOVA_API_KEY跑
一切都在Docker中运行——不需要本地Python环境。
# Build all six services (MCP server, 3 specialists, synthesis, orchestrator)
docker compose build
# Start backend services (waits for health checks automatically)
docker compose up -d mcp-server style-agent provenance-agent valuation-agent synthesis-agent
# Run the orchestrator interactively (attaches to stdin for chat)
docker compose run --rm orchestrator
# Stop everything when done
docker compose down当地发展(可选)
使用ADK的内置工具进行本地开发(adk web),设置虚拟 环境:
python3.12 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip install pre-commit
pre-commit install建筑
六个独立的服务,每个都在自己的容器中。所有服务都在听 相同的内部端口(8080),并通过Docker服务名称进行通信——每个代理都没有 需要港口管理。
| 服务 | Docker服务名称 | 角色 |
|---|---|---|
| 编排器 | orchestrator | 管道协调器(聊天UI) |
| MCP工具服务器 | mcp-server | 所有代理的共享工具 |
| 风格分析师 | style-agent | 风格、技术、条件 |
| 来源专家 | provenance-agent | 所有权历史,法律 |
| 市场估价师 | valuation-agent | 拍卖可比资产、保险 |
| 合成剂 | synthesis-agent | 多数票,最终裁决 |
| Eval跑步器(可选) | eval-runner | ADK评估+pytest 上 appraisal-net (个人资料 eval) |
投票词汇: 专家, cast_vote (MCP)、合成提示和评估测试都使用相同的标签: 验证, 进一步验证, 拒绝 (参见 shared/vote_vocabulary.py).遗产 买 / 保持 仍被接受 cast_vote 并映射到AUTHENTICATE/VERIFY_FURTHER以实现向后兼容性。
添加新的专业代理只需要两个更改:
- 向添加条目
agents.yaml - 创建一个
_agent/目录与prompt.txt和agent.py
查询示例
You: Appraise a Monet Water Lilies, oil on canvas, 80x100cm, painted in 1906.
Acquired via Christie's London 1989 (Lot 42). Condition good, minor craquelure.
Country of origin: France.文章系列
此仓库附带了关于建筑生产的多部分Substack系列 使用Google ADK、A2A和MCP的多代理系统。
| 标签 | 文章 | 它涵盖了什么 |
|---|---|---|
part-1 | 第1部分:架构和Swarm投票 | A2A服务、MCP工具、并行投票、OTEL、Docker、Bedrock Nova |
part-1.v2 | 第1.v2部分:针对Scale进行重构 | 配置驱动的注册表、代理工厂、统一端口、Docker服务名称路由、生产注意事项 |
part-2 | 第2部分:评估 | 黄金数据集、注册表驱动的pytest、LLM作为判断者(Nova)、CI管道、HTML报告 |
part-3 | 第三部分:红队 *(即将推出)* | 迅速注射、操纵选票 |
part-4 | 第四部分:生产安全 *(即将推出)* | 授权、速率限制、秘密管理 |
查看任何标签以查看该阶段的代码:
git checkout part-1 # Original working swarm
git checkout part-1.v2 # Refactored for scalability
git checkout part-2 # Evaluation suite发生了什么变化 part-1 到 part-1.v2
part-1 是一个具有硬编码代理定义的工作群——每个代理 有一个唯一的端口,代理元数据在4+个文件中重复,并添加了一个新的 专家要求编辑6个文件。
part-1.v2 可扩展到100多个代理的重构:
- 配置驱动的代理注册表 (
agents.yaml+shared/registry.py)--单身
所有代理元数据的真实来源。添加新代理=一个YAML条目+ 一个提示文件。
- 代理工厂 (
shared/agent_factory.py)--消除了跨4的复制粘贴
几乎相同的代理模块。每个代理模块现在都少于12行。
- 统一内部端口(8080) --所有集装箱都在同一个端口上监听。
服务间通信使用Docker服务名称(http://style-agent:8080) 而不是唯一的端口。
- Docker中的编排器 --编排器与所有其他编排器一起在Docker中运行
服务,使用服务名称路由。不需要主机端口映射。
- 动态合成提示 --投票逻辑使用“N名专家”和
“ceil(N/2)”而不是硬编码的“3名专家”和“2/3”。
- 已删除死代码 --未使用的功能,冗余
load_dotenv()电话,
未使用的进口商品被清理干净。
- 生产注意事项 --记录所有单点故障(MCP
服务器、LLM API密钥、会话状态)。
评估和缩放
详细信息在 评估/README.md.摘要:
评估金字塔(随着你的成长,要运行什么)
| 层次 | 目的 | 已在此仓库中实现 | 建议在缩放时使用(10+试剂) |
|---|---|---|---|
| 1.合同 | 快速、确定性:黄金vs黄金 agents.yaml、工具顺序、注册表策略 | pytest evaluation/unit evaluation/integration (也 持续集成 在 .github/workflows/evaluation.yml) | 添加 故障 案例(例如MCP无法访问→ 优雅的VERIFY_FORTHER+置信度0.0,无碰撞) |
| 2.单代理执行 | adk eval 每 eval_package | evaluation/run_evals.sh + list_eval_packages.py;金子在下面 `evaluation/golden/ | |
| /` | CI矩阵/分片:将eval包拆分到并行作业中,以限制时钟时间(例如GitHub Actions strategy.matrix +切片脚本 list_eval_packages.py 输出) | ||
| 3.多智能体交互 | 编排A2A+合成 | adk eval orchestrator 上 evaluation/golden/swarm/trajectory_evalset.json | 更多 精选的 群体情景;成对/n-wise用于高风险边缘 |
| 4.实时/可观察性 | 真实网络、延迟、错误 | evaluation/trace_eval/ 旅馆对jsonl(otel_logs/otel.log) | 夜间浸泡、暂存门、SLO式门槛 |
Docker网络上的Eval运行器
主机运行 adk eval 无法解决 http://mcp-server:8080 或 style-agent:8080The eval-runner 服务构建仓库映像,连接 appraisal-net,设置MCP和RemoteA2A基本URL,并运行 ./evaluation/run_evals.sh.
# Start the swarm (no eval-runner in the default profile)
docker compose up -d mcp-server style-agent provenance-agent valuation-agent synthesis-agent jaeger
# One-shot full eval suite (default: CI-friendly ADK config inside compose)
docker compose --profile eval run --rm eval-runner
# Examples
docker compose --profile eval run --rm eval-runner --unit-only
docker compose --profile eval run --rm -e ADK_EVAL_CONFIG=/app/evaluation/test_config.json eval-runnereval-runner 山丘 otel_logs 只读 用于在日志存在时跟踪pytest。结果(JUnitXML+ADK eval JSON)被写入 eval_results/ 在主机上,以及 HTML报告 生成于 eval_results/eval_report.html.
超过10名专家
- 碎片/矩阵(最佳实践): 分区
eval_package将名称放入不相交的集合中并运行 并行CI作业 (每个作业都在运行adk eval仅针对其碎片)。这是 编排,不是pytest功能——请参阅上面的B层行。 - 保持 A级 在每一个PR;移动满 B级 或判断苛刻的标准 每夜的 或 预发布 如果成本或时间增长。
可观测性
随着 Docker Compose,编排器将文件导出OTEL JSONL写入 otel_logs/otel.log 在主机上(目录绑定挂载+ OTEL_LOG_PATH).其他服务将跟踪发送到 猎手 通过Docker网络上的OTLP(http://jaeger:4318).在主机上,Jaeger UI默认为 http://localhost:16686 (OTLP HTTP打开 4318).如果这些主机端口已被占用,请设置 JAEGER_UI_HOST_PORT 和 JAEGER_OTLP_HTTP_HOST_PORT 在 .env (参见 .env.example).对于一个 本地 python main.py 运行时,默认文件为 otel.log 除非您设置 OTEL_LOG_PATH.
tail -f otel_logs/otel.log # after compose orchestrator
# or
tail -f otel.log # local CLI default生产注意事项
这是一个 示例/学习项目,而不是生产部署。这 架构正确地演示了多代理模式,但有几个方面 生产使用前需要硬化。以下是已知单曲的指南 故障点(SPOF)以及如何减轻它们。
1.MCP服务器——共享工具层(最高风险单点故障)
问题: 一个 mcp-server 实例为所有专业代理提供服务。如果 它下降了,每个专家都同时失败了——他们无法调用任何工具, 不产生有用的输出,合成试剂收到空报告。
在规模上(100个代理),一个MCP服务器处理数百个并发工具调用 没有连接池或背压。
生产缓解措施:
- 复制MCP服务器 负载均衡器(例如Nginx、HAProxy或
Kubernetes服务)。由于MCP工具是无状态的,因此任何副本都可以处理 任何请求。
- 添加健康感知重试 在每个专业代理中。如果工具调用失败,
在放弃之前,以指数回退重试。Google ADK支持自定义 这个逻辑可以存在的工具包装器。
- 断路器型式 --连续N次失败后,停止调用
MCP服务器需要冷却一段时间,而不是重试。图书馆 喜欢 circuitbreaker 或 tenacity 让这变得简单明了。
- 连接池 --在代理和
MCP服务器,而不是每次工具调用都打开新连接。在100个代理中, 这大大降低了TCP开销。
- 优雅降级 --如果专家无法接触到工具,它应该
以0.0的置信度返回部分报告并投票 进一步验证 (词汇与 cast_vote 和合成:认证/验证_敦促/拒绝), 而不是默默地失败。合成剂已经处理了丢失的选票 这种方式。此路径的自动测试是 推荐 但还没有在套房里。
2.单个LLM API密钥/端点
问题: 所有代理共享一个 DEFAULT_MODEL 指向单个API 端点(Amazon Nova)。如果该端点发生中断,则对密钥进行速率限制,或 密钥被撤销,每个代理同时失败。
生产缓解措施:
- 长期或长期API密钥 --为专家使用单独的密钥,而不是。
因此,一层的速率限制不会级联到另一层。
- 后备模型配置 --在中定义主要和次要模型
agents.yaml如果初级返回429/503,则回退到次级 (例如不同的地区或完全不同的提供商)。
- 限速意识 --跟踪每个代理的令牌使用情况并主动限制
在达到提供商限制之前。对于100名特工来说,这一点尤为重要 进行并发LLM调用。
- 请求排队 --使用
具有并发限制的请求队列,用于平滑突发流量。
3.编排者——切入点
问题: 编排者(main.py)是一个运行 腺苷酸激酶 Runner 并向所有代理发送A2A呼叫。如果它崩溃了,电流 请求失败。
为什么这是可以接受的(与MCP SPOF不同):
- 编排者是 无状态 --它为每个对象从头开始重建状态
请求。崩溃会丢失一个正在进行的请求,而不是所有系统状态。
- 它包含 零域逻辑 --所有的智慧都生活在遥远的地方
代理人。编排器是一个薄的协调层,类似于API 网关。
- 它是 水平可扩展性很小 --在后面运行N个编排器副本
负载平衡器。自从 InMemorySessionService 根据进程和请求, 复制品是独立的。
- 这是 标准ADK图案 --谷歌ADK
Runner+
SequentialAgent + ParallelAgent 被设计为单一协调 点。对抗这种模式意味着对抗框架。
生产缓解措施(扩展到演示之外时):
- 替换
InMemorySessionService具有持久后端(Redis,
PostgreSQL),因此会话状态在进程重启后仍然存在,并且可以在多个进程之间共享 复制品。
- 运行多个副本 负载平衡器后面。每个复制品都运行自己的
Runner 独立实例。
- 添加请求级别超时 --如果专业代理在N内没有回应
秒,编排器应取消该分支而不是挂起 无限期。
- 结构化错误处理 --包起来
runner.run_async()抄送
try/except捕获并记录A2A通信失败,然后返回部分 结果显示给用户,而不是崩溃。
4.会话状态持久性
问题: InMemorySessionService 将会话状态存储在进程内存中。 如果编排器在对话过程中重新启动,则所有上下文都将丢失。
生产缓解措施:
- 使用Google ADK的数据库支持会话服务进行持久化。
- 使用TTL将会话状态存储在Redis中,以便自动清理。
- 对于多回合对话,在外部保存对话历史记录,以便
可以重新播放到新会话中。
5.Docker网络作为单一故障域
问题: 所有服务共享一个Docker网桥网络(appraisal-net). 网络分区或Docker守护进程问题会导致一切崩溃。
生产缓解措施:
- 使用适当的pod反亲和规则部署到Kubernetes,以便代理扩散
跨节点。
- 使用服务网格(Istio、Linkerd)进行自动重试、断路、,
以及代理之间的可观察性。
- 将关键服务(MCP服务器、合成代理)分离到专用节点上。
总结:SPOF风险排名
| 组件 | 风险 | 失败后的影响 | 缓解难度 |
|---|---|---|---|
| MCP服务器 | 高 | 所有专家同时失败 | 中等--复制+重试 |
| LLM API端点 | 高 | 所有代理同时失败 | 中等--回退模式+速率限制 |
| 编排器 | 低 | 一个用户的请求失败 | 很简单——在LB后面添加副本 |
| 会话状态 | 低 | 重启时上下文丢失 | 简单--持久会话后端 |
| Docker网络 | 低 | 总停机时间 | 中等--Kubernetes+服务网格 |
