Technical Guide

تصميم سير عمل الذكاء الاصطناعي اللي فعلاً يشتغل في الإنتاج

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

10 يوليو 202518 دقيقة للقراءةفريق هندسة أورنتس

ليش أغلب سير عمل الذكاء الاصطناعي يفشل في الإنتاج

هنا شي تعلمناه بالطريقة الصعبة: أنك تخلي الذكاء الاصطناعي يسوي شي مبهر في ديمو سهل. إنك تخليه يسوي نفس الشي بشكل موثوق، آلاف المرات في اليوم، مع بيانات حقيقية وحالات حدية حقيقية؟ هنا أغلب الفرق تصطدم بالحيط.

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

الفرق بين النموذج الأولي ونظام الإنتاج مش النموذج اللي تستخدمه. هو كل شي حواليه.

خليني أوريك كيف نصمم سير عمل الذكاء الاصطناعي الحين فعلياً، بعد ما تعلمنا هذي الدروس.

تشريح خط أنابيب ذكاء اصطناعي للإنتاج

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

المدخل → التحقق → المعالجة المسبقة → معالجة الذكاء الاصطناعي → المعالجة اللاحقة → التحقق → المخرج
          ↓            ↓                    ↓                      ↓              ↓
       [معالجة     [التحويل           [استدعاء LLM             [التحليل      [فحص
       الخطأ]      + الإثراء]          + الأدوات]              + التنسيق]    الجودة]

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

المرحلة 1: التحقق من المدخلات والتطبيع

لا تثق بالبيانات الواردة أبداً. أبداً. تعلمنا هذا لما عميل أرسلنا "مستند نصي" اللي كان فعلياً ملف بايناري 50 ميجا. الخط اختنق، الطابور تكدس، وقضينا عطلة نهاية الأسبوع نصلحه.

const validateInput = async (input) => {
  const checks = {
    exists: input !== null && input !== undefined,
    sizeOk: Buffer.byteLength(input, 'utf8') < MAX_INPUT_SIZE,
    formatOk: isValidFormat(input),
    contentOk: !containsMaliciousPatterns(input)
  };

  const failures = Object.entries(checks)
    .filter(([_, passed]) => !passed)
    .map(([check]) => check);

  if (failures.length > 0) {
    throw new ValidationError(`فشل المدخل: ${failures.join(', ')}`);
  }

  return normalizeInput(input);
};

شو لازم تتحقق منه:

  • حدود حجم الملف (خليها أقل مما تتوقع)
  • التحقق من التنسيق (هل هو فعلاً JSON، مش بس مسمى .json؟)
  • ترميز الأحرف (مشاكل UTF-8 راح تطاردك)
  • أمان المحتوى (لا ترسل محتوى ضار لـ LLM حقك)

المرحلة 2: المعالجة المسبقة والإثراء

المدخلات الخام نادراً تروح مباشرة لـ LLM. عادةً تحتاج تحولها، تضيف سياق أو تقسمها لقطع.

مهمة المعالجة المسبقةمتى تستخدممثال
التقسيم (Chunking)مستندات طويلة تتجاوز حدود السياقتقسيم PDF من 100 صفحة لقطع 2000 توكن
الإثراءسياق إضافي مطلوبإضافة تاريخ العميل قبل معالجة تذكرة الدعم
الاستخراجبس أجزاء من المدخل مهمةسحب بس حقل "description" من payload JSON
التحويلتحويل التنسيق مطلوبتحويل HTML لـ markdown لمعالجة أنظف
إزالة التكرارالمحتوى المتكرر يضيع التوكناتحذف الفقرات المتكررة من محتوى مسحوب

هنا استراتيجية تقسيم نستخدمها فعلياً:

const chunkDocument = (text, options = {}) => {
  const {
    maxTokens = 2000,
    overlap = 200,
    preserveParagraphs = true
  } = options;

  const chunks = [];
  let currentChunk = '';

  const paragraphs = text.split(/\n\n+/);

  for (const para of paragraphs) {
    const combined = currentChunk + '\n\n' + para;

    if (estimateTokens(combined) > maxTokens) {
      if (currentChunk) {
        chunks.push(currentChunk.trim());
        // الحفاظ على التداخل لاستمرارية السياق
        currentChunk = getLastNTokens(currentChunk, overlap) + '\n\n' + para;
      } else {
        // فقرة واحدة طويلة جداً، تقسيم إجباري
        chunks.push(...forceSplitParagraph(para, maxTokens));
        currentChunk = '';
      }
    } else {
      currentChunk = combined;
    }
  }

  if (currentChunk.trim()) {
    chunks.push(currentChunk.trim());
  }

  return chunks;
};

