متعدد الوكلاءبرنامج تعليميالذكاء الاصطناعيRustv0.1.8

بناء أنظمة متعددة الوكلاء باستخدام ADK-Rust

تعرف على متى وكيفية استخدام أنماط الوكلاء المتعددين المختلفة—من التنسيق البسيط إلى التنسيق المعقد القائم على الرسوم البيانية—مع أمان النوع وأداء Rust.

·15 دقيقة قراءة·ADK-Rust v0.1.8

1. مقدمة

المشكلة: الذكاء الاصطناعي الذي يصل إلى طريق مسدود

لقد قمت ببناء وكيل الذكاء الاصطناعي الأول الخاص بك. إنه مثير للإعجاب - يمكنه الإجابة على الأسئلة، وتلخيص المستندات، وربما حتى كتابة التعليمات البرمجية. ولكن بعد ذلك تصطدم بالواقع:

"هل يمكنك التحقق من فاتورتي الأخيرة ومساعدتي أيضًا في إعداد الـ API؟"

وكيلك يواجه صعوبة. لم يتم تدريبه على أنظمة الفوترة ووثائق المطورين وسير عمل استكشاف الأخطاء وإصلاحها. موجه التعليمات الخاص به يبلغ بالفعل 2000 رمز محاولًا تغطية كل شيء. جودة الاستجابة تتدهور.

هذا هو سقف الوكيل الواحد. مع نمو تطبيقك، ستواجه مقايضات مؤلمة:

  • الموجهات المتضخمة: كل قدرة جديدة تعني تعليمات أطول، وزمن استجابة أعلى، ومزيدًا من الارتباك للنموذج
  • خبير في كل شيء: وكيل واحد يتعامل مع الفوترة والدعم والمبيعات يصبح متوسط الأداء في المجالات الثلاثة
  • صيانة مستحيلة: تغيير منطق الفوترة يجب ألا يعرض سير عمل الدعم الخاص بك للخطر
  • لا تخصص: لا يمكن لوكيل الرياضيات الخاص بك أن يمتلك أداة حاسبة بينما يمتلك وكيل البحث الخاص بك بحث الويب - فهم يتشاركون كل شيء

الحل: وكلاء متخصصون يعملون معًا

تحل أنظمة الوكلاء المتعددين هذه المشكلة عن طريق تقسيم المهام المعقدة إلى أدوار متخصصة. بدلاً من خبير عام واحد مرهق، يمكنك إنشاء متخصصين مركزين:

  • خدمة العملاء: يقوم coordinator بتوجيه المستخدمين إلى متخصصي الفوترة أو الدعم الفني أو المبيعات - كل منهم لديه تدريب وأدوات مركزة
  • إنشاء المحتوى: يقوم وكيل بحث بجمع الحقائق، ويقوم writer بصياغة السرد، ويقوم محرر بالصقل - كل وكيل يتقن مهارة واحدة
  • توليد الكود: يصمم المخطط البنية، وينفذ coder، ويكتشف المراجع الأخطاء—وجهات النظر المختلفة تحسن الجودة

النتيجة؟ يبقى كل وكيل مركزًا، وتظل المطالبات قابلة للإدارة، ويمكنك تحديث منطق الفوترة دون الحاجة إلى دعم. إنها خدمات مصغرة للذكاء الاصطناعي.

ما ستتعلمه

يوفر ADK-Rust ثلاثة أنماط قوية تدريجيًا لتنسيق الوكلاء المتعددين. سيعلمك هذا البرنامج التعليمي:

  • متى تستخدم كل نمط بناءً على متطلباتك
  • كيفية تنفيذها باستخدام كود Rust الجاهز للإنتاج
  • لماذا تعتبر المقايضات المعمارية مهمة لحالة الاستخدام الخاصة بك

2. اختيار النمط الصحيح

قبل الغوص في الكود، دعنا نفهم ما يقدمه كل نمط:

النمطالأفضل لـمستوى التحكمالتعقيد
المنسقتسليم المحادثاتيقرر LLMمنخفض
AgentToolمعالجة الاستجابةيعالج المنسقمتوسط
رسم بياني للمشرفسير العمل المعقدةإدارة الحالة الكاملةعالية

3. النمط 1: المنسق (الوكلاء الفرعيون)

🎯 حالة الاستخدام: توجيه خدمة العملاء

