コア型
ADK-Rustの基盤を形成するadk-coreの基本的な型とトレイト。
ContentとPart
ADK内のすべてのメッセージはContentとPartを通して流れます。これらの型を理解することは、Agent、Tool、およびコールバックを扱う上で不可欠です。
Content
Contentは会話における単一のメッセージを表します。role(送信者)と、1つ以上のparts(実際のコンテンツ)を持っています。
役割:
"user"- ユーザーからのメッセージ"model"- AIモデルからの応答"tool"- Tool実行からの結果
use adk_core::Content;
// Simple text message from user
let user_msg = Content::new("user")
.with_text("What's the weather like?");
// Model response
let model_msg = Content::new("model")
.with_text("The weather is sunny and 72°F.");
// Multiple text parts in one message
let detailed_msg = Content::new("user")
.with_text("Here's my question:")
.with_text("What is the capital of France?");
マルチモーダルContent:
Contentは、テキストに加えて画像、音声、PDF、およびその他のバイナリデータを含むことができます。
// Image from bytes (e.g., read from file)
let image_bytes = std::fs::read("photo.jpg")?;
let content = Content::new("user")
.with_text("What's in this image?")
.with_inline_data("image/jpeg", image_bytes);
// Image from URL (model fetches it)
let content = Content::new("user")
.with_text("Describe this image")
.with_file_uri("image/png", "https://example.com/chart.png");
// PDF document
let pdf_bytes = std::fs::read("report.pdf")?;
let content = Content::new("user")
.with_text("Summarize this document")
.with_inline_data("application/pdf", pdf_bytes);
Part
Partは、メッセージ内のさまざまな種類のコンテンツを表すenumです。
pub enum Part {
// Plain text
Text { text: String },
// Binary data embedded in the message
InlineData { mime_type: String, data: Vec<u8> },
// Reference to external file (URL or cloud storage)
FileData { mime_type: String, file_uri: String },
// Model requesting a tool call
FunctionCall { name: String, args: Value, id: Option<String> },
// Result of a tool execution
FunctionResponse { function_response: FunctionResponseData, id: Option<String> },
}
Partを直接作成する:
use adk_core::Part;
// Text part
let text = Part::text_part("Hello, world!");
// Image from bytes
let image = Part::inline_data("image/png", png_bytes);
// Image from URL
let remote_image = Part::file_data("image/jpeg", "https://example.com/photo.jpg");
Partを検査する:
// Get text content (returns None for non-text parts)
if let Some(text) = part.text() {
println!("Text: {}", text);
}
// Get MIME type (for InlineData and FileData)
if let Some(mime) = part.mime_type() {
println!("MIME type: {}", mime);
}
// Get file URI (for FileData only)
if let Some(uri) = part.file_uri() {
println!("File URI: {}", uri);
}
// Check if part contains media (image, audio, video, etc.)
if part.is_media() {
println!("This part contains binary media");
}
Partを反復処理する:
for part in &content.parts {
match part {
Part::Text { text } => println!("Text: {}", text),
Part::InlineData { mime_type, data } => {
println!("Binary data: {} ({} bytes)", mime_type, data.len());
}
Part::FileData { mime_type, file_uri } => {
println!("File: {} at {}", mime_type, file_uri);
}
Part::FunctionCall { name, args, .. } => {
println!("Tool call: {}({})", name, args);
}
Part::FunctionResponse { function_response, .. } => {
println!("Tool result: {} -> {}",
function_response.name,
function_response.response
);
}
}
}
Agentトレイト
Agentトレイトは、ADKにおけるすべてのAgentの中核となる抽象化です。LlmAgent、ワークフローAgent、カスタムAgentなど、すべてのAgentタイプがこのトレイトを実装します。
#[async_trait]
pub trait Agent: Send + Sync {
/// Unique identifier for this agent
fn name(&self) -> &str;
/// Human-readable description of what this agent does
fn description(&self) -> &str;
/// Child agents (for workflow agents like Sequential, Parallel)
fn sub_agents(&self) -> &[Arc<dyn Agent>];
/// Execute the agent and return a stream of events
async fn run(&self, ctx: Arc<dyn InvocationContext>) -> Result<EventStream>;
}
主要なポイント:
name(): ロギング、Agent間でのやり取り、および識別のために使用されます。マルチAgentシステム内では一意である必要があります。description(): LLMがAgentをToolとして使用する場合やルーティングの判断のために表示されます。sub_agents(): 子Agentを返します。リーフAgent(LlmAgent)の場合は空で、コンテナ(SequentialAgent、ParallelAgent)の場合は子Agentが格納されます。run(): 主要な実行メソッドです。コンテキストを受け取り、EventStreamを返します。
なぜEventStreamなのか?
Agentは単一のレスポンスではなく、EventStream(Result<Event>のストリーム)を返すのは、以下の理由によります。
- ストリーミング: UX向上のため、レスポンスをトークンごとにストリーミングできます。
- Toolの呼び出し: 実行中に複数のToolの呼び出しと応答が発生します。
- 状態変更: 状態の更新がイベントとして発行されます。
- Agent間のやり取り: Agent間のやり取りはイベントを通じて通知されます。
Tool Trait
Tool は、エージェントの会話能力を拡張します。これらを使用すると、エージェントは API、データベース、ファイルシステムと連携したり、計算を実行したりできます。
#[async_trait]
pub trait Tool: Send + Sync {
/// ツール名(モデルからの関数呼び出しで使用されます)
fn name(&self) -> &str;
/// LLMがこのツールを使用するタイミングを判断するのに役立つ説明
fn description(&self) -> &str;
/// 期待されるパラメーターを定義するJSON Schema
fn parameters_schema(&self) -> Option<Value> { None }
/// レスポンス形式を定義するJSON Schema
fn response_schema(&self) -> Option<Value> { None }
/// このツールが非同期で実行されるかどうか(タスクIDをすぐに返します)
fn is_long_running(&self) -> bool { false }
/// 指定された引数でツールを実行します
async fn execute(&self, ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value>;
}
重要なポイント:
name(): モデルがこのツールを呼び出すために使用する関数名です。短く分かりやすい名前にしてください(例:get_weather、search_database)。description(): モデルがツールを使用するタイミングを理解するために重要です。ツールの機能と使用するタイミングを具体的に記述してください。parameters_schema(): モデルに提供する引数を伝えるJSON Schemaです。これがなければ、モデルは推測します。execute(): 解析された引数をserde_json::Valueとして受け取ります。結果をJSONとして返します。
カスタムツールの実装:
use adk_core::{Tool, ToolContext, Result};
use async_trait::async_trait;
use serde_json::{json, Value};
use std::sync::Arc;
struct WeatherTool {
api_key: String,
}
#[async_trait]
impl Tool for WeatherTool {
fn name(&self) -> &str {
"get_weather"
}
fn description(&self) -> &str {
"Get current weather for a city. Use this when the user asks about weather conditions."
}
fn parameters_schema(&self) -> Option<Value> {
Some(json!({
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "都市名(例:'London', 'New York')"
},
"units": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度単位"
}
},
"required": ["city"]
}))
}
async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
let city = args["city"].as_str().unwrap_or("Unknown");
let units = args["units"].as_str().unwrap_or("celsius");
// ここで天気APIを呼び出す...
Ok(json!({
"city": city,
"temperature": 22,
"units": units,
"condition": "sunny"
}))
}
}
長時間実行されるツール:
長時間かかる操作(ファイル処理、外部API呼び出しなど)の場合、ツールを長時間実行としてマークします。
fn is_long_running(&self) -> bool { true }
長時間実行されるツールは、すぐにタスクIDを返します。モデルは、保留中にツールを再度呼び出さないように指示されます。
Toolset Trait
Toolsetはコンテキストに基づいてツールを動的に提供します。以下の場合に使用します。
- ツールがユーザー権限に依存する場合
- ツールが外部ソース(MCPサーバー)からロードされる場合
- 実行中にツールの利用可能性が変化する場合
#[async_trait]
pub trait Toolset: Send + Sync {
/// Toolset identifier
fn name(&self) -> &str;
/// Return available tools for the current context
async fn tools(&self, ctx: Arc<dyn ReadonlyContext>) -> Result<Vec<Arc<dyn Tool>>>;
}
例: 権限ベースのToolset:
struct AdminToolset {
admin_tools: Vec<Arc<dyn Tool>>,
user_tools: Vec<Arc<dyn Tool>>,
}
#[async_trait]
impl Toolset for AdminToolset {
fn name(&self) -> &str { "admin_toolset" }
async fn tools(&self, ctx: Arc<dyn ReadonlyContext>) -> Result<Vec<Arc<dyn Tool>>> {
if ctx.user_id().starts_with("admin_") {
Ok(self.admin_tools.clone())
} else {
Ok(self.user_tools.clone())
}
}
}
Context Traits
Contextは、実行中にAgentやToolに情報とサービスを提供します。Context traitには階層があり、それぞれがより多くの機能を追加します。
ReadonlyContext
あらゆる場所で利用可能な基本情報:
pub trait ReadonlyContext: Send + Sync {
/// Unique ID for this invocation
fn invocation_id(&self) -> &str;
/// Name of the currently executing agent
fn agent_name(&self) -> &str;
/// User identifier
fn user_id(&self) -> &str;
/// Application name
fn app_name(&self) -> &str;
/// Session identifier
fn session_id(&self) -> &str;
/// The user's input that triggered this invocation
fn user_content(&self) -> &Content;
}
CallbackContext
アーティファクトへのアクセスを追加します(ReadonlyContextを拡張):
pub trait CallbackContext: ReadonlyContext {
/// Access to artifact storage (if configured)
fn artifacts(&self) -> Option<Arc<dyn Artifacts>>;
}
ToolContext
Toolの実行用(CallbackContextを拡張):
pub trait ToolContext: CallbackContext {
/// ID of the function call that triggered this tool
fn function_call_id(&self) -> &str;
/// Get current event actions (transfer, escalate, etc.)
fn actions(&self) -> EventActions;
/// Set event actions (e.g., trigger a transfer)
fn set_actions(&self, actions: EventActions);
/// Search long-term memory
async fn search_memory(&self, query: &str) -> Result<Vec<MemoryEntry>>;
}
InvocationContext
Agent実行のための完全なContext(CallbackContextを拡張):
pub trait InvocationContext: CallbackContext {
/// The agent being executed
fn agent(&self) -> Arc<dyn Agent>;
/// Memory service (if configured)
fn memory(&self) -> Option<Arc<dyn Memory>>;
/// Current session with state and history
fn session(&self) -> &dyn Session;
/// Execution configuration
fn run_config(&self) -> &RunConfig;
/// Signal that this invocation should end
fn end_invocation(&self);
/// Check if invocation has been ended
fn ended(&self) -> bool;
}
セッションと状態
セッションは会話を追跡します。状態はセッション内にデータを保存します。
セッション
pub trait Session: Send + Sync {
/// ユニークなセッション識別子
fn id(&self) -> &str;
/// このセッションが属するアプリケーション
fn app_name(&self) -> &str;
/// このセッションを所有するユーザー
fn user_id(&self) -> &str;
/// 可変の状態ストレージ
fn state(&self) -> &dyn State;
/// この会話の以前のメッセージ
fn conversation_history(&self) -> Vec<Content>;
}
状態
スコープ付きプレフィックスを持つキーバリュー型ストレージ:
pub trait State: Send + Sync {
/// キーによる値の取得
fn get(&self, key: &str) -> Option<Value>;
/// 値の設定
fn set(&mut self, key: String, value: Value);
/// すべてのキーバリューペアの取得
fn all(&self) -> HashMap<String, Value>;
}
状態プレフィックス:
状態キーは、スコープと永続性を制御するためにプレフィックスを使用します:
| プレフィックス | スコープ | 永続性 | ユースケース |
|---|---|---|---|
user: | ユーザーレベル | すべてのセッションで | ユーザー設定、設定 |
app: | アプリケーションレベル | アプリケーション全体 | 共有設定 |
temp: | ターンレベル | 各ターン後にクリア | 一時的な計算データ |
| (なし) | セッションレベル | このセッションのみ | 会話コンテキスト |
// コールバックまたはツール内
let state = ctx.session().state();
// ユーザー設定 (セッション間で永続化)
state.set("user:theme".into(), json!("dark"));
// セッション固有のデータ
state.set("current_topic".into(), json!("weather"));
// 一時的なデータ (このターン後にクリアされる)
state.set("temp:step_count".into(), json!(1));
// 値の読み取り
if let Some(theme) = state.get("user:theme") {
println!("Theme: {}", theme);
}
エラー処理
ADK はすべての操作に統合されたエラータイプを使用します:
pub enum AdkError {
Agent(String), // Agent 実行エラー
Tool(String), // Tool 実行エラー
Model(String), // LLM API エラー
Session(String), // Session ストレージエラー
Artifact(String), // Artifact ストレージエラー
Config(String), // 設定エラー
Io(std::io::Error), // ファイル/ネットワーク I/O エラー
Json(serde_json::Error), // JSON パースエラー
}
pub type Result<T> = std::result::Result<T, AdkError>;
Tool におけるエラー処理:
async fn execute(&self, ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
let city = args["city"]
.as_str()
.ok_or_else(|| AdkError::Tool("Missing 'city' parameter".into()))?;
let response = reqwest::get(&format!("https://api.weather.com/{}", city))
.await
.map_err(|e| AdkError::Tool(format!("API error: {}", e)))?;
Ok(json!({ "weather": "sunny" }))
}
EventStream
Agent は単一の応答ではなく、イベントのストリームを返します:
pub type EventStream = Pin<Box<dyn Stream<Item = Result<Event>> + Send>>;
イベントの処理:
use futures::StreamExt;
let mut stream = agent.run(ctx).await?;
while let Some(result) = stream.next().await {
match result {
Ok(event) => {
// テキストコンテンツの確認
if let Some(content) = event.content() {
for part in &content.parts {
if let Some(text) = part.text() {
print!("{}", text);
}
}
}
// 状態変更の確認
for (key, value) in event.state_delta() {
println!("State changed: {} = {}", key, value);
}
// これが最終応答であるか確認
if event.is_final_response() {
println!("\n[Done]");
}
}
Err(e) => {
eprintln!("Error: {}", e);
break;
}
}
}
Llm トレイト
すべての LLM プロバイダーが実装するトレイトです。
#[async_trait]
pub trait Llm: Send + Sync {
/// モデル識別子 (例: "gemini-2.0-flash", "gpt-4o")
fn name(&self) -> &str;
/// コンテンツを生成 (ストリーミングまたは非ストリーミング)
async fn generate_content(
&self,
request: LlmRequest,
stream: bool,
) -> Result<LlmResponseStream>;
}
LlmRequest:
pub struct LlmRequest {
pub contents: Vec<Content>, // 会話履歴
pub tools: Vec<ToolDeclaration>, // 利用可能なツール
pub system_instruction: Option<String>, // システムプロンプト
pub config: GenerateContentConfig, // 温度、max_tokens など
}
LlmResponse:
pub struct LlmResponse {
pub content: Option<Content>, // 生成されたコンテンツ
pub finish_reason: Option<FinishReason>, // 生成が停止した理由
pub usage: Option<UsageMetadata>, // トークン数
pub partial: bool, // これはストリーミングのチャンクか?
pub turn_complete: bool, // ターンが完了したか?
}
すべてのプロバイダー (Gemini, OpenAI, Anthropic, Ollama など) がこのトレイトを実装しており、相互に交換可能です:
// 1行変更するだけでプロバイダーを切り替え
let model: Arc<dyn Llm> = Arc::new(GeminiModel::new(&key, "gemini-2.0-flash")?);
// let model: Arc<dyn Llm> = Arc::new(OpenAIClient::new(config)?);
// let model: Arc<dyn Llm> = Arc::new(AnthropicClient::new(config)?);
let agent = LlmAgentBuilder::new("assistant")
.model(model)
.build()?;