الأحداث
الأحداث هي اللبنات الأساسية لتاريخ المحادثات في 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 طلبًا:
- حدث رسالة المستخدم: يتم إنشاء حدث جديد بمدخلات المستخدم
- معالجة الـ Agent: يتلقى الـ
agentسجل المحادثة (جميع الأحداث السابقة) - حدث استجابة الـ Agent: يتم تسجيل استجابة الـ
agentكحدث جديد - أحداث تنفيذ الـ Tool: قد يؤدي كل استدعاء
toolإلى إنشاء أحداث إضافية - تحديثات الحالة: يتم دمج
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:
- إدخال المستخدم: يقوم Runner بتغليف رسائل المستخدم في Event مع
author = "user" - استجابات Agent: تنتج Agents كائنات Event (مع تعيين
author = agent.name()) لتوصيل الاستجابات - إخراج LLM: تقوم طبقة تكامل النموذج بترجمة إخراج LLM (نص، استدعاءات دالة) إلى كائنات Event
- نتائج Tool: بعد تنفيذ Tool، يقوم الإطار بإنشاء Event يحتوي على استجابة Tool
تدفق المعالجة
عند إنشاء Event، فإنه يتبع مسار المعالجة هذا:
- التوليد: يتم إنشاء Event وينتجه مصدره (Agent، Tool، أو معالج إدخال المستخدم)
- استلام Runner: يستقبل Runner الذي ينفذ Agent هذا الـ Event
- معالجة SessionService: يرسل Runner الـ Event إلى SessionService، والذي يقوم بما يلي:
- تطبيق الفروق: يدمج
state_deltaفي حالة الجلسة ويحدث سجلات Artifact - إنهاء البيانات الوصفية: يعيّن
idفريدًا إذا لم يكن موجودًا، ويضبطtimestamp - الاستمرار في السجل: يضيف الـ Event إلى
session.events
- تطبيق الفروق: يدمج
- إخراج الدفق: ينتج 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"));
أفضل الممارسات
-
مناعة الأحداث: يجب عدم تعديل الأحداث مطلقًا بعد إنشائها. إنها تشكل سجل تدقيق غير قابل للتغيير.
-
إدارة State: استخدم
state_deltaلجميع تغييرات State بدلًا من تعديل State مباشرة. يضمن ذلك تتبع التغييرات في سجل الأحداث. -
مؤلفون ذوو معنى: عيّن أسماء مؤلفين واضحة ووصْفية لتسهيل فهم سجلات الأحداث.
-
التلخيص الانتقائي: استخدم
skip_summarizationللأحداث المطولة أو الداخلية التي قد تُربك سجل المحادثة. -
تجميع الاستدعاءات: حافظ على نفس
invocation_idلجميع الأحداث التي تم إنشاؤها أثناء استدعاء Agent واحد للحفاظ على التجميع المنطقي. -
تتبع Artifact: قم دائمًا بتحديث
artifact_deltaعند إنشاء أو تعديل Artifacts للحفاظ على الاتساق. -
معالجة Stream: تعامل دائمًا مع الأخطاء عند معالجة تدفقات الأحداث. قد تفشل الأحداث بسبب أخطاء LLM، أو إخفاقات Tool، أو مشكلات الشبكة.
-
التحقق من Content: تحقق دائمًا مما إذا كان
llm_response.contentهوSomeقبل الوصول إلى Parts. قد لا تحتوي بعض الأحداث (مثل تحديثات State البحتة) على Content. -
مطابقة الأنماط: استخدم مطابقة الأنماط في Rust للتعامل بأناقة مع أنواع الأحداث المختلفة وأجزاء Content.
-
التسجيل: استخدم
invocation_idلربط جميع الأحداث ضمن تفاعل مستخدم واحد لأغراض التصحيح والمراقبة.
الوثائق ذات الصلة
- Sessions - إدارة Sessions ودورة حياتها
- State Management - العمل مع Session State
- Artifacts - إدارة البيانات الثنائية
- Multi-Agent Systems - عمليات نقل Agents وتنسيقها
- Callbacks - اعتراض الأحداث وتعديلها
السابق: ← Artifacts | التالي: Telemetry →