코어 타입

ADK-Rust의 기반을 형성하는 adk-core의 기본 타입 및 트레이트.

Content 및 Part

ADK의 모든 메시지는 ContentPart를 통해 흐릅니다. 이 타입들을 이해하는 것은 agents, tools, callbacks를 사용하는 데 필수적입니다.

Content

Content는 대화에서 단일 메시지를 나타냅니다. 여기에는 role (누가 보냈는지)과 하나 이상의 parts (실제 내용)가 있습니다.

역할:

  • "user" - 사용자로부터의 메시지
  • "model" - AI 모델의 응답
  • "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를 위한 핵심 추상화입니다. LLM Agent, 워크플로 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 시스템 내에서 고유해야 합니다.
  • description(): Agent가 Tool로 사용되거나 라우팅 결정을 내릴 때 LLM에 표시됩니다.
  • sub_agents(): 하위 Agent를 반환합니다. 리프 Agent(LlmAgent)의 경우 비어 있고, 컨테이너 Agent(SequentialAgent, ParallelAgent)의 경우 채워집니다.
  • run(): 주요 실행 메서드입니다. 컨텍스트를 수신하고 이벤트 스트림을 반환합니다.

EventStream을 사용하는 이유?

Agent는 단일 응답 대신 EventStream (Result<Event> 스트림)을 반환하는데, 그 이유는 다음과 같습니다:

  1. Streaming: 더 나은 사용자 경험을 위해 응답을 토큰별로 스트리밍할 수 있습니다.
  2. Tool calls: 실행 중에 여러 Tool 호출 및 응답이 발생합니다.
  3. State changes: 상태 업데이트는 이벤트로 발생합니다.
  4. Transfers: Agent 전송은 이벤트를 통해 신호됩니다.

Tool Trait

Tool은 대화 외의 에이전트 기능을 확장합니다. 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(): 모델이 이 Tool을 호출하는 데 사용하는 함수 이름입니다. 짧고 설명적으로 유지하십시오 (예: get_weather, search_database).
  • description(): 모델이 언제 Tool을 사용해야 하는지 이해하는 데 중요합니다. Tool이 무엇을 하는지, 언제 사용해야 하는지 구체적으로 설명하십시오.
  • parameters_schema(): 모델에 어떤 인수를 제공해야 하는지 알려주는 JSON Schema입니다. 이것이 없으면 모델이 추측합니다.
  • execute(): 파싱된 인수를 serde_json::Value로 받습니다. 결과를 JSON으로 반환합니다.

커스텀 Tool 구현:

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 { 
        "도시의 현재 날씨를 가져옵니다. 사용자가 날씨 상태에 대해 물을 때 사용하십시오."
    }
    
    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");
        
        // Call weather API here...
        
        Ok(json!({
            "city": city,
            "temperature": 22,
            "units": units,
            "condition": "sunny"
        }))
    }
}

장기 실행 Tool:

오래 걸리는 작업 (파일 처리, 외부 API 호출)의 경우, Tool을 장기 실행으로 표시하십시오:

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

장기 실행 Tool은 즉시 task ID를 반환합니다. 모델은 작업이 보류 중인 동안 Tool을 다시 호출하지 않도록 지시받습니다.


Toolset 트레잇

Toolset은 컨텍스트에 따라 도구를 동적으로 제공합니다. 다음 경우에 사용하십시오:

  • 도구가 사용자 권한에 따라 달라지는 경우
  • 도구가 외부 소스(MCP 서버)에서 로드되는 경우
  • 실행 중에 도구 가용성이 변경되는 경우
#[async_trait]
pub trait Toolset: Send + Sync {
    /// Toolset 식별자
    fn name(&self) -> &str;
    
    /// 현재 컨텍스트에서 사용 가능한 Tool을 반환합니다.
    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 트레잇

Context는 실행 중에 Agent와 Tool에 정보와 서비스를 제공합니다. 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

아티팩트 액세스를 추가합니다 (ReadonlyContext 확장):

pub trait CallbackContext: ReadonlyContext {
    /// 아티팩트 저장소 액세스 (구성된 경우)
    fn artifacts(&self) -> Option<Arc<dyn Artifacts>>;
}

ToolContext

Tool 실행용 (CallbackContext 확장):

pub trait ToolContext: CallbackContext {
    /// 이 Tool을 트리거한 함수 호출의 ID
    fn function_call_id(&self) -> &str;
    
    /// 현재 이벤트 액션 가져오기 (전달, 에스컬레이션 등)
    fn actions(&self) -> EventActions;
    
    /// 이벤트 액션 설정 (예: 전달 트리거)
    fn set_actions(&self, actions: EventActions);
    
    /// 장기 기억 검색
    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;
}

세션 및 상태

Session은 대화를 추적합니다. State는 session 내에 데이터를 저장합니다.

Session

pub trait Session: Send + Sync {
    /// Unique session identifier
    fn id(&self) -> &str;
    
    /// Application this session belongs to
    fn app_name(&self) -> &str;
    
    /// User who owns this session
    fn user_id(&self) -> &str;
    
    /// Mutable state storage
    fn state(&self) -> &dyn State;
    
    /// Previous messages in this conversation
    fn conversation_history(&self) -> Vec<Content>;
}

State

스코프 접두사가 있는 키-값 저장소:

pub trait State: Send + Sync {
    /// Get a value by key
    fn get(&self, key: &str) -> Option<Value>;
    
    /// Set a value
    fn set(&mut self, key: String, value: Value);
    
    /// Get all key-value pairs
    fn all(&self) -> HashMap<String, Value>;
}

State 접두사:

State 키는 스코프와 지속성을 제어하기 위해 접두사를 사용합니다:

접두사스코프지속성사용 사례
user:사용자 레벨모든 session에서사용자 기본 설정, 설정
app:애플리케이션 레벨애플리케이션 전반공유 구성
temp:턴 레벨각 턴마다 지워짐임시 계산 데이터
(없음)Session 레벨이 session에서만대화 컨텍스트
// In a callback or tool
let state = ctx.session().state();

// User preference (persists across sessions)
state.set("user:theme".into(), json!("dark"));

// Session-specific data
state.set("current_topic".into(), json!("weather"));

// Temporary data (cleared after this turn)
state.set("temp:step_count".into(), json!(1));

// Read values
if let Some(theme) = state.get("user:theme") {
    println!("Theme: {}", theme);
}

오류 처리

ADK는 모든 작업에 대해 통합된 오류 타입을 사용합니다:

pub enum AdkError {
    Agent(String),      // Agent execution errors
    Tool(String),       // Tool execution errors
    Model(String),      // LLM API errors
    Session(String),    // Session storage errors
    Artifact(String),   // Artifact storage errors
    Config(String),     // Configuration errors
    Io(std::io::Error), // File/network I/O errors
    Json(serde_json::Error), // JSON parsing errors
}

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) => {
            // Check for text content
            if let Some(content) = event.content() {
                for part in &content.parts {
                    if let Some(text) = part.text() {
                        print!("{}", text);
                    }
                }
            }
            
            // Check for state changes
            for (key, value) in event.state_delta() {
                println!("State changed: {} = {}", key, value);
            }
            
            // Check if this is the final response
            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,    // Temperature, 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 등)가 이 특성을 구현하므로 상호 교환하여 사용할 수 있습니다:

// 한 줄만 변경하여 제공자 전환
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 →