Artifacts

توفر Artifacts طريقة لتخزين واسترجاع البيانات الثنائية (الصور وملفات PDF وملفات الصوت وما إلى ذلك) داخل تطبيقات الـ agent الخاصة بك. يتعامل نظام الـ artifact مع ترقيم الإصدارات وتحديد النطاق (namespace scoping) واستمرارية البيانات عبر الـ sessions.

Overview

يتكون نظام الـ artifact في ADK-Rust من:

  • Part: تمثيل البيانات الأساسي الذي يمكن أن يحمل نصًا أو بيانات ثنائية مع MIME types
  • ArtifactService: الـ trait الذي يحدد عمليات تخزين الـ artifact
  • InMemoryArtifactService: تطبيق في الذاكرة للتطوير والاختبار
  • ScopedArtifacts: غلاف يبسط عمليات الـ artifact من خلال التعامل التلقائي مع سياق الـ session

يتم تحديد نطاق الـ Artifacts حسب الـ application، والـ user، والـ session، مما يوفر العزل والتنظيم. يمكن أن تكون الملفات ذات نطاق session (افتراضي) أو ذات نطاق user (باستخدام البادئة user:).

Part Representation

يمثل الـ enum Part البيانات التي يمكن تخزينها كـ artifacts:

pub enum Part {
    Text { text: String },
    InlineData { mime_type: String, data: Vec<u8> },
    FunctionCall { name: String, args: serde_json::Value },
    FunctionResponse { name: String, response: serde_json::Value },
}

بالنسبة للـ artifacts، ستستخدم بشكل أساسي:

  • Part::Text لبيانات النص
  • Part::InlineData للبيانات الثنائية مع MIME types

Basic Usage

أبسط طريقة للعمل مع الـ artifacts هي من خلال الـ trait Artifacts، المتاح في الـ agent contexts:

use adk_rust::prelude::*;

// In an agent tool or callback
async fn save_report(ctx: &ToolContext) -> Result<Value> {
    let artifacts = ctx.artifacts();
    
    // Save text data
    let version = artifacts.save(
        "report.txt",
        &Part::Text { text: "Report content".to_string() }
    ).await?;
    
    // Save binary data
    let image_data = vec![0xFF, 0xD8, 0xFF]; // JPEG header
    artifacts.save(
        "chart.jpg",
        &Part::InlineData {
            mime_type: "image/jpeg".to_string(),
            data: image_data,
        }
    ).await?;
    
    Ok(json!({ "saved": true, "version": version }))
}

ArtifactService Trait

يحدد الـArtifactService trait العمليات الأساسية لإدارة الـartifact:

#[async_trait]
pub trait ArtifactService: Send + Sync {
    async fn save(&self, req: SaveRequest) -> Result<SaveResponse>;
    async fn load(&self, req: LoadRequest) -> Result<LoadResponse>;
    async fn delete(&self, req: DeleteRequest) -> Result<()>;
    async fn list(&self, req: ListRequest) -> Result<ListResponse>;
    async fn versions(&self, req: VersionsRequest) -> Result<VersionsResponse>;
}

عملية Save

حفظ artifact مع ترقيم تلقائي أو صريح للإصدارات:

use adk_artifact::{InMemoryArtifactService, SaveRequest};
use adk_core::Part;

let service = InMemoryArtifactService::new();

let response = service.save(SaveRequest {
    app_name: "my_app".to_string(),
    user_id: "user_123".to_string(),
    session_id: "session_456".to_string(),
    file_name: "document.pdf".to_string(),
    part: Part::InlineData {
        mime_type: "application/pdf".to_string(),
        data: pdf_bytes,
    },
    version: None, // Auto-increment version
}).await?;

println!("Saved as version: {}", response.version);

عملية Load

تحميل أحدث إصدار أو إصدار محدد:

use adk_artifact::LoadRequest;

// Load latest version
let response = service.load(LoadRequest {
    app_name: "my_app".to_string(),
    user_id: "user_123".to_string(),
    session_id: "session_456".to_string(),
    file_name: "document.pdf".to_string(),
    version: None, // Load latest
}).await?;

// Load specific version
let response = service.load(LoadRequest {
    app_name: "my_app".to_string(),
    user_id: "user_123".to_string(),
    session_id: "session_456".to_string(),
    file_name: "document.pdf".to_string(),
    version: Some(2), // Load version 2
}).await?;

