الأحداث

الأحداث هي اللبنات الأساسية لتاريخ المحادثات في adk-rust. يتم تسجيل كل تفاعل مع Agent — سواء كانت رسالة مستخدم، استجابة Agent، أو تنفيذ Tool — كحدث. تشكل الأحداث سجلًا غير قابل للتغيير يلتقط مسار التنفيذ الكامل لـ Session Agent.

نظرة عامة

يخدم نظام الأحداث عدة أغراض حيوية:

  • تاريخ المحادثة: تشكل الأحداث السجل الزمني لجميع التفاعلات في Session
  • إدارة الحالة: تحمل الأحداث تغييرات الحالة من خلال حقل state_delta
  • تتبع المخرجات: تسجل الأحداث عمليات المخرجات من خلال حقل artifact_delta
  • تنسيق الـ Agent: تتيح الأحداث عمليات نقل وتصعيد الـ Agent
  • التصحيح والمراقبة: توفر الأحداث مسار تدقيق كامل لسلوك الـ Agent

بنية الحدث

يمثل Event تفاعلاً فرديًا في المحادثة. يستخدم adk-rust نوع Event موحدًا يتضمن LlmResponse، متطابقًا مع نمط التصميم المستخدم في ADK-Go:

pub struct Event {
    pub id: String,                    // Unique event identifier (UUID)
    pub timestamp: DateTime<Utc>,      // When the event occurred
    pub invocation_id: String,         // Links related events in a single invocation
    pub branch: String,                // For future branching support
    pub author: String,                // Who created this event (user, agent name, tool name)
    pub llm_response: LlmResponse,     // Contains content and LLM metadata
    pub actions: EventActions,         // Side effects and metadata
    pub long_running_tool_ids: Vec<String>,  // IDs of long-running tools
}

يحتوي هيكل LlmResponse على:

pub struct LlmResponse {
    pub content: Option<Content>,      // The message content (text, parts, etc.)
    pub usage_metadata: Option<UsageMetadata>,
    pub finish_reason: Option<FinishReason>,
    pub partial: bool,                 // True for streaming partial responses
    pub turn_complete: bool,           // True when the turn is complete
    pub interrupted: bool,             // True if generation was interrupted
    pub error_code: Option<String>,
    pub error_message: Option<String>,
}

يتم الوصول إلى Content بشكل متسق عبر event.llm_response.content:

if let Some(content) = &event.llm_response.content {
    for part in &content.parts {
        if let Part::Text { text } = part {
            println!("{}", text);
        }
    }
}

الحقول الرئيسية

  • id: معرّف UUID فريد يحدد هذا الحدث المعين. يستخدم لاسترجاع الأحداث وترتيبها.

  • timestamp: الطابع الزمني بتوقيت UTC عند إنشاء الحدث. يتم ترتيب الأحداث زمنيًا في الـ Session.

  • invocation_id: يجمع الأحداث التي تنتمي إلى نفس استدعاء Agent. عندما يعالج Agent رسالة ما، تشارك جميع الأحداث التي تم إنشاؤها (استجابة Agent، استدعاءات Tool، استدعاءات الـ Agent الفرعية) نفس الـ invocation_id.

  • branch: محجوز لوظائف التفرع المستقبلية. غير مستخدم حاليًا ولكنه يسمح بتفرع المحادثة في الإصدارات المستقبلية.

  • author: يحدد من أنشأ الحدث:

    • رسائل المستخدم: عادةً "user" أو معرّف مستخدم
    • استجابات Agent: اسم الـ Agent
    • عمليات تنفيذ Tool: اسم الـ Tool
    • أحداث النظام: "system"
  • llm_response: يحتوي على محتوى الرسالة وبيانات LLM الوصفية. يتم الوصول إلى Content عبر event.llm_response.content. يمكن أن يحتوي نوع Content على نص أو أجزاء متعددة الوسائط (صور، صوت) أو بيانات منظمة. قد تحتوي بعض الأحداث (مثل تحديثات الحالة البحتة) على content: None.

EventActions

