ワークフローエージェント

ワークフローエージェントは、シーケンシャルパイプライン、並列実行、反復ループといった予測可能なパターンで複数のエージェントをオーケストレートします。AI推論を使用する LlmAgent とは異なり、ワークフローエージェントは決定論的な実行パスに従います。

クイックスタート

新しいプロジェクトを作成します。

cargo new workflow_demo
cd workflow_demo

Cargo.toml に依存関係を追加します。

[dependencies]
adk-rust = "0.2.0"
tokio = { version = "1.40", features = ["full"] }
dotenvy = "0.15"

.env を作成します。

echo 'GOOGLE_API_KEY=your-api-key' > .env

SequentialAgent

SequentialAgentは、サブエージェントを順番に実行します。各エージェントは、前のエージェントからの蓄積された会話履歴を参照します。

使用場面

  • 出力が次のステップに供給される多段階パイプライン
  • 調査 → 分析 → 要約 のワークフロー
  • データ変換チェーン

完全な例

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

実行:

cargo run

対話例

You: Rustプログラミング言語について教えてください

🔄 [researcher] 調査中...
Rustに関する主要な事実を以下に示します:
1. 2010年にMozillaで作成されたシステムプログラミング言語
2. 所有権システムによるガベージコレクションなしのメモリ安全性
3. ゼロコスト抽象化と最小限のランタイム
4. Stack Overflowで7年間「最も愛されている言語」に選ばれる
5. Firefox、Discord、Dropbox、Linuxカーネルなどで使用されている

🔄 [analyzer] 分析中...
主な洞察:
1. Rustはメモリ安全性とパフォーマンスのトレードオフを解決する
2. 高い開発者満足度が急速な採用を促進する
3. 主要テクノロジー企業からの信頼が本番環境での利用可能性を裏付ける

🔄 [summarizer] 要約中...
Rustは、所有権モデルを通じてガベージコレクションなしでメモリ安全性を実現するシステム言語です。
2010年にMozillaで作成され、7年連続で最も愛されている言語と評価されています。
DiscordやLinuxカーネルなどの主要企業は、そのゼロコスト抽象化とパフォーマンス保証のためにRustを採用しています。

動作の仕組み

┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│  Researcher │ →  │   Analyzer  │ →  │  Summarizer │
│   (ステップ 1)  │    │   (ステップ 2)  │    │   (ステップ 3)  │
└─────────────┘    └─────────────┘    └─────────────┘
       ↓                  ↓                  ↓
 "主要な事実..."    "洞察..."    "エグゼクティブサマリー"
  1. ユーザーメッセージは最初のAgent (Researcher) に送られます
  2. Researcherの応答が履歴に追加されます
  3. Analyzerは以下を参照します: ユーザーメッセージ + Researcherの応答
  4. Summarizerは以下を参照します: ユーザーメッセージ + Researcher + Analyzerの応答
  5. 最後のAgentが完了したときにパイプラインが完了します

ParallelAgent

ParallelAgent は、すべてのサブエージェントを並行して実行します。各 agent は同じ入力を受け取り、独立して動作します。

使用するケース

  • 同じトピックに対する複数の視点
  • ファンアウト処理(同じ入力、異なる分析)
  • 速度が重要なマルチタスクシナリオ

完全な例

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")?);

    // それぞれ異なるペルソナを持つ3人の analyst (並列実行にとって重要)
    let technical = LlmAgentBuilder::new("technical_analyst")
        .instruction("あなたはシニアソフトウェアアーキテクトです。 \
                     以下にのみ焦点を当ててください: コード品質、システムアーキテクチャ、スケーラビリティ、 \
                     セキュリティの脆弱性、技術スタックの選択肢。\
                     応答は「🔧 TECHNICAL:」で始め、2〜3つの箇条書きで記述してください。")
        .model(model.clone())
        .build()?;

    let business = LlmAgentBuilder::new("business_analyst")
        .instruction("あなたはビジネス戦略家であり、MBAの卒業生です。 \
                     以下にのみ焦点を当ててください: 市場機会、収益モデル、競争、 \
                     コスト構造、市場投入戦略。\
                     応答は「💼 BUSINESS:」で始め、2〜3つの箇条書きで記述してください。")
        .model(model.clone())
        .build()?;

    let user_exp = LlmAgentBuilder::new("ux_analyst")
        .instruction("あなたはUXリサーチャー兼デザイナーです。 \
                     以下にのみ焦点を当ててください: ユーザー体験、アクセシビリティ、課題点、 \
                     ビジュアルデザイン、ユーザー満足度指標。\
                     応答は「🎨 UX:」で始め、2〜3つの箇条書きで記述してください。")
        .model(model.clone())
        .build()?;

    // ParallelAgent を作成
    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!("⚡ 並列分析: Technical | Business | UX");
    println!("   (3つすべてが同時に実行されます!)");
    println!();

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