التداخل مهم جداً. بدونه، المعلومات اللي تمتد عبر حدود القطع تضيع. تعلمنا هذا لما ملخص المستندات حقنا كان يفوت نقاط مهمة صادف وقعت بين القطع.

تصميم نواة معالجة الذكاء الاصطناعي

هنا تصير استدعاءات LLM الفعلية. لكن استدعاء واحد نادراً يكفي للمهام المعقدة.

نمط سلسلة البرومبت

بدلاً من برومبت ضخم يحاول يسوي كل شي، قسم المهام المعقدة لخطوات مركزة:

[فهم] → [تخطيط] → [تنفيذ] → [تحقق] → [تنسيق]

مثال: معالجة شكوى عميل

const processComplaint = async (complaint) => {
  // الخطوة 1: فهم - استخراج المعلومات الرئيسية
  const analysis = await llm.call({
    system: `استخرج معلومات منظمة من شكاوى العملاء.
             أرجع JSON مع: issue_type, urgency, customer_emotion, key_details`,
    user: complaint
  });

  // الخطوة 2: تخطيط - تحديد استراتيجية الرد
  const strategy = await llm.call({
    system: `بناءً على تحليل الشكوى هذا، حدد استراتيجية الرد.
             اعتبر: خيارات الحل، احتياجات التصعيد، أهلية التعويض`,
    user: JSON.stringify(analysis)
  });

  // الخطوة 3: تنفيذ - توليد الرد
  const response = await llm.call({
    system: `اكتب رد للعميل باتباع هذي الاستراتيجية.
             النبرة: متعاطفة، مهنية. الطول: 2-3 فقرات.`,
    user: `الاستراتيجية: ${JSON.stringify(strategy)}\nالشكوى الأصلية: ${complaint}`
  });

  // الخطوة 4: تحقق - فحص الجودة
  const verification = await llm.call({
    system: `راجع رد العميل هذا. تحقق من:
             - يعالج كل المخاوف؟ - نبرة مناسبة؟ - خطوات تالية قابلة للتنفيذ؟
             أرجع: {approved: boolean, issues: string[]}`,
    user: response
  });

  if (!verification.approved) {
    return await regenerateWithFeedback(response, verification.issues);
  }

  return response;
};

كل خطوة أبسط، أكثر قابلية للاختبار وأسهل في التصحيح. لما شي يخطئ، تعرف بالضبط أي مرحلة فشلت.

متى تسلسل vs. متى توازي

مش كل شي لازم يكون متسلسل. هكذا نقرر:

النمطاستخدم عندمامثال
سلسلة متتابعةكل خطوة تعتمد على مخرج الخطوة السابقةفهم → تخطيط → تنفيذ
تنفيذ متوازيالخطوات مستقلةتحليل عدة مستندات بالتزامن
Map-Reduceتحتاج تعالج عناصر ثم تجمعهاتلخيص كل قطعة، ثم دمج الملخصات
تفرع شرطيمسارات مختلفة لمدخلات مختلفةاستعلامات بسيطة vs. تحليل معقد

مثال التنفيذ المتوازي:

const analyzeDocuments = async (documents) => {
  // معالجة كل المستندات بالتوازي
  const analyses = await Promise.all(
    documents.map(doc => analyzeDocument(doc))
  );

  // Reduce: دمج في تقرير نهائي
  const combinedReport = await llm.call({
    system: 'اصنع من هذي التحليلات الفردية تقرير متماسك',
    user: analyses.map((a, i) => `المستند ${i + 1}:\n${a}`).join('\n\n---\n\n')
  });

  return combinedReport;
};

منطق التفرع: التوجيه للمعالج الصحيح

المدخلات من العالم الحقيقي تتنوع بشكل كبير. "رسالة عميل" ممكن تكون شكوى، سؤال، مجاملة أو سبام. كل واحدة تحتاج معالجة مختلفة.

