المشغّل

بيئة تشغيل التنفيذ من adk-runner التي تنسّق تنفيذ الوكيل.

نظرة عامة

يدير Runner دورة الحياة الكاملة لتنفيذ الوكيل:

  • إدارة الجلسات (إنشاء الجلسات واستردادها)
  • حقن الذاكرة (البحث عن الذكريات ذات الصلة وحقنها)
  • معالجة المصنوعات (الوصول إلى المصنوعات ضمن النطاق)
  • بث الأحداث (معالجة الأحداث وإعادة توجيهها)
  • نقل الوكلاء (التعامل مع عمليات التسليم بين وكلاء متعددين)
Rendering architecture…

التثبيت

[dependencies]
adk-runner = "2.1.0"

RunnerConfig

اضبط المشغّل باستخدام الخدمات المطلوبة:

use adk_runner::{Runner, RunnerConfig};
use adk_session::InMemorySessionService;
use adk_artifact::InMemoryArtifactService;
use std::sync::Arc;

let config = RunnerConfig {
    app_name: "my_app".to_string(),
    agent: Arc::new(my_agent),
    session_service: Arc::new(InMemorySessionService::new()),
    artifact_service: Some(Arc::new(InMemoryArtifactService::new())),
    memory_service: None,
    plugin_manager: None,
    run_config: None,
    compaction_config: None,
    context_cache_config: None,
    cache_capable: None,
    request_context: None,
    cancellation_token: None,
};

let runner = Runner::new(config)?;

استخدم منشئ typestate لإنشاء Runner. يفرض المنشئ الحقول المطلوبة في وقت الترجمة ويضبط جميع الحقول الاختيارية على قيم افتراضية، لذا لن تؤدي إضافة حقول جديدة في الإصدارات المستقبلية إلى كسر التعليمات البرمجية لديك:

use adk_runner::Runner;

let runner = Runner::builder()
    .app_name("my_app")
    .agent(Arc::new(my_agent))
    .session_service(Arc::new(InMemorySessionService::new()))
    // Optional fields — only set what you need
    .artifact_service(Arc::new(InMemoryArtifactService::new()))
    .build()?;

يتطلب المنشئ ثلاثة حقول: app_name وagent وsession_service. كل ما عدا ذلك اختياري وله قيم افتراضية مناسبة. لا تتوفر الطريقة build() إلا بعد تعيين الحقول المطلوبة الثلاثة — ويؤدي فقدان أحدها إلى خطأ في وقت الترجمة، وليس في وقت التشغيل.

حقول الإعدادات

الحقلالنوعمطلوبالوصف
app_nameStringنعممعرّف التطبيق
agentArc<dyn Agent>نعمالوكيل الجذر المراد تنفيذه
session_serviceArc<dyn SessionService>نعمالواجهة الخلفية لتخزين الجلسات
artifact_serviceOption<Arc<dyn ArtifactService>>لاتخزين العناصر
memory_serviceOption<Arc<dyn Memory>>لاالذاكرة طويلة الأمد
plugin_managerOption<Arc<PluginManager>>لاخطافات دورة حياة المكوّن الإضافي
compaction_configOption<EventsCompactionConfig>لاإعدادات ضغط السياق
run_configOption<RunConfig>لاخيارات التنفيذ
context_cache_configOption<ContextCacheConfig>لادورة حياة ذاكرة التخزين المؤقت للسياق على مستوى المشغّل (تجريبية — راجع أدناه)
cache_capableOption<Arc<dyn CacheCapable>>لامرجع النموذج القابل للتخزين المؤقت (تجريبي — راجع أدناه)
request_contextOption<RequestContext>لاسياق برمجية وسيطة للمصادقة
cancellation_tokenOption<CancellationToken>لاالإلغاء التعاوني

التخزين المؤقت للمطالبات

التخزين المؤقت مسؤولية على مستوى المزوّد ولا يتطلب أي إعدادات لـ Runner. يتولى كل تكامل مع المزوّد ذلك عند تجميع الطلب:

المزوّدالآليةالافتراضي
Anthropic / Bedrockنقاط توقف cache_controlمفعّل (AnthropicConfig::prompt_caching، إلغاء الاشتراك باستخدام with_prompt_caching(false))
OpenAIالتخزين المؤقت للمطالبات من جانب الخادم، PromptCacheRetention للاحتفاظتلقائي
Geminiالتخزين المؤقت الضمني في 2.5/3.x — الحصول على خصم للبادئة المشتركة دون تغيير في التعليمات البرمجيةتلقائي

تُصبح إصابات ذاكرة التخزين المؤقت قابلة للملاحظة دون أي توصيل إضافي: إذ يسجّل تكامل Gemini ‏cachedContentTokenCount مع كل استجابة.

يُعدّ context_cache_config وcache_capable تجريبيين، وينبغي تركهما دون ضبط. وهما يوجّهان cachedContents API الصريح في Gemini من خلال Runner. ويتطلب ذلك API أن تستبدل ذاكرة التخزين المؤقت ‏system_instruction وtools وtool_config — ويُرفض إرسال ذاكرة تخزين مؤقت إلى جانب أيٍّ منها مع INVALID_ARGUMENT. يختار Runner ذاكرة التخزين المؤقت قبل أن يحلّ الوكيل أدواته، ولذلك لا يمكنه إنشاء ذلك الطلب، كما أن تمكين هذه الحقول لا ينتج حاليًا إصابات لذاكرة التخزين المؤقت. ينتمي التخزين المؤقت المضمون، بدلًا من التخزين الأفضل جهدًا، لـ Gemini إلى تكامل النموذج، جنبًا إلى جنب مع كيفية تنفيذ المزوّدين الآخرين لذلك.

تشغيل الوكلاء

نفّذ وكيلًا باستخدام إدخال المستخدم:

use adk_core::{Content, SessionId, UserId};
use futures::StreamExt;

let user_content = Content::new("user").with_text("Hello!");

let mut stream = runner.run(
    UserId::new("user-123")?,
    SessionId::new("session-456")?,
    user_content,
).await?;

while let Some(event) = stream.next().await {
    match event {
        Ok(e) => {
            if let Some(content) = e.content() {
                for part in &content.parts {
                    if let Some(text) = part.text() {
                        print!("{}", text);
                    }
                }
            }
        }
        Err(e) => eprintln!("Error: {}", e),
    }
}

طريقة مساعدة للسلاسل النصية

بالنسبة إلى مواضع الاستدعاء البسيطة، تقبل run_str() وسيطات &str عادية وتعالج تحويل النوع الجديد داخليًا:

let mut stream = runner.run_str(
    "user-123",
    "session-456",
    Content::new("user").with_text("Hello!"),
).await?;

إذا فشل التحقق من السلسلة (كانت فارغة، أو احتوت على بايتات فارغة، أو تجاوزت حد الطول)، فإن run_str() يعيد خطأً قبل بدء حلقة الوكيل. وتظل طريقة run() الحالية، مع UserId/SessionId المكتوبتين نوعيًا، دون تغيير.

المقاطعة وعزل عمليات التشغيل

يُسجَّل التشغيل بمجرد أن يعيد run() دفقه، وتُلغى عملية تسجيله عند إسقاط ذلك الدفق — بما في ذلك عند إسقاطه دون استطلاعه على الإطلاق.

الطريقةالنطاق
interrupt(session_id)يلغي كل عمليات التنفيذ الجارية لهذا المعرّف الخاص بالجلسة، عبر التطبيقات والمستخدمين
interrupt_identity(app_name, user_id, session_id)يلغي عمليات التنفيذ لهوية مطابقة تمامًا
active_runs()هوية كل عملية تشغيل جارية؛ وتعني الهوية المتكررة عمليات تشغيل متزامنة
active_session_ids()معرّفات الجلسات المزالة تكراراتها لعمليات التشغيل الجارية
// Cancel one tenant's run without touching another that shares the session ID
let cancelled = runner.interrupt_identity("my-app", "user-1", "session-1");