match response.part {
    Part::InlineData { mime_type, data } => {
        println!("Loaded {} bytes of {}", data.len(), mime_type);
    }
    _ => {}
}

عملية List

سرد جميع الـartifacts في جلسة:

use adk_artifact::ListRequest;

let response = service.list(ListRequest {
    app_name: "my_app".to_string(),
    user_id: "user_123".to_string(),
    session_id: "session_456".to_string(),
}).await?;

for file_name in response.file_names {
    println!("Found artifact: {}", file_name);
}

عملية Delete

حذف إصدار محدد أو جميع الإصدارات:

use adk_artifact::DeleteRequest;

// Delete specific version
service.delete(DeleteRequest {
    app_name: "my_app".to_string(),
    user_id: "user_123".to_string(),
    session_id: "session_456".to_string(),
    file_name: "document.pdf".to_string(),
    version: Some(1), // Delete version 1
}).await?;

// Delete all versions
service.delete(DeleteRequest {
    app_name: "my_app".to_string(),
    user_id: "user_123".to_string(),
    session_id: "session_456".to_string(),
    file_name: "document.pdf".to_string(),
    version: None, // Delete all versions
}).await?;

عملية Versions

سرد جميع إصدارات الـartifact:

use adk_artifact::VersionsRequest;

let response = service.versions(VersionsRequest {
    app_name: "my_app".to_string(),
    user_id: "user_123".to_string(),
    session_id: "session_456".to_string(),
    file_name: "document.pdf".to_string(),
}).await?;

println!("Available versions: {:?}", response.versions);
// Output: [3, 2, 1] (sorted newest first)

إصدار

تدعم البيانات الاصطناعية (Artifacts) تحديد الإصدار تلقائيًا:

  • عند الحفظ دون تحديد إصدار، يقوم النظام بزيادة الإصدار تلقائيًا من أحدث إصدار
  • يتم تعيين الإصدار 1 للحفظ الأول
  • كل حفظ لاحق يزيد رقم الإصدار
  • يمكنك تحميل أو حذف أو الاستعلام عن إصدارات محددة
// First save - becomes version 1
let v1 = service.save(SaveRequest {
    file_name: "data.json".to_string(),
    part: Part::Text { text: "v1 data".to_string() },
    version: None,
    // ... other fields
}).await?;
assert_eq!(v1.version, 1);

// Second save - becomes version 2
let v2 = service.save(SaveRequest {
    file_name: "data.json".to_string(),
    part: Part::Text { text: "v2 data".to_string() },
    version: None,
    // ... other fields
}).await?;
assert_eq!(v2.version, 2);

// Load latest (version 2)
let latest = service.load(LoadRequest {
    file_name: "data.json".to_string(),
    version: None,
    // ... other fields
}).await?;

// Load specific version
let old = service.load(LoadRequest {
    file_name: "data.json".to_string(),
    version: Some(1),
    // ... other fields
}).await?;

نطاق مساحة الاسم

يمكن تحديد نطاق البيانات الاصطناعية على مستويين:

محددة بنطاق الجلسة (الافتراضي)

افتراضيًا، تكون البيانات الاصطناعية محددة بنطاق جلسة معينة. كل جلسة لديها مساحة اسم بيانات اصطناعية معزولة خاصة بها:

// Session 1
service.save(SaveRequest {
    session_id: "session_1".to_string(),
    file_name: "notes.txt".to_string(),
    // ... other fields
}).await?;

// Session 2 - different artifact with same name
service.save(SaveRequest {
    session_id: "session_2".to_string(),
    file_name: "notes.txt".to_string(),
    // ... other fields
}).await?;

// هذه بيانات اصطناعية منفصلة

محددة بنطاق المستخدم

البيانات الاصطناعية التي تحتوي على البادئة user: تتم مشاركتها عبر جميع الجلسات لمستخدم واحد:

// Save in session 1
service.save(SaveRequest {
    session_id: "session_1".to_string(),
    file_name: "user:profile.jpg".to_string(), // user: prefix
    // ... other fields
}).await?;

// Load in session 2 - same artifact
let profile = service.load(LoadRequest {
    session_id: "session_2".to_string(),
    file_name: "user:profile.jpg".to_string(),
    // ... other fields
}).await?;

تمكّن البادئة user: ما يلي:

  • مشاركة البيانات عبر محادثات متعددة
  • تفضيلات المستخدم الدائمة
  • التخزين المؤقت على مستوى المستخدم