التوجيه المبني على التصنيف

const routeCustomerMessage = async (message) => {
  const classification = await llm.call({
    system: `صنف رسالة العميل هذي في فئة واحدة بالضبط:
             - complaint: عميل يعبر عن عدم رضا
             - question: عميل يبحث عن معلومات
             - feedback: ملاحظات عامة أو اقتراحات
             - urgent: مشاكل أمان، تهديدات قانونية، تصعيد تنفيذي
             - spam: رسائل غير مهمة أو آلية

             أرجع بس اسم الفئة.`,
    user: message
  });

  const handlers = {
    complaint: handleComplaint,
    question: handleQuestion,
    feedback: handleFeedback,
    urgent: escalateToHuman,
    spam: markAsSpam
  };

  const handler = handlers[classification.toLowerCase()] || handleUnknown;
  return await handler(message);
};

التوجيه المبني على الثقة

أحياناً الذكاء الاصطناعي مش متأكد. ادمج هذا عدم اليقين في توجيهك:

const routeWithConfidence = async (input) => {
  const result = await llm.call({
    system: `حلل ووجه هذا الطلب. أرجع JSON:
             {
               "category": "string",
               "confidence": 0.0-1.0,
               "reasoning": "ليش هذي الفئة"
             }`,
    user: input
  });

  if (result.confidence < 0.7) {
    // ثقة منخفضة - خذ مدخل بشري أو استخدم بديل
    return await handleLowConfidence(input, result);
  }

  if (result.confidence < 0.9 && isHighStakes(result.category)) {
    // ثقة متوسطة في قرارات مهمة - تحقق
    return await verifyThenRoute(input, result);
  }

  // ثقة عالية - تابع تلقائياً
  return await routeToHandler(result.category, input);
};

معالجة الأخطاء: لأن الأشياء راح تتكسر

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

تسلسل الأخطاء

class AIWorkflowError extends Error {
  constructor(message, stage, recoverable = true) {
    super(message);
    this.stage = stage;
    this.recoverable = recoverable;
  }
}

class ValidationError extends AIWorkflowError {
  constructor(message) {
    super(message, 'validation', true);
  }
}

class LLMError extends AIWorkflowError {
  constructor(message, type) {
    super(message, 'llm_call', type !== 'rate_limit');
    this.type = type; // 'timeout', 'rate_limit', 'invalid_response', 'refused'
  }
}

class OutputError extends AIWorkflowError {
  constructor(message) {
    super(message, 'output', true);
  }
}

استراتيجيات إعادة المحاولة اللي تشتغل

أخطاء مختلفة تحتاج مناهج إعادة محاولة مختلفة:

نوع الخطأاستراتيجية إعادة المحاولةأقصى محاولاتBackoff
Rate limitانتظر وأعد المحاولة5أسي مع عشوائية
Timeoutأعد المحاولة فوراً3خطي
رد غير صالحأعد المحاولة مع ملاحظات2لا شي
النموذج رفضأعد الصياغة وأعد المحاولة2لا شي
خطأ خادمانتظر وأعد المحاولة3أسي
const withRetry = async (operation, options = {}) => {
  const {
    maxRetries = 3,
    backoffMs = 1000,
    backoffMultiplier = 2,
    shouldRetry = () => true
  } = options;

  let lastError;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error) {
      lastError = error;

      if (!shouldRetry(error) || attempt === maxRetries) {
        throw error;
      }

      const delay = backoffMs * Math.pow(backoffMultiplier, attempt);
      const jitter = Math.random() * delay * 0.1;
      await sleep(delay + jitter);
    }
  }

  throw lastError;
};

// الاستخدام مع استدعاءات LLM
const safeLLMCall = async (params) => {
  return await withRetry(
    () => llm.call(params),
    {
      maxRetries: 3,
      shouldRetry: (error) => {
        if (error.type === 'rate_limit') return true;
        if (error.type === 'timeout') return true;
        if (error.type === 'server_error') return true;
        return false;
      }
    }
  );
};

الإصلاح الذاتي للردود غير الصالحة

لما الـ LLM يرجع خردة، أحياناً تقدر تصلحها:

const parseWithRecovery = async (llmResponse, expectedSchema) => {
  // المحاولة الأولى: التحليل المباشر
  try {
    const parsed = JSON.parse(llmResponse);
    if (validateSchema(parsed, expectedSchema)) {
      return parsed;
    }
  } catch (e) {
    // تحليل JSON فشل، استمر للاستعادة
  }

  // محاولة الاستعادة: اطلب من LLM يصلح مخرجه
  const fixed = await llm.call({
    system: `الرد التالي لازم يكون JSON صالح يطابق هذا المخطط:
             ${JSON.stringify(expectedSchema)}

             صلح الرد ليكون JSON صالح. أرجع بس الـ JSON المصلح.`,
    user: llmResponse
  });

  try {
    const parsed = JSON.parse(fixed);
    if (validateSchema(parsed, expectedSchema)) {
      return parsed;
    }
  } catch (e) {
    // لسا مكسور
  }

  // البديل الأخير: استخرج اللي تقدر عليه
  return extractPartialData(llmResponse, expectedSchema);
};

المراقبة والقابلية للملاحظة

ما تقدر تصلح اللي ما تشوفه. هنا اللي نتتبعه:

المقاييس الرئيسية

المقياسليش مهمعتبة التنبيه
زمن الاستجابة (p50, p95, p99)تجربة المستخدم، خطر انتهاء الوقتp95 > 10 ثانية
معدل النجاحالصحة العامة< 95%
استخدام التوكناتالتحكم في التكاليف> 150% من الأساس
معدل إعادة المحاولةعدم استقرار مخفي> 10%
توزيع التصنيفاتكشف الانحرافتغير كبير من الأساس
درجات جودة المخرجاتكشف التدهورالمتوسط < 0.8

التسجيل المنظم

كل تشغيل لسير العمل لازم ينتج سجلات قابلة للتتبع:

const runWorkflow = async (input, context) => {
  const runId = generateRunId();
  const startTime = Date.now();

  const log = (stage, data) => {
    logger.info({
      runId,
      stage,
      timestamp: Date.now(),
      elapsed: Date.now() - startTime,
      ...data
    });
  };

  try {
    log('start', { inputSize: input.length });

    const validated = await validate(input);
    log('validated', { valid: true });

    const processed = await process(validated);
    log('processed', {
      tokensUsed: processed.usage.total,
      model: processed.model
    });

    const output = await format(processed);
    log('complete', {
      success: true,
      outputSize: output.length,
      totalTime: Date.now() - startTime
    });

    return output;

  } catch (error) {
    log('error', {
      error: error.message,
      stage: error.stage,
      recoverable: error.recoverable
    });
    throw error;
  }
};

جمع كل شي: مثال كامل

خلينا نبني سير عمل تحليل مستندات يستخدم كل اللي ناقشناه:

class DocumentAnalysisPipeline {
  constructor(options = {}) {
    this.maxChunkSize = options.maxChunkSize || 3000;
    this.concurrency = options.concurrency || 5;
  }

  async run(document) {
    // المرحلة 1: التحقق
    const validated = await this.validate(document);

    // المرحلة 2: المعالجة المسبقة
    const chunks = await this.preprocess(validated);

    // المرحلة 3: تحليل متوازي مع التحكم في التزامن
    const analyses = await this.analyzeChunks(chunks);

    // المرحلة 4: التجميع
    const synthesis = await this.synthesize(analyses);

    // المرحلة 5: فحص الجودة
    const final = await this.qualityCheck(synthesis, document);

    return final;
  }

  async validate(document) {
    if (!document || typeof document !== 'string') {
      throw new ValidationError('المستند لازم يكون نص غير فارغ');
    }

    if (document.length > 1000000) {
      throw new ValidationError('المستند يتجاوز الحجم الأقصى');
    }

    return document;
  }

  async preprocess(document) {
    return chunkDocument(document, {
      maxTokens: this.maxChunkSize,
      overlap: 200
    });
  }

  async analyzeChunks(chunks) {
    const results = [];

    // المعالجة على دفعات للتحكم في التزامن
    for (let i = 0; i < chunks.length; i += this.concurrency) {
      const batch = chunks.slice(i, i + this.concurrency);
      const batchResults = await Promise.all(
        batch.map((chunk, idx) => this.analyzeChunk(chunk, i + idx))
      );
      results.push(...batchResults);
    }

    return results;
  }

