LlmAgent

LlmAgentは、Large Language Modelを推論と意思決定に使用するADK-Rustの中核的なエージェントタイプです。

クイックスタート

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

cargo new llm_agent
cd llm_agent

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

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

APIキーを使用して.envを作成します。

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

src/main.rsを置き換えます。

use adk_rust::prelude::*;
use adk_rust::{SessionId, UserId};
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 = GeminiModel::new(&api_key, "gemini-3.7-flash")?;

    let agent = LlmAgentBuilder::new("my_agent")
        .instruction("You are a helpful assistant.")
        .model(Arc::new(model))
        .build()?;

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

実行します。

cargo run

エージェントとの対話

対話型プロンプトが表示されます。

🤖 Agent ready! Type your questions (or 'exit' to quit).

You: Hello! What can you help me with?
Assistant: Hello! I'm a helpful assistant. I can help you with:
- Answering questions on various topics
- Explaining concepts clearly
- Having a conversation

What would you like to know?

You: exit
👋 Goodbye!

指示によるエージェントの動作の調整

instruction()メソッドは、エージェントの個性と動作を定義します。これは、すべての応答を導くシステムプロンプトです。

// A formal business assistant
let formal_agent = LlmAgentBuilder::new("formal_assistant")
    .instruction("You are a professional business consultant. \
                 Use formal language. Be concise and data-driven.")
    .model(Arc::new(model))
    .build()?;

// A friendly coding tutor
let tutor_agent = LlmAgentBuilder::new("code_tutor")
    .instruction("You are a friendly coding tutor for beginners. \
                 Explain concepts simply. Use examples. \
                 Encourage questions. Never make the user feel bad for not knowing.")
    .model(Arc::new(model))
    .build()?;

// A creative storyteller
let story_agent = LlmAgentBuilder::new("storyteller")
    .instruction("You are a creative storyteller. \
                 Craft engaging narratives with vivid descriptions. \
                 Use plot twists and memorable characters.")
    .model(Arc::new(model))
    .build()?;

出力例

ユーザープロンプト: 「Rustとは何ですか?」

フォーマルなビジネスアシスタント:

Rust is a systems programming language focused on performance, memory safety, 
and concurrency. Key metrics: 0 runtime overhead, 0 garbage collection. 
Adoption growing 30% YoY in enterprise. Recommended for performance-critical 
applications where reliability is paramount.

親しみやすいコーディングチューター:

Great question! 🎉 Rust is a programming language that helps you write fast 
and safe code. Think of it like a helpful friend who stops you from making 
mistakes before they happen!

Here's a simple example:
  let name = "Alice";  // This creates a variable

What makes Rust special is that it catches bugs at compile time, not when 
your program is running. Pretty cool, right? Want me to explain more?

創造的なストーリーテラー:

In the twilight of 2010, in a secret Mozilla laboratory, a lone developer 
named Graydon Hoare breathed life into something extraordinary—a language 
that would challenge the old gods of C and C++.

They called it Rust, named for a fungus that survives against all odds. 
Like a phoenix rising from the ashes of segmentation faults and memory 
leaks, Rust promised a new era: speed without sacrifice, power without peril.

And so, the revolution began...

指示のテンプレート化

指示では、{var}構文を使用した変数の挿入をサポートしています。変数は実行時にセッション状態から解決されます。

let agent = LlmAgentBuilder::new("personalized")
    .instruction("You are helping {user_name}. Their role is {user_role}. \
                 Tailor your responses to their expertise level.")
    .model(Arc::new(model))
    .build()?;

テンプレートを使用する手順:

  1. 指示内にテンプレート変数を含めてエージェントを作成する
  2. 状態を管理するためにRunnerとSessionServiceを設定する
  3. テンプレートに一致する状態変数を使用してセッションを作成する
  4. エージェントを実行する - テンプレートは自動的に置き換えられます

完全に動作する例を以下に示します。

