多智能体教程人工智能Rustv0.1.8

使用 ADK-Rust 构建多智能体系统

了解何时以及如何使用不同的多智能体模式——从简单的协调到复杂的基于图的编排——并利用 Rust 的类型安全和高性能。

·阅读时长15分钟·ADK-Rust v0.1.8

1. 简介

问题:触及瓶颈的AI

您已经构建了您的第一个AI代理。它令人印象深刻——可以回答问题、总结文档,甚至可能编写代码。但随后现实来袭:

"您能帮我查看上一个发票,并协助我设置 API 吗?"

您的代理陷入困境。它没有经过账单系统、开发者文档和故障排除工作流程的训练。它的指令提示词已经达到2000个token,试图涵盖所有内容。响应质量随之下降。

这就是单代理的瓶颈。随着您的应用程序增长,您将面临痛苦的权衡:

  • 臃肿的提示词: 每增加一项新功能都意味着更长的指令、更高的延迟以及模型更多的困惑
  • 万金油: 一个代理同时处理账单、支持和销售,结果在三者上都表现平平
  • 难以维护: 更改账单逻辑不应冒着破坏您的支持流程的风险
  • 缺乏专业化: 您的数学代理无法拥有计算器工具,而您的研究代理却拥有网页搜索——它们共享一切

解决方案:专业化代理协同工作

多代理系统通过将复杂任务分解为专业角色来解决此问题。您不是创建一个不堪重负的通才,而是创建专注的专家:

  • 客户服务: 一个 coordinator 将用户路由到账单、技术支持或销售专家——每个专家都拥有专注的训练和工具
  • 内容创作: 一个研究代理收集事实,一个 writer 撰写叙述,一个编辑进行润色——每个代理都精通一项技能
  • 代码生成: 规划师设计架构,coder 负责实现,审阅者发现错误——不同的视角提升质量

结果如何?每个代理都保持专注,提示词易于管理,并且您可以在不影响支持的情况下更新计费逻辑。这是面向 AI 的微服务。

您将学到什么

ADK-Rust 提供了三种渐进式强大的多代理编排模式。本教程将教您:

  • 根据您的需求何时使用每种模式
  • 如何使用生产就绪的 Rust 代码实现它们
  • 为什么架构权衡对您的特定用例很重要

2. 选择正确的模式

在深入代码之前,让我们了解每种模式的特点:

模式最适合控制级别复杂性
协调器对话交接LLM 决定
AgentTool响应处理协调器处理
主管图复杂工作流完整状态管理

3. 模式 1:协调器(子代理)

🎯 用例:客户服务路由

您正在构建一个客户服务机器人。用户可能会询问账单、请求技术帮助或咨询新功能。每个领域都需要专业知识,但用户不应该需要知道联系哪个部门。

协调器模式使用自动代理转移。当您通过 .sub_agent() 添加子代理时,ADK-Rust 会注入一个 transfer_to_agent 工具。LLM 根据对话决定何时移交。

用户协调器路由请求至专家计费子代理支持子代理销售子代理transfer_to_agenttransfer_to_agenttransfer_to_agent

主要特点

  • 无缝移交:用户自然地与专家继续对话
  • LLM 驱动的路由:coordinator 根据对话上下文做出决定
  • 对话连续性:会话历史记录在转移过程中保持不变
  • 无响应处理:转移后专家直接与用户对话

实现

use adk_rust::prelude::*;
use std::sync::Arc;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let api_key = std::env::var("GOOGLE_API_KEY")?;
    let model = Arc::new(GeminiModel::new(&api_key, "gemini-2.0-flash")?);

    // Specialist: Billing Agent
    // Clear description helps the coordinator know when to transfer
    let billing_agent = LlmAgentBuilder::new("billing_agent")
        .description(
            "Handles all billing questions: invoices, payments, refunds,              subscription plans, and account charges. Transfer here for              any money-related questions."
        )
        .instruction(
            "You are a billing specialist. Answer questions about invoices,              payments, and subscription plans. Be concise and accurate.              If asked about technical issues, suggest transferring to support."
        )
        .model(model.clone())
        .build()?;

    // Specialist: Technical Support Agent
    let support_agent = LlmAgentBuilder::new("support_agent")
        .description(
            "Provides technical support: troubleshooting, bug reports,              feature questions, and integration help. Transfer here for              any technical problems."
        )
        .instruction(
            "You are a technical support specialist. Help users troubleshoot              issues step by step. Ask clarifying questions when needed.              Be patient and thorough."
        )
        .model(model.clone())
        .build()?;

    // Coordinator: Routes to specialists
    let coordinator = LlmAgentBuilder::new("coordinator")
        .description("Customer service coordinator")
        .instruction(
            "You are a friendly customer service coordinator. Your job is to:\n             1. Greet users warmly\n             2. Understand their needs\n             3. Route to the right specialist:\n                - Billing questions → transfer to billing_agent\n                - Technical issues → transfer to support_agent\n             4. Handle general questions yourself\n\n             Always explain who you're connecting them with."
        )
        .model(model.clone())
        .sub_agent(Arc::new(billing_agent))
        .sub_agent(Arc::new(support_agent))
        .build()?;

    // Run with the built-in launcher
    Launcher::new(Arc::new(coordinator))
        .run()
        .await?;

    Ok(())
}

