核心类型

来自 adk-core 的基础类型和 trait,它们构成了 ADK-Rust 的基础。

Content 和 Part

ADK 中的每条消息都流经 ContentPart。理解这些类型对于使用 Agent、Tool 和回调至关重要。

Content

Content 表示对话中的一条单一消息。它包含一个 role(发送者)和一个或多个 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 Trait

Agent trait 是 ADK 中所有 Agent 的核心抽象。每种 Agent 类型——LLM Agent、工作流 Agent、自定义 Agent——都实现了这个 trait。

#[async_trait]
pub trait Agent: Send + Sync {
    /// 此 Agent 的唯一标识符
    fn name(&self) -> &str;
    
    /// 此 Agent 功能的人类可读描述
    fn description(&self) -> &str;
    
    /// 子 Agent(用于 SequentialAgent, ParallelAgent 等工作流 Agent)
    fn sub_agents(&self) -> &[Arc<dyn Agent>];
    
    /// 执行 Agent 并返回 EventStream
    async fn run(&self, ctx: Arc<dyn InvocationContext>) -> Result<EventStream>;
}

要点:

  • name(): 用于日志记录、Agent 间转移和身份识别。在多 Agent 系统中必须是唯一的。
  • description(): 当 Agent 用作 FunctionTool 或用于路由决策时,会显示给 LLMs。
  • sub_agents(): 返回子 Agent。对于叶子 Agent (LlmAgent) 为空,对于容器 (SequentialAgent, ParallelAgent) 则会填充。
  • run(): 主要执行方法。接收上下文并返回 EventStream。

为何使用 EventStream?

Agent 返回 EventStream(一个 Result<Event> 流),而不是单个响应,原因如下:

  1. 流式传输:响应可以按 token 流式传输,以提供更好的用户体验
  2. Tool 调用:执行期间会发生多次 Tool 调用和响应
  3. 状态变更:状态更新会作为事件发出
  4. 转移:Agent 转移通过事件发出信号

工具 Trait

工具将 agent 的能力扩展到对话之外。它们允许 agent 与 API、数据库、文件系统交互,或执行计算。

#[async_trait]
pub trait Tool: Send + Sync {
    /// Tool name (used in function calls from the model)
    fn name(&self) -> &str;
    
    /// Description shown to the LLM to help it decide when to use this tool
    fn description(&self) -> &str;
    
    /// JSON Schema defining the expected parameters
    fn parameters_schema(&self) -> Option<Value> { None }
    
    /// JSON Schema defining the response format
    fn response_schema(&self) -> Option<Value> { None }
    
    /// Whether this tool runs asynchronously (returns task ID immediately)
    fn is_long_running(&self) -> bool { false }
    
    /// Execute the tool with given arguments
    async fn execute(&self, ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value>;
}

要点:

  • name(): 模型用于调用此工具的函数名称。保持简短和描述性(例如,get_weathersearch_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": "City name (e.g., 'London', 'New York')"
                },
                "units": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "Temperature units"
                }
            },
            "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");
        
        // Call weather API here...
        
        Ok(json!({
            "city": city,
            "temperature": 22,
            "units": units,
            "condition": "sunny"
        }))
    }
}

长时间运行的工具:

对于需要长时间运行的操作(文件处理、外部 API 调用),将工具标记为长时间运行:

fn is_long_running(&self) -> bool { true }

长时间运行的工具会立即返回一个任务 ID。模型被指示在任务待处理期间不要再次调用该工具。


Toolset 特性

Toolset 根据上下文动态提供工具。在以下情况使用此特性:

  • 工具依赖于用户权限
  • 工具从外部源(MCP servers)加载
  • 工具可用性在执行期间发生变化
#[async_trait]
pub trait Toolset: Send + Sync {
    /// Toolset 标识符
    fn name(&self) -> &str;
    
    /// 返回当前上下文可用的工具
    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 特性

Contexts 在执行期间向 agents 和 tools 提供信息和服务。Context 特性存在一个层级结构,每个层级都增加了更多功能。

ReadonlyContext

随处可用的基本信息:

pub trait ReadonlyContext: Send + Sync {
    /// 此调用的唯一 ID
    fn invocation_id(&self) -> &str;
    
    /// 当前正在执行的 agent 的名称
    fn agent_name(&self) -> &str;
    
    /// 用户标识符
    fn user_id(&self) -> &str;
    
    /// 应用程序名称
    fn app_name(&self) -> &str;
    
    /// Session 标识符
    fn session_id(&self) -> &str;
    
    /// 触发此调用的用户输入
    fn user_content(&self) -> &Content;
}

CallbackContext

添加 artifact 访问(扩展 ReadonlyContext):

pub trait CallbackContext: ReadonlyContext {
    /// 访问 artifact 存储(如果已配置)
    fn artifacts(&self) -> Option<Arc<dyn Artifacts>>;
}

ToolContext

用于 tool 执行(扩展 CallbackContext):

pub trait ToolContext: CallbackContext {
    /// 触发此 tool 的 function call ID
    fn function_call_id(&self) -> &str;
    
    /// 获取当前事件 actions(transfer、escalate 等)
    fn actions(&self) -> EventActions;
    
    /// 设置事件 actions(例如,触发 transfer)
    fn set_actions(&self, actions: EventActions);
    
    /// 搜索长期 memory
    async fn search_memory(&self, query: &str) -> Result<Vec<MemoryEntry>>;
}

InvocationContext

agent 执行的完整上下文(扩展 CallbackContext):

pub trait InvocationContext: CallbackContext {
    /// 正在执行的 agent
    fn agent(&self) -> Arc<dyn Agent>;
    
    /// Memory 服务(如果已配置)
    fn memory(&self) -> Option<Arc<dyn Memory>>;
    
    /// 具有状态和历史记录的当前 Session
    fn session(&self) -> &dyn Session;
    
    /// 执行配置
    fn run_config(&self) -> &RunConfig;
    
    /// 标志此调用应结束
    fn end_invocation(&self);
    
    /// 检查调用是否已结束
    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>;

工具中的错误处理:

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,    // 温度、最大 tokens 等
}

LlmResponse:

pub struct LlmResponse {
    pub content: Option<Content>,         // 生成的内容
    pub finish_reason: Option<FinishReason>, // 生成停止的原因
    pub usage: Option<UsageMetadata>,     // Token 计数
    pub partial: bool,                    // 这是否是流式数据块?
    pub turn_complete: bool,              // 本轮是否完成?
}

所有提供商(Gemini、OpenAI、Anthropic、Ollama 等)都实现了此特性,使得它们可以互换使用:

// 通过更改一行代码来切换提供商
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()?;

上一页: ← Introduction | 下一页: Runner →