يكون معرّف الجلسة فريدًا فقط ضمن تطبيق ومستخدم، لذا فإن interrupt(session_id) هو الشكل الأوسع، بينما interrupt_identity هو الشكل الأدق. يُفضّل استخدام interrupt_identity عندما يخدم Runner واحد أكثر من تطبيق أو مستخدم.

تُتتبَّع عمليات التشغيل باستخدام معرّف تشغيل فريد بدلًا من معرّف الجلسة، لذا تُتتبَّع عمليتا تشغيل للهوية نفسها بشكل منفصل، وتلغي كل منهما تسجيل نفسها فقط.

الاستمرارية مرتبطة بالهوية

يُكتب كل حدث يحفظه Runner — أدوار المستخدم، واستجابات النموذج، وأحداث النقل، وأحداث الإضافات، وأحداث الضغط — من خلال SessionService::append_event_for_identity باستخدام مجموعة (app_name, user_id, session_id) الكاملة. لذلك يمكن لـ SessionService الذي يكون مفتاحه الطبيعي مركبًا أن يربط كل حدث بمستأجره، وأن يرفض مسار append_event لمعرّف الجلسة الخام أو يتجاهله بالكامل.

تدفق التنفيذ

┌─────────────────────────────────────────────────────────────┐
│                     Runner.run()                            │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                  1. Session Retrieval                       │
│                                                             │
│   SessionService.get(app_name, user_id, session_id)        │
│   → Creates new session if not exists                       │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                  2. Agent Selection                         │
│                                                             │
│   Check session state for active agent                      │
│   → Use root agent or transferred agent                     │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                3. Context Creation                          │
│                                                             │
│   InvocationContext with:                                   │
│   - Session (mutable)                                       │
│   - Artifacts (scoped to session)                          │
│   - Memory (if configured)                                  │
│   - Run config                                              │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                  4. Agent Execution                         │
│                                                             │
│   agent.run(ctx) → EventStream                             │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                 5. Event Processing                         │
│                                                             │
│   For each event:                                           │
│   - Update session state                                    │
│   - Handle transfers                                        │
│   - Forward to caller                                       │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                  6. Session Save                            │
│                                                             │
│   SessionService.append_event(session, events)             │
└─────────────────────────────────────────────────────────────┘

InvocationContext

السياق المقدَّم إلى الوكلاء أثناء التنفيذ:

pub trait InvocationContext: CallbackContext {
    /// The agent being executed
    fn agent(&self) -> Arc<dyn Agent>;
    
    /// Memory service (if configured)
    fn memory(&self) -> Option<Arc<dyn Memory>>;
    
    /// Current session
    fn session(&self) -> &dyn Session;
    
    /// Execution configuration
    fn run_config(&self) -> &RunConfig;
    
    /// Signal end of invocation
    fn end_invocation(&self);
    
    /// Check if invocation has ended
    fn ended(&self) -> bool;
}

RunConfig

خيارات التنفيذ:

pub struct RunConfig {
    /// Streaming mode for responses
    pub streaming_mode: StreamingMode,
    // ... other fields (tool_confirmation_decisions, cached_content, etc.)
}

ToolExecutionStrategy

يتحكم في كيفية إرسال استدعاءات الأدوات المتعددة من استجابة LLM واحدة:

الاستراتيجيةالسلوك
Sequential (افتراضي)تنفيذ الأدوات واحدة تلو الأخرى بالترتيب الذي يعيده LLM
Parallelتنفيذ جميع الأدوات بشكل متزامن؛ ويتولى المستدعي مسؤولية السلامة
Autoنفّذ المجموعة الفرعية الآمنة للقراءة فقط بالتزامن، ثم نفّذ جميع الاستدعاءات المتبقية بالتسلسل

تعيين لكل وكيل عبر LlmAgentBuilder:

use adk_core::ToolExecutionStrategy;

let agent = LlmAgentBuilder::new("fast_agent")
    .model(model)
    .tool_execution_strategy(ToolExecutionStrategy::Auto)
    .tool(Arc::new(
        search_tool
            .with_read_only(true)
            .with_concurrency_safe(true),
    ))
    .tool(Arc::new(save_tool)) // runs after the concurrent safe subset
    .build()?;

