코어 타입
ADK-Rust의 기반을 형성하는 adk-core의 기본 타입 및 트레이트.
Content 및 Part
ADK의 모든 메시지는 Content와 Part를 통해 흐릅니다. 이 타입들을 이해하는 것은 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> 스트림)을 반환하는데, 그 이유는 다음과 같습니다:
- Streaming: 더 나은 사용자 경험을 위해 응답을 토큰별로 스트리밍할 수 있습니다.
- Tool calls: 실행 중에 여러 Tool 호출 및 응답이 발생합니다.
- State changes: 상태 업데이트는 이벤트로 발생합니다.
- 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 →