الأنواع الأساسية
الأنواع والسمات الأساسية من adk-core التي تشكّل أساس ADK-Rust.
المحتوى والجزء
تمر كل رسالة في ADK عبر Content وPart. يُعد فهم هذه الأنواع أمرًا ضروريًا للعمل مع الوكلاء والأدوات وعمليات الاستدعاء اللاحقة.
المحتوى
يمثّل Content رسالة واحدة في محادثة. وله role (المرسل) وواحد أو أكثر من parts (المحتوى الفعلي).
الأدوار:
"user"- رسائل من المستخدم"model"- استجابات نموذج الذكاء الاصطناعي"tool"- نتائج تنفيذ الأداة
use adk_core::Content;
// Simple text message from user
let user_msg = Content::new("user")
.with_text("What's the weather like?");
// Model response
let model_msg = Content::new("model")
.with_text("The weather is sunny and 72°F.");
// Multiple text parts in one message
let detailed_msg = Content::new("user")
.with_text("Here's my question:")
.with_text("What is the capital of France?");
المحتوى متعدد الوسائط:
يمكن أن يتضمن المحتوى صورًا وملفات صوتية وPDFs وبيانات ثنائية أخرى إلى جانب النص:
// Image from bytes (e.g., read from file)
let image_bytes = std::fs::read("photo.jpg")?;
let content = Content::new("user")
.with_text("What's in this image?")
.with_inline_data("image/jpeg", image_bytes);
// Image from URL (model fetches it)
let content = Content::new("user")
.with_text("Describe this image")
.with_file_uri("image/png", "https://example.com/chart.png");
// PDF document
let pdf_bytes = std::fs::read("report.pdf")?;
let content = Content::new("user")
.with_text("Summarize this document")
.with_inline_data("application/pdf", pdf_bytes);
الجزء
Part هو تعداد يمثّل أنواعًا مختلفة من المحتوى داخل رسالة:
pub enum Part {
// Plain text
Text { text: String },
// Binary data embedded in the message
InlineData {
mime_type: String,
data: Vec<u8>,
uri: Option<String>,
annotations: Option<Value>,
},
// Reference to external file (URL or cloud storage)
FileData {
mime_type: String,
file_uri: String,
annotations: Option<Value>,
},
// Model requesting a tool call
FunctionCall { name: String, args: Value, id: Option<String> },
// Result of a tool execution
FunctionResponse {
function_response: FunctionResponseData,
id: Option<String>,
annotations: Option<Value>,
},
// A complete resource embedded in the message (source URI + inline contents)
EmbeddedResource { resource: EmbeddedResource },
}
الموارد المضمّنة:
يحمل Part::EmbeddedResource موردًا كاملًا — مصدر URI بالإضافة إلى محتوى نصي أو ثنائي مضمن — بما يحاكي كتلة الموارد المضمّنة MCP / ACP. يتيح ذلك تمرير سياق الملف المذكور في @ ذهابًا وإيابًا دون فقدان عبر استمرارية الجلسة وطبقات البروتوكول مثل adk-acp.
use adk_core::{BlobResourceContents, Content, EmbeddedResource, TextResourceContents};
// Text resource (verbatim UTF-8 payload, never base64-encoded)
let text = EmbeddedResource::Text(TextResourceContents::new(
"file:///src/main.rs",
Some("text/x-rust".to_string()),
"fn main() {}",
));
// Binary resource (checked constructor rejects payloads over MAX_INLINE_DATA_SIZE)
let blob = EmbeddedResource::Blob(BlobResourceContents::new(
"file:///logo.png",
Some("image/png".to_string()),
vec![0x89, 0x50, 0x4E, 0x47],
)?);
let content = Content::new("user")
.with_text("Here is the entry point and the logo:")
.with_embedded_resource(text)
.with_embedded_resource(blob);
EmbeddedResource هو تعداد غير موسوم مع متغيري Text(TextResourceContents) وBlob(BlobResourceContents)، ويحمل كل منهما uri مطلوبًا وmime_type اختياريًا. المتغير إضافي: إذ تُفك تسلسل قيم Content المتسلسلة الأقدم دون تغيير، ولا تتأثر مواضع إنشاء Part::InlineData الحالية.
إنشاء الأجزاء مباشرةً:
use adk_core::Part;
// Text part
let text = Part::text_part("Hello, world!");
// Image from bytes
let image = Part::inline_data("image/png", png_bytes);
// Image from URL
let remote_image = Part::file_data("image/jpeg", "https://example.com/photo.jpg");
فحص الأجزاء:
// Get text content (returns None for non-text parts)
if let Some(text) = part.text() {
println!("Text: {}", text);
}
// Get MIME type (for InlineData and FileData)
if let Some(mime) = part.mime_type() {
println!("MIME type: {}", mime);
}
// Get file URI (for FileData only)
if let Some(uri) = part.file_uri() {
println!("File URI: {}", uri);
}
// Check if part contains media (image, audio, video, etc.)
if part.is_media() {
println!("This part contains binary media");
}
التكرار عبر الأجزاء:
for part in &content.parts {
match part {
Part::Text { text } => println!("Text: {}", text),
Part::InlineData { mime_type, data } => {
println!("Binary data: {} ({} bytes)", mime_type, data.len());
}
Part::FileData { mime_type, file_uri } => {
println!("File: {} at {}", mime_type, file_uri);
}
Part::FunctionCall { name, args, .. } => {
println!("Tool call: {}({})", name, args);
}
Part::FunctionResponse { function_response, .. } => {
println!("Tool result: {} -> {}",
function_response.name,
function_response.response
);
}
Part::EmbeddedResource { resource } => {
println!("Embedded resource: {}", resource.uri());
}
}
}
سمة الوكيل
تُعد سمة Agent التجريد الأساسي لجميع الوكلاء في ADK. يطبّق كل نوع من أنواع الوكلاء — وكلاء LLM ووكلاء سير العمل والوكلاء المخصصون — هذه السمة.
#[async_trait]
pub trait Agent: Send + Sync {
/// Unique identifier for this agent
fn name(&self) -> &str;
/// Human-readable description of what this agent does
fn description(&self) -> &str;
/// Child agents (for workflow agents like Sequential, Parallel)
fn sub_agents(&self) -> &[Arc<dyn Agent>];
/// Execute the agent and return a stream of events
async fn run(&self, ctx: Arc<dyn InvocationContext>) -> Result<EventStream>;
}
النقاط الأساسية:
name(): يُستخدم للتسجيل وعمليات النقل وتحديد الهوية. يجب أن يكون فريدًا ضمن نظام متعدد الوكلاء.description(): يُعرض على LLMs عند استخدام الوكيل كأداة أو لاتخاذ قرارات التوجيه.sub_agents(): يعيد الوكلاء التابعين. يكون فارغًا للوكلاء الطرفيين (LlmAgent)، ومعبّأً للحاويات (SequentialAgent، ParallelAgent).run(): طريقة التنفيذ الرئيسية. تستقبل السياق وتعيد تدفقًا من الأحداث.
لماذا EventStream؟
يعيد الوكلاء EventStream (تدفقًا من Result<Event>) بدلًا من استجابة واحدة للأسباب التالية:
- البث: يمكن بث الاستجابات رمزًا تلو الآخر لتحسين تجربة المستخدم
- استدعاءات الأدوات: تحدث استدعاءات متعددة للأدوات واستجاباتها أثناء التنفيذ
- تغييرات الحالة: تُصدر تحديثات الحالة كأحداث
- عمليات النقل: يُشار إلى عمليات نقل الوكلاء من خلال الأحداث
سمة الأداة
توسّع الأدوات إمكانات الوكلاء بما يتجاوز المحادثة. فهي تتيح للوكلاء التفاعل مع APIs وقواعد البيانات وأنظمة الملفات، أو إجراء العمليات الحسابية.
#[async_trait]
pub trait Tool: Send + Sync {
/// Tool name (used in function calls from the model)
fn name(&self) -> &str;
/// Description shown to the LLM to help it decide when to use this tool
fn description(&self) -> &str;
/// JSON Schema defining the expected parameters
fn parameters_schema(&self) -> Option<Value> { None }
/// JSON Schema defining the response format
fn response_schema(&self) -> Option<Value> { None }
/// Whether this tool runs asynchronously (returns task ID immediately)
fn is_long_running(&self) -> bool { false }
/// Execute the tool with given arguments
async fn execute(&self, ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value>;
}
النقاط الرئيسية:
name(): اسم الدالة الذي يستخدمه النموذج لاستدعاء هذه الأداة. اجعله قصيرًا وواضحًا (مثلget_weather،search_database).description(): مهم للغاية لكي يفهم النموذج متى يستخدم الأداة. كن محددًا بشأن ما تفعله الأداة ومتى تُستخدم.parameters_schema(): مخطط JSON يوضح للنموذج الوسائط التي يجب تقديمها. ومن دونه، سيخمّن النموذج.execute(): يستقبل الوسائط المُحلَّلة بصيغةserde_json::Value. أعد النتيجة بصيغة JSON.
تنفيذ أداة مخصصة:
use adk_core::{Tool, ToolContext, Result};
use async_trait::async_trait;
use serde_json::{json, Value};
use std::sync::Arc;
struct WeatherTool {
api_key: String,
}
#[async_trait]
impl Tool for WeatherTool {
fn name(&self) -> &str {
"get_weather"
}
fn description(&self) -> &str {
"Get current weather for a city. Use this when the user asks about weather conditions."
}
fn parameters_schema(&self) -> Option<Value> {
Some(json!({
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "City name (e.g., 'London', 'New York')"
},
"units": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature units"
}
},
"required": ["city"]
}))
}
async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
let city = args["city"].as_str().unwrap_or("Unknown");
let units = args["units"].as_str().unwrap_or("celsius");
// Call weather API here...
Ok(json!({
"city": city,
"temperature": 22,
"units": units,
"condition": "sunny"
}))
}
}
الأدوات طويلة التشغيل:
بالنسبة إلى العمليات التي تستغرق وقتًا طويلًا (معالجة الملفات، استدعاءات API الخارجية)، حدّد الأداة على أنها طويلة التشغيل:
fn is_long_running(&self) -> bool { true }
تعيد الأدوات طويلة التشغيل معرّف مهمة فورًا. ويُوجَّه النموذج إلى عدم استدعاء الأداة مرة أخرى أثناء كونها قيد الانتظار.
سمة مجموعة الأدوات
توفر Toolset الأدوات ديناميكيًا استنادًا إلى السياق. استخدم هذا عندما:
- تعتمد الأدوات على أذونات المستخدم
- تُحمَّل الأدوات من مصادر خارجية (خوادم MCP)
- يتغير توفر الأدوات أثناء التنفيذ
#[async_trait]
pub trait Toolset: Send + Sync {
/// Toolset identifier
fn name(&self) -> &str;
/// Return available tools for the current context
async fn tools(&self, ctx: Arc<dyn ReadonlyContext>) -> Result<Vec<Arc<dyn Tool>>>;
}
مثال: مجموعة أدوات قائمة على الأذونات:
struct AdminToolset {
admin_tools: Vec<Arc<dyn Tool>>,
user_tools: Vec<Arc<dyn Tool>>,
}
#[async_trait]
impl Toolset for AdminToolset {
fn name(&self) -> &str { "admin_toolset" }
async fn tools(&self, ctx: Arc<dyn ReadonlyContext>) -> Result<Vec<Arc<dyn Tool>>> {
if ctx.user_id().starts_with("admin_") {
Ok(self.admin_tools.clone())
} else {
Ok(self.user_tools.clone())
}
}
}
سمات السياق
توفر السياقات المعلومات والخدمات للوكلاء والأدوات أثناء التنفيذ. توجد بنية هرمية لسمات السياق، حيث تضيف كل سمة قدرات إضافية.
ReadonlyContext
المعلومات الأساسية المتاحة في كل مكان:
pub trait ReadonlyContext: Send + Sync {
/// Unique ID for this invocation
fn invocation_id(&self) -> &str;
/// Name of the currently executing agent
fn agent_name(&self) -> &str;
/// User identifier
fn user_id(&self) -> &str;
/// Application name
fn app_name(&self) -> &str;
/// Session identifier
fn session_id(&self) -> &str;
/// The user's input that triggered this invocation
fn user_content(&self) -> &Content;
}
CallbackContext
تضيف الوصول إلى المصنوعات (وتمتد من ReadonlyContext):
pub trait CallbackContext: ReadonlyContext {
/// Access to artifact storage (if configured)
fn artifacts(&self) -> Option<Arc<dyn Artifacts>>;
}
ToolContext
لتنفيذ الأدوات (وتمتد من CallbackContext):
pub trait ToolContext: CallbackContext {
/// ID of the function call that triggered this tool
fn function_call_id(&self) -> &str;
/// Get current event actions (transfer, escalate, etc.)
fn actions(&self) -> EventActions;
/// Set event actions (e.g., trigger a transfer)
fn set_actions(&self, actions: EventActions);
/// Search long-term memory
async fn search_memory(&self, query: &str) -> Result<Vec<MemoryEntry>>;
}
InvocationContext
السياق الكامل لتنفيذ الوكيل (ويمتد من CallbackContext):
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 with state and history
fn session(&self) -> &dyn Session;
/// Execution configuration
fn run_config(&self) -> &RunConfig;
/// Signal that this invocation should end
fn end_invocation(&self);
/// Check if invocation has been ended
fn ended(&self) -> bool;
// Capability methods — all have defaults, so an implementation may omit them.
/// Whether the run has been cancelled, e.g. by `Runner::interrupt`.
/// Default: `false`.
fn is_cancelled(&self) -> bool;
/// Authenticated scopes for the caller. Default: empty.
fn user_scopes(&self) -> Vec<String>;
/// Request-scoped metadata. Default: empty.
fn request_metadata(&self) -> HashMap<String, serde_json::Value>;
/// Resolve a secret through the configured provider. Default: `Ok(None)`.
async fn get_secret(&self, name: &str) -> Result<Option<String>>;
}
الحفاظ على القدرات في السياقات المشتقة
تتمتع أساليب القدرات المذكورة أعلاه، بالإضافة إلى shared_state() وartifacts() في
CallbackContext، جميعها بقيم افتراضية متساهلة — false أو فارغة أو None. ويكتسب ذلك
أهميته عندما يكون السياق مشتقًا: إذ يغلّف وكلاء سير العمل سياق المستدعي
لتغيير أمر واحد (فرع أو محتوى المستخدم أو الجلسة)، ويؤدي الغلاف الذي لا
يمرر أسلوبًا من أساليب القدرات إلى الرجوع بصمت إلى قيمته الافتراضية بدلًا من
فشل الترجمة.
تمرر كل الأغلفة في مساحة العمل جميع هذه الأساليب، وتفرض مجموعة اختبارات
التوافق ذلك من خلال تمرير سياق ذي قيم غير افتراضية لكل قدرة عبر كل غلاف،
بما في ذلك مكدس مركب. وعمليًا، يعني هذا أن الإلغاء والأسرار والنطاقات
والبيانات الوصفية للطلب والحالة المشتركة والمصنوعات والذاكرة تصل إلى وكيل
فرعي بغض النظر عما إذا كان يعمل مباشرةً أو ضمن LoopAgent أو ضمن ParallelAgent أو
مع حقن المهارات.
ملاحظة: إذا نفّذت
InvocationContextبنفسك عن طريق تغليف سياق آخر، فمرّر كل طريقة مذكورة أعلاه. لا يوفّرToolContextكلًّا منis_cancelledوrequest_metadata، ولذلك فإن الوكيل المستدعى كأداة لا يراقب الإلغاء حاليًا.
الجلسة والحالة
تتتبّع الجلسات المحادثات. وتخزّن الحالة البيانات داخل الجلسات.
الجلسة
pub trait Session: Send + Sync {
/// Unique session identifier
fn id(&self) -> &str;
/// Application this session belongs to
fn app_name(&self) -> &str;
/// User who owns this session
fn user_id(&self) -> &str;
/// Mutable state storage
fn state(&self) -> &dyn State;
/// Previous messages in this conversation
fn conversation_history(&self) -> Vec<Content>;
}
الحالة
تخزين ذو قيمة مفتاحية مع بادئات محددة النطاق:
pub trait State: Send + Sync {
/// Get a value by key
fn get(&self, key: &str) -> Option<Value>;
/// Set a value
fn set(&mut self, key: String, value: Value);
/// Get all key-value pairs
fn all(&self) -> HashMap<String, Value>;
}
بادئات الحالة:
تستخدم مفاتيح الحالة بادئات للتحكم في النطاق والاستمرارية:
| البادئة | النطاق | الاستمرارية | حالة الاستخدام |
|---|---|---|---|
user: | على مستوى المستخدم | عبر جميع الجلسات | تفضيلات المستخدم، الإعدادات |
app: | على مستوى التطبيق | على مستوى التطبيق بالكامل | التكوين المشترك |
temp: | على مستوى الدور | يُمسح في كل دور | بيانات الحساب المؤقتة |
| (لا شيء) | على مستوى الجلسة | هذه الجلسة فقط | سياق المحادثة |
// In a callback or tool
let state = ctx.session().state();
// User preference (persists across sessions)
state.set("user:theme".into(), json!("dark"));
// Session-specific data
state.set("current_topic".into(), json!("weather"));
// Temporary data (cleared after this turn)
state.set("temp:step_count".into(), json!(1));
// Read values
if let Some(theme) = state.get("user:theme") {
println!("Theme: {}", theme);
}
معالجة الأخطاء
يستخدم ADK نوع خطأ مُهيكلًا وموحدًا لجميع العمليات:
use adk_core::{AdkError, ErrorComponent, ErrorCategory};
// AdkError is a struct with component, category, code, message, retry hint, and details
let err = AdkError::new(
ErrorComponent::Model,
ErrorCategory::RateLimited,
"model.openai.rate_limited",
"Too many requests",
);
// Backward-compatible shorthand (produces .legacy codes)
let err = AdkError::model("something went wrong");
// Check error properties
err.is_retryable(); // true for RateLimited, Unavailable, Timeout
err.is_not_found(); // category == NotFound
err.http_status_code(); // 429 for RateLimited
err.to_problem_json(); // structured JSON for HTTP responses
pub type Result<T> = std::result::Result<T, AdkError>;
معالجة الأخطاء في الأدوات:
async fn execute(&self, ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
let city = args["city"]
.as_str()
.ok_or_else(|| AdkError::tool("Missing 'city' parameter"))?;
let response = reqwest::get(&format!("https://api.weather.com/{}", city))
.await
.map_err(|e| AdkError::tool(format!("API error: {e}")))?;
Ok(json!({ "weather": "sunny" }))
}
راجع إرشادات التطوير للاطلاع على الدليل الكامل لمعالجة الأخطاء.
الهوية
يستخدم ADK-Rust بدائيات هوية مُنَمّطة لمنع الخلط غير المقصود بين المعرّفات، ولجعل عنونة الجلسات واضحة. توجد ثلاث طبقات متميزة للهوية، تؤدي كل منها غرضًا مختلفًا.
هوية المصادقة (RequestContext)
تمثل هوية المصادقة الجهة التي تمت مصادقتها والإجراءات المسموح لها بتنفيذها. وهي موجودة في RequestContext، ويتم ملؤها عند HTTP أو عند نقطة دخول Studio:
user_id— المستخدم الذي تمت مصادقتهscopes— نطاقات التفويض (مثل["read", "write"])metadata— أزواج مفتاح-قيمة إضافية على مستوى الطلب
لا تتضمن هوية المصادقة معلومات الجلسة أو الاستدعاء. وهي تجيب عن السؤال: "من يُجري هذا الطلب؟"
هوية الجلسة (AdkIdentity)
هوية الجلسة هي العنوان الثابت لجلسة محادثة. وتتكون من ثلاثة حقول مُنَمّطة:
use adk_core::{AdkIdentity, AppName, SessionId, UserId};
let identity = AdkIdentity::new(
AppName::try_from("weather-app")?,
UserId::try_from("tenant:alice@example.com")?,
SessionId::generate(),
);
// Each field is a typed wrapper — no accidental swaps
assert_eq!(identity.app_name.as_ref(), "weather-app");
يستبدل AdkIdentity نمط تمرير (app_name, user_id, session_id) على شكل ثلاث سلاسل منفصلة. وهو يلغي أخطاء ترتيب المعلمات ويجعل نموذج العنونة واضحًا عبر جميع خلفيات الجلسات.
تجيب هوية الجلسة عن السؤال: "أي جلسة محادثة تتم عنونتها؟"
هوية التنفيذ (ExecutionIdentity)
توسّع هوية التنفيذ هوية الجلسة بإحداثيات وقت التشغيل الخاصة بكل استدعاء:
use adk_core::{ExecutionIdentity, InvocationId};
let exec = ExecutionIdentity {
adk: identity.clone(),
invocation_id: InvocationId::generate(),
branch: String::new(),
agent_name: "planner".to_string(),
};
يخزن وقت التشغيل ExecutionIdentity داخليًا، بحيث يعمل إنشاء الأحداث ونقل الوكلاء وربط بيانات القياس عن بُعد باستخدام معرّفات تم التحقق منها — من دون إعادة تحليل السلاسل الأولية بعد التحليل الأولي عند الحد الفاصل.
تجيب هوية التنفيذ عن السؤال: "أي استدعاء لأي جلسة قيد التشغيل حاليًا، وأي وكيل ينفذها؟"
أنواع المعرّفات الطرفية
تشترك الأنواع الطرفية الأربعة — AppName، UserId، SessionId، InvocationId — جميعًا في قواعد التحقق نفسها:
- يجب ألا تكون فارغة
- يجب ألا تحتوي على بايتات فارغة
- يجب ألا يتجاوز طولها 512 بايتًا
- يُسمح بأحرف مثل
:و@و/(لا يرتبط التحقق بترميز مفتاح الواجهة الخلفية)
تستخدم الأنواع الأربعة #[serde(transparent)]، ولذلك تستمر حمولات JSON وSSE الحالية في ترميز المعرّفات كسلاسل نصية عادية.
use adk_core::{AppName, SessionId, InvocationId};
// Parse at trust boundaries
let app = AppName::try_from("my-app")?;
// Generate internally
let session = SessionId::generate();
let invocation = InvocationId::generate();
// Use as &str anywhere
println!("app={}, session={}", app.as_ref(), session.as_ref());
قراءة الهوية ذات النوع من السياق
يوفّر كل من ReadonlyContext وSession أساليب مساعدة ذات نوع لتحليل قيم السلاسل النصية الأساسية عند الطلب:
// From a ReadonlyContext (agent or tool execution)
let identity = ctx.try_identity()?; // AdkIdentity
let exec = ctx.try_execution_identity()?; // ExecutionIdentity
let app = ctx.try_app_name()?; // AppName
// From a Session
let identity = session.try_identity()?; // AdkIdentity
let user = session.try_user_id()?; // UserId
تُرجع هذه الأساليب Result<T> — فإذا كانت السلسلة النصية الأساسية غير صالحة (مثلًا، فارغة)، فستحصل على خطأ وصفي بدلًا من حدوث ذعر.
حالات الاستخدام
| حالة الاستخدام | طبقة الهوية | السبب |
|---|---|---|
| العزل متعدد المستأجرين | AdkIdentity | يضمن الثلاثي (app, user, session) عدم تجاوز الجلسات لحدود المستأجرين |
| ترابط التدقيق | ExecutionIdentity | يربط كل حدث باستدعاء ووكيل وجلسة محددة |
| إعادة التشغيل والاستئناف | AdkIdentity + InvocationId | يتيح عنوان الجلسة الثابت بالإضافة إلى معرّف الاستدعاء إعادة تشغيل حتمية |
| عقود إضافات/أدوات أكثر أمانًا | معرّفات الأوراق المعرّفة بالأنواع | تتلقى الإضافات AppName وUserId بدلًا من السلاسل النصية الأولية — دون عمليات تبديل عرضية |
| التحقق عند الحدود | TryFrom<&str> | التحليل مرة واحدة عند نقطة دخول HTTP، ورفض الإدخال غير الصالح مبكرًا مع خطأ 400 |
ملخص فصل الهوية
RequestContext → Auth: who is authenticated, what scopes
↓ (user binding)
AdkIdentity → Session: which app + user + session
↓ (+ invocation)
ExecutionIdentity → Runtime: which invocation, branch, agent
هذه الطبقات الثلاث منفصلة عمدًا. قد تشترك RequestContext في نوع UserId نفسه، لكن لا يتم دمج الهياكل مطلقًا. تبقى مخاوف المصادقة ضمن المصادقة، وتبقى عنونة الجلسة ضمن هوية الجلسة، وتبقى إحداثيات وقت التشغيل ضمن هوية التنفيذ.
EventStream
تعيد الوكلاء تدفقًا من الأحداث بدلًا من استجابة واحدة:
pub type EventStream = Pin<Box<dyn Stream<Item = Result<Event>> + Send>>;
أحداث المعالجة:
use futures::StreamExt;
let mut stream = agent.run(ctx).await?;
while let Some(result) = stream.next().await {
match result {
Ok(event) => {
// Check for text content
if let Some(content) = event.content() {
for part in &content.parts {
if let Some(text) = part.text() {
print!("{}", text);
}
}
}
// Check for state changes
for (key, value) in event.state_delta() {
println!("State changed: {} = {}", key, value);
}
// Check if this is the final response
if event.is_final_response() {
println!("\n[Done]");
}
}
Err(e) => {
eprintln!("Error: {}", e);
break;
}
}
}
سمة Llm
السمة التي تطبقها جميع موفري LLM:
#[async_trait]
pub trait Llm: Send + Sync {
/// Model identifier (e.g., "gemini-3.7-flash", "gpt-5.6-terra")
fn name(&self) -> &str;
/// Generate content (streaming or non-streaming)
async fn generate_content(
&self,
request: LlmRequest,
stream: bool,
) -> Result<LlmResponseStream>;
}
LlmRequest:
pub struct LlmRequest {
pub contents: Vec<Content>, // Conversation history
pub tools: Vec<ToolDeclaration>, // Available tools
pub system_instruction: Option<String>, // System prompt
pub config: GenerateContentConfig, // Temperature, max_tokens, etc.
}
طرق إنشاء LlmRequest:
يوفر LlmRequest طرق إنشاء لتسهيل البناء:
use adk_core::{LlmRequest, Content, GenerateContentConfig};
use serde_json::json;
// Basic request
let request = LlmRequest::new(vec![
Content::new("user").with_text("Hello!")
]);
// With system instruction
let request = LlmRequest::new(contents)
.with_system_instruction("You are a helpful assistant.");
// With response schema for structured output
let request = LlmRequest::new(contents)
.with_response_schema(json!({
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "number" }
},
"required": ["name", "age"]
}));
// With full config
let config = GenerateContentConfig {
temperature: Some(0.7),
max_output_tokens: Some(1024),
response_schema: Some(schema),
..Default::default()
};
let request = LlmRequest::new(contents).with_config(config);
| الطريقة | الوصف |
|---|---|
new(contents) | إنشاء طلب مع سجل المحادثة |
with_system_instruction(text) | تعيين موجّه النظام |
with_response_schema(schema) | تعيين مخطط JSON للإخراج المنظم |
with_config(config) | تعيين إعدادات التوليد الكاملة |
with_tools(tools) | إضافة تعريفات الأدوات |
LlmResponse:
pub struct LlmResponse {
pub content: Option<Content>, // Generated content
pub finish_reason: Option<FinishReason>, // Why generation stopped
pub usage: Option<UsageMetadata>, // Token counts
pub partial: bool, // Is this a streaming chunk?
pub turn_complete: bool, // Is the turn finished?
}
تُطبّق جميع موفّرات الخدمة (Gemini وOpenAI وAnthropic وOllama وغير ذلك) هذه السمة، مما يجعلها قابلة للتبادل:
// Switch providers by changing one line
let model: Arc<dyn Llm> = Arc::new(GeminiModel::new(&key, "gemini-3.7-flash")?);
// let model: Arc<dyn Llm> = Arc::new(OpenAIClient::new(config)?);
// let model: Arc<dyn Llm> = Arc::new(AnthropicClient::new(config)?);
let agent = LlmAgentBuilder::new("assistant")
.model(model)
.build()?;