use adk_rust::prelude::*;
use adk_rust::{SessionId, UserId};
use adk_rust::runner::{Runner, RunnerConfig};
use adk_rust::session::{CreateRequest, InMemorySessionService, SessionService};
use adk_rust::futures::StreamExt;
use serde_json::json;
use std::collections::HashMap;
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 = GeminiModel::new(&api_key, "gemini-3.7-flash")?;

    // 1. Agent with templated instruction
    let agent = LlmAgentBuilder::new("personalized")
        .instruction("You are helping {user_name}. Their role is {user_role}. \
                     Tailor your responses to their expertise level.")
        .model(Arc::new(model))
        .build()?;

    // 2. Create session service and runner
    let session_service = Arc::new(InMemorySessionService::new());
    let runner = Runner::new(RunnerConfig {
        app_name: "templating_demo".to_string(),
        agent: Arc::new(agent),
        session_service: session_service.clone(),
        artifact_service: None,
        memory_service: None,
        run_config: None,
    })?;

    // 3. Create session with state variables
    let mut state = HashMap::new();
    state.insert("user_name".to_string(), json!("Alice"));
    state.insert("user_role".to_string(), json!("Senior Developer"));

    let session = session_service.create(CreateRequest {
        app_name: "templating_demo".to_string(),
        user_id: "user123".to_string(),
        session_id: None,
        state,
    }).await?;

    // 4. Run the agent - instruction becomes:
    // "You are helping Alice. Their role is Senior Developer..."
    let mut response_stream = runner.run(
        UserId::new("user123")?,
        SessionId::new(session.id())?,
        Content::new("user").with_text("Explain async/await in Rust"),
    ).await?;

    // Print the response
    while let Some(event) = response_stream.next().await {
        let event = event?;
        if let Some(content) = event.content() {
            for part in &content.parts {
                if let Part::Text { text } = part {
                    print!("{}", text);
                }
            }
        }
    }

    Ok(())
}

テンプレート変数の種類:

パターン例ソース
{var}{user_name}セッション状態
{prefix:var}{user:name}、{app:config}プレフィックス付き状態
{var?}{user_name?}任意(存在しない場合は空)
{artifact.file}{artifact.resume.pdf}成果物の内容

出力例:

テンプレート:"You are helping {user_name}. Their role is {user_role}."
次のようになります:"You are helping Alice. Their role is Senior Developer."

これにより、エージェントはユーザーの名前と専門知識のレベルに基づいて、パーソナライズされたコンテンツを返します!


ツールの追加

ツールを使うと、エージェントに会話以外の能力を持たせることができます。データの取得、計算、ウェブ検索、外部のAPIsの呼び出しなどが可能です。LLMは、ユーザーのリクエストに基づいてツールを使用するタイミングを判断します。

ツールの仕組み

  1. エージェントがユーザーメッセージを受信 → 「東京の天気はどうですか?」
  2. LLMがツールの呼び出しを判断 → get_weatherを{"city": "Tokyo"}とともに選択
  3. ツールが実行 → {"temperature": "22°C", "condition": "sunny"}を返す
  4. LLMがレスポンスを整形 → 「東京の天気は晴れで、気温は22°Cです。」

FunctionToolを使用したツールの作成

FunctionToolはツールを作成する最も簡単な方法です。任意の非同期Rust関数をラップし、LLMから呼び出せるようにします。名前、説明、JSON引数を受け取り、JSON結果を返すハンドラー関数を指定します。

let weather_tool = FunctionTool::new(
    "get_weather",                              // Tool name (used by LLM)
    "Get the current weather for a city",       // Description (helps LLM decide when to use it)
    |_ctx, args| async move {                   // Handler function
        let city = args.get("city")             // Extract arguments from JSON
            .and_then(|v| v.as_str())
            .unwrap_or("unknown");
        Ok(json!({ "city": city, "temperature": "22°C" }))  // Return JSON result
    },
);

組み込みのプロバイダー固有ツールを、同じエージェント内のFunctionToolインスタンスと混在させられるようになりました。ADKはネイティブツールの宣言をプロバイダーに転送しつつ、通常の関数ツールをローカルで実行します。

複数ツールエージェントの構築

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

cargo new tool_agent
cd tool_agent

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

[dependencies]
adk-rust = { version = "2.1.0", features = ["tools"] }
tokio = { version = "1.40", features = ["full"] }
dotenvy = "0.15"
serde_json = "1.0"

.envを作成します。

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

src/main.rsを3つのツールを持つエージェントに置き換えます。

