함수 도구

사용자 지정 Rust 함수로 에이전트 기능을 확장합니다.


함수 도구란 무엇인가요?

함수 도구를 사용하면 에이전트에 대화 이상의 기능을 제공할 수 있습니다. APIs 호출, 계산 수행, 데이터베이스 액세스 또는 사용자 지정 로직 실행 등이 가능합니다. LLM은 사용자의 요청에 따라 도구를 사용할 시점을 결정합니다.

주요 특징:

  • 🚀 #[tool] 매크로 - 보일러플레이트 없는 도구 등록(권장)
  • 🔧 FunctionTool::new() - 모든 async 함수를 수동으로 래핑
  • 📝 JSON 매개변수 - 유연한 입력/출력
  • 🎯 타입 안전 스키마 - schemars를 통해 타입에서 자동으로 JSON 스키마 생성
  • 🔗 컨텍스트 액세스 - 세션 상태, 아티팩트, 메모리

도구 실행 파이프라인

Rendering architecture…

도구를 만드는 가장 빠른 방법입니다. 매크로는 문서 주석을 설명으로 읽고 인수 타입에서 JSON 스키마를 파생합니다.

use adk_tool::tool;
use adk_core::AdkError;
use schemars::JsonSchema;
use serde::Deserialize;
use serde_json::{json, Value};

#[derive(Deserialize, JsonSchema)]
struct WeatherArgs {
    /// The city to look up
    city: String,
    /// Temperature unit (celsius or fahrenheit)
    unit: Option<String>,
}

/// Get the current weather for a city.
#[tool]
async fn get_weather(args: WeatherArgs) -> Result<Value, AdkError> {
    Ok(json!({ "temp": 22, "city": args.city }))
}

// Generated: pub struct GetWeather; — implements adk_core::Tool
// Use it: agent_builder.tool(Arc::new(GetWeather))

도구에 세션 컨텍스트가 필요한 경우 첫 번째 매개변수로 Arc<dyn ToolContext>를 추가합니다.

use adk_core::ToolContext;
use std::sync::Arc;

/// Search the user's saved documents.
#[tool]
async fn search_docs(
    ctx: Arc<dyn ToolContext>,
    args: SearchArgs,
) -> Result<Value, AdkError> {
    let user_id = ctx.user_id();
    // ... use context for scoped access
}

도구 메타데이터 속성

매크로에서 직접 도구를 읽기 전용, 동시성 안전 또는 장시간 실행으로 표시할 수 있습니다.

/// Look up cached data — no side effects, safe for parallel dispatch.
#[tool(read_only, concurrency_safe)]
async fn cache_lookup(args: LookupArgs) -> Result<Value, AdkError> {
    Ok(json!({"result": "cached"}))
}

/// Start a long-running background report.
#[tool(long_running)]
async fn generate_report(args: ReportArgs) -> Result<Value, AdkError> {
    Ok(json!({"task_id": "abc123", "status": "processing"}))
}

사용 가능한 속성(모두 선택 사항이며 자유롭게 조합 가능):

속성효과
read_onlyis_read_only() → true — 동시 Auto 디스패치에 필요한 두 신호 중 하나
concurrency_safeis_concurrency_safe() → true — 동시 Auto 디스패치에 필요한 두 신호 중 하나
long_runningis_long_running() → true — LLM이(가) 보류 중인 도구를 다시 호출하지 않도록 방지

속성이 없는 일반 #[tool]은 기본값(모든 false)을 유지하므로 기존 코드는 영향을 받지 않습니다.


대안: FunctionTool::new()

동적 도구를 사용하거나 명시적 등록을 선호하는 경우:

FunctionTool::new()으로 도구를 만들고, 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-3.7-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이 전달할 매개변수를 알 수 없어 도구를 호출하지 않을 수 있습니다.

작동 방식:

  1. 사용자가 묻습니다: "도쿄의 날씨는 어떤가요?"
  2. LLM이 {"location": "Tokyo"}와 함께 get_weather을 호출하기로 결정합니다.
  3. 도구가 {"location": "Tokyo", "temperature": "22°C", "conditions": "sunny"}을 반환합니다.
  4. LLM이 응답을 형식화합니다: "도쿄의 날씨는 섭씨 22도로 맑습니다."

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"))?;
        
        let quantity = args.get("quantity")
            .and_then(|v| v.as_i64())
            .ok_or_else(|| adk_core::AdkError::tool("quantity is required"))?;
        
        // 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와 함께 타입이 지정된 구조체를 사용합니다:

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")),
        };
        Ok(json!({ "result": result }))
    },
)
.with_parameters_schema::<CalculatorParams>();

스키마는 schemars을 사용하여 Rust 타입에서 자동으로 생성됩니다.