InMemoryArtifactService

توفر InMemoryArtifactService تطبيقًا في الذاكرة مناسبًا للتطوير والاختبار:

use adk_artifact::InMemoryArtifactService;
use std::sync::Arc;

let service = Arc::new(InMemoryArtifactService::new());

// استخدم مع الوكلاء
let agent = LlmAgentBuilder::new("my_agent")
    .model(model)
    .build()?;

// يمكن تمرير الخدمة إلى الـ runners أو استخدامها مباشرة

ملاحظة: لا يتم الاحتفاظ بالبيانات على القرص. للاستخدام في الإنتاج، فكر في تطبيق ArtifactService مخصص مدعوم بقاعدة بيانات أو تخزين سحابي.

ScopedArtifacts

يقوم مغلف ScopedArtifacts بتبسيط عمليات البيانات الاصطناعية عن طريق حقن سياق الجلسة تلقائيًا:

use adk_artifact::{ScopedArtifacts, InMemoryArtifactService};
use std::sync::Arc;

let service = Arc::new(InMemoryArtifactService::new());

let artifacts = ScopedArtifacts::new(
    service,
    "my_app".to_string(),
    "user_123".to_string(),
    "session_456".to_string(),
);

// واجهة برمجة تطبيقات بسيطة - لا حاجة لتحديد التطبيق/المستخدم/الجلسة
let version = artifacts.save("file.txt", &Part::Text {
    text: "content".to_string()
}).await?;

let part = artifacts.load("file.txt").await?;
let files = artifacts.list().await?;

هذه هي نفس الواجهة المتاحة من خلال ToolContext::artifacts() و CallbackContext::artifacts().

أنماط شائعة

تحليل الصور باستخدام نماذج الوسائط المتعددة

عندما تريد أن يقوم LLM بتحليل صورة مخزنة كـ artifact، تحتاج إلى استخدام BeforeModel callback لحقن الصورة مباشرة في LLM request. يتبع هذا النمط adk-go.

لماذا لا نستخدم tool؟ استجابات Tool في LLM APIs هي نص JSON. إذا قامت tool بإرجاع بيانات صورة (حتى لو كانت مشفرة بـ base64-encoded)، فإن النموذج يراها كنص، وليس كصورة فعلية. للتحليل الحقيقي متعدد الوسائط، يجب تضمين الصورة كـ Part::InlineData في محتوى المحادثة.

use adk_rust::prelude::*;
use adk_rust::artifact::{ArtifactService, InMemoryArtifactService, SaveRequest, LoadRequest};
use std::sync::Arc;

#[tokio::main]
async fn main() -> Result<()> {
    let api_key = std::env::var("GOOGLE_API_KEY")?;
    let model = Arc::new(GeminiModel::new(&api_key, "gemini-2.5-flash")?);

    // Create artifact service and save an image
    let artifact_service = Arc::new(InMemoryArtifactService::new());
    let image_bytes = std::fs::read("photo.png")?;

    artifact_service.save(SaveRequest {
        app_name: "image_app".to_string(),
        user_id: "user".to_string(),
        session_id: "init".to_string(),
        file_name: "user:photo.png".to_string(),  // user-scoped for cross-session access
        part: Part::InlineData {
            data: image_bytes,
            mime_type: "image/png".to_string(),
        },
        version: None,
    }).await?;

    // Clone for use in callback
    let callback_service = artifact_service.clone();

    let agent = LlmAgentBuilder::new("image_analyst")
        .description("Analyzes images")
        .instruction("You are an image analyst. Describe what you see in the image.")
        .model(model)
        // Use BeforeModel callback to inject image into the request
        .before_model_callback(Box::new(move |_ctx, mut request| {
            let service = callback_service.clone();
            Box::pin(async move {
                // Load the image artifact
                if let Ok(response) = service.load(LoadRequest {
                    app_name: "image_app".to_string(),
                    user_id: "user".to_string(),
                    session_id: "init".to_string(),
                    file_name: "user:photo.png".to_string(),
                    version: None,
                }).await {
                    // Inject image into the last user content
                    if let Some(last_content) = request.contents.last_mut() {
                        if last_content.role == "user" {
                            last_content.parts.push(response.part);
                        }
                    }
                }

                // Continue with the modified request
                Ok(BeforeModelResult::Continue(request))
            })
        }))
        .build()?;

    // Now when users ask "What's in the image?", the model will see the actual image
    Ok(())
}

