プラグイン

adk-plugin クレートは、エージェント向けのライフサイクルフックシステムを提供します。プラグインは、エージェントのコードを変更することなく、ツール呼び出し、モデル呼び出し、実行イベントをインターセプトします。これは、ロギング、ガードレール、キャッシュ、コスト追跡、カスタムミドルウェアに役立ちます。

概要

プラグインシステムは EnhancedPlugin トレイトを中心に構築されています。必要なフックだけを実装します。

  • before_run / after_run — エージェントの呼び出し全体をラップ
  • before_tool / after_tool — ツール実行をインターセプト(引数の変更、ショートサーキット、結果の検査)
  • before_model / after_model — LLM 呼び出しをインターセプト(リクエストの変更、レスポンスのキャッシュ)
  • on_event — エージェントが発行するすべてのイベントを監視

プラグインは優先度順のパイプラインで実行されるため、組み合わせ可能なミドルウェアスタックを構築できます。

インストール

[dependencies]
adk-plugin = "2.1.0"

# Or via umbrella crate (included in standard tier)
adk-rust = { version = "2.1.0", features = ["standard"] }

クイックスタート

use adk_plugin::{EnhancedPlugin, PluginContext, ToolCallInfo, ToolResultInfo};
use adk_core::{Content, Result};
use async_trait::async_trait;
use serde_json::Value;

struct LoggingPlugin;

#[async_trait]
impl EnhancedPlugin for LoggingPlugin {
    fn name(&self) -> &str { "logging" }
    fn priority(&self) -> i32 { 0 }

    async fn before_tool(
        &self,
        ctx: &PluginContext,
        tool_call: &mut ToolCallInfo,
    ) -> Result<Option<Value>> {
        tracing::info!(
            tool = tool_call.name,
            args = %tool_call.args,
            "tool call started"
        );
        Ok(None) // Continue to actual tool execution
    }

    async fn after_tool(
        &self,
        ctx: &PluginContext,
        tool_result: &mut ToolResultInfo,
    ) -> Result<()> {
        tracing::info!(
            tool = tool_result.name,
            duration_ms = tool_result.duration_ms,
            "tool call completed"
        );
        Ok(())
    }
}

EnhancedPlugin トレイト

#[async_trait]
pub trait EnhancedPlugin: Send + Sync {
    /// Unique plugin identifier
    fn name(&self) -> &str;

    /// Execution order (lower = runs first) [default: 0]
    fn priority(&self) -> i32 { 0 }

    /// Called before agent execution starts
    async fn before_run(&self, ctx: &PluginContext) -> Result<()> { Ok(()) }

    /// Called after agent execution completes
    async fn after_run(&self, ctx: &PluginContext) -> Result<()> { Ok(()) }

    /// Called before each tool execution.
    /// Return Some(value) to short-circuit (skip tool, return this value).
    /// Return None to continue with normal execution.
    async fn before_tool(
        &self,
        ctx: &PluginContext,
        tool_call: &mut ToolCallInfo,
    ) -> Result<Option<Value>> { Ok(None) }

    /// Called after each tool execution.
    /// Can modify the result before it's returned to the LLM.
    async fn after_tool(
        &self,
        ctx: &PluginContext,
        tool_result: &mut ToolResultInfo,
    ) -> Result<()> { Ok(()) }

    /// Called before each model (LLM) call.
    /// Can modify the request or short-circuit with a cached response.
    async fn before_model(
        &self,
        ctx: &PluginContext,
        request: &mut ModelCallInfo,
    ) -> Result<Option<Content>> { Ok(None) }

    /// Called after each model call.
    /// Can modify the response before it's processed.
    async fn after_model(
        &self,
        ctx: &PluginContext,
        response: &mut ModelResultInfo,
    ) -> Result<()> { Ok(()) }

    /// Called for every event emitted during execution.
    async fn on_event(
        &self,
        ctx: &PluginContext,
        event: &Event,
    ) -> Result<()> { Ok(()) }
}

ツール呼び出しのインターセプト

引数の変更

実行前にツールの引数を変更します。

async fn before_tool(
    &self,
    ctx: &PluginContext,
    tool_call: &mut ToolCallInfo,
) -> Result<Option<Value>> {
    // Inject default values
    if tool_call.name == "search" {
        if tool_call.args.get("limit").is_none() {
            tool_call.args["limit"] = serde_json::json!(10);
        }
    }
    Ok(None) // Continue to tool execution
}

ショートサーキット(ツール実行のスキップ)

ツールを呼び出さずに、値を直接返します。

async fn before_tool(
    &self,
    ctx: &PluginContext,
    tool_call: &mut ToolCallInfo,
) -> Result<Option<Value>> {
    // Check cache
    let cache_key = format!("{}:{}", tool_call.name, tool_call.args);
    if let Some(cached) = self.cache.get(&cache_key).await {
        return Ok(Some(cached)); // Short-circuit: return cached result
    }
    Ok(None) // Cache miss: proceed with tool execution
}