تحتوي بنية EventActions على بيانات وصفية وآثار جانبية مرتبطة بحدث ما:

pub struct EventActions {
    pub state_delta: HashMap<String, Value>,    // State changes to apply
    pub artifact_delta: HashMap<String, i64>,   // Artifact version changes
    pub skip_summarization: bool,               // Skip this event in summaries
    pub transfer_to_agent: Option<String>,      // Transfer control to another agent
    pub escalate: bool,                         // Escalate to human or supervisor
}

state_delta

يحتوي الحقل state_delta على أزواج مفتاح-قيمة تمثل تغييرات في حالة الـ session. عندما يتم إلحاق حدث بـ session، يتم دمج هذه التغييرات في حالة الـ session.

يمكن لمفاتيح الحالة استخدام بادئات للتحكم في النطاق:

  • app:key - حالة بنطاق التطبيق (مشتركة بين جميع المستخدمين)
  • user:key - حالة بنطاق المستخدم (مشتركة بين جميع الـ sessions لمستخدم واحد)
  • temp:key - حالة مؤقتة (يتم مسحها بين الاستدعاءات)
  • لا توجد بادئة - حالة بنطاق الـ session (افتراضي)

Example:

let mut actions = EventActions::default();
actions.state_delta.insert("user_name".to_string(), json!("Alice"));
actions.state_delta.insert("temp:current_step".to_string(), json!(3));

artifact_delta

يتتبع الحقل artifact_delta التغييرات في العناصر. المفاتيح هي أسماء العناصر، والقيم هي أرقام الإصدار. يتيح ذلك للنظام تتبع العناصر التي تم إنشاؤها أو تعديلها أثناء حدث ما.

Example:

actions.artifact_delta.insert("report.pdf".to_string(), 1);
actions.artifact_delta.insert("chart.png".to_string(), 2);

skip_summarization

عندما تكون true، سيتم استبعاد هذا الحدث من ملخصات المحادثة. مفيد للأحداث الداخلية، أو معلومات تصحيح الأخطاء، أو مخرجات الـ tool المطولة التي لا ينبغي أن تكون جزءًا من سير المحادثة الرئيسي.

transfer_to_agent

عندما يتم تعيينها لاسم agent، يتم نقل التحكم إلى هذا الـ agent. يتيح ذلك سير عمل متعدد الـ agent حيث يمكن لـ agent واحد التسليم إلى آخر. يجب تكوين الـ agent الهدف كـ sub-agent.

Example:

actions.transfer_to_agent = Some("specialist_agent".to_string());

escalate

عندما تكون true، تشير إلى أنه يجب تصعيد المحادثة إلى عامل بشري أو supervisor agent. يعتمد سلوك التصعيد المحدد على تنفيذ تطبيقك.

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

تُشكّل الأحداث سجل المحادثة من خلال التراكم بترتيب زمني داخل الـ session. عندما يعالج agent طلبًا:

  1. حدث رسالة المستخدم: يتم إنشاء حدث جديد بمدخلات المستخدم
  2. معالجة الـ Agent: يتلقى الـ agent سجل المحادثة (جميع الأحداث السابقة)
  3. حدث استجابة الـ Agent: يتم تسجيل استجابة الـ agent كحدث جديد
  4. أحداث تنفيذ الـ Tool: قد يؤدي كل استدعاء tool إلى إنشاء أحداث إضافية
  5. تحديثات الحالة: يتم دمج state deltas من جميع الأحداث في حالة الـ session

يتم إنشاء سجل المحادثة عن طريق:

  • استرداد جميع الأحداث من الـ session بترتيب زمني
  • تحويل محتوى كل حدث إلى التنسيق المناسب لـ LLM
  • تضمين معلومات الحالة من state deltas المتراكمة
  • تصفية الأحداث التي تم وضع علامة skip_summarization عليها عند الاقتضاء

مثال على تدفق الحدث

Session Start
  ↓
[Event 1] User: "What's the weather in Tokyo?"
  ↓
[Event 2] Agent: "Let me check that for you."
  ↓
