Multi-AgentTutorialKIRustv0.1.8

Aufbau von Multi-Agenten-Systemen mit ADK-Rust

Erfahren Sie, wann und wie Sie verschiedene Multi-Agenten-Muster einsetzen – von einfacher Koordination bis hin zu komplexer graphenbasierter Orchestrierung – mit der Typsicherheit und Leistung von Rust.

·15 Min. Lesezeit·ADK-Rust v0.1.8

1. Einführung

Das Problem: KI, die an ihre Grenzen stößt

Sie haben Ihren ersten KI-Agenten gebaut. Er ist beeindruckend – er kann Fragen beantworten, Dokumente zusammenfassen, vielleicht sogar Code schreiben. Doch dann kommt die Realität:

"Können Sie meine letzte Rechnung überprüfen und mir auch bei der Einrichtung des API helfen?"

Ihr Agent hat Schwierigkeiten. Er wurde nicht auf Abrechnungssysteme UND Entwicklerdokumente UND Fehlerbehebungsworkflows trainiert. Sein Anweisungsprompt ist bereits 2000 Tokens lang und versucht, alles abzudecken. Die Antwortqualität verschlechtert sich.

Dies ist die Grenze des Einzelagenten. Wenn Ihre Anwendung wächst, stehen Sie vor schmerzhaften Kompromissen:

  • Aufgeblähte Prompts: Jede neue Funktion bedeutet längere Anweisungen, höhere Latenz und mehr Verwirrung für das Modell
  • Tausendsassa: Ein Agent, der Abrechnung, Support UND Vertrieb abwickelt, wird in allen dreien mittelmäßig
  • Unmögliche Wartung: Das Ändern der Abrechnungslogik sollte nicht das Risiko bergen, Ihre Support-Workflows zu unterbrechen
  • Keine Spezialisierung: Ihr Mathematik-Agent kann kein Taschenrechner-Tool haben, während Ihr Recherche-Agent eine Websuche hat – sie teilen alles

Die Lösung: Spezialisierte Agenten arbeiten zusammen

Multi-Agenten-Systeme lösen dies, indem sie komplexe Aufgaben in spezialisierte Rollen zerlegen. Anstatt eines überforderten Generalisten erstellen Sie fokussierte Spezialisten:

  • Kundenservice: Ein coordinator leitet Benutzer an Abrechnungs-, technischen Support- oder Vertriebsspezialisten weiter – jeder mit fokussiertem Training und Tools
  • Inhaltserstellung: Ein Recherche-Agent sammelt Fakten, ein writer erstellt die Erzählung, ein Redakteur poliert – jeder Agent beherrscht eine Fähigkeit
  • Codegenerierung: Ein Planer entwirft die Architektur, ein coder implementiert, ein Prüfer fängt Fehler ab – unterschiedliche Perspektiven verbessern die Qualität

Das Ergebnis? Jeder Agent bleibt fokussiert, Prompts bleiben überschaubar, und Sie können die Abrechnungslogik aktualisieren, ohne den Support zu berühren. Es sind Microservices für KI.

Was Sie lernen werden

ADK-Rust bietet drei schrittweise leistungsfähigere Muster für die Multi-Agenten-Orchestrierung. Dieses Tutorial wird Ihnen beibringen:

  • Wann Sie jedes Muster basierend auf Ihren Anforderungen verwenden sollten
  • Wie Sie diese mit produktionsreifem Rust-Code implementieren
  • Warum die architektonischen Kompromisse für Ihren spezifischen Anwendungsfall wichtig sind

2. Das richtige Muster wählen

Bevor wir uns in den Code stürzen, lassen Sie uns verstehen, was jedes Muster bietet:

MusterAm besten geeignet fürKontrollebeneKomplexität
KoordinatorGesprächsübergabenLLM entscheidetNiedrig
AgentToolAntwortverarbeitungKoordinator verarbeitetMittel
Supervisor-GraphKomplexe WorkflowsVollständiges ZustandsmanagementHoch

3. Muster 1: Der Koordinator (Unter-Agenten)

🎯 Anwendungsfall: Kundenservice-Routing

