أدوات الدوال

وسّع إمكانات الوكيل باستخدام دوال Rust مخصّصة.


ما هي أدوات الدوال؟

تتيح لك أدوات الدوال منح الوكلاء قدرات تتجاوز المحادثة، مثل استدعاء APIs، وإجراء الحسابات، والوصول إلى قواعد البيانات، أو تنفيذ أي منطق مخصّص. يقرر LLM متى يستخدم أداةً استنادًا إلى طلب المستخدم.

أبرز النقاط:

  • 🚀 وحدة الماكرو #[tool] - تسجيل الأدوات دون تعليمات برمجية متكررة (موصى به)
  • 🔧 FunctionTool::new() - تغليف أي دالة غير متزامنة يدويًا
  • 📝 معلمات JSON - إدخال وإخراج مرنان
  • 🎯 مخططات آمنة الأنواع - إنشاء مخطط JSON تلقائيًا من الأنواع عبر schemars
  • 🔗 الوصول إلى السياق - حالة الجلسة، والقطع الأثرية، والذاكرة

مسار تنفيذ الأداة

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 من إعادة استدعاء أداة معلّقة

Plain #[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 استدعاء get_weather باستخدام {"location": "Tokyo"}
  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:

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 خارجية)، استخدم النمط غير الحاجب:

  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 من استدعاء الأداة بشكل متكرر.


بث التقدم من أداة

يمكن للأدوات طويلة التشغيل دفع مخرجات وسيطة إلى واجهة المستخدم أثناء استمرار تنفيذها، بحيث يرى المستخدم stdout لأمر shell أو سجلات الإنشاء أو وحدات بايت التنزيل مباشرةً بدلًا من انتظار النتيجة النهائية. استدعِ 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 الافتراضي لا ينفذ أي إجراء، ولذلك لا تتأثر الأدوات وعمليات التشغيل الحالية التي لا تبث المخرجات. لا تُصدر التقدمَ إلا الأدوات التي تختار تفعيله، ولا تلاحظه إلا المستهلكات التي تتحقق من tool_progress_stream().

التقدّم محدود وفاقد للمعلومات. يمكن لأداة أن تنتج المخرجات أسرع مما يستهلكها عميل — مثل سجل مترجم، أو أمر shell، أو حلقة لا نهائية — لذلك يضع إطار العمل حدًا لما يحتفظ به ويمرّره بدلًا من أن ينمو بلا حدود:

الحدالقيمةعند تجاوزه
عمق قائمة الانتظار لكل دفعة أدوات256 حدثًاتنتظر الأداة مدة تصل إلى 100 مللي ثانية لإتاحة مساحة، ثم يتم إسقاط الجزء
البايتات لكل جزء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

أفضل الممارسات

  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 نشطًا، تنفذ حلقة الإرسال أولًا الاستدعاءات بالتزامن عندما تعيد أدواتها المحددة 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() → متجه فارغ. كل من 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 استقبال الصور والصوت و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
        }]
    }))
}

يعمل الإطار تلقائيًا على:

  1. اكتشاف inline_data/file_data عبر FunctionResponseData::from_tool_result()
  2. ترميز البيانات الثنائية المضمنة باستخدام Base64
  3. تضمين الأجزاء داخل كائن السلك functionResponse (بما يتوافق مع تنسيق API الخاص بـGemini 3)

مراجع الملفات

بالنسبة إلى الملفات الكبيرة المخزنة خارجيًا، استخدم 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.7-flash. تُرجع النماذج الأقدم خطأ 400.

راجع examples/multimodal_function_response/ للحصول على مثال كامل يعمل.


السابق: ← mistral.rs | التالي: الأدوات المضمنة →

أدوات الدوال - وثائق ADK-Rust | ADK-Rust