💡 ヒント: parallel agent の指示は、独自のペルソナ、重点分野、応答プレフィックスを使用して、非常に明確に区別してください。これにより、各 agent が独自の出力を生成することが保証されます。

対話例

You: モバイルバンキングアプリを評価してください

🔧 TECHNICAL:
• 堅牢な API セキュリティが必要: OAuth 2.0、証明書ピンニング、暗号化ストレージ
• 同期機能を備えたオフラインモードは、複雑な状態管理と競合解決が必要
• 生体認証の統合は、iOS/Android プラットフォーム間で大きく異なる

💼 BUSINESS:
• 競争の激しい市場 - 独自の差別化要因が必要(ネオバンク、従来の銀行)
• 収益モデル: インターチェンジ手数料、プレミアムティア、または融資商品のクロスセル
• 規制遵守コストが重要: PCI-DSS、地域銀行法、KYC/AML

🎨 UX:
• 重要: タスクの高速完了 - 残高確認は3秒以内である必要がある
• アクセシビリティが不可欠: スクリーンリーダーのサポート、ハイコントラストモード、大きなタッチターゲット
• 信頼指標が重要: セキュリティバッジ、なじみのある銀行パターン

動作原理

                    ┌─────────────────┐
                    │  ユーザーメッセージ │
                    └────────┬────────┘
         ┌───────────────────┼───────────────────┐
         ↓                   ↓                   ↓
  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐
  │  技術担当者   │    │  ビジネス担当者 │    │     UX      │
  │               │    │               │    │               │
  └──────┬──────┘    └──────┬──────┘    └──────┬──────┘
         ↓                   ↓                   ↓
    (応答 1)       (応答 2)       (応答 3)

すべての agent は同時に開始され、完了すると結果がストリームされます。


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

実行例

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    │  │  │  →
     │  │  │ (レビュー) │    │ (改善または  │  │  │
     │  │  └──────────┘    │  exit_loop)  │  │  │
     │  │                  └──────────────┘  │  │
     │  └────────────────────────────────────┘  │
     │         ↑_____________↓                  │
     │         終了するまで繰り返す               │
     └──────────────────────────────────────────┘

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-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::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-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.

動作の仕組み

┌─────────────────┐
│  ユーザーメッセージ   │
└────────┬────────┘
         ↓
┌─────────────────┐
│   LLMが分類     │  "technical" / "general" / "creative"
│   (smart_router)│
└────────┬────────┘
         ↓
    ┌────┴────┬──────────┐
    ↓         ↓          ↓
┌───────┐ ┌───────┐ ┌─────────┐
│ tech  │ │general│ │creative │
│expert │ │helper │ │ writer  │
└───────┘ └───────┘ └─────────┘

ワークフローAgentの組み合わせ

ワークフローAgentは、複雑なパターンに対応するためにネストできます。

Sequential + Parallel + Loop

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::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(
    "user".to_string(),
    session.id().to_string(), 
    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)

LoopAgent

LoopAgent::new("name", vec![agent1, agent2])
    .with_max_iterations(5)     // Safety limit (recommended)
    .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

// LoopAgentから抜け出すためにエージェントに追加します
.tool(Arc::new(ExitLoopTool::new()))

前へ: LlmAgent | 次へ: マルチAgentシステム →