Sie entwickeln einen Kundenservice-Bot. Benutzer könnten Fragen zur Abrechnung stellen, technische Hilfe anfordern oder sich nach neuen Funktionen erkundigen. Jede Domäne erfordert spezialisiertes Wissen, aber Benutzer sollten nicht wissen müssen, welche Abteilung sie kontaktieren sollen.

Das Koordinator-Muster verwendet die automatische Agentenübertragung. Wenn Sie Unter-Agenten über .sub_agent() hinzufügen, injiziert ADK-Rust ein transfer_to_agent-Tool. Der LLM entscheidet basierend auf der Konversation, wann die Übergabe erfolgen soll.

BenutzerKoordinatorLeitet Anfragen weiteran SpezialistenAbrechnungUnteragentSupportUnteragentVertriebUnteragenttransfer_to_agenttransfer_to_agenttransfer_to_agent

Hauptmerkmale

  • Nahtlose Übergabe: Benutzer setzt das Gespräch natürlich mit dem Spezialisten fort
  • LLM-gesteuertes Routing: Der coordinator entscheidet basierend auf dem Konversationskontext
  • Konversationskontinuität: Der Sitzungsverlauf wird über Übertragungen hinweg beibehalten
  • Keine Antwortverarbeitung: Spezialist spricht nach der Übertragung direkt mit dem Benutzer

Implementierung

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(())
}

Beispielkonversation

Benutzer: Hallo, ich habe eine Frage zu meiner Rechnung

[coordinator]: Hallo! Gerne helfe ich Ihnen bei Ihrer Abrechnungsfrage. Lassen Sie mich Sie mit unserem Abrechnungsspezialisten verbinden, der Ihnen helfen kann.

System: 🔄 Übertragung an: billing_agent

[billing_agent]: Hallo! Ich bin der Abrechnungsspezialist. Ich kann Ihnen bei Rechnungen, Zahlungen und Abonnementfragen helfen. Was möchten Sie über Ihre Rechnung wissen?

Benutzer: Warum wurde mir diesen Monat zweimal etwas berechnet?

[billing_agent]: Ich werde diese doppelte Abbuchung für Sie prüfen...

✅ Wann den Koordinator verwenden

  • Benutzer sollten direkt mit Spezialisten interagieren
  • Routing-Entscheidungen sind unkompliziert
  • Sie müssen Spezialistenantworten nicht verarbeiten
  • Der Gesprächsfluss ist linear (ein Spezialist nach dem anderen)

4. Muster 2: Agenten als Werkzeuge (AgentTool)

🎯 Anwendungsfall: Wissensaggregation

Sie entwickeln einen intelligenten Assistenten, der Fragen aus mehreren Domänen beantwortet. Ein Benutzer fragt: „Was sind 15 % von 250, und warum ist diese Zahl in der Geschichte bedeutsam?“ Sie müssen einen Mathematikexperten und dann einen Trivia-Experten hinzuziehen und deren Antworten kombinieren.

Das AgentTool-Muster verpackt Agenten als aufrufbare Werkzeuge. Im Gegensatz zu Sub-Agenten ruft der coordinator Spezialisten programmatisch auf und empfängt deren Antworten, um sie zu verarbeiten oder zu kombinieren, bevor er dem Benutzer antwortet.

BenutzerKoordinatorRuft Agenten als Werkzeuge aufVerarbeitet AntwortenAggregiert ErgebnisseKehrt zum Benutzer zurückMathematik-ExperteAgentTool+ RechnerTrivia-ExperteAgentToolLLM WissenForscherAgentTool+ web_searchAufruf →← Antwort

Wesentliche Unterschiede zum Koordinator

Koordinator (Sub-Agenten)

  • • Spezialist spricht direkt mit dem Benutzer
  • • Ein Spezialist nach dem anderen
  • • Keine Antwortverarbeitung

AgentTool

  • • Koordinator empfängt Antworten
  • • Kann mehrere Spezialisten aufrufen
  • • Aggregiert und fasst zusammen

Implementierung

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(())
}

Beispiel: Frage aus mehreren Domänen

Benutzer: Was sind 15 % von 250, und ist diese Zahl in der Geschichte bedeutsam?