نقاط رئيسية:

  • استخدم BeforeModelResult::Continue(request) لتمرير الـ request المعدل إلى النموذج
  • استخدم BeforeModelResult::Skip(response) إذا كنت تريد إرجاع استجابة مخبأة بدلاً من ذلك
  • يتم حقن الصورة كـ Part::InlineData، والتي يفسرها Gemini كبيانات صورة فعلية
  • استخدم البادئة user: لـ artifacts التي يجب أن تكون متاحة عبر الجلسات

تحليل مستندات PDF

يمكن لنماذج Gemini معالجة مستندات PDF بشكل أصلي باستخدام نفس نمط BeforeModel callback. يتم حقن ملفات PDF باستخدام MIME type application/pdf:

// Save PDF as artifact
artifact_service.save(SaveRequest {
    app_name: "my_app".to_string(),
    user_id: "user".to_string(),
    session_id: "init".to_string(),
    file_name: "user:document.pdf".to_string(),
    part: Part::InlineData {
        data: pdf_bytes,
        mime_type: "application/pdf".to_string(),
    },
    version: None,
}).await?;

// Use BeforeModel callback to inject PDF (same pattern as images)
.before_model_callback(Box::new(move |_ctx, mut request| {
    let service = callback_service.clone();
    Box::pin(async move {
        if let Ok(response) = service.load(LoadRequest {
            file_name: "user:document.pdf".to_string(),
            // ... other fields
        }).await {
            if let Some(last_content) = request.contents.last_mut() {
                if last_content.role == "user" {
                    last_content.parts.push(response.part);
                }
            }
        }
        Ok(BeforeModelResult::Continue(request))
    })
}))

قدرات Gemini لـ PDF:

  • استخراج وتحليل محتوى النص
  • الإجابة على الأسئلة المتعلقة بالمستندات
  • تلخيص أقسام أو مستندات كاملة
  • معالجة ما يصل إلى حوالي 1000 صفحة
  • دعم OCR للمستندات الممسوحة ضوئيًا

انظر examples/artifacts/chat_pdf.rs للحصول على مثال عملي كامل.

تخزين الصور المولدة

async fn generate_and_save_image(ctx: &ToolContext) -> Result<Value> {
    let artifacts = ctx.artifacts();
    
    // Generate image (pseudo-code)
    let image_bytes = generate_image().await?;
    
    let version = artifacts.save(
        "generated_image.png",
        &Part::InlineData {
            mime_type: "image/png".to_string(),
            data: image_bytes,
        }
    ).await?;
    
    Ok(json!({
        "message": "Image saved",
        "file": "generated_image.png",
        "version": version
    }))
}

تحميل ومعالجة المستندات

async fn process_document(ctx: &ToolContext, filename: &str) -> Result<Value> {
    let artifacts = ctx.artifacts();
    
    // Load the document
    let part = artifacts.load(filename).await?;
    
    match part {
        Part::InlineData { mime_type, data } => {
            // Process based on MIME type
            let result = match mime_type.as_str() {
                "application/pdf" => process_pdf(&data)?,
                "image/jpeg" | "image/png" => process_image(&data)?,
                _ => return Err(AdkError::Artifact("Unsupported type".into())),
            };
            
            Ok(json!({ "result": result }))
        }
        _ => Err(AdkError::Artifact("Expected binary data".into())),
    }
}

سجل الإصدارات

async fn show_history(ctx: &ToolContext, filename: &str) -> Result<Value> {
    let artifacts = ctx.artifacts();
    
    // Get all files
    let files = artifacts.list().await?;
    
    if !files.contains(&filename.to_string()) {
        return Ok(json!({ "error": "File not found" }));
    }
    
    // Note: versions() is not available on the simple Artifacts trait
    // You would need access to the underlying ArtifactService
    
    Ok(json!({
        "file": filename,
        "exists": true
    }))
}

مرجع API

للاطلاع على وثائق API الكاملة، راجع:

  • adk_core::Artifacts - سمة بسيطة لاستخدام Agent
  • adk_artifact::ArtifactService - سمة خدمة كاملة
  • adk_artifact::InMemoryArtifactService - تطبيق في الذاكرة
  • adk_artifact::ScopedArtifacts - غلاف ذو نطاق

ذو صلة


السابق: ← دووال الاستدعاء | التالي: الأحداث →