对话示例

用户: 您好,我有一个关于账单的问题

[coordinator]: 您好!我很乐意帮助您解决账单问题。让我为您转接我们的账单专家,他可以协助您。

系统: 🔄 转接至:billing_agent

[billing_agent]: 您好!我是账单专家。我可以帮助您处理发票、付款和订阅问题。您想了解关于账单的什么信息?

用户: 为什么我这个月被收了两次费?

[billing_agent]: 我将为您调查那笔重复收费...

✅ 何时使用协调器

  • 用户应直接与专家互动
  • 路由决策简单明了
  • 您无需处理专家响应
  • 对话流程是线性的(一次一位专家)

4. 模式 2:将代理作为工具 (AgentTool)

🎯 用例:知识聚合

您正在构建一个智能助手,用于回答跨多个领域的问题。用户提问“250 的 15% 是多少,这个数字在历史上有什么意义?”您需要调用数学专家,然后调用知识问答专家,并结合他们的答案。

AgentTool 模式将代理封装为可调用的工具。与子代理不同,coordinator 以编程方式调用专家,并在回复用户之前接收他们的响应进行处理或组合。

用户协调器将代理作为工具调用处理响应聚合结果返回给用户数学专家AgentTool+ 计算器冷知识专家AgentToolLLM 知识研究员AgentTool+ web_search调用 →← 响应

与协调器的主要区别

协调器(子代理)

  • • 专家直接与用户对话
  • • 一次一位专家
  • • 无需响应处理

AgentTool

  • • 协调器接收响应
  • • 可调用多位专家
  • • 聚合和总结

实现

use adk_agent::LlmAgentBuilder;
use adk_tool::{AgentTool, FunctionTool};
use adk_core::ToolContext;
use serde_json::{json, Value};
use std::sync::Arc;

// Calculator tool for the math agent
async fn calculator(
    _ctx: Arc<dyn ToolContext>,
    args: Value
) -> Result<Value, adk_core::AdkError> {
    let operation = args["operation"].as_str().unwrap_or("add");
    let a = args["a"].as_f64().unwrap_or(0.0);
    let b = args["b"].as_f64().unwrap_or(0.0);

    let result = match operation {
        "add" => a + b,
        "multiply" => a * b,
        "percent" => a * (b / 100.0),
        _ => return Err(adk_core::AdkError::Tool(
            format!("Unknown operation: {}", operation)
        )),
    };

    Ok(json!({ "result": result }))
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let api_key = std::env::var("GOOGLE_API_KEY")?;
    let model = Arc::new(GeminiModel::new(&api_key, "gemini-2.5-flash")?);

    // Create calculator tool
    let calc_tool = FunctionTool::new(
        "calculator",
        "Performs arithmetic: add, multiply, percent.          Args: operation (string), a (number), b (number)",
        calculator,
    );

    // Math Expert agent - has its own tools
    let math_agent = LlmAgentBuilder::new("math_expert")
        .description(
            "A math expert that performs calculations. Use for any              math-related questions, percentages, or numerical analysis."
        )
        .instruction(
            "You are a math expert. Use the calculator tool for              calculations. Show your work step by step. Be precise."
        )
        .model(model.clone())
        .tool(Arc::new(calc_tool))
        .build()?;

    // Trivia Expert agent - uses LLM knowledge
    let trivia_agent = LlmAgentBuilder::new("trivia_expert")
        .description(
            "A trivia and history expert. Use for questions about              historical facts, pop culture, science facts, and trivia."
        )
        .instruction(
            "You are a trivia expert with vast knowledge across domains.              Answer questions accurately and include interesting related facts."
        )
        .model(model.clone())
        .build()?;

    // Wrap agents as tools with configuration
    let math_tool = AgentTool::new(Arc::new(math_agent))
        .skip_summarization(false)   // Summarize lengthy responses
        .forward_artifacts(true);     // Pass through any generated files

    let trivia_tool = AgentTool::new(Arc::new(trivia_agent))
        .skip_summarization(false);

    // Coordinator uses agents as tools
    let coordinator = LlmAgentBuilder::new("coordinator")
        .description("Smart assistant that combines expert knowledge")
        .instruction(
            "You are a helpful assistant with access to expert agents:\n             - math_expert: For calculations and math problems\n             - trivia_expert: For facts, history, and trivia\n\n             When questions span multiple domains, call multiple experts              and synthesize their responses into a cohesive answer."
        )
        .model(model)
        .tool(Arc::new(math_tool))
        .tool(Arc::new(trivia_tool))
        .build()?;

    Launcher::new(Arc::new(coordinator)).run().await?;
    Ok(())
}