[Event 3] Tool (weather_api): {"temp": 22, "condition": "sunny"}
  ↓
[Event 4] Agent: "It's 22°C and sunny in Tokyo."
  ↓
Session State Updated

يبني كل حدث على الأحداث السابقة، مما يخلق سجل تدقيق كامل للمحادثة.

التعامل مع الأحداث

الوصول إلى الأحداث من Session

use adk_rust::session::{SessionService, GetRequest};

// استرداد Session مع أحداثه
let session = session_service.get(GetRequest {
    app_name: "my_app".to_string(),
    user_id: "user_123".to_string(),
    session_id: session_id.clone(),
    num_recent_events: None,  // Get all events
    after: None,
}).await?;

// الوصول إلى الأحداث
let events = session.events();
println!("Total events: {}", events.len());

// التكرار عبر الأحداث (ملاحظة: أحداث الـ Session تستخدم llm_response.content)
for i in 0..events.len() {
    if let Some(event) = events.at(i) {
        println!("Event {}: {} by {} at {}",
            event.id,
            event.llm_response.content.as_ref().map(|_| "has content").unwrap_or("no content"),
            event.author,
            event.timestamp
        );
    }
}

فحص تفاصيل الحدث

// الحصول على حدث محدد من Session (يستخدم llm_response.content)
if let Some(event) = events.at(0) {
    // التحقق من المؤلف
    println!("Author: {}", event.author);

    // التحقق من المحتوى (أحداث الـ Session تستخدم llm_response.content)
    if let Some(content) = &event.llm_response.content {
        for part in &content.parts {
            if let Part::Text { text } = part {
                println!("Text: {}", text);
            }
        }
    }

    // التحقق من تغييرات الحالة
    if !event.actions.state_delta.is_empty() {
        println!("State changes:");
        for (key, value) in &event.actions.state_delta {
            println!("  {} = {}", key, value);
        }
    }

    // التحقق من عمليات نقل Agent
    if let Some(target) = &event.actions.transfer_to_agent {
        println!("Transfers to: {}", target);
    }

    // التحقق من الـ artifacts
    if !event.actions.artifact_delta.is_empty() {
        println!("Artifacts modified:");
        for (name, version) in &event.actions.artifact_delta {
            println!("  {} (v{})", name, version);
        }
    }
}

تحديد سجل الأحداث

بالنسبة للمحادثات الطويلة، قد ترغب في استرداد الأحداث الحديثة فقط:

// الحصول على آخر 10 أحداث فقط
let session = session_service.get(GetRequest {
    app_name: "my_app".to_string(),
    user_id: "user_123".to_string(),
    session_id: session_id.clone(),
    num_recent_events: Some(10),
    after: None,
}).await?;

كيف تتدفق الأحداث: التوليد والمعالجة

يساعد فهم كيفية إنشاء الأحداث ومعالجتها في توضيح كيفية إدارة الإطار للإجراءات والسجل.

مصادر التوليد

تُنشأ الأحداث في نقاط مختلفة خلال دورة حياة تنفيذ Agent:

  1. إدخال المستخدم: يقوم Runner بتغليف رسائل المستخدم في Event مع author = "user"
  2. استجابات Agent: تنتج Agents كائنات Event (مع تعيين author = agent.name()) لتوصيل الاستجابات
  3. إخراج LLM: تقوم طبقة تكامل النموذج بترجمة إخراج LLM (نص، استدعاءات دالة) إلى كائنات Event
  4. نتائج Tool: بعد تنفيذ Tool، يقوم الإطار بإنشاء Event يحتوي على استجابة Tool

تدفق المعالجة