use adk_rust::prelude::*;
use adk_rust::Launcher;
use serde_json::json;
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 = GeminiModel::new(&api_key, "gemini-3.7-flash")?;

    // Tool 1: Weather lookup
    let weather_tool = FunctionTool::new(
        "get_weather",
        "Get the current weather for a city. Parameters: city (string)",
        |_ctx, args| async move {
            let city = args.get("city").and_then(|v| v.as_str()).unwrap_or("unknown");
            Ok(json!({ "city": city, "temperature": "22°C", "condition": "sunny" }))
        },
    );

    // Tool 2: Calculator
    let calculator = FunctionTool::new(
        "calculate",
        "Perform arithmetic. Parameters: a (number), b (number), operation (add/subtract/multiply/divide)",
        |_ctx, args| async move {
            let a = args.get("a").and_then(|v| v.as_f64()).unwrap_or(0.0);
            let b = args.get("b").and_then(|v| v.as_f64()).unwrap_or(0.0);
            let op = args.get("operation").and_then(|v| v.as_str()).unwrap_or("add");
            let result = match op {
                "add" => a + b,
                "subtract" => a - b,
                "multiply" => a * b,
                "divide" => if b != 0.0 { a / b } else { 0.0 },
                _ => 0.0,
            };
            Ok(json!({ "result": result }))
        },
    );

    // Tool 3: Built-in Google Search (Note: Currently unsupported in ADK-Rust)
    // let search_tool = GoogleSearchTool::new();

    // Build agent with weather and calculator tools
    let agent = LlmAgentBuilder::new("multi_tool_agent")
        .instruction("You are a helpful assistant. Use tools when needed: \
                     - get_weather for weather questions \
                     - calculate for math")
        .model(Arc::new(model))
        .tool(Arc::new(weather_tool))
        .tool(Arc::new(calculator))
        // .tool(Arc::new(search_tool))  // Currently unsupported
        .build()?;

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

エージェントを実行します。

cargo run

操作例

You: What's 15% of 250?
Assistant: [Using calculate tool with a=250, b=0.15, operation=multiply]
15% of 250 is 37.5.

You: What's the weather in Tokyo?
Assistant: [Using get_weather tool with city=Tokyo]
The weather in Tokyo is sunny with a temperature of 22°C.

You: Search for latest Rust features
Assistant: I don't have access to search functionality at the moment, but I can help with other questions about Rust or perform calculations!

JSONスキーマを使用した構造化出力

構造化データが必要なアプリケーションでは、output_schema()を使用します。

use adk_rust::prelude::*;
use serde_json::json;
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 = GeminiModel::new(&api_key, "gemini-3.7-flash")?;

    let extractor = LlmAgentBuilder::new("entity_extractor")
        .instruction("Extract entities from the given text.")
        .model(Arc::new(model))
        .output_schema(json!({
            "type": "object",
            "properties": {
                "people": {
                    "type": "array",
                    "items": { "type": "string" }
                },
                "locations": {
                    "type": "array",
                    "items": { "type": "string" }
                },
                "dates": {
                    "type": "array",
                    "items": { "type": "string" }
                }
            },
            "required": ["people", "locations", "dates"]
        }))
        .build()?;

    println!("Entity extractor ready!");
    Ok(())
}

プロバイダーによるスキーマの適用方法

output_schemaはGenerateContentConfig::response_schemaとしてプロバイダーに送られます。
プロバイダーがそれをどのように扱うかは異なりますが、いずれの場合もエージェントが結果を検証します。

プロバイダーネイティブな適用
Gemini完全なスキーマをレスポンススキーマとして送信
OpenAIおよびOpenAI互換完全なスキーマを厳密なjson_schemaレスポンス形式として送信
OpenRouter完全なスキーマ
DeepSeekJSON 構文のみ — DeepSeekの JSON 出力モードには json_schema バリアントがないため、スキーマはエージェントの検証によって適用されます

プロバイダーが構文のみを強制する場合、または何も強制しない場合でも、エージェントはスキーマを指示として挿入し、返信を検証します。そのため、適合しない回答は不正なデータとして返されるのではなく、再試行の原因になります。

注: DeepSeek では、JSON Output がオンの場合、プロンプトに「json」という単語が含まれている必要があります。そうでない場合、API は空のコンテンツを返すことがあります。プロンプトにその語がすでに含まれていない場合、アダプターが自動的に追加します。

JSON Output の例

入力: 「ジョンは12月25日にパリでサラに会った」

出力:

{
  "people": ["John", "Sarah"],
  "locations": ["Paris"],
  "dates": ["December 25th"]
}

高度な機能

コンテンツの包含

会話履歴の可視性を制御します。

// Full history (default)
.include_contents(IncludeContents::Default)

// Stateless - sees only injected instructions plus the current user turn
.include_contents(IncludeContents::None)

Output キー

エージェントのレスポンスをセッション状態に保存します。

.output_key("summary")  // Response saved to state["summary"]

動的な指示

実行時に指示を計算します。

.instruction_provider(|ctx| {
    Box::pin(async move {
        let user_id = ctx.user_id();
        Ok(format!("You are assisting user {}.", user_id))
    })
})

コールバック

エージェントの動作をインターセプトします。

.before_model_callback(|ctx, request| {
    Box::pin(async move {
        println!("About to call LLM with {} messages", request.contents.len());
        Ok(BeforeModelResult::Continue)
    })
})

ビルダーリファレンス