示例:多领域问题

用户: 250 的 15% 是多少,这个数字在历史上有什么意义?

系统: // 协调器调用 math_expert 工具

[math_expert 响应]: 250 的 15% 是 37.5

系统: // 协调器调用 trivia_expert 工具

[trivia_expert 回应]: 37 和 38 在历史上不那么引人注目,但 37.5°C 是人体体温...

系统: // 协调器进行综合

[coordinator]: 250 的 15% 等于 37.5。有趣的是,37.5°C (99.5°F) 接近人体平均体温 37°C,使其成为一个具有医学意义的数字!

✅ 何时使用 AgentTool

  • 您需要整合多位专家的回应
  • 协调器应总结或筛选专家输出
  • 专家拥有自己的工具(嵌套能力)
  • 您希望对代理调用进行程序化控制

5. 模式 3:主管图

🎯 用例:内容创作流程

您正在构建一个内容创作系统。给定一个主题,您需要:(1) 研究它,(2) 撰写文章,(3) 添加代码示例。supervisor 根据任务动态决定顺序,并且工作人员可以循环返回进行修订。

主管图模式使用 ADK-Rust 的基于图的工作流系统。supervisor 代理动态路由到工作器,具有完整的状态管理和循环执行支持。

START主管决定下一个工作代理研究员工作者作者工作者编码员工作者END循环返回"完成" → 最终确定 → END共享状态• research_output• written_content• code_output

为何使用图?

  • 动态路由:主管根据当前状态决定下一个工作器
  • 循环执行:工作器可以循环返回进行迭代
  • 共享状态:所有节点读/写一个共同的状态对象
  • 条件边:基于 LLM 决策的不同路径
  • 递归限制:防止无限循环

实现