結果の変更

実行後にツールの結果を変更します。

async fn after_tool(
    &self,
    ctx: &PluginContext,
    tool_result: &mut ToolResultInfo,
) -> Result<()> {
    // Redact sensitive data from results
    if let Some(obj) = tool_result.result.as_object_mut() {
        if obj.contains_key("ssn") {
            obj.insert("ssn".into(), serde_json::json!("***-**-****"));
        }
    }
    Ok(())
}

モデル呼び出しのインターセプト

リクエストの変更

送信前に LLM リクエストを変更します。

async fn before_model(
    &self,
    ctx: &PluginContext,
    request: &mut ModelCallInfo,
) -> Result<Option<Content>> {
    // Add system context to every request
    if let Some(ref mut instruction) = request.system_instruction {
        instruction.push_str("\nAlways respond in JSON format.");
    }
    Ok(None)
}

レスポンスのキャッシュ

async fn before_model(
    &self,
    ctx: &PluginContext,
    request: &mut ModelCallInfo,
) -> Result<Option<Content>> {
    let key = self.hash_request(request);
    if let Some(cached) = self.cache.get(&key).await {
        return Ok(Some(cached)); // Return cached response
    }
    Ok(None)
}

async fn after_model(
    &self,
    ctx: &PluginContext,
    response: &mut ModelResultInfo,
) -> Result<()> {
    // Cache the response for future calls
    let key = self.hash_request(&response.original_request);
    self.cache.set(&key, response.content.clone()).await;
    Ok(())
}

優先度ベースのパイプライン

プラグインは優先度順に実行されます(小さい番号が先に実行されます)。

struct AuthPlugin;
impl EnhancedPlugin for AuthPlugin {
    fn name(&self) -> &str { "auth" }
    fn priority(&self) -> i32 { -10 } // Runs first
    // ...
}

struct LogPlugin;
impl EnhancedPlugin for LogPlugin {
    fn name(&self) -> &str { "log" }
    fn priority(&self) -> i32 { 0 } // Runs second
    // ...
}

struct CachePlugin;
impl EnhancedPlugin for CachePlugin {
    fn name(&self) -> &str { "cache" }
    fn priority(&self) -> i32 { 10 } // Runs last
    // ...
}

before_* フックでは、プラグインは優先度の低い順に実行されます。after_* フックでは逆順(優先度の高い順)に実行され、入れ子構造のミドルウェアパターンが形成されます。

PluginContext — 共有状態

PluginContext は、呼び出し中にすべてのプラグインからアクセスできる共有状態を提供します。

use adk_plugin::PluginContext;

async fn before_tool(
    &self,
    ctx: &PluginContext,
    tool_call: &mut ToolCallInfo,
) -> Result<Option<Value>> {
    // Read shared state
    let call_count: u64 = ctx.get("tool_call_count").unwrap_or(0);

    // Write shared state
    ctx.set("tool_call_count", call_count + 1);

    // Access invocation metadata
    let user_id = ctx.user_id();
    let session_id = ctx.session_id();
    let agent_name = ctx.agent_name();

    Ok(None)
}

エージェントへのプラグインの登録

use adk_agent::LlmAgentBuilder;
use std::sync::Arc;

let agent = LlmAgentBuilder::new("my_agent")
    .model(model)
    .instruction("You are a helpful assistant.")
    .tool(Arc::new(my_tool))
    .plugin(Arc::new(LoggingPlugin))
    .plugin(Arc::new(CachePlugin::new(cache_store)))
    .plugin(Arc::new(CostTrackingPlugin::new()))
    .build()?;

例:コスト追跡プラグイン

use adk_plugin::{EnhancedPlugin, PluginContext, ModelResultInfo};
use adk_core::Result;
use std::sync::atomic::{AtomicU64, Ordering};

struct CostPlugin {
    total_tokens: AtomicU64,
}

#[async_trait]
impl EnhancedPlugin for CostPlugin {
    fn name(&self) -> &str { "cost_tracker" }
    fn priority(&self) -> i32 { 100 }

    async fn after_model(
        &self,
        ctx: &PluginContext,
        response: &mut ModelResultInfo,
    ) -> Result<()> {
        if let Some(usage) = &response.usage {
            let tokens = usage.prompt_tokens + usage.completion_tokens;
            self.total_tokens.fetch_add(tokens as u64, Ordering::Relaxed);
            tracing::info!(
                total_tokens = self.total_tokens.load(Ordering::Relaxed),
                "token usage updated"
            );
        }
        Ok(())
    }
}

前へ: ← アクションノード | 次へ: セッション →

プラグイン - ADK-Rust ドキュメント | ADK-Rust