أنت تبني روبوت خدمة عملاء. قد يسأل المستخدمون عن الفواتير، أو يطلبون مساعدة فنية، أو يستفسرون عن ميزات جديدة. يتطلب كل مجال معرفة متخصصة، ولكن لا ينبغي أن يحتاج المستخدمون إلى معرفة القسم الذي يجب الاتصال به.

يستخدم نمط المنسق نقل الوكيل التلقائي. عند إضافة وكلاء فرعيين عبر .sub_agent()، يقوم ADK-Rust بحقن أداة transfer_to_agent. يقرر LLM متى يتم التسليم بناءً على المحادثة.

المستخدمالمنسقيوجه الطلباتإلى المتخصصينالفواتيروكيل فرعيالدعموكيل فرعيالمبيعاتوكيل فرعيtransfer_to_agenttransfer_to_agenttransfer_to_agent

الخصائص الرئيسية

  • تسليم سلس: يواصل المستخدم المحادثة بشكل طبيعي مع المتخصص
  • التوجيه المدفوع بـ LLM: يقرر coordinator بناءً على سياق المحادثة
  • استمرارية المحادثة: يتم الاحتفاظ بسجل الجلسة عبر عمليات النقل
  • لا معالجة للاستجابة: يتحدث المتخصص مباشرة إلى المستخدم بعد النقل

التنفيذ

use adk_rust::prelude::*;
use std::sync::Arc;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let api_key = std::env::var("GOOGLE_API_KEY")?;
    let model = Arc::new(GeminiModel::new(&api_key, "gemini-2.0-flash")?);

    // Specialist: Billing Agent
    // Clear description helps the coordinator know when to transfer
    let billing_agent = LlmAgentBuilder::new("billing_agent")
        .description(
            "Handles all billing questions: invoices, payments, refunds,              subscription plans, and account charges. Transfer here for              any money-related questions."
        )
        .instruction(
            "You are a billing specialist. Answer questions about invoices,              payments, and subscription plans. Be concise and accurate.              If asked about technical issues, suggest transferring to support."
        )
        .model(model.clone())
        .build()?;

    // Specialist: Technical Support Agent
    let support_agent = LlmAgentBuilder::new("support_agent")
        .description(
            "Provides technical support: troubleshooting, bug reports,              feature questions, and integration help. Transfer here for              any technical problems."
        )
        .instruction(
            "You are a technical support specialist. Help users troubleshoot              issues step by step. Ask clarifying questions when needed.              Be patient and thorough."
        )
        .model(model.clone())
        .build()?;

    // Coordinator: Routes to specialists
    let coordinator = LlmAgentBuilder::new("coordinator")
        .description("Customer service coordinator")
        .instruction(
            "You are a friendly customer service coordinator. Your job is to:\n             1. Greet users warmly\n             2. Understand their needs\n             3. Route to the right specialist:\n                - Billing questions → transfer to billing_agent\n                - Technical issues → transfer to support_agent\n             4. Handle general questions yourself\n\n             Always explain who you're connecting them with."
        )
        .model(model.clone())
        .sub_agent(Arc::new(billing_agent))
        .sub_agent(Arc::new(support_agent))
        .build()?;

    // Run with the built-in launcher
    Launcher::new(Arc::new(coordinator))
        .run()
        .await?;

    Ok(())
}

مثال على محادثة

المستخدم: مرحباً، لدي سؤال حول فاتورتي

[coordinator]: مرحباً! يسعدني مساعدتك في سؤالك المتعلق بالفواتير. دعني أوصلك بأخصائي الفواتير لدينا الذي يمكنه مساعدتك.

النظام: 🔄 تحويل إلى: billing_agent

[billing_agent]: مرحباً! أنا أخصائي الفواتير. يمكنني المساعدة في الفواتير والمدفوعات وأسئلة الاشتراك. ماذا تود أن تعرف عن فاتورتك؟

المستخدم: لماذا تم تحميلي مرتين هذا الشهر؟

[billing_agent]: سأتحقق من هذا الرسم المكرر لك...

✅ متى تستخدم المنسق

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

4. النمط 2: الوكلاء كأدوات (AgentTool)

🎯 حالة الاستخدام: تجميع المعرفة

أنت تبني مساعدًا ذكيًا يجيب على أسئلة تمتد عبر مجالات متعددة. يسأل المستخدم "ما هو 15% من 250، ولماذا هذا الرقم مهم في التاريخ؟" تحتاج إلى استدعاء خبير رياضيات، ثم خبير معلومات عامة، ودمج إجاباتهم.

