함수 도구
사용자 지정 Rust 함수로 에이전트 기능을 확장합니다.
함수 도구란 무엇인가요?
함수 도구를 사용하면 API 호출, 계산 수행, 데이터베이스 접근 또는 사용자 지정 로직과 같이 대화 이상의 기능을 에이전트에 부여할 수 있습니다. LLM은 사용자의 요청에 따라 Tool을 사용할 시기를 결정합니다.
주요 특징:
- 🔧 호출 가능한 Tool로 모든 async 함수를 래핑합니다.
- 📝 JSON 파라미터 - 유연한 입력/출력
- 🎯 타입 안전 스키마 - 선택적 JSON Schema 유효성 검사
- 🔗 컨텍스트 접근 - session 상태, 아티팩트, 메모리
1단계: 기본 Tool
FunctionTool::new()를 사용하여 Tool을 생성하고, LLM이 어떤 파라미터를 전달해야 할지 알 수 있도록 항상 스키마를 추가하세요.
use adk_rust::prelude::*;
use adk_rust::Launcher;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::sync::Arc;
#[derive(JsonSchema, Serialize, Deserialize)]
struct WeatherParams {
/// The city or location to get weather for
location: String,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
dotenvy::dotenv().ok();
let api_key = std::env::var("GOOGLE_API_KEY")?;
let model = GeminiModel::new(&api_key, "gemini-2.5-flash")?;
// Weather tool with proper schema
let weather_tool = FunctionTool::new(
"get_weather",
"Get current weather for a location",
|_ctx, args| async move {
let location = args.get("location")
.and_then(|v| v.as_str())
.unwrap_or("unknown");
Ok(json!({
"location": location,
"temperature": "22°C",
"conditions": "sunny"
}))
},
)
.with_parameters_schema::<WeatherParams>(); // Required for LLM to call correctly!
let agent = LlmAgentBuilder::new("weather_agent")
.instruction("You help users check the weather. Always use the get_weather tool.")
.model(Arc::new(model))
.tool(Arc::new(weather_tool))
.build()?;
Launcher::new(Arc::new(agent)).run().await?;
Ok(())
}
⚠️ 중요: 항상
.with_parameters_schema<T>()를 사용하세요. 이것이 없으면 LLM은 어떤 파라미터를 전달해야 할지 알지 못하고 Tool을 호출하지 못할 수 있습니다.
작동 방식:
- 사용자 질문: "도쿄 날씨는 어때?"
- LLM은
{"location": "Tokyo"}파라미터와 함께get_weather를 호출하기로 결정합니다. - Tool은
{"location": "Tokyo", "temperature": "22°C", "conditions": "sunny"}를 반환합니다. - LLM이 응답을 구성합니다: "도쿄 날씨는 22°C로 맑습니다."
2단계: 파라미터 처리
JSON args에서 파라미터를 추출합니다:
let order_tool = FunctionTool::new(
"process_order",
"Process an order. Parameters: product_id (required), quantity (required), priority (optional)",
|_ctx, args| async move {
// Required parameters - return error if missing
let product_id = args.get("product_id")
.and_then(|v| v.as_str())
.ok_or_else(|| adk_core::AdkError::Tool("product_id is required".into()))?;
let quantity = args.get("quantity")
.and_then(|v| v.as_i64())
.ok_or_else(|| adk_core::AdkError::Tool("quantity is required".into()))?;
// Optional parameter with default
let priority = args.get("priority")
.and_then(|v| v.as_str())
.unwrap_or("normal");
Ok(json!({
"order_id": "ORD-12345",
"product_id": product_id,
"quantity": quantity,
"priority": priority,
"status": "confirmed"
}))
},
);
3단계: 스키마를 사용한 타입 지정 매개변수
복잡한 도구의 경우, JSON Schema와 함께 타입이 지정된 struct를 사용합니다:
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(JsonSchema, Serialize, Deserialize)]
struct CalculatorParams {
/// The arithmetic operation to perform
operation: Operation,
/// First operand
a: f64,
/// Second operand
b: f64,
}
#[derive(JsonSchema, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
enum Operation {
Add,
Subtract,
Multiply,
Divide,
}
let calculator = FunctionTool::new(
"calculator",
"Perform arithmetic operations",
|_ctx, args| async move {
let params: CalculatorParams = serde_json::from_value(args)?;
let result = match params.operation {
Operation::Add => params.a + params.b,
Operation::Subtract => params.a - params.b,
Operation::Multiply => params.a * params.b,
Operation::Divide if params.b != 0.0 => params.a / params.b,
Operation::Divide => return Err(adk_core::AdkError::Tool("Cannot divide by zero".into())),
};
Ok(json!({ "result": result }))
},
)
.with_parameters_schema::<CalculatorParams>();
스키마는 schemars를 사용하여 Rust 타입으로부터 자동 생성됩니다.
4단계: 다중 도구 Agent
하나의 Agent에 여러 도구를 추가합니다:
let agent = LlmAgentBuilder::new("assistant")
.instruction("Help with calculations, conversions, and weather.")
.model(Arc::new(model))
.tool(Arc::new(calc_tool))
.tool(Arc::new(convert_tool))
.tool(Arc::new(weather_tool))
.build()?;
LLM은 사용자 요청에 따라 올바른 도구를 자동으로 선택합니다.
오류 처리
도구별 오류에 대해 AdkError::Tool을 반환합니다:
let divide_tool = FunctionTool::new(
"divide",
"Divide two numbers",
|_ctx, args| async move {
let a = args.get("a").and_then(|v| v.as_f64())
.ok_or_else(|| adk_core::AdkError::Tool("Parameter 'a' is required".into()))?;
let b = args.get("b").and_then(|v| v.as_f64())
.ok_or_else(|| adk_core::AdkError::Tool("Parameter 'b' is required".into()))?;
if b == 0.0 {
return Err(adk_core::AdkError::Tool("Cannot divide by zero".into()));
}
Ok(json!({ "result": a / b }))
},
);
오류 메시지는 LLM으로 전달되며, LLM은 재시도하거나 다른 입력을 요청할 수 있습니다.
도구 컨텍스트
ToolContext를 통해 세션 정보에 접근합니다:
#[derive(JsonSchema, Serialize, Deserialize)]
struct GreetParams {
#[serde(default)]
message: Option<String>,
}
let greet_tool = FunctionTool::new(
"greet",
"Greet the user with session info",
|ctx, _args| async move {
let user_id = ctx.user_id();
let session_id = ctx.session_id();
let agent_name = ctx.agent_name();
Ok(json!({
"greeting": format!("Hello, user {}!", user_id),
"session": session_id,
"served_by": agent_name
}))
},
)
.with_parameters_schema::<GreetParams>();
사용 가능한 컨텍스트:
ctx.user_id()- 현재 사용자 IDctx.session_id()- 현재 세션 IDctx.agent_name()- Agent의 이름ctx.artifacts()- 아티팩트 저장소 접근ctx.search_memory(query)- 메모리 검색 서비스
오래 실행되는 Tool
데이터 처리, 외부 API 호출 등 상당한 시간이 소요되는 작업을 위해서는 논블로킹(non-blocking) 패턴을 사용합니다:
- Start Tool은
task_id를 즉시 반환합니다. - 백그라운드 작업은 비동기적으로 실행됩니다.
- Status Tool을 통해 사용자는 진행 상황을 확인할 수 있습니다.
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(JsonSchema, Serialize, Deserialize)]
struct ReportParams {
topic: String,
}
#[derive(JsonSchema, Serialize, Deserialize)]
struct StatusParams {
task_id: String,
}
// Shared task store
let tasks: Arc<RwLock<HashMap<String, TaskState>>> = Arc::new(RwLock::new(HashMap::new()));
let tasks1 = tasks.clone();
let tasks2 = tasks.clone();
// Tool 1: Start (returns immediately)
let start_tool = FunctionTool::new(
"generate_report",
"Start generating a report. Returns task_id immediately.",
move |_ctx, args| {
let tasks = tasks1.clone();
async move {
let topic = args.get("topic").and_then(|v| v.as_str()).unwrap_or("general").to_string();
let task_id = format!("task_{}", rand::random::<u32>());
// Store initial state
tasks.write().await.insert(task_id.clone(), TaskState {
status: "processing".to_string(),
progress: 0,
result: None,
});
// Spawn background work (non-blocking!)
let tasks_bg = tasks.clone();
let tid = task_id.clone();
tokio::spawn(async move {
// Simulate work...
tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
if let Some(t) = tasks_bg.write().await.get_mut(&tid) {
t.status = "completed".to_string();
t.result = Some("Report complete".to_string());
}
});
// Return immediately with task_id
Ok(json!({"task_id": task_id, "status": "processing"}))
}
},
)
.with_parameters_schema::<ReportParams>()
.with_long_running(true); // Mark as long-running
// Tool 2: Check status
let status_tool = FunctionTool::new(
"check_report_status",
"Check report generation status",
move |_ctx, args| {
let tasks = tasks2.clone();
async move {
let task_id = args.get("task_id").and_then(|v| v.as_str()).unwrap_or("");
if let Some(t) = tasks.read().await.get(task_id) {
Ok(json!({"status": t.status, "result": t.result}))
} else {
Ok(json!({"error": "Task not found"}))
}
}
},
)
.with_parameters_schema::<StatusParams>();
핵심 사항:
.with_long_running(true)는 Agent에게 이Tool이 보류 상태를 반환함을 알려줍니다.- 이
Tool은tokio::spawn()을 사용하여 작업을 생성하고 즉시 반환합니다. - 사용자가 진행 상황을 폴링(poll)할 수 있도록 상태 확인
Tool을 제공합니다.
이는 LLM이 Tool을 반복적으로 호출하는 것을 방지하기 위한 참고 사항을 추가합니다.
실행 예제
cd official_docs_examples/tools/function_tools_test
# Basic tool with closure
cargo run --bin basic
# Tool with typed JSON schema
cargo run --bin with_schema
# Multi-tool agent (3 tools)
cargo run --bin multi_tool
# Tool context (session info)
cargo run --bin context
# Long-running tool
cargo run --bin long_running
모범 사례
- 명확한 설명 -
LLM이Tool을 언제 사용해야 하는지 이해하도록 돕습니다. - 입력 유효성 검사 - 누락된 매개변수에 대해 유용한 오류 메시지를 반환합니다.
- 구조화된 JSON 반환 - 명확한 필드 이름을 사용합니다.
- Tool 집중 유지 - 각
Tool은 하나의 작업을 잘 수행해야 합니다. - 스키마 사용 - 복잡한
Tool의 경우 매개변수 스키마를 정의합니다.
관련 항목
- Built-in Tools - 미리 빌드된
Tool(GoogleSearch,ExitLoop) - MCP Tools -
Model Context Protocol통합 - LlmAgent -
Tool을Agent에 추가하기
이전: ← mistral.rs | 다음: Built-in Tools →