4단계: 다중 도구 에이전트

하나의 에이전트에 여러 도구를 추가합니다:

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은 사용자의 요청에 따라 적절한 도구를 자동으로 선택합니다.


오류 처리

도구별 오류가 발생하면 Tool 컴포넌트를 사용하여 오류를 반환합니다:

use adk_core::{AdkError, ErrorComponent, ErrorCategory};

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(|| AdkError::new(
                ErrorComponent::Tool,
                ErrorCategory::InvalidInput,
                "tool.divide.missing_param",
                "Parameter 'a' is required",
            ))?;
        let b = args.get("b").and_then(|v| v.as_f64())
            .ok_or_else(|| AdkError::new(
                ErrorComponent::Tool,
                ErrorCategory::InvalidInput,
                "tool.divide.missing_param",
                "Parameter 'b' is required",
            ))?;
        
        if b == 0.0 {
            return Err(AdkError::new(
                ErrorComponent::Tool,
                ErrorCategory::InvalidInput,
                "tool.divide.division_by_zero",
                "Cannot divide by zero",
            ));
        }
        
        Ok(json!({ "result": a / b }))
    },
);

빠른 마이그레이션을 위해 이전 버전과 호환되는 축약형도 사용할 수 있습니다:

Err(AdkError::tool("Parameter 'a' is required"))

오류 메시지는 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() - 현재 사용자 ID
  • ctx.session_id() - 현재 세션 ID
  • ctx.agent_name() - 에이전트 이름
  • ctx.artifacts() - 아티팩트 저장소 액세스
  • ctx.search_memory(query) - 메모리 서비스 검색

장시간 실행되는 도구

상당한 시간이 걸리는 작업(데이터 처리, 외부 APIs)에는 비차단 패턴을 사용합니다.

  1. 도구 시작은 task_id와 함께 즉시 반환됩니다.
  2. 백그라운드 작업은 비동기적으로 실행됩니다.
  3. 상태 도구를 사용하면 사용자가 진행 상황을 확인할 수 있습니다.
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)은 이 도구가 대기 중 상태를 반환한다는 것을 에이전트에 알립니다.
  • 이 도구는 tokio::spawn()을 사용해 작업을 생성하고 즉시 반환합니다.
  • 사용자가 진행 상황을 폴링할 수 있도록 상태 확인 도구를 제공합니다.

이렇게 하면 LLM이 도구를 반복적으로 호출하는 것을 방지하는 메모가 추가됩니다.


도구에서 스트리밍 진행 상황 가져오기

장시간 실행되는 도구는 계속 실행되는 동안 중간 출력을 UI로 전송할 수 있으므로, 사용자는 최종 결과를 기다리는 대신 셸 명령의 표준 출력, 빌드 로그 또는 다운로드되는 바이트를 실시간으로 확인할 수 있습니다. 출력이 도착하면 ToolContext::emit_progress를 호출합니다.

use adk_core::{Result, Tool, ToolContext};
use std::sync::Arc;

#[async_trait::async_trait]
impl Tool for BuildTool {
    // ... name(), description(), parameters_schema() ...

    async fn execute(&self, ctx: Arc<dyn ToolContext>, args: serde_json::Value) -> Result<serde_json::Value> {
        // Emit chunks as they arrive — each becomes a partial Event on the
        // agent's EventStream, the SAME stream the model's reply travels on.
        ctx.emit_progress("stdout", "Compiling project...\n").await;
        ctx.emit_progress("stdout", "Build finished in 4.2s\n").await;
        ctx.emit_progress("stderr", "warning: unused variable `x`\n").await;

        // The final return value is still the complete result the model consumes.
        Ok(serde_json::json!({ "status": "ok", "warnings": 1 }))
    }
}

시그니처:

async fn emit_progress(&self, stream: &str, chunk: &str)
  • stream — 청크의 레이블입니다. "stdout", "stderr" 또는 사용자 지정 채널을 사용할 수 있습니다.
  • chunk — 내보낼 텍스트입니다. 터미널 스타일 출력에는 줄마다 내보냅니다.

UI에 전달되는 방식. 프레임워크는 각 청크를 에이전트의 EventStream에 부분 Event으로 전달합니다. 소비자는 event.tool_progress_stream()를 사용해 이를 감지하고 실시간으로 렌더링합니다. 두 번째 채널이나 로그 스크래핑은 없습니다. 진행 상황, 모델 텍스트 및 최종 도구 결과가 모두 하나의 순서가 보장된 스트림으로 전달됩니다.

