أدوات الدوال
وسّع قدرات الوكيل بدوال Rust مخصّصة.
ما هي أدوات الدوال؟
تتيح لك أدوات الدوال منح الوكلاء قدرات تتجاوز المحادثة - استدعاء APIs، إجراء الحسابات، الوصول إلى قواعد البيانات، أو أي منطق مخصّص. يقرّر LLM متى يستخدم أداةً ما بناءً على طلب المستخدم.
أبرز النقاط:
- 🚀
#[tool]macro - تسجيل الأدوات بدون أي boilerplate (مُوصى به)- 🔧
FunctionTool::new()- تغليف أي دالة async يدويًا- 📝 JSON parameters - إدخال/إخراج مرن
- 🎯 Type-safe schemas - إنشاء JSON Schema تلقائيًا من الأنواع عبر schemars
- 🔗 Context access - حالة الجلسة، والـ artifacts، والذاكرة
خط أنابيب تنفيذ الأداة
الموصى به: ماكرو #[tool]
أسرع طريقة لإنشاء الأدوات. يقرأ الماكرو تعليق التوثيق الخاص بك بوصفه الوصف، ويستنتج مخطط 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_only | is_read_only() → true — أحد إشارتين مطلوبتين لإرسال Auto المتزامن |
concurrency_safe | is_concurrency_safe() → true — أحد إشارتين مطلوبتين لإرسال Auto المتزامن |
long_running | is_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-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 ما المعاملات التي يجب تمريرها وقد لا يستدعي الأداة.
كيف يعمل:
- يسأل المستخدم: "ما حالة الطقس في طوكيو؟"
- يقرر LLM استدعاء
get_weatherباستخدام{"location": "Tokyo"} - تُرجع الأداة
{"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"))?;
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:
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>();
يتم توليد المخطط تلقائيًا من أنواع Rust باستخدام schemars.
الخطوة 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()- معرف المستخدم الحاليctx.session_id()- معرف الجلسة الحاليةctx.agent_name()- اسم الوكيلctx.artifacts()- الوصول إلى تخزين الأصولctx.search_memory(query)- خدمة البحث في الذاكرة
الأدوات طويلة الأمد
للعمليات التي تستغرق وقتًا كبيرًا (معالجة البيانات، APIs الخارجية)، استخدم النمط غير الحاجب:
- بدء الأداة يعود فورًا مع task_id
- العمل في الخلفية يُنفَّذ بشكل غير متزامن
- أداة الحالة تتيح للمستخدمين التحقق من التقدم
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 من استدعاء الأداة بشكل متكرر.
بث التقدم من أداة
يمكن للأدوات طويلة الأمد دفع مخرجات وسيطة إلى واجهة المستخدم أثناء التنفيذ، بحيث يرى المستخدم مخرجات stdout لأمر shell، أو سجلات build، أو بايتات التنزيل مباشرة بدلًا من انتظار النتيجة النهائية. استدعِ 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— النص المراد إصداره (أصدر لكل سطر للمخرجات الشبيهة بالطرفية).
كيف يصل إلى واجهة المستخدم. يعيد الإطار تمرير كل كتلة كـ Event جزئي على
EventStream الخاص بالوكيل. يكتشفه المستهلك باستخدام event.tool_progress_stream() ويعرضه مباشرة. لا توجد قناة ثانية ولا استخراج للسجلات — فالتقدم، ونص النموذج، والنتيجة النهائية للأداة كلها تصل في تدفق مرتب واحد.
متوافق مع الإصدارات السابقة. emit_progress الافتراضي هو no-op، لذا لا تتأثر الأدوات والمشغلات الموجودة التي لا تبث. فقط الأدوات التي تختار ذلك تُصدر التقدم، وفقط المستهلكون الذين يتحققون من tool_progress_stream() يرونه.
التقدم محدود وقد يضيع. يمكن لأداة أن تنتج مخرجات أسرع مما يستهلكه العميل — سجل مترجم، أمر shell، حلقة خارجة عن السيطرة — لذا يحدد الإطار ما سيحتفظ به ويمرره بدلًا من النمو بلا حدود:
| الحد | القيمة | عند تجاوزه |
|---|---|---|
| عمق الطابور لكل دفعة أداة | 256 حدثًا | تنتظر الأداة حتى 100 ms لتوفير مساحة، ثم يتم إسقاط الجزء |
| عدد البايتات لكل جزء | 8 KiB | يتم اقتطاع الجزء عند حدّ حرف |
| البايتات لكل استدعاء أداة | 1 MiB | لا يتم تمرير التقدم المتبقي |
عندما يتم إسقاط الإخراج لأيٍّ من هذه الأسباب، يتم إصدار حدث تقدّم واحد بالضبط يحمل النص [adk: tool progress truncated] لتلك الاستدعاء، بحيث تكون هناك فجوة مرئية دائمًا بدلًا من أن تكون صامتة. لذلك فإن المستهلك البطيء يبطئ الأداة لفترة قصيرة، لكنه لا يستطيع أن يعلقها إلى أجل غير مسمى أو يستنزف الذاكرة.
تنطبق هذه الحدود على التقدّم فقط. لا تتأثر النتيجة النهائية للأداة، لذا قم باقتطاع النتائج الكبيرة داخل الأداة إذا كان ذلك مهمًا بالنسبة لك.
راجع مثال
streaming_bashللحصول على واجهة ويب كاملة تعرض إخراجbashالمباشر ونتائج الأدوات أحادية الإطلاق (read_file،grep،glob) من قناة أحداث واحدة. توجد أداة التدفقbashنفسها فيadk-devtools.
أمثلة التشغيل
cargo adk new tool_agent --template tools
cd tool_agent
cargo run
أفضل الممارسات
- أوصاف واضحة - ساعد LLM على فهم متى يجب استخدام الأداة
- تحقق من المدخلات - أعد رسائل خطأ مفيدة للمعاملات المفقودة
- أعد JSON منظَّمة - استخدم أسماء حقول واضحة
- أبقِ الأدوات مركزة - يجب أن تقوم كل أداة بشيء واحد بشكل جيد
- استخدم المخططات - بالنسبة للأدوات المعقدة، عرّف مخططات المعاملات
- وسم الأدوات الآمنة للقراءة فقط - اضبط كلًّا من
.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 نشطًا، تشغّل حلقة الإرسال الاستدعاءات أولًا بشكل متزامن عندما تُرجع الأدوات المحددة true من كلٍّ من is_read_only() وis_concurrency_safe(). ثم تنفذ جميع الاستدعاءات المتبقية بالتسلسل. ToolExecutionStrategy::Parallel هو تجاوز صريح يتجاوز هذه الإشارات، لذا فإن المستدعي هو المسؤول عن أمان التزامن.
SimpleToolContext: استخدام الأدوات خارج حلقة الوكيل
عندما تحتاج إلى استدعاء أداة خارج حلقة الوكيل (الاختبار، وضع خادم MCP، تفويض الوكيل الفرعي)، استخدم SimpleToolContext بدلًا من تنفيذ التسلسل الهرمي الكامل للصفة ToolContext:
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.
ذات صلة
- الأدوات المدمجة - أدوات مُنشأة مسبقًا (GoogleSearch، ExitLoop)
- أدوات MCP - تكامل بروتوكول سياق النموذج
- LlmAgent - إضافة الأدوات إلى الوكلاء
استجابات الوظائف متعددة الوسائط
تدعم نماذج Gemini 3 استقبال الصور والصوت وPDFs ومراجع الملفات في استجابات الوظائف — وليس فقط JSON. يمكن للأدوات إرجاع بيانات متعددة الوسائط من خلال تضمين مصفوفتَي inline_data و/أو file_data في قيمة الإرجاع JSON:
/// 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
}]
}))
}
يقوم الإطار تلقائيًا بما يلي:
- يكتشف
inline_data/file_dataعبرFunctionResponseData::from_tool_result() - يشفّر البيانات الثنائية المضمَّنة باستخدام Base64
- يضم الأجزاء داخل الكائن السلكي
functionResponse(بما يطابق صيغة Gemini 3 API)
مراجع الملفات
بالنسبة للملفات الكبيرة المخزنة خارجيًا، استخدم file_data مع URI بدلًا من تضمين البايتات:
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 (
gemini-3-flash-preview،gemini-3-pro-preview). النماذج الأقدم تُرجع خطأ 400.
راجع examples/multimodal_function_response/ للحصول على مثال كامل يعمل.
السابق: ← mistral.rs | التالي: الأدوات المدمجة →