Workflow-Agenten
Workflow-Agenten orchestrieren mehrere Agenten in vorhersehbaren Mustern – sequenzielle Pipelines, parallele Ausführung oder iterative Schleifen. Anders als LlmAgent, das KI-Reasoning verwendet, folgen Workflow-Agenten deterministischen Ausführungspfaden.
Schnellstart
Erstelle ein neues Projekt:
cargo new workflow_demo
cd workflow_demo
Füge Abhängigkeiten zu Cargo.toml hinzu:
[dependencies]
adk-rust = "2.0.0"
tokio = { version = "1.40", features = ["full"] }
dotenvy = "0.15"
Erstelle .env:
echo 'GOOGLE_API_KEY=your-api-key' > .env
SequentialAgent
SequentialAgent führt Unteragenten nacheinander aus. Jeder Agent sieht den angesammelten Gesprächsverlauf der vorherigen Agenten.
Wann verwenden
- Mehrstufige Pipelines, bei denen die Ausgabe den nächsten Schritt speist
- Research → Analyse → Zusammenfassung-Workflows
- Datenumwandlungsketten
Vollständiges Beispiel
Ersetze src/main.rs:
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-2.5-flash")?);
// Step 1: Research agent gathers information
let researcher = LlmAgentBuilder::new("researcher")
.instruction("Research the given topic. List 3-5 key facts or points. \
Be factual and concise.")
.model(model.clone())
.output_key("research") // Saves output to state
.build()?;
// Step 2: Analyzer agent identifies patterns
let analyzer = LlmAgentBuilder::new("analyzer")
.instruction("Based on the research above, identify 2-3 key insights \
or patterns. What's the bigger picture?")
.model(model.clone())
.output_key("analysis")
.build()?;
// Step 3: Summarizer creates final output
let summarizer = LlmAgentBuilder::new("summarizer")
.instruction("Create a brief executive summary combining the research \
and analysis. Keep it under 100 words.")
.model(model.clone())
.build()?;
// Create the sequential pipeline
let pipeline = SequentialAgent::new(
"research_pipeline",
vec![Arc::new(researcher), Arc::new(analyzer), Arc::new(summarizer)],
).with_description("Research → Analyze → Summarize");
println!("📋 Sequential Pipeline: Research → Analyze → Summarize");
println!();
Launcher::new(Arc::new(pipeline)).run().await?;
Ok(())
}
Führe es aus:
cargo run
Beispielinteraktion
You: Tell me about Rust programming language
🔄 [researcher] Researching...
Here are key facts about Rust:
1. Systems programming language created at Mozilla in 2010
2. Memory safety without garbage collection via ownership system
3. Zero-cost abstractions and minimal runtime
4. Voted "most loved language" on Stack Overflow for 7 years
5. Used by Firefox, Discord, Dropbox, and Linux kernel
🔄 [analyzer] Analyzing...
Key insights:
1. Rust solves the memory safety vs performance tradeoff
2. Strong developer satisfaction drives rapid adoption
3. Trust from major tech companies validates production-readiness
🔄 [summarizer] Summarizing...
Rust is a systems language that achieves memory safety without garbage
collection through its ownership model. Created at Mozilla in 2010, it's
been rated the most loved language for 7 consecutive years. Major companies
like Discord and Linux kernel adopt it for its zero-cost abstractions
and performance guarantees.
Wie es funktioniert
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Researcher │ → │ Analyzer │ → │ Summarizer │
│ (step 1) │ │ (step 2) │ │ (step 3) │
└─────────────┘ └─────────────┘ └─────────────┘
↓ ↓ ↓
"Key facts..." "Insights..." "Executive summary"
- Die Nutzernachricht geht an den ersten Agenten (Researcher)
- Die Antwort des Researchers wird dem Verlauf hinzugefügt
- Der Analyzer sieht: Nutzernachricht + Antwort des Researchers
- Der Summarizer sieht: Nutzernachricht + Antworten von Researcher + Analyzer
- Die Pipeline ist abgeschlossen, wenn der letzte Agent fertig ist
ParallelAgent
ParallelAgent führt alle Unteragenten gleichzeitig aus. Jeder Agent erhält dieselbe Eingabe und arbeitet unabhängig.
Die Unabhängigkeit ist erzwungen, nicht nur beabsichtigt: Jeder Unteragent läuft auf seinem eigenen
Gesprächszweig ({parent}.{parallel_agent}.{sub_agent}), und Verlaufslayouts
sind auf diesen Zweig beschränkt. Ein Unteragent sieht die Unterhaltung, die zur
Aufteilung geführt hat, aber nicht, was seine Geschwister erzeugt haben, sodass parallele Zweige
den Kontext der anderen nicht verunreinigen können. Verwende ParallelAgent::with_shared_state(), wenn
du möchtest, dass Unteragenten zusammenarbeiten.
Zweige werden gemeinsam abgefragt, sodass die Laufzeitkosten ungefähr denen des langsamsten Zweigs entsprechen statt der Summe aller, und ein langsamer Zweig hält die anderen nicht auf. Zwei Konsequenzen, die man bei der Gestaltung berücksichtigen sollte:
- Ereignisse vermischen sich. Ereignisse treffen in der Reihenfolge ein, in der die Zweige sie erzeugen, nicht
gruppiert pro Unteragent. Verwende
event.author(oderevent.branch), um ein Stück seinem Zweig zuzuordnen. Eine Reihenfolge über Zweiggrenzen hinweg ist nicht garantiert. - Ein Fehler beendet den Lauf, deterministisch gewählt. Jeder Zweig läuft trotzdem bis zum Abschluss, und danach wird ein einzelner terminaler Fehler gemeldet. Wenn mehr als ein Zweig fehlschlägt, ist der gemeldete Fehler der des frühesten Unteragenten in Deklarationsreihenfolge – nicht derjenige, der in Echtzeit zuerst fehlgeschlagen ist, was bei gleichzeitiger Ausführung ein Rennen wäre.
Wenn der Ereignisstrom früh beendet wird, werden noch laufende Zweige beendet.
Wann verwenden
- Mehrere Perspektiven auf dasselbe Thema
- Fan-out-Verarbeitung (gleiche Eingabe, unterschiedliche Analysen)
- Zeitsensible Multi-Task-Szenarien
Vollständiges Beispiel
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-2.5-flash")?);
// Three analysts with DISTINCT personas (important for parallel execution)
let technical = LlmAgentBuilder::new("technical_analyst")
.instruction("You are a senior software architect. \
FOCUS ONLY ON: code quality, system architecture, scalability, \
security vulnerabilities, and tech stack choices. \
Start your response with '🔧 TECHNICAL:' and give 2-3 bullet points.")
.model(model.clone())
.build()?;
let business = LlmAgentBuilder::new("business_analyst")
.instruction("You are a business strategist and MBA graduate. \
FOCUS ONLY ON: market opportunity, revenue model, competition, \
cost structure, and go-to-market strategy. \
Start your response with '💼 BUSINESS:' and give 2-3 bullet points.")
.model(model.clone())
.build()?;
let user_exp = LlmAgentBuilder::new("ux_analyst")
.instruction("You are a UX researcher and designer. \
FOCUS ONLY ON: user journey, accessibility, pain points, \
visual design, and user satisfaction metrics. \
Start your response with '🎨 UX:' and give 2-3 bullet points.")
.model(model.clone())
.build()?;
// Create parallel agent
let multi_analyst = ParallelAgent::new(
"multi_perspective",
vec![Arc::new(technical), Arc::new(business), Arc::new(user_exp)],
).with_description("Technical + Business + UX analysis in parallel");
println!("⚡ Parallel Analysis: Technical | Business | UX");
println!(" (All three run simultaneously!)");
println!();
Launcher::new(Arc::new(multi_analyst)).run().await?;
Ok(())
}
💡 Tipp: Mache die Anweisungen paralleler Agenten mit einzigartigen Personas, Fokusbereichen und Antwortpräfixen deutlich unterschiedlich. So stellst du sicher, dass jeder Agent eine einzigartige Ausgabe erzeugt.
Beispielinteraktion
You: Evaluate a mobile banking app
🔧 TECHNICAL:
• Requires robust API security: OAuth 2.0, certificate pinning, encrypted storage
• Offline mode with sync requires complex state management and conflict resolution
• Biometric auth integration varies significantly across iOS/Android platforms
💼 BUSINESS:
• Highly competitive market - need unique differentiator (neobanks, traditional banks)
• Revenue model: interchange fees, premium tiers, or lending products cross-sell
• Regulatory compliance costs significant: PCI-DSS, regional banking laws, KYC/AML
🎨 UX:
• Critical: fast task completion - check balance must be < 3 seconds
• Accessibility essential: screen reader support, high contrast mode, large touch targets
• Trust indicators important: security badges, familiar banking patterns
Wie es funktioniert
┌─────────────────┐
│ User Message │
└────────┬────────┘
┌───────────────────┼───────────────────┐
↓ ↓ ↓
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Technical │ │ Business │ │ UX │
│ Analyst │ │ Analyst │ │ Analyst │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
↓ ↓ ↓
(response 1) (response 2) (response 3)
Alle Agenten starten gleichzeitig und die Ergebnisse werden gestreamt, sobald sie fertig sind.
LoopAgent
LoopAgent führt Unteragenten wiederholt aus, bis eine Abbruchbedingung erfüllt ist oder die maximale Anzahl an Iterationen erreicht wurde.
Wann verwenden
- Iterative Verfeinerung (Entwurf → Kritik → Verbesserung → Wiederholen)
- Wiederholungslogik mit Verbesserung
- Qualitätsprüfungen, die mehrere Durchgänge erfordern
ExitLoopTool
Um eine Schleife vorzeitig zu beenden, gib einem Agenten ExitLoopTool. Wenn es aufgerufen wird, signalisiert es der Schleife, zu stoppen.
Vollständiges Beispiel
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-2.5-flash")?);
// Critic agent evaluates content
let critic = LlmAgentBuilder::new("critic")
.instruction("Review the content for quality. Score it 1-10 and list \
specific improvements needed. Be constructive but critical.")
.model(model.clone())
.build()?;
// Refiner agent improves based on critique
let refiner = LlmAgentBuilder::new("refiner")
.instruction("Apply the critique to improve the content. \
If the score is 8 or higher, call exit_loop to finish. \
Otherwise, provide an improved version.")
.model(model.clone())
.tool(Arc::new(ExitLoopTool::new())) // Can exit the loop
.build()?;
// Create inner sequential: critic → refiner
let critique_refine = SequentialAgent::new(
"critique_refine_step",
vec![Arc::new(critic), Arc::new(refiner)],
);
// Wrap in loop with max 3 iterations
let iterative_improver = LoopAgent::new(
"iterative_improver",
vec![Arc::new(critique_refine)],
).with_max_iterations(3)
.with_description("Critique-refine loop (max 3 passes)");
println!("🔄 Iterative Improvement Loop");
println!(" critic → refiner → repeat (max 3x or until quality >= 8)");
println!();
Launcher::new(Arc::new(iterative_improver)).run().await?;
Ok(())
}
Beispielinteraktion
You: Write a tagline for a coffee shop
🔄 Iteration 1
[critic] Score: 5/10. "Good coffee here" is too generic. Needs:
- Unique value proposition
- Emotional connection
- Memorable phrasing
[refiner] Improved: "Where every cup tells a story"
🔄 Iteration 2
[critic] Score: 7/10. Better! But could be stronger:
- More action-oriented
- Hint at the experience
[refiner] Improved: "Brew your perfect moment"
🔄 Iteration 3
[critic] Score: 8/10. Strong, action-oriented, experiential.
Minor: could be more distinctive.
[refiner] Score is 8+, quality threshold met!
[exit_loop called]
Final: "Brew your perfect moment"
Wie es funktioniert
┌──────────────────────────────────────────┐
│ LoopAgent │
│ ┌────────────────────────────────────┐ │
│ │ SequentialAgent │ │
│ │ ┌──────────┐ ┌──────────────┐ │ │
→ │ │ │ Critic │ → │ Refiner │ │ │ →
│ │ │ (review) │ │ (improve or │ │ │
│ │ └──────────┘ │ exit_loop) │ │ │
│ │ └──────────────┘ │ │
│ └────────────────────────────────────┘ │
│ ↑_____________↓ │
│ repeat until exit │
└──────────────────────────────────────────┘
ConditionalAgent (Regelbasiert)
ConditionalAgent verzweigt die Ausführung basierend auf einer synchronen, regelbasierten Bedingung. Verwende dies für deterministisches Routing wie A/B-Tests oder umgebungsbasiertes Routing.
ConditionalAgent::new("router", |ctx| ctx.session().state().get("premium")..., premium_agent)
.with_else(basic_agent)
Hinweis: Für LLM-basiertes intelligentes Routing verwende stattdessen
LlmConditionalAgent.
LlmConditionalAgent (LLM-basiert)
LlmConditionalAgent verwendet ein LLM zum Klassifizieren von Nutzereingaben und leitet sie an den passenden Unteragenten weiter. Das ist ideal für intelligentes Routing, bei dem die Routing-Entscheidung das Verständnis des Inhalts erfordert.
Wann verwenden
- Intent-Klassifizierung - Routing basierend darauf, wonach der Nutzer fragt
- Mehrwege-Routing - Mehr als 2 Ziele
- Kontextbewusstes Routing - Erfordert Verständnis, nicht nur Schlüsselwörter
Vollständiges Beispiel
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-2.5-flash")?);
// Create specialist agents
let tech_agent: Arc<dyn Agent> = Arc::new(
LlmAgentBuilder::new("tech_expert")
.instruction("You are a senior software engineer. Be precise and technical.")
.model(model.clone())
.build()?
);
let general_agent: Arc<dyn Agent> = Arc::new(
LlmAgentBuilder::new("general_helper")
.instruction("You are a friendly assistant. Explain simply, use analogies.")
.model(model.clone())
.build()?
);
let creative_agent: Arc<dyn Agent> = Arc::new(
LlmAgentBuilder::new("creative_writer")
.instruction("You are a creative writer. Be imaginative and expressive.")
.model(model.clone())
.build()?
);
// LLM classifies the query and routes accordingly
let router = LlmConditionalAgent::builder("smart_router", model.clone())
.instruction("Classify the user's question as exactly ONE of: \
'technical' (coding, debugging, architecture), \
'general' (facts, knowledge, how-to), \
'creative' (writing, stories, brainstorming). \
Respond with ONLY the category name.")
.route("technical", tech_agent)
.route("general", general_agent.clone())
.route("creative", creative_agent)
.default_route(general_agent)
.build()?;
println!("🧠 LLM-Powered Intelligent Router");
Launcher::new(Arc::new(router)).run().await?;
Ok(())
}
Beispielinteraktion
You: How do I fix a borrow error in Rust?
[Routing to: technical]
[Agent: tech_expert]
A borrow error occurs when Rust's ownership rules are violated...
You: What's the capital of France?
[Routing to: general]
[Agent: general_helper]
The capital of France is Paris! It's a beautiful city...
You: Write me a haiku about the moon
[Routing to: creative]
[Agent: creative_writer]
Silver orb above,
Shadows dance on silent waves—
Night whispers secrets.
Wie es funktioniert
┌─────────────────┐
│ User Message │
└────────┬────────┘
↓
┌─────────────────┐
│ LLM Classifies│ "technical" / "general" / "creative"
│ (smart_router)│
└────────┬────────┘
↓
┌────┴────┬──────────┐
↓ ↓ ↓
┌───────┐ ┌───────┐ ┌─────────┐
│ tech │ │general│ │creative │
│expert │ │helper │ │ writer │
└───────┘ └───────┘ └─────────┘
Workflow-Agenten kombinieren
Workflow-Agenten können für komplexe Muster verschachtelt werden.
Sequenziell + Parallel + Schleife
use adk_rust::prelude::*;
use std::sync::Arc;
// 1. Parallel analysis from multiple perspectives
let parallel_analysis = ParallelAgent::new(
"multi_analysis",
vec![Arc::new(tech_analyst), Arc::new(biz_analyst)],
);
// 2. Synthesize the parallel results
let synthesizer = LlmAgentBuilder::new("synthesizer")
.instruction("Combine all analyses into a unified recommendation.")
.model(model.clone())
.build()?;
// 3. Quality loop: critique and refine
let quality_loop = LoopAgent::new(
"quality_check",
vec![Arc::new(critic), Arc::new(refiner)],
).with_max_iterations(2);
// Final pipeline: parallel → synthesize → quality loop
let full_pipeline = SequentialAgent::new(
"full_analysis_pipeline",
vec![
Arc::new(parallel_analysis),
Arc::new(synthesizer),
Arc::new(quality_loop),
],
);
Workflow-Ausführung nachverfolgen
Um zu sehen, was in einem Workflow passiert, aktiviere Tracing:
use adk_rust::prelude::*;
use adk_rust::{SessionId, UserId};
use adk_rust::runner::{Runner, RunnerConfig};
use adk_rust::futures::StreamExt;
use std::sync::Arc;
// Create pipeline as before...
// Use Runner instead of Launcher for detailed control
let session_service = Arc::new(InMemorySessionService::new());
let runner = Runner::new(RunnerConfig {
app_name: "workflow_trace".to_string(),
agent: Arc::new(pipeline),
session_service: session_service.clone(),
artifact_service: None,
memory_service: None,
run_config: None,
})?;
let session = session_service.create(CreateRequest {
app_name: "workflow_trace".to_string(),
user_id: "user".to_string(),
session_id: None,
state: Default::default(),
}).await?;
let mut stream = runner.run(
UserId::new("user")?,
SessionId::new(session.id())?,
Content::new("user").with_text("Analyze Rust"),
).await?;
// Process each event to see workflow execution
while let Some(event) = stream.next().await {
let event = event?;
// Show which agent is responding
println!("📍 Agent: {}", event.author);
// Show the response content
if let Some(content) = event.content() {
for part in &content.parts {
if let Part::Text { text } = part {
println!(" {}", text);
}
}
}
println!();
}
API-Referenz
SequentialAgent
SequentialAgent::new("name", vec![agent1, agent2, agent3])
.with_description("Optional description")
.before_callback(callback) // Called before execution
.after_callback(callback) // Called after execution
ParallelAgent
ParallelAgent::new("name", vec![agent1, agent2, agent3])
.with_description("Optional description")
.before_callback(callback)
.after_callback(callback)
Wenn irgendein Unteragent fehlschlägt, räumt ParallelAgent alle verbleibenden Futures ab, bevor der erste Fehler weitergegeben wird, um Ressourcenlecks zu verhindern.
LoopAgent
LoopAgent::new("name", vec![agent1, agent2])
.with_max_iterations(5) // Safety limit (recommended, default: 1000)
.with_description("Optional description")
.before_callback(callback)
.after_callback(callback)
ConditionalAgent
ConditionalAgent::new("name", |ctx| condition_fn, if_agent)
.with_else(else_agent) // Optional else branch
.with_description("Optional description")
ExitLoopTool
// Add to an agent to let it exit a LoopAgent
.tool(Arc::new(ExitLoopTool::new()))
Vorherige: LlmAgent | Nächste: Multi-Agenten-Systeme →