多智能体系统
通过将专门的智能体组合成团队,构建复杂的应用程序。
智能体层级概览
你将构建的内容
在本指南中,你将创建一个客户服务系统,其中协调器会将查询路由给专业智能体:
┌─────────────────────┐
User Query │ │
────────────────▶ │ COORDINATOR │
│ "Route to expert" │
└──────────┬──────────┘
│
┌───────────────┴───────────────┐
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ BILLING AGENT │ │ SUPPORT AGENT │
│ │ │ │
│ 💰 Payments │ │ 🔧 Tech Issues │
│ 📄 Invoices │ │ 🐛 Bug Reports │
│ 💳 Subscriptions│ │ ❓ How-To │
└──────────────────┘ └──────────────────┘
核心概念:
- 协调器 - 接收所有请求,并决定由谁处理
- 专业智能体 - 专注于特定领域并在其中表现出色的智能体
- 转交 - 从协调器无缝交接给专业智能体
可移植的 TeamSpec
当同一团队必须在生成的项目、CLI、Studio 预览环境和 Enterprise 部署中以完全相同的方式执行时,请使用 TeamSpec。该规范是可序列化的数据;compile 将每个声明的名称绑定到任意现有的 Agent 实现,并返回一个可执行的 CompiledTeam 根节点。它不会新增原子智能体类型。
use adk_agent::{
Agent, RelationshipKind, TeamMemberSpec, TeamRelationship, TeamSpec,
};
use std::sync::Arc;
let spec = TeamSpec {
name: "support_team".into(),
description: "Customer support routing".into(),
coordinator: "coordinator".into(),
members: vec![
TeamMemberSpec::new("coordinator"),
TeamMemberSpec::new("billing_agent"),
TeamMemberSpec::new("research_agent"),
],
relationships: vec![
TeamRelationship::new(
"coordinator", "billing_agent", RelationshipKind::Handoff,
),
TeamRelationship::new(
"coordinator", "research_agent", RelationshipKind::Delegate,
),
],
policy: Default::default(),
};
let root = spec.compile(vec![
Arc::new(coordinator) as Arc<dyn Agent>,
Arc::new(billing_agent),
Arc::new(research_agent),
])?;
直接使用 crate 时,如果规范包含 Delegate,请启用 adk-agent 的 team-tools 特性。使用 umbrella crate 时,请启用 tools(或 standard 层级)。仅包含转交的规范使用基础 adk-agent 特性集即可编译。
| 关系 | 控制行为 | 降低后的机制 |
|---|---|---|
Handoff | 调用方转移控制权且不会恢复执行 | 运行器 transfer_to_agent |
Delegate | 调用方调用成员、接收其结果并恢复执行 | AgentTool |
每个成员的目标都是精确的。编译器会拒绝重复的成员和边、缺失的端点、循环、不可达成员、无效边界、缺失的 agent 绑定以及多余的绑定。委托边可以携带输入/输出 JSON 模式、上下文和历史投影、精确的状态键、超时和审批要求、重试/回退行为以及熔断器。 交接边支持有界的启动重试/回退;仅限委托的字段会被拒绝,而不是被静默忽略。回退必须作为单独声明的边;验证绝不会扩大其权限。
团队范围的策略会限制交接/委托深度、并发数、事件数、模型和工具调用次数、令牌数、成本、关系数量以及墙钟时间。终止规则可以在发生升级、最终作者精确匹配或出现文本标记时停止。每个事件都会在 __adk_team_execution_v1
中携带可序列化的执行回执,其中包括冻结的成员名单、因果委托/交接边、汇总用量和终止状态。使用 CompiledTeam::execution_snapshot 检查该回执,并在使用相同调用 ID 恢复之前使用 restore_execution_snapshot。
活动中的交接会在其冻结的目标处恢复。未解决的委托不会被自动重放,因为通用的 Agent
契约无法证明工具调用具有幂等性;运行时会返回可执行的错误。应通过其自身的检查点机制恢复该委托,或明确失败/重新启动该操作。
对于可复用的架构,TeamArchitectureTemplate 会将监督器、路由器和层级预设降级为显式的 TeamSpec 数据。
WorkflowArchitectureTemplate 会基于现有工作流代理编译顺序、并行、扇出/扇入以及有界审查循环预设。基于能力的解析可通过 TeamAgentRegistry 使用;选择过程具有确定性,可以要求健康状态、版本、摘要和信任标签,并且选定的成员清单会被冻结在执行回执中。
执行平面能力
编译会检查源代理声明的 AgentCapabilities。委托源必须使用调用作用域内的运行时工具,移交源必须发出转移,而获批准的委托必须支持精确调用确认。
LlmAgent 和 CodeActAgent 声明其支持的运行时能力;自定义代理保留保守默认值,并且可以覆盖 capabilities()。
团队保留标准的 ADK 行为:Runner 插件、代理/模型/工具回调、护栏、取消、请求身份、记忆、工件、会话和事件持久化仍会包装现有成员代理。compile_with_hooks 为团队、成员以及精确关系边界添加有序的异步 TeamLifecycleHook 回调。钩子可以观察成功/失败,或拒绝待处理的操作,而不会扩大其目标范围。Runner 还会在经过验证的移交之前立即调用根代理的异步 govern_transfer 钩子。
原生 team.run、team.member.run 和 team.relationship.execute 跟踪跨度会通过常规的 adk-telemetry 导出器传递团队/成员/边的身份、状态和关系持续时间。运行时故障使用稳定的结构化代码,例如 agent.team.policy_denied、agent.team.budget_exceeded 和
agent.team.resume_unsafe。
委托契约区分状态读取(stateKeys)和写入(stateWriteKeys)。当父级值在委托期间发生变化时,stateMerge: rejectConflicts 会拒绝子级写入,而 artifactPrefixes 会限制工件名称。独立的 AgentTool 提供相同的控制。为保持兼容性,其默认行为仍为后写入者胜出;Team 关系默认拒绝冲突。
CompiledTeam::resume_plan 描述了恢复回执后唯一安全的下一步操作:协调器、交接目标、支持检查点的委托,或不安全的故障闭合委托。validate_team_replay 验证因果回执完整性,而 analyze_execution 报告无提供商拓扑覆盖率、失败情况以及用于测试和评估流水线的最大因果深度。
BlackboardSpec 涵盖受治理的轮询和选择器驱动的群聊。它限制轮次和转录投影,并且选择器转换使用精确的允许列表。现有的 sub_agent 组合方式仍受支持,以保持向后兼容,但它是内存中的树,而不是可移植的团队定义。
快速开始
1. 创建项目
cargo new multi_agent_demo
cd multi_agent_demo
将依赖项添加到 Cargo.toml:
[dependencies]
adk-rust = "2.1.0"
tokio = { version = "1", features = ["full"] }
dotenvy = "0.15"
使用你的 API 密钥创建 .env:
echo 'GOOGLE_API_KEY=your-api-key' > .env
2. 客户服务示例
以下是一个完整的可运行示例:
use adk_rust::prelude::*;
use adk_rust::Launcher;
use std::sync::Arc;
#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let api_key = std::env::var("GOOGLE_API_KEY")?;
let model = Arc::new(GeminiModel::new(&api_key, "gemini-3.7-flash")?);
// Specialist: Billing Agent
let billing_agent = LlmAgentBuilder::new("billing_agent")
.description("Handles billing questions: payments, invoices, subscriptions, refunds")
.instruction("You are a billing specialist. Help customers with:\n\
- Invoice questions and payment history\n\
- Subscription plans and upgrades\n\
- Refund requests\n\
- Payment method updates\n\
Be professional and provide clear information about billing matters.")
.model(model.clone())
.build()?;
// Specialist: Technical Support Agent
let support_agent = LlmAgentBuilder::new("support_agent")
.description("Handles technical support: bugs, errors, troubleshooting, how-to questions")
.instruction("You are a technical support specialist. Help customers with:\n\
- Troubleshooting errors and bugs\n\
- How-to questions about using the product\n\
- Configuration and setup issues\n\
- Performance problems\n\
Be patient and provide step-by-step guidance.")
.model(model.clone())
.build()?;
// Coordinator: Routes to appropriate specialist
let coordinator = LlmAgentBuilder::new("coordinator")
.description("Main customer service coordinator")
.instruction("You are a customer service coordinator. Analyze each customer request:\n\n\
- For BILLING questions (payments, invoices, subscriptions, refunds):\n\
Transfer to billing_agent\n\n\
- For TECHNICAL questions (errors, bugs, how-to, troubleshooting):\n\
Transfer to support_agent\n\n\
- For GENERAL greetings or unclear requests:\n\
Respond yourself and ask clarifying questions\n\n\
When transferring, briefly acknowledge the customer and explain the handoff.")
.model(model.clone())
.sub_agent(Arc::new(billing_agent))
.sub_agent(Arc::new(support_agent))
.build()?;
println!("🏢 Customer Service Center");
println!(" Coordinator → Billing Agent | Support Agent");
println!();
Launcher::new(Arc::new(coordinator)).run().await?;
Ok(())
}
交互示例:
You: I have a question about my last invoice
[Agent: coordinator]
Assistant: I'll connect you with our billing specialist to help with your invoice question.
[Agent: billing_agent]
Assistant: Hello! I can help you with your invoice. What specific question do you have about your last invoice?
You: Why was I charged twice?
[Agent: billing_agent]
Assistant: I understand your concern about the duplicate charge. Let me help you investigate this...
多智能体交接的工作原理
整体概览
当你将子智能体添加到父智能体后,LLM 将获得交接控制权的能力:
┌─────────────────────┐
User Message │ │
─────────────────▶ COORDINATOR │
│ │
└──────────┬──────────┘
│
"This is a billing question..."
│
┌────────────────┴────────────────┐
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ billing_agent │ │ support_agent │
│ 💰 Payments │ │ 🔧 Tech Issues │
│ 📄 Invoices │ │ 🐛 Bug Reports │
└──────────────────┘ └──────────────────┘
逐步交接流程
以下是用户提出账单问题时的具体处理过程:
┌──────────────────────────────────────────────────────────────────────┐
│ STEP 1: User sends message │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ User: "Why was I charged twice on my invoice?" │
│ │
│ ↓ │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ COORDINATOR AGENT │ │
│ │ Receives message first │ │
│ └──────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────────────┐
│ STEP 2: LLM analyzes and decides to transfer │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ 🧠 LLM thinks: "This is about an invoice charge..." │
│ "Invoice = billing topic..." │
│ "I should transfer to billing_agent" │
│ │
│ 📞 LLM calls: transfer_to_agent(agent_name="billing_agent") │
│ │
└──────────────────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────────────┐
│ STEP 3: Runner detects transfer and invokes target │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ transfer event ┌─────────────────┐ │
│ │ Runner │ ─────────────────────▶ │ billing_agent │ │
│ └─────────┘ (same user message) └─────────────────┘ │
│ │
│ • Runner finds "billing_agent" in agent tree │
│ • Creates new context with SAME user message │
│ • Invokes billing_agent immediately │
│ │
└──────────────────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────────────┐
│ STEP 4: Target agent responds │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ billing_agent responds │ │
│ │ │ │
│ │ "I can help with your duplicate │ │
│ │ charge. Let me investigate..." │ │
│ └─────────────────────────────────────────┘ │
│ │
│ ✅ User sees seamless response - no interruption! │
│ │
└──────────────────────────────────────────────────────────────────────┘
实现这一过程的关键因素
| 组件 | 作用 |
|---|---|
.sub_agent() | 在父级下注册专用代理 |
transfer_to_agent 工具 | 存在子代理时自动注入 |
| Agent descriptions | 帮助 LLM 决定由哪个 agent 处理什么 |
| Runner | 检测转移事件并调用目标 agent |
| Shared session | 在转移过程中保留状态和历史记录 |
添加子代理前后对比
没有子代理 - 一个代理完成所有工作:
User ──▶ coordinator ──▶ Response (handles billing AND support)
使用子代理 - 专业代理负责各自的领域:
User ──▶ coordinator ──▶ billing_agent ──▶ Response (billing expert)
──▶ support_agent ──▶ Response (tech expert)
分层多代理系统
对于复杂场景,你可以创建多级层次结构。每个代理都可以拥有自己的子代理,从而形成一棵树:
可视化:三级内容团队
┌─────────────────────┐
│ PROJECT MANAGER │ ← Level 1: Top-level coordinator
│ "Manage projects" │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ CONTENT CREATOR │ ← Level 2: Mid-level coordinator
│ "Coordinate R&W" │
└──────────┬──────────┘
│
┌────────────────┴────────────────┐
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ RESEARCHER │ │ WRITER │ ← Level 3: Specialists
│ │ │ │
│ 📚 Gather facts │ │ ✍️ Write content │
│ 🔍 Analyze data │ │ 📝 Polish text │
│ 📊 Find sources │ │ 🎨 Style & tone │
└──────────────────┘ └──────────────────┘
请求如何向下流转
User: "Create a blog post about electric vehicles"
│
▼
┌─────────────────────────────────────────────────────────────┐
│ PROJECT MANAGER: "This is a content task" │
│ → transfers to content_creator │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ CONTENT CREATOR: "Need research first, then writing" │
│ → transfers to researcher │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ RESEARCHER: "Here's what I found about EVs..." │
│ → provides research summary │
└─────────────────────────────────────────────────────────────┘
完整示例代码
use adk_rust::prelude::*;
use adk_rust::Launcher;
use std::sync::Arc;
#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let api_key = std::env::var("GOOGLE_API_KEY")?;
let model = Arc::new(GeminiModel::new(&api_key, "gemini-3.7-flash")?);
// Level 3: Leaf specialists
let researcher = LlmAgentBuilder::new("researcher")
.description("Researches topics and gathers comprehensive information")
.instruction("You are a research specialist. When asked to research a topic:\n\
- Gather key facts and data\n\
- Identify main themes and subtopics\n\
- Note important sources or references\n\
Provide thorough, well-organized research summaries.")
.model(model.clone())
.build()?;
let writer = LlmAgentBuilder::new("writer")
.description("Writes polished content based on research")
.instruction("You are a content writer. When asked to write:\n\
- Create engaging, clear content\n\
- Use appropriate tone for the audience\n\
- Structure content logically\n\
- Polish for grammar and style\n\
Produce professional, publication-ready content.")
.model(model.clone())
.build()?;
// Level 2: Content coordinator
let content_creator = LlmAgentBuilder::new("content_creator")
.description("Coordinates content creation by delegating research and writing")
.instruction("You are a content creation lead. For content requests:\n\n\
- If RESEARCH is needed: Transfer to researcher\n\
- If WRITING is needed: Transfer to writer\n\
- For PLANNING or overview: Handle yourself\n\n\
Coordinate between research and writing phases.")
.model(model.clone())
.sub_agent(Arc::new(researcher))
.sub_agent(Arc::new(writer))
.build()?;
// Level 1: Top-level manager
let project_manager = LlmAgentBuilder::new("project_manager")
.description("Manages projects and coordinates with content team")
.instruction("You are a project manager. For incoming requests:\n\n\
- For CONTENT creation tasks: Transfer to content_creator\n\
- For PROJECT STATUS or general questions: Handle yourself\n\n\
Keep track of overall project goals and deadlines.")
.model(model.clone())
.sub_agent(Arc::new(content_creator))
.build()?;
println!("📊 Hierarchical Multi-Agent System");
println!();
println!(" project_manager");
println!(" └── content_creator");
println!(" ├── researcher");
println!(" └── writer");
println!();
Launcher::new(Arc::new(project_manager)).run().await?;
Ok(())
}
代理层次结构:
project_manager
└── content_creator
├── researcher
└── writer
示例提示词:
- “创建一篇关于医疗保健领域人工智能的博客文章” → 项目经理 → 内容创作者 → 写作者
- “研究电动汽车” → 项目经理 → 内容创作者 → 研究员
子代理配置
使用 sub_agent() 构建器方法向任意 LlmAgent 添加子代理:
let parent = LlmAgentBuilder::new("parent")
.description("Coordinates specialized tasks")
.instruction("Route requests to appropriate specialists.")
.model(model.clone())
.sub_agent(Arc::new(specialist_a))
.sub_agent(Arc::new(specialist_b))
.build()?;
要点:
- 每个代理都可以拥有多个子代理
- 子代理可以拥有自己的子代理(多级层次结构)
- 代理名称在层次结构中必须唯一
- 描述有助于 LLM 决定将请求转交给哪个代理
编写有效的转交指令
要实现成功的代理转交,请提供清晰的指令和描述:
父代理指令
let coordinator = LlmAgentBuilder::new("coordinator")
.description("Main customer service coordinator")
.instruction("You are a customer service coordinator. Analyze each request:\n\n\
- For BILLING questions (payments, invoices, subscriptions):\n\
Transfer to billing_agent\n\n\
- For TECHNICAL questions (errors, bugs, troubleshooting):\n\
Transfer to support_agent\n\n\
- For GENERAL greetings or unclear requests:\n\
Respond yourself and ask clarifying questions")
.model(model.clone())
.sub_agent(Arc::new(billing_agent))
.sub_agent(Arc::new(support_agent))
.build()?;
子代理描述
let billing_agent = LlmAgentBuilder::new("billing_agent")
.description("Handles billing questions: payments, invoices, subscriptions, refunds")
.instruction("You are a billing specialist. Help with payment and subscription issues.")
.model(model.clone())
.build()?;
let support_agent = LlmAgentBuilder::new("support_agent")
.description("Handles technical support: bugs, errors, troubleshooting, how-to questions")
.instruction("You are a technical support specialist. Provide step-by-step guidance.")
.model(model.clone())
.build()?;
最佳实践:
- 使用描述性代理名称,清楚表明其用途
- 编写详细描述 - LLM 会使用这些描述来决定转交
- 在描述中加入与用户可能请求相匹配的具体关键词
- 在父代理指令中提供明确的委派规则
- 在各代理描述中使用一致的术语
测试你的多代理系统
运行示例
cargo run --manifest-path examples/tier_examples/enterprise/Cargo.toml --bin 14-enterprise-multi-agent
示例测试提示词
客户服务:
- “我对上一张发票有疑问” → 应路由至
billing_agent - “应用一直崩溃” → 应路由至
support_agent - “如何升级我的套餐?” → 应路由至
billing_agent - “您好,我需要帮助” → 应由
coordinator继续处理以进行澄清
层级式:
- “撰写一篇关于医疗保健领域 AI 的博客文章” → 项目经理 → 内容创作者 → 撰稿人
- “研究电动汽车的历史” → 项目经理 → 内容创作者 → 研究员
- “我们当前项目的状态如何?” → 应由
project_manager继续处理
调试转移问题
如果转移未按预期工作:
- 检查代理名称 - 在转移调用中必须完全匹配
- 检查描述 - 使其更加具体并包含丰富的关键词
- 明确指令 - 明确说明何时进行转移
- 测试边界情况 - 尝试含义模糊的请求,观察路由行为
- 查找转移指示器 -
[Agent: name]会显示哪个代理正在响应
全局指令
基本用法
let agent = LlmAgentBuilder::new("assistant")
.description("A helpful assistant")
.global_instruction(
"You are a professional assistant for Acme Corp. \
Always maintain a friendly but professional tone. \
Our company values are: customer-first, innovation, and integrity."
)
.instruction("Help users with their questions and tasks.")
.model(model.clone())
.build()?;
全局指令与代理指令
- 全局指令:应用于层级结构中的所有代理,设置总体个性和上下文
- 代理指令:针对每个代理,定义其特定角色和行为
这两种指令都会包含在对话历史中,其中全局指令位于最前面。
动态全局指令
对于更高级的场景,可以使用全局指令提供程序动态计算指令:
use adk_core::GlobalInstructionProvider;
let provider: GlobalInstructionProvider = Arc::new(|ctx| {
Box::pin(async move {
// Access context information
let user_id = ctx.user_id();
// Compute dynamic instruction
let instruction = format!(
"You are assisting user {}. Tailor your responses to their preferences.",
user_id
);
Ok(instruction)
})
});
let agent = LlmAgentBuilder::new("assistant")
.description("A personalized assistant")
.global_instruction_provider(provider)
.model(model.clone())
.build()?;
状态变量注入
全局指令和代理指令都支持使用 {variable} 语法注入状态变量:
// Set state in a previous agent or tool
// state["company_name"] = "Acme Corp"
// state["user_role"] = "manager"
let agent = LlmAgentBuilder::new("assistant")
.global_instruction(
"You are an assistant for {company_name}. \
The user is a {user_role}."
)
.instruction("Help with {user_role}-level tasks.")
.model(model.clone())
.build()?;
框架会自动将会话状态中的值注入指令模板。
常见的多代理模式
协调器/分发器模式
中央 agent 将请求路由到专用子 agent:
let billing = LlmAgentBuilder::new("billing")
.description("Handles billing and payment questions")
.model(model.clone())
.build()?;
let support = LlmAgentBuilder::new("support")
.description("Provides technical support")
.model(model.clone())
.build()?;
let coordinator = LlmAgentBuilder::new("coordinator")
.instruction("Route requests to billing or support agents as appropriate.")
.sub_agent(Arc::new(billing))
.sub_agent(Arc::new(support))
.model(model.clone())
.build()?;
示例对话:
User: I have a question about my last invoice
[Agent: coordinator]
Assistant: I'll connect you with our billing specialist.
🔄 [Transfer requested to: billing]
[Agent: billing]
Assistant: Hello! I can help you with your invoice.
What specific question do you have?
User: Why was I charged twice?
[Agent: billing]
Assistant: Let me investigate that duplicate charge for you...
要点:
- 协调器分析请求并将其转交给计费 agent
- 计费 agent 在同一轮中立即响应
- 后续消息继续由计费 agent 处理
- 转交指示器(
🔄)显示何时发生交接
分层任务分解
用于分解复杂任务的多级层次结构:
// Low-level specialists
let researcher = LlmAgentBuilder::new("researcher")
.description("Researches topics and gathers information")
.model(model.clone())
.build()?;
let writer = LlmAgentBuilder::new("writer")
.description("Writes content based on research")
.model(model.clone())
.build()?;
// Mid-level coordinator
let content_creator = LlmAgentBuilder::new("content_creator")
.description("Creates content by coordinating research and writing")
.sub_agent(Arc::new(researcher))
.sub_agent(Arc::new(writer))
.model(model.clone())
.build()?;
// Top-level manager
let project_manager = LlmAgentBuilder::new("project_manager")
.description("Manages content creation projects")
.sub_agent(Arc::new(content_creator))
.model(model.clone())
.build()?;
与工作流 Agent 结合
多 agent 系统与工作流 agent(Sequential、Parallel、Loop)配合使用效果良好:
use adk_agent::workflow::{SequentialAgent, ParallelAgent};
// Create specialized agents
let validator = LlmAgentBuilder::new("validator")
.instruction("Validate the input data.")
.output_key("validation_result")
.model(model.clone())
.build()?;
let processor = LlmAgentBuilder::new("processor")
.instruction("Process data if {validation_result} is valid.")
.output_key("processed_data")
.model(model.clone())
.build()?;
// Combine in a sequential workflow
let pipeline = SequentialAgent::new(
"validation_pipeline",
vec![Arc::new(validator), Arc::new(processor)]
);
// Use the pipeline as a sub-agent
let coordinator = LlmAgentBuilder::new("coordinator")
.description("Coordinates data processing")
.sub_agent(Arc::new(pipeline))
.model(model.clone())
.build()?;
Agent 之间的通信
层次结构中的 agent 通过共享的会话状态进行通信:
// Agent A saves data to state
let agent_a = LlmAgentBuilder::new("agent_a")
.instruction("Analyze the topic and save key points.")
.output_key("key_points") // Automatically saves output to state
.model(model.clone())
.build()?;
// Agent B reads data from state
let agent_b = LlmAgentBuilder::new("agent_b")
.instruction("Expand on the key points: {key_points}")
.model(model.clone())
.build()?;
output_key 配置会自动将 agent 的最终响应保存到会话状态中,使其可供后续 agent 使用。
AgentTool 状态与工件转发
使用 AgentTool 将 agent 封装为工具时,子 agent 的状态更改和工件会自动转发到父上下文:
use adk_tool::AgentTool;
// Create a sub-agent that modifies state
let data_processor = LlmAgentBuilder::new("data_processor")
.instruction("Process the data and save results.")
.output_key("processed_data")
.model(model.clone())
.build()?;
// Wrap as a tool - state_delta and artifact_delta are forwarded
let processor_tool = AgentTool::new(Arc::new(data_processor));
// Parent agent can use the tool and see state changes
let coordinator = LlmAgentBuilder::new("coordinator")
.instruction("Use the data_processor tool, then access {processed_data}.")
.model(model.clone())
.tool(Arc::new(processor_tool))
.build()?;
AgentTool 在内部以非流式模式(StreamingMode::None)运行子 agent,因此子 agent 会先累积完整响应,然后再将其返回给父 agent。这样可以避免部分流式数据块产生空结果的问题。
这使得使用 AgentTool 模式时,父 agent 与子 agent 之间能够实现无缝的数据流转。
运行多 Agent 系统
使用启动器
Launcher 提供了一种运行和测试多 agent 系统的简便方式:
use adk_rust::Launcher;
let coordinator = /* your multi-agent setup */;
Launcher::new(Arc::new(coordinator))
.run()
.await?;
运行模式:
# Interactive console mode
cargo run --manifest-path examples/tier_examples/enterprise/Cargo.toml --bin 14-enterprise-multi-agent
# Use a generated API project when you need HTTP serving
cargo adk new multi_agent_api --template api
功能:
- Agent 指示器:显示正在响应的 agent
[Agent: coordinator] - 转移可视化:显示转移事件
🔄 [Transfer requested to: billing_agent] - 无缝交接:转移后目标 agent 会立即响应
- 对话历史:在 agent 转移过程中保持上下文
测试转移
验证多 agent 系统是否正常运行:
- 检查 agent 名称:它们响应时应显示在方括号中
- 查找转移指示器:agent 交接时应显示 (
🔄) - 验证立即响应:目标 agent 无需重新提示即可响应
- 测试不同的请求类型:确保路由正确
- 检查边界情况:例如转移到不存在的 agent
调试转移问题
如果转移无法正常工作:
- 验证子 agent 是否已添加:通过
.sub_agent() - 检查 agent 描述:LLM 使用这些描述来决定转移
- 查看指令:父 agent 应说明何时进行转移
- 检查 agent 名称:必须与转移调用中的名称完全匹配
- 启用日志记录:在事件流中查看转移操作
最佳实践
- 清晰的描述:编写描述性的 agent 名称和描述,帮助 LLM 做出良好的转移决策
- 具体的指令:为每个 agent 提供清晰、专注于其角色的指令
- 使用全局指令:在所有 agent 之间设置一致的个性和上下文
- 状态管理:使用
output_key和状态变量进行 agent 间通信 - 限制层级深度:保持层级较浅(2-3 层),以便更好地维护
- 测试转移逻辑:验证 agent 是否会针对不同请求转移到正确的子 agent