Workflow-Agenten
Workflow-Agenten koordinieren mehrere Agenten in vorhersehbaren Mustern – sequenziellen Pipelines, paralleler Ausführung oder iterativen Schleifen. Im Gegensatz zu LlmAgent, der KI-Argumentation nutzt, folgen Workflow-Agenten deterministischen Ausführungspfaden.
Schnellstart
Ein neues Projekt erstellen:
cargo new workflow_demo
cd workflow_demo
Abhängigkeiten zu Cargo.toml hinzufügen:
[dependencies]
adk-rust = "0.2.0"
tokio = { version = "1.40", features = ["full"] }
dotenvy = "0.15"
.env erstellen:
echo 'GOOGLE_API_KEY=your-api-key' > .env
SequentialAgent
SequentialAgent führt Unter-Agents nacheinander aus. Jeder Agent sieht den kumulierten Konversationsverlauf von vorherigen Agents.
Wann verwenden
- Mehrstufige Pipelines, bei denen die Ausgabe in den nächsten Schritt fließt
- Workflows wie Forschung → Analyse → Zusammenfassung
- Datenumwandlungsketten
Vollständiges Beispiel
Ersetzen Sie 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(())
}
Ausführen:
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.
Funktionsweise
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Researcher │ → │ Analyzer │ → │ Summarizer │
│ (step 1) │ │ (step 2) │ │ (step 3) │
└─────────────┘ └─────────────┘ └─────────────┘
↓ ↓ ↓
"Key facts..." "Insights..." "Executive summary"
- Benutzernachricht geht an den ersten Agent (Researcher)
- Die Antwort des Researcher wird zum Verlauf hinzugefügt
- Analyzer sieht: Benutzernachricht + Antwort des Researcher
- Summarizer sieht: Benutzernachricht + Researcher + Analyzer-Antworten
- Die Pipeline ist abgeschlossen, wenn der letzte Agent fertig ist
ParallelAgent
ParallelAgent führt alle Unter-Agents gleichzeitig aus. Jeder Agent erhält dieselbe Eingabe und arbeitet unabhängig.
Wann einzusetzen
- Mehrere Perspektiven zum gleichen Thema
- Fan-out-Verarbeitung (gleiche Eingabe, unterschiedliche Analysen)
- Geschwindigkeitskritische 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: Gestalten Sie die Anweisungen für den ParallelAgent sehr spezifisch mit einzigartigen Personas, Fokusbereichen und Antwortpräfixen. Dies stellt sicher, dass jeder Agent eine einzigartige Ausgabe liefert.
Beispielinteraktion
Sie: Bewerten Sie eine Mobile-Banking-App
🔧 TECHNICAL:
• Erfordert robuste API-Sicherheit: OAuth 2.0, Zertifikats-Pinning, verschlüsselter Speicher
• Offline-Modus mit Synchronisierung erfordert komplexes Zustandsmanagement und Konfliktlösung
• Die Integration biometrischer Authentifizierung variiert erheblich zwischen iOS-/Android-Plattformen
💼 BUSINESS:
• Stark umkämpfter Markt – benötigt einzigartiges Alleinstellungsmerkmal (Neobanken, traditionelle Banken)
• Erlösmodell: Interbankenentgelte, Premium-Stufen oder Cross-Selling von Kreditprodukten
• Erhebliche Kosten für die Einhaltung gesetzlicher Vorschriften: PCI-DSS, regionale Bankgesetze, KYC/AML
🎨 UX:
• Kritisch: schnelle Aufgabenerledigung – Kontostandprüfung muss < 3 Sekunden dauern
• Barrierefreiheit unerlässlich: Unterstützung für Bildschirmlesegeräte, Hochkontrastmodus, große Berührungsziele
• Vertrauensindikatoren wichtig: Sicherheitsplaketten, vertraute Bankmuster
Funktionsweise
┌─────────────────┐
│ Benutzernachricht │
└────────┬────────┘
┌───────────────────┼───────────────────┐
↓ ↓ ↓
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Technischer │ │ Business │ │ UX │
│ Analyst │ │ Analyst │ │ Analyst │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
↓ ↓ ↓
(Antwort 1) (Antwort 2) (Antwort 3)
Alle Agents starten gleichzeitig und die Ergebnisse werden gestreamt, sobald sie abgeschlossen sind.
LoopAgent
LoopAgent führt Unter-Agenten wiederholt aus, bis eine Exit-Bedingung erfüllt oder die maximale Anzahl an Iterationen erreicht ist.
Anwendungsfälle
- Iterative Verfeinerung (Entwurf → Kritik → Verbesserung → Wiederholung)
- Wiederholungslogik mit Verbesserung
- Qualitäts-Gates, die mehrere Durchläufe erfordern
ExitLoopTool
Um eine Schleife vorzeitig zu beenden, geben Sie einem Agenten das ExitLoopTool. Wenn es aufgerufen wird, signalisiert es der Schleife, anzuhalten.
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] Bewertung: 5/10. "Good coffee here" ist zu generisch. Benötigt:
- Einzigartiges Wertversprechen
- Emotionale Verbindung
- Einprägsame Formulierung
[refiner] Verbessert: "Where every cup tells a story"
🔄 Iteration 2
[critic] Bewertung: 7/10. Besser! Könnte aber stärker sein:
- Aktionorientierter
- Hinweis auf das Erlebnis
[refiner] Verbessert: "Brew your perfect moment"
🔄 Iteration 3
[critic] Bewertung: 8/10. Stark, aktionorientiert, erlebnisorientiert.
Kleinigkeit: könnte unverwechselbarer sein.
[refiner] Bewertung ist 8+, Qualitätsschwelle erreicht!
[exit_loop called]
Final: "Brew your perfect moment"
Funktionsweise
┌──────────────────────────────────────────┐
│ LoopAgent │
│ ┌────────────────────────────────────┐ │
│ │ SequentialAgent │ │
│ │ ┌──────────┐ ┌──────────────┐ │ │
→ │ │ │ Critic │ → │ Refiner │ │ │ →
│ │ │ (Bewertung) │ │ (verbessern oder │ │
│ │ └──────────┘ │ exit_loop) │ │ │
│ │ └──────────────┘ │ │
│ └────────────────────────────────────┘ │
│ ↑_____________↓ │
│ Wiederholen bis zum Beenden │
└──────────────────────────────────────────┘
ConditionalAgent (Regelbasiert)
ConditionalAgent verzweigt die Ausführung basierend auf einer synchronen, regelbasierten Bedingung. Verwenden Sie 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 verwenden Sie stattdessen
LlmConditionalAgent.
LlmConditionalAgent (LLM-Basiert)
LlmConditionalAgent verwendet ein LLM zur Klassifizierung von Benutzereingaben und zur Weiterleitung an den entsprechenden Unter-Agenten. Dies ist ideal für intelligentes Routing, bei dem die Routing-Entscheidung ein Verständnis des Inhalts erfordert.
Wann zu verwenden
- Absichtsklassifizierung - Routen basierend auf dem, was der Benutzer fragt
- Mehrwege-Routing - Mehr als 2 Ziele
- Kontextsensitives 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")?);
// Spezialisten-Agenten erstellen
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 klassifiziert die Anfrage und routet entsprechend
let router = LlmConditionalAgent::new("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-gesteuerter intelligenter 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
┌─────────────────┐
│ Benutzernachricht │
└────────┬────────┘
↓
┌─────────────────┐
│ LLM Klassifiziert│ "technical" / "general" / "creative"
│ (smart_router)│
└────────┬────────┘
↓
┌────┴────┬──────────┐
↓ ↓ ↓
┌───────┐ ┌───────┐ ┌─────────┐
│Technik│ │Allgemein│ │Kreativ │
│Experte│ │ Helfer │ │ Autor │
└───────┘ └───────┘ └─────────┘
Kombinieren von Workflow-Agents
Workflow-Agents können für komplexe Muster verschachtelt werden.
Sequential + Parallel + Loop
use adk_rust::prelude::*;
use std::sync::Arc;
// 1. Parallele Analyse aus mehreren Perspektiven
let parallel_analysis = ParallelAgent::new(
"multi_analysis",
vec![Arc::new(tech_analyst), Arc::new(biz_analyst)],
);
// 2. Synthetisiere die parallelen Ergebnisse
let synthesizer = LlmAgentBuilder::new("synthesizer")
.instruction("Combine all analyses into a unified recommendation.")
.model(model.clone())
.build()?;
// 3. Qualitätsschleife: Kritik und Verfeinerung
let quality_loop = LoopAgent::new(
"quality_check",
vec![Arc::new(critic), Arc::new(refiner)],
).with_max_iterations(2);
// Finale Pipeline: parallel → synthetisieren → Qualitätsschleife
let full_pipeline = SequentialAgent::new(
"full_analysis_pipeline",
vec![
Arc::new(parallel_analysis),
Arc::new(synthesizer),
Arc::new(quality_loop),
],
);
Verfolgen der Workflow-Ausführung
Um zu sehen, was in einem Workflow geschieht, aktivieren Sie das Tracing:
use adk_rust::prelude::*;
use adk_rust::runner::{Runner, RunnerConfig};
use adk_rust::futures::StreamExt;
use std::sync::Arc;
// Pipeline wie zuvor erstellen...
// Runner statt Launcher für detaillierte Kontrolle verwenden
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(
"user".to_string(),
session.id().to_string(),
Content::new("user").with_text("Analyze Rust"),
).await?;
// Jedes Ereignis verarbeiten, um die Workflow-Ausführung zu sehen
while let Some(event) = stream.next().await {
let event = event?;
// Zeigt, welcher Agent antwortet
println!("📍 Agent: {}", event.author);
// Zeigt den Antwortinhalt
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) // Vor der Ausführung aufgerufen
.after_callback(callback) // Nach der Ausführung aufgerufen
ParallelAgent
ParallelAgent::new("name", vec![agent1, agent2, agent3])
.with_description("Optional description")
.before_callback(callback)
.after_callback(callback)
LoopAgent
LoopAgent::new("name", vec![agent1, agent2])
.with_max_iterations(5) // Sicherheitslimit (empfohlen)
.with_description("Optional description")
.before_callback(callback)
.after_callback(callback)
ConditionalAgent
ConditionalAgent::new("name", |ctx| condition_fn, if_agent)
.with_else(else_agent) // Optionaler Else-Zweig
.with_description("Optional description")
ExitLoopTool
// Zu einem Agent hinzufügen, damit er einen LoopAgent verlassen kann
.tool(Arc::new(ExitLoopTool::new()))
Zurück: LlmAgent | Weiter: Multi-Agenten-Systeme →