يلف نمط AgentTool الوكلاء كأدوات قابلة للاستدعاء. على عكس الوكلاء الفرعيين، يستدعي coordinator المتخصصين برمجيًا ويتلقى استجاباتهم لمعالجتها أو دمجها قبل الرد على المستخدم.

المستخدمالمنسقيستدعي الوكلاء كأدواتيعالج الاستجاباتيجمع النتائجيعود إلى المستخدمخبير الرياضياتAgentTool+ آلة حاسبةخبير المعلومات العامةAgentToolLLM معرفةالباحثAgentTool+ web_searchاستدعاء →← استجابة

الاختلافات الرئيسية عن المنسق

المنسق (الوكلاء الفرعيون)

  • • يتحدث المتخصص مع المستخدم مباشرة
  • • متخصص واحد في كل مرة
  • • لا توجد معالجة للاستجابة

AgentTool

  • • يتلقى المنسق الاستجابات
  • • يمكنه استدعاء عدة متخصصين
  • • يجمع ويلخص

التنفيذ

use adk_agent::LlmAgentBuilder;
use adk_tool::{AgentTool, FunctionTool};
use adk_core::ToolContext;
use serde_json::{json, Value};
use std::sync::Arc;

// Calculator tool for the math agent
async fn calculator(
    _ctx: Arc<dyn ToolContext>,
    args: Value
) -> Result<Value, adk_core::AdkError> {
    let operation = args["operation"].as_str().unwrap_or("add");
    let a = args["a"].as_f64().unwrap_or(0.0);
    let b = args["b"].as_f64().unwrap_or(0.0);

    let result = match operation {
        "add" => a + b,
        "multiply" => a * b,
        "percent" => a * (b / 100.0),
        _ => return Err(adk_core::AdkError::Tool(
            format!("Unknown operation: {}", operation)
        )),
    };

    Ok(json!({ "result": result }))
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let api_key = std::env::var("GOOGLE_API_KEY")?;
    let model = Arc::new(GeminiModel::new(&api_key, "gemini-2.5-flash")?);

    // Create calculator tool
    let calc_tool = FunctionTool::new(
        "calculator",
        "Performs arithmetic: add, multiply, percent.          Args: operation (string), a (number), b (number)",
        calculator,
    );

    // Math Expert agent - has its own tools
    let math_agent = LlmAgentBuilder::new("math_expert")
        .description(
            "A math expert that performs calculations. Use for any              math-related questions, percentages, or numerical analysis."
        )
        .instruction(
            "You are a math expert. Use the calculator tool for              calculations. Show your work step by step. Be precise."
        )
        .model(model.clone())
        .tool(Arc::new(calc_tool))
        .build()?;

    // Trivia Expert agent - uses LLM knowledge
    let trivia_agent = LlmAgentBuilder::new("trivia_expert")
        .description(
            "A trivia and history expert. Use for questions about              historical facts, pop culture, science facts, and trivia."
        )
        .instruction(
            "You are a trivia expert with vast knowledge across domains.              Answer questions accurately and include interesting related facts."
        )
        .model(model.clone())
        .build()?;

    // Wrap agents as tools with configuration
    let math_tool = AgentTool::new(Arc::new(math_agent))
        .skip_summarization(false)   // Summarize lengthy responses
        .forward_artifacts(true);     // Pass through any generated files

    let trivia_tool = AgentTool::new(Arc::new(trivia_agent))
        .skip_summarization(false);

    // Coordinator uses agents as tools
    let coordinator = LlmAgentBuilder::new("coordinator")
        .description("Smart assistant that combines expert knowledge")
        .instruction(
            "You are a helpful assistant with access to expert agents:\n             - math_expert: For calculations and math problems\n             - trivia_expert: For facts, history, and trivia\n\n             When questions span multiple domains, call multiple experts              and synthesize their responses into a cohesive answer."
        )
        .model(model)
        .tool(Arc::new(math_tool))
        .tool(Arc::new(trivia_tool))
        .build()?;

    Launcher::new(Arc::new(coordinator)).run().await?;
    Ok(())
}

مثال: سؤال متعدد المجالات

المستخدم: ما هو 15% من 250، وهل هذا الرقم مهم في التاريخ؟

النظام: // يستدعي المنسق أداة math_expert

[يستجيب math_expert]: 15% من 250 هو 37.5

النظام: // المنسق يستدعي أداة trivia_expert