하위 호환성. 기본 emit_progress은 아무 작업도 수행하지 않으므로 스트리밍하지 않는 기존 도구와 실행자는 영향을 받지 않습니다. 스트리밍을 선택한 도구만 진행 상황을 내보내며, tool_progress_stream()을 확인하는 소비자만 이를 확인할 수 있습니다.

진행은 제한되며 손실이 발생합니다. 도구는 클라이언트가 소비하는 것보다 더 빠르게 출력을 생성할 수 있습니다. 예를 들어 컴파일러 로그, 셸 명령 또는 종료되지 않는 루프가 이에 해당합니다. 따라서 프레임워크는 보유하고 전달할 데이터를 제한 없이 계속 늘리는 대신, 보유하고 전달할 수 있는 양을 제한합니다:

제한값초과 시
도구 배치당 대기열 깊이256개 이벤트도구가 공간을 위해 최대 100ms 동안 대기한 후 청크가 삭제됨
청크당 바이트8 KiB문자의 경계에서 청크가 잘림
도구 호출당 바이트 수1 MiB남은 진행 상황은 전달되지 않음

이러한 이유로 출력이 삭제될 때마다 해당 호출에 대해 [adk: tool progress truncated] 텍스트를 전달하는 진행 이벤트가 정확히 하나씩 발생하므로, 공백이 조용히 사라지지 않고 항상 표시됩니다. 따라서 느린 소비자는 도구를 잠시 느리게 만들 수는 있지만, 도구를 무기한 중단시키거나 메모리를 소진시킬 수는 없습니다.

이러한 제한은 progress에만 적용됩니다. 도구의 최종 결과에는 영향을 주지 않으므로, 필요하다면 도구 내부에서 큰 결과를 잘라내십시오.

단일 이벤트 피드에서 실시간 bash 출력과 일회성 도구 결과(read_file, grep, glob)를 렌더링하는 완전한 웹 UI는 streaming_bash 예제를 참조하십시오. 스트리밍 bash 도구 자체는 adk-devtools에 있습니다.


예제 실행

cargo adk new tool_agent --template tools
cd tool_agent
cargo run

모범 사례

  1. 명확한 설명 - LLM이 도구를 언제 사용해야 하는지 이해하도록 도움
  2. 입력 검증 - 매개변수가 누락된 경우 유용한 오류 메시지 반환
  3. 구조화된 JSON 반환 - 명확한 필드 이름 사용
  4. 도구의 역할 집중 - 각 도구가 한 가지 작업을 잘 수행하도록 구성
  5. 스키마 사용 - 복잡한 도구의 경우 매개변수 스키마 정의
  6. 안전한 읽기 전용 도구 표시 - .with_read_only(true)과 .with_concurrency_safe(true)을 모두 설정하여 Auto 디스패치가 해당 도구를 동시 실행 대상에 포함할 수 있도록 구성

도구 메타데이터: 읽기 전용 및 동시성

더 효율적인 디스패치를 위해 도구를 읽기 전용 또는 동시성 안전으로 표시하십시오.

// A lookup tool that performs no side effects
let lookup = FunctionTool::new("lookup", "Look up data", |_ctx, args| async move {
    Ok(json!({"result": "cached data"}))
})
.with_read_only(true)
.with_concurrency_safe(true); // Auto mode requires both signals

// A mutation tool (defaults: read_only=false, concurrency_safe=false)
let update = FunctionTool::new("update", "Update record", |_ctx, args| async move {
    Ok(json!({"updated": true}))
});

ToolExecutionStrategy::Auto이 활성화되면 디스패치 루프는 먼저 선택된 도구가 is_read_only()와 is_concurrency_safe() 모두에서 true를 반환하는 호출을 동시에 실행합니다. 그런 다음 나머지 호출을 모두 순차적으로 실행합니다. ToolExecutionStrategy::Parallel은 이러한 신호를 무시하는 명시적 재정의이므로, 동시성 안전성은 이를 호출하는 측이 책임집니다.


SimpleToolContext: 에이전트 루프 외부에서 도구 사용

에이전트 루프 외부에서 도구를 호출해야 하는 경우(테스트, MCP 서버 모드, 하위 에이전트 위임)에는 전체 ToolContext 특성 계층 구조를 구현하는 대신 SimpleToolContext을 사용하세요.

use adk_tool::SimpleToolContext;
use adk_core::ToolContext;
use std::sync::Arc;

// Construct with just a caller name — all other fields get sensible defaults
let ctx = SimpleToolContext::new("my-test-harness");

// Optionally override the function call ID
let ctx = SimpleToolContext::new("my-mcp-server")
    .with_function_call_id("custom-call-id");

// Bind a session ID so session-aware tools (and MCP servers that key state by
// session) see a stable identifier instead of the empty default.
let ctx = SimpleToolContext::new("my-mcp-server")
    .with_session_id("session-42");

