وكلاء سير العمل
ينسّق وكلاء سير العمل عدة وكلاء وفق أنماط يمكن التنبؤ بها—مثل مسارات التنفيذ المتسلسلة، أو التنفيذ المتوازي، أو الحلقات التكرارية. وبخلاف LlmAgent الذي يستخدم الاستدلال بالذكاء الاصطناعي، يتبع وكلاء سير العمل مسارات تنفيذ حتمية.
البدء السريع
أنشئ مشروعًا جديدًا:
cargo new workflow_demo
cd workflow_demo
أضف التبعيات إلى Cargo.toml:
[dependencies]
adk-rust = "2.1.0"
tokio = { version = "1.40", features = ["full"] }
dotenvy = "0.15"
أنشئ .env:
echo 'GOOGLE_API_KEY=your-api-key' > .env
SequentialAgent
ينفّذ SequentialAgent الوكلاء الفرعيين واحدًا تلو الآخر. ويرى كل وكيل سجل المحادثة المتراكم من الوكلاء السابقين.
متى يُستخدم
- مسارات متعددة الخطوات تنتقل فيها المخرجات إلى الخطوة التالية
- سير عمل البحث ← التحليل ← التلخيص
- سلاسل تحويل البيانات
مثال كامل
استبدل src/main.rs:
use adk_rust::prelude::*;
use adk_rust::Launcher;
use std::sync::Arc;
#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let api_key = std::env::var("GOOGLE_API_KEY")?;
let model = Arc::new(GeminiModel::new(&api_key, "gemini-3.7-flash")?);
// Step 1: Research agent gathers information
let researcher = LlmAgentBuilder::new("researcher")
.instruction("Research the given topic. List 3-5 key facts or points. \
Be factual and concise.")
.model(model.clone())
.output_key("research") // Saves output to state
.build()?;
// Step 2: Analyzer agent identifies patterns
let analyzer = LlmAgentBuilder::new("analyzer")
.instruction("Based on the research above, identify 2-3 key insights \
or patterns. What's the bigger picture?")
.model(model.clone())
.output_key("analysis")
.build()?;
// Step 3: Summarizer creates final output
let summarizer = LlmAgentBuilder::new("summarizer")
.instruction("Create a brief executive summary combining the research \
and analysis. Keep it under 100 words.")
.model(model.clone())
.build()?;
// Create the sequential pipeline
let pipeline = SequentialAgent::new(
"research_pipeline",
vec![Arc::new(researcher), Arc::new(analyzer), Arc::new(summarizer)],
).with_description("Research → Analyze → Summarize");
println!("📋 Sequential Pipeline: Research → Analyze → Summarize");
println!();
Launcher::new(Arc::new(pipeline)).run().await?;
Ok(())
}
شغّله:
cargo run
مثال على التفاعل
You: Tell me about Rust programming language
🔄 [researcher] Researching...
Here are key facts about Rust:
1. Systems programming language created at Mozilla in 2010
2. Memory safety without garbage collection via ownership system
3. Zero-cost abstractions and minimal runtime
4. Voted "most loved language" on Stack Overflow for 7 years
5. Used by Firefox, Discord, Dropbox, and Linux kernel
🔄 [analyzer] Analyzing...
Key insights:
1. Rust solves the memory safety vs performance tradeoff
2. Strong developer satisfaction drives rapid adoption
3. Trust from major tech companies validates production-readiness
🔄 [summarizer] Summarizing...
Rust is a systems language that achieves memory safety without garbage
collection through its ownership model. Created at Mozilla in 2010, it's
been rated the most loved language for 7 consecutive years. Major companies
like Discord and Linux kernel adopt it for its zero-cost abstractions
and performance guarantees.
آلية العمل
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Researcher │ → │ Analyzer │ → │ Summarizer │
│ (step 1) │ │ (step 2) │ │ (step 3) │
└─────────────┘ └─────────────┘ └─────────────┘
↓ ↓ ↓
"Key facts..." "Insights..." "Executive summary"
- تنتقل رسالة المستخدم إلى الوكيل الأول (الباحث)
- تُضاف استجابة الباحث إلى السجل
- يرى المحلل: رسالة المستخدم + استجابة الباحث
- يرى الملخّص: رسالة المستخدم + استجابة الباحث + استجابة المحلل
- يكتمل المسار عند انتهاء الوكيل الأخير
ParallelAgent
ينفّذ ParallelAgent جميع الوكلاء الفرعيين بشكل متزامن. ويتلقى كل وكيل الإدخال نفسه ويعمل بشكل مستقل.
يُفرض الاستقلال، ولا يقتصر على كونه مقصودًا فحسب: إذ يعمل كل وكيل فرعي في
فرع محادثة خاص به ({parent}.{parallel_agent}.{sub_agent})، وتكون قراءات السجل
محددة النطاق بهذا الفرع. يرى الوكيل الفرعي المحادثة التي أدت إلى
التوزيع المتشعب، لكنه لا يرى ما أنتجه نظراؤه، ولذلك لا يمكن للفروع المتزامنة
أن تلوّث سياق بعضها بعضًا. استخدم ParallelAgent::with_shared_state() عندما
تريد أن ينسّق الوكلاء الفرعيون فيما بينهم.
يُستطلع كل فرع معًا، ولذلك تكون التكلفة الفعلية تقريبًا تكلفة أبطأ فرع بدلًا من مجموع تكاليفها جميعًا، كما أن الفرع البطيء لا يؤخر الفروع الأخرى. وينتج عن ذلك أثران مهمان ينبغي مراعاتهما عند التصميم:
- تتداخل الأحداث. تصل الأحداث بالترتيب الذي تنتجها به الفروع، وليس مجمّعةً لكل وكيل فرعي. استخدم
event.author(أوevent.branch) لإسناد جزء إلى فرعه. ولا يُضمن الترتيب بين الفروع. - خطأ واحد ينهي التشغيل، ويُختار بشكل حتمي. يستمر كل فرع حتى الاكتمال، ثم يُعرض خطأ نهائي واحد. عند فشل أكثر من فرع، يكون الخطأ المُبلّغ عنه هو خطأ الوكيل الفرعي الأسبق في ترتيب التصريح — وليس الخطأ الذي حدث أولًا وفق الزمن الفعلي، إذ كانت التزامنية ستجعل ذلك سباقًا.
يؤدي إسقاط تدفق الأحداث مبكرًا إلى إنهاء الفروع التي لا تزال قيد التنفيذ.
متى يُستخدم
- وجهات نظر متعددة حول الموضوع نفسه
- معالجة التوزيع (المدخل نفسه، وتحليلات مختلفة)
- سيناريوهات متعددة المهام الحساسة للسرعة
مثال كامل
use adk_rust::prelude::*;
use adk_rust::Launcher;
use std::sync::Arc;
#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let api_key = std::env::var("GOOGLE_API_KEY")?;
let model = Arc::new(GeminiModel::new(&api_key, "gemini-3.7-flash")?);
// Three analysts with DISTINCT personas (important for parallel execution)
let technical = LlmAgentBuilder::new("technical_analyst")
.instruction("You are a senior software architect. \
FOCUS ONLY ON: code quality, system architecture, scalability, \
security vulnerabilities, and tech stack choices. \
Start your response with '🔧 TECHNICAL:' and give 2-3 bullet points.")
.model(model.clone())
.build()?;
let business = LlmAgentBuilder::new("business_analyst")
.instruction("You are a business strategist and MBA graduate. \
FOCUS ONLY ON: market opportunity, revenue model, competition, \
cost structure, and go-to-market strategy. \
Start your response with '💼 BUSINESS:' and give 2-3 bullet points.")
.model(model.clone())
.build()?;
let user_exp = LlmAgentBuilder::new("ux_analyst")
.instruction("You are a UX researcher and designer. \
FOCUS ONLY ON: user journey, accessibility, pain points, \
visual design, and user satisfaction metrics. \
Start your response with '🎨 UX:' and give 2-3 bullet points.")
.model(model.clone())
.build()?;
// Create parallel agent
let multi_analyst = ParallelAgent::new(
"multi_perspective",
vec![Arc::new(technical), Arc::new(business), Arc::new(user_exp)],
).with_description("Technical + Business + UX analysis in parallel");
println!("⚡ Parallel Analysis: Technical | Business | UX");
println!(" (All three run simultaneously!)");
println!();
Launcher::new(Arc::new(multi_analyst)).run().await?;
Ok(())
}
💡 تلميح: اجعل تعليمات الوكلاء المتوازيين متميزةً للغاية، باستخدام شخصيات ومجالات تركيز وبوادئ استجابة فريدة. يضمن ذلك أن ينتج كل وكيل مخرجات فريدة.
تفاعل نموذجي
You: Evaluate a mobile banking app
🔧 TECHNICAL:
• Requires robust API security: OAuth 2.0, certificate pinning, encrypted storage
• Offline mode with sync requires complex state management and conflict resolution
• Biometric auth integration varies significantly across iOS/Android platforms
💼 BUSINESS:
• Highly competitive market - need unique differentiator (neobanks, traditional banks)
• Revenue model: interchange fees, premium tiers, or lending products cross-sell
• Regulatory compliance costs significant: PCI-DSS, regional banking laws, KYC/AML
🎨 UX:
• Critical: fast task completion - check balance must be < 3 seconds
• Accessibility essential: screen reader support, high contrast mode, large touch targets
• Trust indicators important: security badges, familiar banking patterns
آلية العمل
┌─────────────────┐
│ User Message │
└────────┬────────┘
┌───────────────────┼───────────────────┐
↓ ↓ ↓
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Technical │ │ Business │ │ UX │
│ Analyst │ │ Analyst │ │ Analyst │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
↓ ↓ ↓
(response 1) (response 2) (response 3)
تبدأ جميع الوكلاء في الوقت نفسه، وتتدفق النتائج عند اكتمالها.
LoopAgent
يشغّل LoopAgent الوكلاء الفرعيين بشكل متكرر حتى استيفاء شرط الخروج أو الوصول إلى الحد الأقصى من التكرارات.
متى يُستخدم
- التحسين التكراري (مسودة ← نقد ← تحسين ← تكرار)
- منطق إعادة المحاولة مع التحسين
- بوابات الجودة التي تتطلب عدة تمريرات
ExitLoopTool
للخروج من الحلقة مبكرًا، امنح الوكيل الرمز ExitLoopTool. وعند استدعائه، فإنه يشير إلى إيقاف الحلقة.
مثال كامل
use adk_rust::prelude::*;
use adk_rust::Launcher;
use std::sync::Arc;
#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let api_key = std::env::var("GOOGLE_API_KEY")?;
let model = Arc::new(GeminiModel::new(&api_key, "gemini-3.7-flash")?);
// Critic agent evaluates content
let critic = LlmAgentBuilder::new("critic")
.instruction("Review the content for quality. Score it 1-10 and list \
specific improvements needed. Be constructive but critical.")
.model(model.clone())
.build()?;
// Refiner agent improves based on critique
let refiner = LlmAgentBuilder::new("refiner")
.instruction("Apply the critique to improve the content. \
If the score is 8 or higher, call exit_loop to finish. \
Otherwise, provide an improved version.")
.model(model.clone())
.tool(Arc::new(ExitLoopTool::new())) // Can exit the loop
.build()?;
// Create inner sequential: critic → refiner
let critique_refine = SequentialAgent::new(
"critique_refine_step",
vec![Arc::new(critic), Arc::new(refiner)],
);
// Wrap in loop with max 3 iterations
let iterative_improver = LoopAgent::new(
"iterative_improver",
vec![Arc::new(critique_refine)],
).with_max_iterations(3)
.with_description("Critique-refine loop (max 3 passes)");
println!("🔄 Iterative Improvement Loop");
println!(" critic → refiner → repeat (max 3x or until quality >= 8)");
println!();
Launcher::new(Arc::new(iterative_improver)).run().await?;
Ok(())
}
تفاعل نموذجي
You: Write a tagline for a coffee shop
🔄 Iteration 1
[critic] Score: 5/10. "Good coffee here" is too generic. Needs:
- Unique value proposition
- Emotional connection
- Memorable phrasing
[refiner] Improved: "Where every cup tells a story"
🔄 Iteration 2
[critic] Score: 7/10. Better! But could be stronger:
- More action-oriented
- Hint at the experience
[refiner] Improved: "Brew your perfect moment"
🔄 Iteration 3
[critic] Score: 8/10. Strong, action-oriented, experiential.
Minor: could be more distinctive.
[refiner] Score is 8+, quality threshold met!
[exit_loop called]
Final: "Brew your perfect moment"
آلية العمل
┌──────────────────────────────────────────┐
│ LoopAgent │
│ ┌────────────────────────────────────┐ │
│ │ SequentialAgent │ │
│ │ ┌──────────┐ ┌──────────────┐ │ │
→ │ │ │ Critic │ → │ Refiner │ │ │ →
│ │ │ (review) │ │ (improve or │ │ │
│ │ └──────────┘ │ exit_loop) │ │ │
│ │ └──────────────┘ │ │
│ └────────────────────────────────────┘ │
│ ↑_____________↓ │
│ repeat until exit │
└──────────────────────────────────────────┘
ConditionalAgent (قائم على القواعد)
ConditionalAgent يفرّع التنفيذ استنادًا إلى شرط متزامن قائم على القواعد. استخدمه للتوجيه الحتمي، مثل اختبار A/B أو التوجيه المستند إلى البيئة.
ConditionalAgent::new("router", |ctx| ctx.session().state().get("premium")..., premium_agent)
.with_else(basic_agent)
ملاحظة: للتوجيه الذكي المستند إلى LLM، استخدم
LlmConditionalAgentبدلًا من ذلك.
LlmConditionalAgent (المستند إلى LLM)
يستخدم LlmConditionalAgent LLM لتصنيف إدخال المستخدم وتوجيهه إلى الوكيل الفرعي المناسب. ويُعد هذا مثاليًا للتوجيه الذكي عندما يتطلب قرار التوجيه فهم المحتوى.
متى يُستخدم؟
- تصنيف النية - التوجيه استنادًا إلى ما يسأل عنه المستخدم
- التوجيه متعدد المسارات - وجود أكثر من وجهتين
- التوجيه المراعي للسياق - يتطلب فهمًا، وليس مجرد كلمات مفتاحية
مثال كامل
use adk_rust::prelude::*;
use adk_rust::Launcher;
use std::sync::Arc;
#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let api_key = std::env::var("GOOGLE_API_KEY")?;
let model = Arc::new(GeminiModel::new(&api_key, "gemini-3.7-flash")?);
// Create specialist agents
let tech_agent: Arc<dyn Agent> = Arc::new(
LlmAgentBuilder::new("tech_expert")
.instruction("You are a senior software engineer. Be precise and technical.")
.model(model.clone())
.build()?
);
let general_agent: Arc<dyn Agent> = Arc::new(
LlmAgentBuilder::new("general_helper")
.instruction("You are a friendly assistant. Explain simply, use analogies.")
.model(model.clone())
.build()?
);
let creative_agent: Arc<dyn Agent> = Arc::new(
LlmAgentBuilder::new("creative_writer")
.instruction("You are a creative writer. Be imaginative and expressive.")
.model(model.clone())
.build()?
);
// LLM classifies the query and routes accordingly
let router = LlmConditionalAgent::builder("smart_router", model.clone())
.instruction("Classify the user's question as exactly ONE of: \
'technical' (coding, debugging, architecture), \
'general' (facts, knowledge, how-to), \
'creative' (writing, stories, brainstorming). \
Respond with ONLY the category name.")
.route("technical", tech_agent)
.route("general", general_agent.clone())
.route("creative", creative_agent)
.default_route(general_agent)
.build()?;
println!("🧠 LLM-Powered Intelligent Router");
Launcher::new(Arc::new(router)).run().await?;
Ok(())
}
تفاعل نموذجي
You: How do I fix a borrow error in Rust?
[Routing to: technical]
[Agent: tech_expert]
A borrow error occurs when Rust's ownership rules are violated...
You: What's the capital of France?
[Routing to: general]
[Agent: general_helper]
The capital of France is Paris! It's a beautiful city...
You: Write me a haiku about the moon
[Routing to: creative]
[Agent: creative_writer]
Silver orb above,
Shadows dance on silent waves—
Night whispers secrets.
آلية العمل
┌─────────────────┐
│ User Message │
└────────┬────────┘
↓
┌─────────────────┐
│ LLM Classifies│ "technical" / "general" / "creative"
│ (smart_router)│
└────────┬────────┘
↓
┌────┴────┬──────────┐
↓ ↓ ↓
┌───────┐ ┌───────┐ ┌─────────┐
│ tech │ │general│ │creative │
│expert │ │helper │ │ writer │
└───────┘ └───────┘ └─────────┘
دمج وكلاء سير العمل
يمكن تداخل وكلاء سير العمل لتنفيذ أنماط معقدة.
تسلسلي + متوازٍ + حلقة
use adk_rust::prelude::*;
use std::sync::Arc;
// 1. Parallel analysis from multiple perspectives
let parallel_analysis = ParallelAgent::new(
"multi_analysis",
vec![Arc::new(tech_analyst), Arc::new(biz_analyst)],
);
// 2. Synthesize the parallel results
let synthesizer = LlmAgentBuilder::new("synthesizer")
.instruction("Combine all analyses into a unified recommendation.")
.model(model.clone())
.build()?;
// 3. Quality loop: critique and refine
let quality_loop = LoopAgent::new(
"quality_check",
vec![Arc::new(critic), Arc::new(refiner)],
).with_max_iterations(2);
// Final pipeline: parallel → synthesize → quality loop
let full_pipeline = SequentialAgent::new(
"full_analysis_pipeline",
vec![
Arc::new(parallel_analysis),
Arc::new(synthesizer),
Arc::new(quality_loop),
],
);
تتبّع تنفيذ سير العمل
لمعرفة ما يحدث داخل سير العمل، فعّل التتبّع:
use adk_rust::prelude::*;
use adk_rust::{SessionId, UserId};
use adk_rust::runner::{Runner, RunnerConfig};
use adk_rust::futures::StreamExt;
use std::sync::Arc;
// Create pipeline as before...
// Use Runner instead of Launcher for detailed control
let session_service = Arc::new(InMemorySessionService::new());
let runner = Runner::new(RunnerConfig {
app_name: "workflow_trace".to_string(),
agent: Arc::new(pipeline),
session_service: session_service.clone(),
artifact_service: None,
memory_service: None,
run_config: None,
})?;
let session = session_service.create(CreateRequest {
app_name: "workflow_trace".to_string(),
user_id: "user".to_string(),
session_id: None,
state: Default::default(),
}).await?;
let mut stream = runner.run(
UserId::new("user")?,
SessionId::new(session.id())?,
Content::new("user").with_text("Analyze Rust"),
).await?;
// Process each event to see workflow execution
while let Some(event) = stream.next().await {
let event = event?;
// Show which agent is responding
println!("📍 Agent: {}", event.author);
// Show the response content
if let Some(content) = event.content() {
for part in &content.parts {
if let Part::Text { text } = part {
println!(" {}", text);
}
}
}
println!();
}
مرجع API
SequentialAgent
SequentialAgent::new("name", vec![agent1, agent2, agent3])
.with_description("Optional description")
.before_callback(callback) // Called before execution
.after_callback(callback) // Called after execution
ParallelAgent
ParallelAgent::new("name", vec![agent1, agent2, agent3])
.with_description("Optional description")
.before_callback(callback)
.after_callback(callback)
إذا فشل أي وكيل فرعي، فإن ParallelAgent يستنزف جميع الوعود المستقبلية المتبقية قبل نشر الخطأ الأول، مما يمنع تسرّب الموارد.
LoopAgent
LoopAgent::new("name", vec![agent1, agent2])
.with_max_iterations(5) // Safety limit (recommended, default: 1000)
.with_description("Optional description")
.before_callback(callback)
.after_callback(callback)
ConditionalAgent
ConditionalAgent::new("name", |ctx| condition_fn, if_agent)
.with_else(else_agent) // Optional else branch
.with_description("Optional description")
ExitLoopTool
// Add to an agent to let it exit a LoopAgent
.tool(Arc::new(ExitLoopTool::new()))
السابق: LlmAgent | التالي: أنظمة متعددة الوكلاء →