メソッド説明
new(name)エージェント名を指定してビルダーを作成します
model(Arc<dyn Llm>)LLM(必須)を設定します
description(text)エージェントの説明
instruction(text)システムプロンプト
tool(Arc<dyn Tool>)静的ツールを追加
toolset(Arc<dyn Toolset>)呼び出しごとに解決される動的ツールセットを追加
output_schema(json)構造化出力用のJSONスキーマ
output_key(key)応答を状態に保存
include_contents(mode)履歴の可視性
max_iterations(n)最大LLMラウンドトリップ数(デフォルト: 100)
tool_execution_strategy(strategy)ツールのディスパッチモード: Sequential、Parallel、または Auto
default_retry_budget(RetryBudget)遅延を挟み、失敗したツールを最大 N 回再試行
tool_retry_budget(name, RetryBudget)ツールごとの再試行オーバーライド
circuit_breaker_threshold(u32)N 回連続して失敗したツールを無効化
on_tool_error(callback)ツールの失敗時に呼び出されるフォールバックハンドラーを登録
after_tool_callback_full(callback)ツール、引数、レスポンスを使用する V2 のリッチなツール後コールバック
build()エージェントを作成

イテレーション制御

max_iterations()メソッドは、エージェントが停止する前に実行できるLLMラウンドトリップの回数を制限します。これは次の用途に役立ちます。

  • ツール呼び出しが際限なく繰り返されるループの防止
  • 本番環境でのコスト管理
  • 複雑なタスクに適切な上限の設定
let agent = LlmAgentBuilder::new("bounded_agent")
    .model(Arc::new(model))
    .instruction("You are a helpful assistant.")
    .tool(Arc::new(my_tool))
    .max_iterations(10)  // Stop after 10 LLM calls
    .build()?;

デフォルトは100イテレーションで、ほとんどのユースケースに十分です。単純なQ&Aエージェントには、より小さい値(5~20)が推奨されます。一方、複雑な多段階推論タスクでは、より大きい値が必要になる場合があります。


動的ツールセット

呼び出しコンテキストに依存するツール(ユーザーごとのブラウザセッションなど)には、.tool()の代わりに.toolset()を使用します。ツールセットは各run()呼び出しの開始時に解決されます。

use adk_browser::{BrowserSessionPool, BrowserToolset, BrowserConfig};
use adk_agent::LlmAgentBuilder;
use std::sync::Arc;

let pool = Arc::new(BrowserSessionPool::new(BrowserConfig::new(), 10));
let toolset = Arc::new(BrowserToolset::with_pool(pool));

let agent = LlmAgentBuilder::new("web_agent")
    .model(model)
    .instruction("You are a web automation assistant.")
    .toolset(toolset)  // Resolved per-user at runtime
    .build()?;

同じエージェント上で静的な.tool()と動的な.toolset()を組み合わせることができます。静的ツールとツールセット間でツール名が重複すると、決定論的なエラーが発生します。

RealtimeAgentBuilderは同じセマンティクスで.toolset()もサポートするため、リアルタイム音声エージェントでも動的なツール解決を利用できます。

ツールセットの構成

複雑なツールセット構成を作成するには、adk-toolのFilteredToolset、MergedToolset、PrefixedToolsetを使用します。

use adk_tool::{BasicToolset, FilteredToolset, MergedToolset, PrefixedToolset, string_predicate};

// Prefix weather tools to avoid name collisions
let weather = Arc::new(PrefixedToolset::new(weather_toolset, "wx"));

// Filter utility tools to only expose search and calculate
let utils = Arc::new(FilteredToolset::new(
    utility_toolset,
    string_predicate(vec!["search".into(), "calculate".into()]),
));

// Merge into a single toolset
let composed = MergedToolset::new("all", vec![weather, utils]);

let agent = LlmAgentBuilder::new("agent")
    .model(model)
    .toolset(Arc::new(composed))
    .build()?;

すべての構成ユーティリティは、McpToolsetやBrowserToolsetを含む、あらゆるToolset実装で動作します。

並列ツール実行

LLMが1つのレスポンスで複数のツール呼び出しを返す場合、それらのディスパッチ方法を制御できます。

use adk_core::ToolExecutionStrategy;

let agent = LlmAgentBuilder::new("fast_agent")
    .model(Arc::new(model))
    .instruction("You are a research assistant. Use multiple tools in parallel.")
    // Auto requires both safety signals for concurrent inclusion
    .tool_execution_strategy(ToolExecutionStrategy::Auto)
    .tool(Arc::new(
        search_tool
            .with_read_only(true)
            .with_concurrency_safe(true),
    ))
    .tool(Arc::new(
        lookup_tool
            .with_read_only(true)
            .with_concurrency_safe(true),
    ))
    .tool(Arc::new(save_tool)) // runs after the concurrent safe subset
    .build()?;