// Use it to execute any tool
let tool_ctx: Arc<dyn ToolContext> = Arc::new(ctx);
let result = my_tool.execute(tool_ctx, json!({"key": "value"})).await?;

기본값: user_id() → "anonymous", session_id() / branch() → "", artifacts() → None, search_memory() → 빈 vec. invocation_id과 function_call_id는 모두 자동 생성되는 UUIDs입니다. 도구가 세션별로 상태를 라우팅하거나 유지할 때는 with_session_id(...)을 사용하여 실제 세션을 설정하세요.


StatefulTool: 호출 간 공유 상태

호출 간에 상태(카운터, 캐시, 연결 풀)를 유지해야 하는 도구에는 StatefulTool<S>을 사용하세요.

use adk_tool::StatefulTool;
use adk_core::ToolContext;
use std::sync::Arc;
use tokio::sync::RwLock;

struct AppCache {
    entries: RwLock<HashMap<String, String>>,
}

let cache = Arc::new(AppCache {
    entries: RwLock::new(HashMap::new()),
});

let cache_tool = StatefulTool::new(
    "cache_lookup",
    "Look up a value in the application cache",
    cache.clone(),
    |state, _ctx, args| async move {
        let key = args["key"].as_str().unwrap_or("");
        let entries = state.entries.read().await;
        let value = entries.get(key).cloned().unwrap_or_default();
        Ok(json!({"key": key, "value": value}))
    },
)
.with_read_only(true)
.with_concurrency_safe(true);

StatefulTool는 호출할 때마다 Arc<S>을 복제하므로(저렴한 참조 카운트 증가), 모든 실행이 동일한 기본 상태를 공유합니다. FunctionTool와 동일한 빌더 메서드인 with_long_running, with_parameters_schema, with_response_schema, with_scopes, with_read_only 및 with_concurrency_safe을 지원합니다.



멀티모달 함수 응답

Gemini 3 모델은 함수 응답에서 JSON뿐만 아니라 이미지, 오디오, PDFs 및 파일 참조도 받을 수 있습니다. 도구는 JSON 반환 값에 inline_data 및/또는 file_data 배열을 포함하여 멀티모달 데이터를 반환할 수 있습니다.

/// Tool that returns a chart image alongside JSON metadata.
async fn generate_chart(
    _ctx: Arc<dyn ToolContext>,
    args: serde_json::Value,
) -> Result<serde_json::Value> {
    let png_bytes: Vec<u8> = render_chart(&args);

    // Include inline_data in the return value — the framework extracts it automatically
    Ok(json!({
        "response": {
            "title": "Q4 Sales",
            "chart_type": "bar"
        },
        "inline_data": [{
            "mime_type": "image/png",
            "data": png_bytes
        }]
    }))
}

프레임워크는 자동으로 다음을 수행합니다.

  1. FunctionResponseData::from_tool_result()를 통해 inline_data/file_data를 감지합니다.
  2. 인라인 바이너리 데이터를 Base64로 인코딩합니다.
  3. Gemini 3 API 형식에 맞게 해당 파트를 functionResponse 와이어 객체 안에 중첩합니다.

파일 참조

외부에 저장된 대용량 파일의 경우 바이트를 직접 포함하는 대신 URI과 함께 file_data을 사용하세요.

Ok(json!({
    "response": { "document_id": "report-2024", "pages": 12 },
    "file_data": [{
        "mime_type": "application/pdf",
        "file_uri": "gs://my-bucket/reports/report-2024.pdf"
    }]
}))

직접 구성

프레임워크 수준의 코드(사용자 지정 에이전트, 변환 계층)의 경우 FunctionResponseData을 직접 구성합니다.

use adk_core::{FunctionResponseData, InlineDataPart, FileDataPart};

// JSON + inline image
let frd = FunctionResponseData::with_inline_data(
    "chart_tool",
    json!({"title": "Q4 Chart"}),
    vec![InlineDataPart { mime_type: "image/png".into(), data: png_bytes }],
);

// JSON + file reference
let frd = FunctionResponseData::with_file_data(
    "doc_tool",
    json!({"status": "ok"}),
    vec![FileDataPart { mime_type: "application/pdf".into(), file_uri: "gs://bucket/file.pdf".into() }],
);

// JSON + both
let frd = FunctionResponseData::with_multimodal("tool", json, inline_parts, file_parts);

참고: 멀티모달 함수 응답에는 gemini-3.7-flash과 같은 Gemini 3 시리즈 모델이 필요합니다. 이전 모델은 400 오류를 반환합니다.

완전히 작동하는 예제는 examples/multimodal_function_response/을 참조하세요.


이전: ← mistral.rs | 다음: 기본 제공 도구 →