[trivia_expert يستجيب]: 37 و 38 أقل أهمية تاريخيًا، لكن 37.5 درجة مئوية هي درجة حرارة جسم الإنسان...

النظام: // المنسق يقوم بالتركيب

[coordinator]: 15% من 250 يساوي 37.5. ومن المثير للاهتمام أن 37.5 درجة مئوية (99.5 درجة فهرنهايت) قريبة من متوسط درجة حرارة جسم الإنسان البالغ 37 درجة مئوية، مما يجعلها رقمًا ذا أهمية طبية!

✅ متى تستخدم AgentTool

  • تحتاج إلى دمج الاستجابات من عدة خبراء
  • يجب على المنسق تلخيص أو تصفية مخرجات المتخصصين
  • يمتلك المتخصصون أدواتهم الخاصة (قدرات متداخلة)
  • تريد تحكمًا برمجيًا في استدعاء الوكيل

5. النمط 3: رسم بياني المشرف

🎯 حالة الاستخدام: مسار إنشاء المحتوى

أنت تبني نظامًا لإنشاء المحتوى. بالنظر إلى موضوع معين، تحتاج إلى: (1) البحث عنه، (2) كتابة مقال، (3) إضافة أمثلة برمجية. يحدد supervisor الترتيب ديناميكيًا بناءً على المهمة، ويمكن للعمال العودة لإجراء المراجعات.

يستخدم نمط رسم بياني المشرف نظام سير العمل القائم على الرسم البياني الخاص بـ ADK-Rust. يقوم وكيل supervisor بتوجيه ديناميكي للعمال، مع دعم كامل لإدارة الحالة والتنفيذ الدوري.

STARTالمشرفيقرر التاليوكيل عاملالباحثالعاملالكاتبالعاملالمبرمجالعاملENDالعودة للدورة"تم" → إنهاء → ENDالحالة المشتركة• research_output• written_content• code_output

لماذا استخدام الرسم البياني؟

  • التوجيه الديناميكي: يقرر المشرف العامل التالي بناءً على الحالة الحالية
  • التنفيذ الدوري: يمكن للعمال العودة لإجراء التكرارات
  • الحالة المشتركة: تقرأ/تكتب جميع العقد إلى كائن حالة مشترك
  • الحواف الشرطية: مسارات مختلفة بناءً على قرارات LLM
  • حدود التكرار: منع الحلقات اللانهائية

التنفيذ