في وضع Auto، تستعلم حلقة التوزيع عن كلٍّ من is_read_only() وis_concurrency_safe(). تُشغَّل أولًا الاستدعاءات التي تُرجع أدواتها المحددة true لكلتا الطريقتين بشكل متزامن؛ ثم تُشغَّل جميع الاستدعاءات المتبقية بالتسلسل. يتجاوز Parallel عمليات التحقق من البيانات الوصفية هذه باعتباره تجاوزًا صريحًا من المستدعي. تُعاد النتائج دائمًا تجميعها بالترتيب الأصلي الذي أعاده LLM، بغض النظر عن الاستراتيجية. تُنتج الأدوات الفاشلة استجابة خطأ JSON من دون إيقاف الدفعة.

pub enum StreamingMode {
    /// No streaming, return complete response
    None,
    /// Server-Sent Events (default)
    SSE,
    /// Bidirectional streaming (realtime)
    Bidi,
}

عمليات نقل الوكلاء

يتعامل Runner تلقائيًا مع عمليات النقل بين عدة وكلاء:

// In an agent's tool or callback
if should_transfer {
    // Set transfer in event actions
    ctx.set_actions(EventActions {
        transfer_to_agent: Some("specialist_agent".to_string()),
        ..Default::default()
    });
}

سيقوم Runner بما يلي:

  1. اكتشاف طلب النقل في الحدث
  2. العثور على الوكيل المستهدف في sub_agents
  3. تحديث حالة الجلسة بالوكيل النشط الجديد
  4. متابعة التنفيذ باستخدام الوكيل الجديد

ضغط السياق

بالنسبة إلى الجلسات طويلة الأمد، فعّل ضغط السياق التلقائي للحفاظ على نافذة سياق LLM ضمن حدود ثابتة:

use adk_runner::{Runner, RunnerConfig, EventsCompactionConfig};
use adk_agent::LlmEventSummarizer;
use std::sync::Arc;

let summarizer = LlmEventSummarizer::new(model.clone());

let config = RunnerConfig {
    // ... other fields ...
    compaction_config: Some(EventsCompactionConfig {
        compaction_interval: 3,  // Compact every 3 invocations
        overlap_size: 1,         // Keep 1 event overlap for continuity
        summarizer: Arc::new(summarizer),
    }),
    // ...
};

عند بدء الضغط، تُستبدل الأحداث الأقدم بحدث ملخّص. يستخدم conversation_history() الملخص تلقائيًا بدلًا من الأحداث الأصلية.

راجع ضغط السياق للاطلاع على الوثائق الكاملة.

التكامل مع Launcher

يستخدم Launcher ‏Runner داخليًا:

// Launcher creates Runner with default services
Launcher::new(agent)
    .app_name("my_app")
    .run()
    .await?;

// Equivalent to using the builder:
let runner = Runner::builder()
    .app_name("my_app")
    .agent(agent)
    .session_service(Arc::new(InMemorySessionService::new()))
    .build()?;

استخدام Runner المخصص

للسيناريوهات المتقدمة، استخدم Runner مباشرةً:

use adk_runner::Runner;

// Production configuration using the builder
let runner = Runner::builder()
    .app_name("production_app")
    .agent(my_agent)
    .session_service(Arc::new(SqliteSessionService::new(db_pool)))
    .artifact_service(Arc::new(S3ArtifactService::new(s3_client)))
    .memory_service(Arc::new(QdrantMemoryService::new(qdrant_client)))
    .build()?;

// Use in HTTP handler with run_str() for convenience
async fn chat_handler(runner: &Runner, request: ChatRequest) -> Response {
    let stream = runner.run_str(
        &request.user_id,
        &request.session_id,
        request.content,
    ).await?;
    
    // Stream events to client
    Response::sse(stream)
}

السابق: ← الأنواع الأساسية | التالي: Launcher →

المشغّل - وثائق ADK-Rust | ADK-Rust