use adk_agent::LlmAgentBuilder;
use adk_graph::{
    StateGraph,
    edge::{START, END},
    node::{AgentNode, ExecutionConfig, NodeOutput},
    state::State,
};
use adk_model::GeminiModel;
use serde_json::json;
use std::sync::Arc;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let api_key = std::env::var("GOOGLE_API_KEY")?;
    let model = Arc::new(GeminiModel::new(&api_key, "gemini-2.0-flash")?);

    // Supervisor: Decides which worker should act next
    let supervisor = LlmAgentBuilder::new("supervisor")
        .description("Routes tasks to specialized workers")
        .instruction(
            "You are a task supervisor. Based on the task and work done so far,              decide who should work next.\n\n             Workers available:\n             - researcher: Gathers information and facts\n             - writer: Writes content based on research\n             - coder: Creates code examples\n\n             Respond with ONLY one word: 'researcher', 'writer', 'coder', or 'done'."
        )
        .model(model.clone())
        .build()?;

    // Workers with specialized roles
    let researcher = LlmAgentBuilder::new("researcher")
        .instruction("Research the topic. Provide key facts as bullet points.")
        .model(model.clone())
        .build()?;

    let writer = LlmAgentBuilder::new("writer")
        .instruction("Write engaging content based on the research provided.")
        .model(model.clone())
        .build()?;

    let coder = LlmAgentBuilder::new("coder")
        .instruction("Write clean, documented code examples for the topic.")
        .model(model.clone())
        .build()?;

    // Create AgentNodes with input/output mappers
    let supervisor_node = AgentNode::new(Arc::new(supervisor))
        .with_input_mapper(|state| {
            let task = state.get("task").and_then(|v| v.as_str()).unwrap_or("");
            let history = state.get("history")
                .and_then(|v| v.as_array())
                .map(|arr| arr.iter()
                    .filter_map(|h| h.get("agent").and_then(|a| a.as_str()))
                    .map(|s| format!("- {} completed", s))
                    .collect::<Vec<_>>()
                    .join("\n"))
                .unwrap_or_default();

            adk_core::Content::new("user").with_text(format!(
                "Task: {}\n\nWork completed:\n{}\n\nWho next?",
                task,
                if history.is_empty() { "None yet" } else { &history }
            ))
        })
        .with_output_mapper(|events| {
            let mut updates = std::collections::HashMap::new();
            for event in events {
                if let Some(content) = event.content() {
                    let text: String = content.parts.iter()
                        .filter_map(|p| p.text())
                        .collect();
                    let next = if text.to_lowercase().contains("researcher") {
                        "researcher"
                    } else if text.to_lowercase().contains("writer") {
                        "writer"
                    } else if text.to_lowercase().contains("coder") {
                        "coder"
                    } else {
                        "done"
                    };
                    updates.insert("next_agent".to_string(), json!(next));
                }
            }
            updates
        });

    // Build the graph
    let graph = StateGraph::with_channels(&[
        "task", "next_agent", "history",
        "research_output", "written_content", "code_output"
    ])
    .add_node(supervisor_node)
    .add_node(AgentNode::new(Arc::new(researcher)))
    .add_node(AgentNode::new(Arc::new(writer)))
    .add_node(AgentNode::new(Arc::new(coder)))
    // Finalize node compiles all outputs
    .add_node_fn("finalize", |ctx| async move {
        let research = ctx.get("research_output").and_then(|v| v.as_str());
        let content = ctx.get("written_content").and_then(|v| v.as_str());
        let code = ctx.get("code_output").and_then(|v| v.as_str());

        let result = format!(
            "=== FINAL OUTPUT ===\n\n{}\n\n{}\n\n{}",
            research.unwrap_or("No research"),
            content.unwrap_or("No content"),
            code.unwrap_or("No code")
        );
        Ok(NodeOutput::new().with_update("final_result", json!(result)))
    })
    // Graph structure
    .add_edge(START, "supervisor")
    .add_conditional_edges(
        "supervisor",
        |state| state.get("next_agent")
            .and_then(|v| v.as_str())
            .unwrap_or("done")
            .to_string(),
        [
            ("researcher", "researcher"),
            ("writer", "writer"),
            ("coder", "coder"),
            ("done", "finalize"),
        ],
    )
    // Workers cycle back to supervisor
    .add_edge("researcher", "supervisor")
    .add_edge("writer", "supervisor")
    .add_edge("coder", "supervisor")
    .add_edge("finalize", END)
    .compile()?
    .with_recursion_limit(15);  // Prevent infinite loops

    // Execute
    let mut input = State::new();
    input.insert("task".to_string(), json!("Create a guide about Rust error handling"));
    input.insert("history".to_string(), json!([]));

    let result = graph.invoke(input, ExecutionConfig::new("content-thread")).await?;
    println!("{}", result.get("final_result").and_then(|v| v.as_str()).unwrap_or(""));

    Ok(())
}

✅ 何时使用主管图

  • 工作流顺序是动态的,并由LLM决定
  • 工作人员可能需要迭代或循环返回
  • 复杂状态需要在代理之间共享
  • 您需要检查点或可恢复的工作流
  • 任务分解需要多个顺序步骤

6. 模式比较

特性协调器AgentTool主管图
用户与谁对话直接与专家对话仅与协调器对话最终输出
多代理调用❌ 一次一个✅ 可并行✅ 编排
响应处理
循环工作流
共享状态仅限会话仅限会话完整图状态
设置复杂性🟢 低🟡 中🔴 高

7. 结论

多智能体系统允许您通过结合专业智能体来构建复杂的 AI 应用程序。根据您的需求选择模式:

  • 协调器: 设置快速,非常适合客户服务路由
  • AgentTool: 当您需要处理或组合响应时
  • 主管图: 复杂、动态、多步骤的工作流

🦀 开始使用

准备好构建您自己的多智能体系统了吗?