عند إنشاء Event، فإنه يتبع مسار المعالجة هذا:

  1. التوليد: يتم إنشاء Event وينتجه مصدره (Agent، Tool، أو معالج إدخال المستخدم)
  2. استلام Runner: يستقبل Runner الذي ينفذ Agent هذا الـ Event
  3. معالجة SessionService: يرسل Runner الـ Event إلى SessionService، والذي يقوم بما يلي:
    • تطبيق الفروق: يدمج state_delta في حالة الجلسة ويحدث سجلات Artifact
    • إنهاء البيانات الوصفية: يعيّن id فريدًا إذا لم يكن موجودًا، ويضبط timestamp
    • الاستمرار في السجل: يضيف الـ Event إلى session.events
  4. إخراج الدفق: ينتج Runner الـ Event المعالج إلى التطبيق المتصل

يضمن هذا التدفق تسجيل تغييرات الحالة والسجل بشكل متسق جنبًا إلى جنب مع محتوى الاتصال.

// Conceptual flow
User Input → Runner → Agent → LLM → Event Generated
                                         ↓
                                   SessionService
                                   - Apply state_delta
                                   - Record in history
                                         ↓
                                   Event Stream → Application

تحديد أنواع الأحداث

عند معالجة الأحداث من Runner، ستحتاج إلى تحديد نوع الـ Event الذي تتعامل معه:

حسب المؤلف

يخبرك حقل author بمن أنشأ الـ Event:

match event.author.as_str() {
    "user" => println!("User input"),
    agent_name => println!("Response from agent: {}", agent_name),
}

حسب نوع المحتوى

تحقق من حقل llm_response.content لتحديد نوع الحمولة:

if let Some(content) = &event.llm_response.content {
    // Check for text content
    let has_text = content.parts.iter().any(|part| {
        matches!(part, Part::Text { .. })
    });

    // Check for function calls (tool requests)
    let has_function_call = content.parts.iter().any(|part| {
        matches!(part, Part::FunctionCall { .. })
    });

    // Check for function responses (tool results)
    let has_function_response = content.parts.iter().any(|part| {
        matches!(part, Part::FunctionResponse { .. })
    });

    if has_text {
        println!("Text message");
    } else if has_function_call {
        println!("Tool call request");
    } else if has_function_response {
        println!("Tool result");
    }
}

حسب الإجراءات

تحقق من حقل actions لإشارات التحكم والتأثيرات الجانبية:

// State changes
if !event.actions.state_delta.is_empty() {
    println!("Event contains state changes");
}

// Agent transfer
if let Some(target) = &event.actions.transfer_to_agent {
    println!("Transfer to agent: {}", target);
}

// Escalation signal
if event.actions.escalate {
    println!("Escalation requested");
}

// Skip summarization
if event.actions.skip_summarization {
    println!("Skip this event in summaries");
}

العمل مع تدفقات الأحداث

عند تشغيل Agent، تتلقى تدفقًا من الأحداث. إليك كيفية معالجتها بفعالية:

معالجة الأحداث من Runner

use futures::StreamExt;

let mut stream = runner.run(
    "user_123".to_string(),
    "session_id".to_string(),
    user_input,
).await?;

while let Some(event_result) = stream.next().await {
    match event_result {
        Ok(event) => {
            // معالجة الحدث
            println!("Event from: {}", event.author);

            // استخراج محتوى النص
            if let Some(content) = &event.llm_response.content {
                for part in &content.parts {
                    if let Part::Text { text } = part {
                        print!("{}", text);
                    }
                }
            }

            // التحقق من تغييرات الحالة
            if !event.actions.state_delta.is_empty() {
                println!("\nState updated: {:?}", event.actions.state_delta);
            }
        }
        Err(e) => {
            eprintln!("Error: {}", e);
            break;
        }
    }
}

استخراج استدعاءات الوظائف

عندما يطلب LLM أداة، يحتوي الحدث على معلومات استدعاء الوظيفة:

if let Some(content) = &event.llm_response.content {
    for part in &content.parts {
        if let Part::FunctionCall { name, args } = part {
            println!("Tool requested: {}", name);
            println!("Arguments: {}", args);

            // قد يقوم تطبيقك بتنفيذ الأداة هنا
            // بناءً على اسم الأداة والوسائط
        }
    }
}

استخراج استجابات الوظائف

بعد تنفيذ أداة، يتم إرجاع النتيجة في استجابة وظيفة:

if let Some(content) = &event.llm_response.content {
    for part in &content.parts {
        if let Part::FunctionResponse { function_response, .. } = part {
            println!("Tool result from: {}", function_response.name);
            println!("Response: {}", function_response.response);

            // معالجة نتيجة الأداة
            // سيستخدم LLM هذا لمتابعة المحادثة
        }
    }
}

أنماط الأحداث الشائعة

فيما يلي تسلسلات الأحداث النموذجية التي ستصادفها:

تبادل النص البسيط

[Event 1] author="user", content=Text("Hello")
[Event 2] author="assistant", content=Text("Hi! How can I help?")

سير عمل استخدام الأداة

[Event 1] author="user", content=Text("What's the weather?")
[Event 2] author="assistant", content=FunctionCall(name="get_weather", args={...})
[Event 3] author="assistant", content=FunctionResponse(name="get_weather", response={...})
[Event 4] author="assistant", content=Text("It's sunny and 72°F")

تحديث الحالة

[Event 1] author="assistant", content=Text("I've saved your preference")
           actions.state_delta={"user_theme": "dark"}

نقل Agent

[Event 1] author="router", content=Text("Transferring to specialist")
           actions.transfer_to_agent=Some("specialist_agent")
[Event 2] author="specialist_agent", content=Text("I can help with that")

بيانات تعريف الحدث والمعرفات

معرف الحدث

لكل حدث id فريد (UUID) لتحديد الهوية بدقة:

println!("Event ID: {}", event.id);

معرف الاستدعاء

يقوم invocation_id بتجميع جميع الأحداث من طلب مستخدم واحد حتى الاستجابة النهائية:

// تشترك جميع الأحداث في تفاعل واحد في نفس invocation_id
println!("Invocation: {}", event.invocation_id);

// استخدم هذا للتسجيل والتتبع
log::info!("Processing event {} in invocation {}", event.id, event.invocation_id);

الطابع الزمني

يتم ختم الأحداث بالوقت للترتيب الزمني:

println!("Event occurred at: {}", event.timestamp.format("%Y-%m-%d %H:%M:%S"));

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

  1. مناعة الأحداث: يجب عدم تعديل الأحداث مطلقًا بعد إنشائها. إنها تشكل سجل تدقيق غير قابل للتغيير.

  2. إدارة State: استخدم state_delta لجميع تغييرات State بدلًا من تعديل State مباشرة. يضمن ذلك تتبع التغييرات في سجل الأحداث.

  3. مؤلفون ذوو معنى: عيّن أسماء مؤلفين واضحة ووصْفية لتسهيل فهم سجلات الأحداث.

  4. التلخيص الانتقائي: استخدم skip_summarization للأحداث المطولة أو الداخلية التي قد تُربك سجل المحادثة.

  5. تجميع الاستدعاءات: حافظ على نفس invocation_id لجميع الأحداث التي تم إنشاؤها أثناء استدعاء Agent واحد للحفاظ على التجميع المنطقي.

  6. تتبع Artifact: قم دائمًا بتحديث artifact_delta عند إنشاء أو تعديل Artifacts للحفاظ على الاتساق.

  7. معالجة Stream: تعامل دائمًا مع الأخطاء عند معالجة تدفقات الأحداث. قد تفشل الأحداث بسبب أخطاء LLM، أو إخفاقات Tool، أو مشكلات الشبكة.

  8. التحقق من Content: تحقق دائمًا مما إذا كان llm_response.content هو Some قبل الوصول إلى Parts. قد لا تحتوي بعض الأحداث (مثل تحديثات State البحتة) على Content.

  9. مطابقة الأنماط: استخدم مطابقة الأنماط في Rust للتعامل بأناقة مع أنواع الأحداث المختلفة وأجزاء Content.

  10. التسجيل: استخدم invocation_id لربط جميع الأحداث ضمن تفاعل مستخدم واحد لأغراض التصحيح والمراقبة.

الوثائق ذات الصلة


السابق: ← Artifacts | التالي: Telemetry →