System: // Koordinator ruft math_expert Werkzeug auf

[math_expert antwortet]: 15 % von 250 sind 37,5

System: // Koordinator ruft trivia_expert Tool auf

[trivia_expert antwortet]: 37 und 38 sind historisch weniger bemerkenswert, aber 37,5 °C ist die menschliche Körpertemperatur...

System: // Koordinator synthetisiert

[coordinator]: 15 % von 250 ergeben 37,5. Interessanterweise liegt 37,5 °C (99,5 °F) nahe an der durchschnittlichen menschlichen Körpertemperatur von 37 °C, was sie zu einer medizinisch bedeutsamen Zahl macht!

✅ Wann AgentTool verwenden

  • Sie müssen Antworten von mehreren Experten kombinieren
  • Der Koordinator sollte die Ausgabe des Spezialisten zusammenfassen oder filtern
  • Spezialisten haben ihre eigenen Tools (verschachtelte Fähigkeiten)
  • Sie möchten die Agentenaufrufe programmatisch steuern

5. Muster 3: Der Supervisor-Graph

🎯 Anwendungsfall: Content-Erstellungspipeline

Sie bauen ein Content-Erstellungssystem auf. Zu einem bestimmten Thema müssen Sie: (1) recherchieren, (2) einen Artikel schreiben, (3) Codebeispiele hinzufügen. Der supervisor entscheidet dynamisch die Reihenfolge basierend auf der Aufgabe, und die Worker können für Überarbeitungen zurückkehren.

Das Supervisor-Graph-Muster verwendet das graphenbasierte Workflow-System von ADK-Rust. Ein supervisor-Agent leitet dynamisch an Worker weiter, mit vollständiger Zustandsverwaltung und Unterstützung für zyklische Ausführung.

STARTSupervisorEntscheidet über nächstenWorker-AgentForscherWorkerAutorWorkerProgrammiererWorkerENDzurückschleifen"erledigt" → finalisieren → ENDGemeinsamer Zustand• research_output• written_content• code_output

Warum einen Graphen verwenden?

  • Dynamisches Routing: Der Supervisor entscheidet den nächsten Worker basierend auf dem aktuellen Zustand
  • Zyklische Ausführung: Worker können für Iterationen zurückkehren
  • Geteilter Zustand: Alle Knoten lesen/schreiben in ein gemeinsames Zustandsobjekt
  • Bedingte Kanten: Unterschiedliche Pfade basierend auf LLM-Entscheidungen
  • Rekursionsgrenzen: Verhindern unendliche Schleifen

Implementierung

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(())
}

✅ Wann den Supervisor-Graphen verwenden

  • Die Workflow-Reihenfolge ist dynamisch und LLM-bestimmt
  • Mitarbeiter müssen möglicherweise iterieren oder zurückkehren
  • Komplexer Zustand muss zwischen Agenten geteilt werden
  • Sie benötigen Checkpointing oder fortsetzbare Workflows
  • Die Aufgabenzerlegung erfordert mehrere sequentielle Schritte

6. Mustervergleich

MerkmalKoordinatorAgentToolSupervisor-Graph
Benutzer spricht mitSpezialist direktNur KoordinatorEndgültige Ausgabe
Multi-Agenten-Aufrufe❌ Einzeln✅ Parallel möglich✅ Orchestriert
Antwortverarbeitung
Zyklische Workflows
Gemeinsamer ZustandNur SitzungNur SitzungVollständiger Graphzustand
Einrichtungskomplexität🟢 Niedrig🟡 Mittel🔴 Hoch

7. Fazit

Multi-Agenten-Systeme ermöglichen es Ihnen, anspruchsvolle KI-Anwendungen durch die Kombination spezialisierter Agenten zu erstellen. Wählen Sie Ihr Muster basierend auf Ihren Bedürfnissen:

  • Koordinator: Schnell einzurichten, ideal für die Weiterleitung im Kundenservice
  • AgentTool: Wenn Sie Antworten verarbeiten oder kombinieren müssen
  • Supervisor-Graph: Komplexe, dynamische, mehrstufige Workflows

🦀 Los geht's

Bereit, Ihr eigenes Multi-Agenten-System zu erstellen?