ワークフローエージェント
ワークフローエージェントは、複数のエージェントを予測可能なパターン(逐次パイプライン、並列実行、反復ループ)でオーケストレーションします。AI 推論を使用する LlmAgent とは異なり、ワークフローエージェントは決定論的な実行経路に従います。
クイックスタート
新しいプロジェクトを作成します。
cargo new workflow_demo
cd workflow_demo
Cargo.toml に依存関係を追加します。
[dependencies]
adk-rust = "2.1.0"
tokio = { version = "1.40", features = ["full"] }
dotenvy = "0.15"
.env を作成します。
echo 'GOOGLE_API_KEY=your-api-key' > .env
SequentialAgent
SequentialAgent はサブエージェントを 1 つずつ実行します。各エージェントは、前のエージェントまでに蓄積された会話履歴を確認できます。
使用する場面
- 出力を次のステップへの入力として渡す複数ステップのパイプライン
- リサーチ → 分析 → 要約のワークフロー
- データ変換チェーン
完全な例
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-3.7-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(())
}
実行します。
cargo run
やり取りの例
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.
仕組み
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Researcher │ → │ Analyzer │ → │ Summarizer │
│ (step 1) │ │ (step 2) │ │ (step 3) │
└─────────────┘ └─────────────┘ └─────────────┘
↓ ↓ ↓
"Key facts..." "Insights..." "Executive summary"
- ユーザーのメッセージが最初のエージェント(Researcher)に渡される
- Researcher の応答が履歴に追加される
- Analyzer は、ユーザーのメッセージ + Researcher の応答を確認する
- Summarizer は、ユーザーのメッセージ + Researcher の応答 + Analyzer の応答を確認する
- 最後のエージェントが完了すると、パイプラインも完了する
ParallelAgent
ParallelAgent はすべてのサブエージェントを同時に実行します。各エージェントは同じ入力を受け取り、独立して処理します。
独立性は単なる意図ではなく、強制されます。各サブエージェントは独自の会話ブランチ({parent}.{parallel_agent}.{sub_agent})上で実行され、履歴の読み取りもそのブランチに限定されます。サブエージェントはファンアウトに至るまでの会話を確認できますが、兄弟エージェントが生成した内容は確認できません。そのため、並行ブランチ同士が互いのコンテキストを汚染することはありません。サブエージェント間の連携を意図する場合は ParallelAgent::with_shared_state() を使用します。
ブランチはまとめてポーリングされるため、実時間のコストはすべてのブランチの合計ではなく、おおむね最も遅いブランチに相当します。また、遅いブランチが他のブランチの進行を妨げることもありません。設計時に考慮すべき重要な帰結は 2 つあります。
- イベントはインターリーブされます。 イベントはサブエージェントごとにまとめられるのではなく、各ブランチが生成した順序で到着します。チャンクをどのブランチに帰属させるかを示すには、
event.author(またはevent.branch)を使用します。ブランチ間の順序は保証されません。 - 1つのエラーで実行が終了します。エラーは決定論的に選択されます。 すべてのブランチは完了まで実行され、その後、単一の終了エラーが報告されます。複数のブランチが失敗した場合、報告されるエラーは、実時間上で最初に失敗したものではなく、宣言順で最も早いサブエージェントのエラーです。並行実行では実時間上の失敗順は競合状態になるためです。
イベントストリームを早期に破棄すると、まだ実行中のブランチが終了します。
使用する場面
- 同じトピックに対する複数の視点
- ファンアウト処理(同じ入力に対する異なる分析)
- 速度が重要なマルチタスクシナリオ
完全な例
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")?);
// 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(())
}
💡 ヒント: 並列エージェントへの指示は、固有のペルソナ、重点領域、応答プレフィックスを使って、互いに大きく異なるものにしてください。これにより、各エージェントが一意の出力を生成できます。
インタラクションの例
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
仕組み
┌─────────────────┐
│ User Message │
└────────┬────────┘
┌───────────────────┼───────────────────┐
↓ ↓ ↓
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Technical │ │ Business │ │ UX │
│ Analyst │ │ Analyst │ │ Analyst │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
↓ ↓ ↓
(response 1) (response 2) (response 3)
すべてのエージェントは同時に開始し、結果は完了したものからストリーミングされます。
LoopAgent
LoopAgent は、終了条件が満たされるか、最大反復回数に達するまで、サブエージェントを繰り返し実行します。
使用する場面
- 反復的な改善(下書き → 批評 → 改善 → 繰り返し)
- 改善を伴う再試行ロジック
- 複数回のパスを必要とする品質ゲート
ExitLoopTool
ループを早期に終了するには、エージェントに ExitLoopTool を与えます。これが呼び出されると、ループの停止を通知します。
完全な例
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")?);
// 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(())
}
インタラクションの例
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"
仕組み
┌──────────────────────────────────────────┐
│ LoopAgent │
│ ┌────────────────────────────────────┐ │
│ │ SequentialAgent │ │
│ │ ┌──────────┐ ┌──────────────┐ │ │
→ │ │ │ Critic │ → │ Refiner │ │ │ →
│ │ │ (review) │ │ (improve or │ │ │
│ │ └──────────┘ │ exit_loop) │ │ │
│ │ └──────────────┘ │ │
│ └────────────────────────────────────┘ │
│ ↑_____________↓ │
│ repeat until exit │
└──────────────────────────────────────────┘
ConditionalAgent(ルールベース)
ConditionalAgent は、同期的でルールベースの条件に基づいて実行を分岐します。A/B テストや環境ベースのルーティングなど、決定論的なルーティングに使用します。
ConditionalAgent::new("router", |ctx| ctx.session().state().get("premium")..., premium_agent)
.with_else(basic_agent)
注: LLM ベースのインテリジェントなルーティングには、代わりに
LlmConditionalAgentを使用してください。
LlmConditionalAgent(LLMベース)
LlmConditionalAgent は LLM を使用してユーザー入力を分類し、適切なサブエージェントにルーティングします。ルーティングの判断に内容の理解が必要なインテリジェントなルーティングに最適です。
使用するタイミング
- インテント分類 - ユーザーが何を尋ねているかに基づいてルーティングする
- 多方向ルーティング - 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")?);
// 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(())
}
インタラクションの例
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.
仕組み
┌─────────────────┐
│ User Message │
└────────┬────────┘
↓
┌─────────────────┐
│ LLM Classifies│ "technical" / "general" / "creative"
│ (smart_router)│
└────────┬────────┘
↓
┌────┴────┬──────────┐
↓ ↓ ↓
┌───────┐ ┌───────┐ ┌─────────┐
│ tech │ │general│ │creative │
│expert │ │helper │ │ writer │
└───────┘ └───────┘ └─────────┘
ワークフローエージェントの組み合わせ
複雑なパターンでは、ワークフローエージェントをネストできます。
シーケンシャル + 並列 + ループ
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),
],
);
ワークフロー実行のトレース
ワークフロー内部で何が起きているかを確認するには、トレースを有効にします。
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 リファレンス
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)
いずれかのサブエージェントが失敗した場合、ParallelAgent は最初のエラーを伝播する前に残りすべての future をドレインし、リソースリークを防ぎます。
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()))
前へ: LlmAgent | 次へ: マルチエージェントシステム →