  async analyzeChunk(chunk, index) {
    return await withRetry(async () => {
      const result = await llm.call({
        system: `حلل هذا القسم من المستند. استخرج:
                 - المواضيع والثيمات الرئيسية
                 - الحقائق والأرقام المهمة
                 - الاقتباسات أو التصريحات الملحوظة
                 - الأسئلة أو فجوات المعلومات

                 أرجع JSON منظم.`,
        user: `القسم ${index + 1}:\n\n${chunk}`
      });

      return parseWithRecovery(result, ANALYSIS_SCHEMA);
    });
  }

  async synthesize(analyses) {
    const combined = analyses.map((a, i) =>
      `القسم ${i + 1}:\n${JSON.stringify(a, null, 2)}`
    ).join('\n\n---\n\n');

    return await llm.call({
      system: `اصنع من تحليلات الأقسام هذي ملخص مستند شامل.
               الهيكل:
               1. ملخص تنفيذي (2-3 جمل)
               2. النتائج الرئيسية (نقاط)
               3. تفاصيل مهمة
               4. فجوات أو أسئلة
               5. توصيات`,
      user: combined
    });
  }

  async qualityCheck(synthesis, originalDocument) {
    const check = await llm.call({
      system: `راجع هذا التحليل للجودة. تحقق من:
               - الدقة: هل يعكس المستند الأصلي؟
               - الاكتمال: هل النقاط الرئيسية مغطاة؟
               - الوضوح: هل هو منظم وواضح؟

               أرجع: {score: 0-1, issues: string[], approved: boolean}`,
      user: `التحليل:\n${synthesis}\n\nالأصلي (أول 2000 حرف):\n${originalDocument.slice(0, 2000)}`
    });

    if (!check.approved) {
      // سجل للمراجعة لكن لا تفشل
      logger.warn({ issues: check.issues, score: check.score });
    }

    return {
      analysis: synthesis,
      qualityScore: check.score,
      qualityIssues: check.issues
    };
  }
}

الأخطاء الشائعة وكيف تتجنبها

بعد بناء عشرات من هذي الأنظمة، هنا الأخطاء اللي نشوفها أكثر:

1. بدون حدود timeout

كل استدعاء LLM يحتاج timeout. حددها بقوة.

// سيء: بدون timeout
const result = await llm.call(params);

// جيد: timeout صريح
const result = await Promise.race([
  llm.call(params),
  new Promise((_, reject) =>
    setTimeout(() => reject(new Error('Timeout')), 30000)
  )
]);

2. تجاهل حدود التوكنات

تتبع الاستخدام وحدد ميزانيات:

const tokenBudget = {
  max: 10000,
  used: 0,

  canSpend(amount) {
    return this.used + amount <= this.max;
  },

  spend(amount) {
    this.used += amount;
    if (this.used > this.max * 0.8) {
      logger.warn('ميزانية التوكنات استهلكت 80%');
    }
  }
};

3. بدون بديل للمسارات الحرجة

دايماً عندك خطة ب:

const processWithFallback = async (input) => {
  try {
    return await primaryProcess(input);
  } catch (error) {
    if (error.recoverable) {
      return await simplifiedProcess(input);
    }
    // مسار حرج - ضعه في الطابور للمعالجة اليدوية
    await queueForManualReview(input, error);
    return { status: 'queued_for_review' };
  }
};

شو الجاي

سير عمل الذكاء الاصطناعي يصير أكثر تطوراً. هنا وين نشوف الأشياء رايحة:

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

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

إذا تبني سير عمل ذكاء اصطناعي وتصطدم بالحيطان، غالباً شفنا مشكلتك قبل. تواصل معنا، وخلينا نلاقي الحل سوا.

Topics covered

تصميم سير عمل الذكاء الاصطناعيبنية خطوط الأنابيبتسلسل البرومبتاتمعالجة الأخطاءتنسيق LLMأتمتة الذكاء الاصطناعيتفرع سير العملهندسة أنظمة الذكاء الاصطناعي

Ready to implement agentic AI?

Our team specializes in building production-ready AI systems. Let's discuss how we can help you leverage agentic AI for your enterprise.

Start a conversation