3つの戦略を利用できます。

  • Sequential(デフォルト) — LLMの順序でツールを1つずつ実行します
  • Parallel — すべてのツールを同時実行します。この明示的なオーバーライドは安全性メタデータを迂回するため、安全性については呼び出し側が責任を負います
  • Auto — ツールが読み取り専用かつ同時実行に安全な呼び出しを先に同時実行し、残りの呼び出しはその後、順番に実行します

結果は、戦略に関係なく、常に元の LLM の順序で返されます。失敗したツールは JSON エラーレスポンスを生成しますが、バッチ処理は中止されません。

戦略は LlmAgentBuilder::tool_execution_strategy() を介してエージェントごとに設定します。設定されていない場合、デフォルトは Sequential です。

ツールの回復性

本番環境のエージェント向けに、再試行予算とサーキットブレーカーを設定します。

use adk_core::RetryBudget;
use std::time::Duration;

let agent = LlmAgentBuilder::new("resilient_agent")
    .model(model)
    .tool(Arc::new(my_tool))
    // Retry all tools up to 2 times with 500ms delay
    .default_retry_budget(RetryBudget::new(2, Duration::from_millis(500)))
    // Override for a specific tool
    .tool_retry_budget("flaky_api", RetryBudget::new(4, Duration::from_secs(1)))
    // Disable a tool after 3 consecutive failures in one invocation
    .circuit_breaker_threshold(3)
    // Provide a fallback when a tool fails
    .on_tool_error(Box::new(|_ctx, tool, _args, error| {
        Box::pin(async move {
            tracing::warn!(tool = tool.name(), %error, "tool failed");
            Ok(None) // None = propagate error; Some(value) = use as fallback
        })
    }))
    .build()?;

ツール実行後のコールバックでは、CallbackContext::tool_outcome() を介して構造化された ToolOutcome メタデータを検査できます。

.after_tool_callback(Box::new(|ctx| {
    Box::pin(async move {
        if let Some(outcome) = ctx.tool_outcome() {
            println!(
                "Tool '{}' {} in {:?} (attempt {})",
                outcome.tool_name,
                if outcome.success { "succeeded" } else { "failed" },
                outcome.duration,
                outcome.attempt,
            );
        }
        Ok(None)
    })
}))

完全な例

複数のツール(天気、計算機、検索)を備え、出力をセッション状態に保存する、本番環境対応のエージェントです。

use adk_rust::prelude::*;
use adk_rust::Launcher;
use serde_json::json;
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 = GeminiModel::new(&api_key, "gemini-3.7-flash")?;

    // Weather tool
    let weather = FunctionTool::new(
        "get_weather",
        "Get weather for a city. Parameters: city (string)",
        |_ctx, args| async move {
            let city = args.get("city").and_then(|v| v.as_str()).unwrap_or("unknown");
            Ok(json!({
                "city": city,
                "temperature": "22°C",
                "humidity": "65%",
                "condition": "partly cloudy"
            }))
        },
    );

    // Calculator tool
    let calc = FunctionTool::new(
        "calculate",
        "Math operations. Parameters: expression (string like '2 + 2')",
        |_ctx, args| async move {
            let expr = args.get("expression").and_then(|v| v.as_str()).unwrap_or("0");
            Ok(json!({ "expression": expr, "result": "computed" }))
        },
    );

    // Build the full agent
    let agent = LlmAgentBuilder::new("assistant")
        .description("A helpful assistant with weather and calculation abilities")
        .instruction("You are a helpful assistant. \
                     Use the weather tool for weather questions. \
                     Use the calculator for math. \
                     Be concise and friendly.")
        .model(Arc::new(model))
        .tool(Arc::new(weather))
        .tool(Arc::new(calc))
        // .tool(Arc::new(GoogleSearchTool::new()))  // Provider-native tools can be mixed with FunctionTool
        .output_key("last_response")
        .build()?;

    println!("✅ Agent '{}' ready!", agent.name());
    Launcher::new(Arc::new(agent)).run().await?;
    Ok(())
}

次のプロンプトを試してください。

You: What's 25 times 4?
Assistant: It's 100.

You: How's the weather in New York?
Assistant: The weather in New York is partly cloudy with a temperature of 22°C and 65% humidity.

You: Calculate 15% tip on $85
Assistant: A 15% tip on $85 is $12.75, making the total $97.75.


前へ: クイックスタート | 次へ: ワークフローエージェント →