use adk_agent::LlmAgentBuilder;
use adk_graph::{
    StateGraph,
    edge::{START, END},
    node::{AgentNode, ExecutionConfig, NodeOutput},
    state::State,
};
use adk_model::GeminiModel;
use serde_json::json;
use std::sync::Arc;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let api_key = std::env::var("GOOGLE_API_KEY")?;
    let model = Arc::new(GeminiModel::new(&api_key, "gemini-2.0-flash")?);

    // Supervisor: Decides which worker should act next
    let supervisor = LlmAgentBuilder::new("supervisor")
        .description("Routes tasks to specialized workers")
        .instruction(
            "You are a task supervisor. Based on the task and work done so far,              decide who should work next.\n\n             Workers available:\n             - researcher: Gathers information and facts\n             - writer: Writes content based on research\n             - coder: Creates code examples\n\n             Respond with ONLY one word: 'researcher', 'writer', 'coder', or 'done'."
        )
        .model(model.clone())
        .build()?;

    // Workers with specialized roles
    let researcher = LlmAgentBuilder::new("researcher")
        .instruction("Research the topic. Provide key facts as bullet points.")
        .model(model.clone())
        .build()?;

    let writer = LlmAgentBuilder::new("writer")
        .instruction("Write engaging content based on the research provided.")
        .model(model.clone())
        .build()?;

    let coder = LlmAgentBuilder::new("coder")
        .instruction("Write clean, documented code examples for the topic.")
        .model(model.clone())
        .build()?;

    // Create AgentNodes with input/output mappers
    let supervisor_node = AgentNode::new(Arc::new(supervisor))
        .with_input_mapper(|state| {
            let task = state.get("task").and_then(|v| v.as_str()).unwrap_or("");
            let history = state.get("history")
                .and_then(|v| v.as_array())
                .map(|arr| arr.iter()
                    .filter_map(|h| h.get("agent").and_then(|a| a.as_str()))
                    .map(|s| format!("- {} completed", s))
                    .collect::<Vec<_>>()
                    .join("\n"))
                .unwrap_or_default();

            adk_core::Content::new("user").with_text(format!(
                "Task: {}\n\nWork completed:\n{}\n\nWho next?",
                task,
                if history.is_empty() { "None yet" } else { &history }
            ))
        })
        .with_output_mapper(|events| {
            let mut updates = std::collections::HashMap::new();
            for event in events {
                if let Some(content) = event.content() {
                    let text: String = content.parts.iter()
                        .filter_map(|p| p.text())
                        .collect();
                    let next = if text.to_lowercase().contains("researcher") {
                        "researcher"
                    } else if text.to_lowercase().contains("writer") {
                        "writer"
                    } else if text.to_lowercase().contains("coder") {
                        "coder"
                    } else {
                        "done"
                    };
                    updates.insert("next_agent".to_string(), json!(next));
                }
            }
            updates
        });

    // Build the graph
    let graph = StateGraph::with_channels(&[
        "task", "next_agent", "history",
        "research_output", "written_content", "code_output"
    ])
    .add_node(supervisor_node)
    .add_node(AgentNode::new(Arc::new(researcher)))
    .add_node(AgentNode::new(Arc::new(writer)))
    .add_node(AgentNode::new(Arc::new(coder)))
    // Finalize node compiles all outputs
    .add_node_fn("finalize", |ctx| async move {
        let research = ctx.get("research_output").and_then(|v| v.as_str());
        let content = ctx.get("written_content").and_then(|v| v.as_str());
        let code = ctx.get("code_output").and_then(|v| v.as_str());

        let result = format!(
            "=== FINAL OUTPUT ===\n\n{}\n\n{}\n\n{}",
            research.unwrap_or("No research"),
            content.unwrap_or("No content"),
            code.unwrap_or("No code")
        );
        Ok(NodeOutput::new().with_update("final_result", json!(result)))
    })
    // Graph structure
    .add_edge(START, "supervisor")
    .add_conditional_edges(
        "supervisor",
        |state| state.get("next_agent")
            .and_then(|v| v.as_str())
            .unwrap_or("done")
            .to_string(),
        [
            ("researcher", "researcher"),
            ("writer", "writer"),
            ("coder", "coder"),
            ("done", "finalize"),
        ],
    )
    // Workers cycle back to supervisor
    .add_edge("researcher", "supervisor")
    .add_edge("writer", "supervisor")
    .add_edge("coder", "supervisor")
    .add_edge("finalize", END)
    .compile()?
    .with_recursion_limit(15);  // Prevent infinite loops

    // Execute
    let mut input = State::new();
    input.insert("task".to_string(), json!("Create a guide about Rust error handling"));
    input.insert("history".to_string(), json!([]));

    let result = graph.invoke(input, ExecutionConfig::new("content-thread")).await?;
    println!("{}", result.get("final_result").and_then(|v| v.as_str()).unwrap_or(""));

    Ok(())
}

✅ متى تستخدم رسم بياني المشرف

  • ترتيب سير العمل ديناميكي ويتم تحديده بواسطة LLM
  • قد يحتاج العمال إلى التكرار أو العودة إلى الخلف
  • يجب مشاركة الحالة المعقدة بين الوكلاء
  • تحتاج إلى نقاط تفتيش أو سير عمل قابل للاستئناف
  • يتطلب تقسيم المهام خطوات متسلسلة متعددة

6. مقارنة الأنماط

الميزةالمنسقAgentToolرسم بياني للمشرف
يتحدث المستخدم إلىالمتخصص مباشرةالمنسق فقطالناتج النهائي
مكالمات متعددة الوكلاء❌ واحدًا تلو الآخر✅ متوازٍ ممكن✅ منسق
معالجة الاستجابة
سير العمل الدوري
الحالة المشتركةالجلسة فقطالجلسة فقطحالة الرسم البياني الكاملة
تعقيد الإعداد🟢 منخفض🟡 متوسط🔴 مرتفع

7. الخلاصة

تتيح لك أنظمة الوكلاء المتعددين بناء تطبيقات ذكاء اصطناعي متطورة من خلال الجمع بين الوكلاء المتخصصين. اختر نمطك بناءً على احتياجاتك:

  • المنسق: سريع الإعداد، رائع لتوجيه خدمة العملاء
  • AgentTool: عندما تحتاج إلى معالجة أو دمج الاستجابات
  • رسم بياني للمشرف: سير عمل معقد وديناميكي ومتعدد الخطوات

🦀 ابدأ

هل أنت مستعد لبناء نظام الوكلاء المتعددين الخاص بك؟