دليل تقني

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

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

26 مارس 202614 دقيقة للقراءةفريق هندسة أورنتس

فخ التقييد بالمزود

كل مشروع ذكاء اصطناعي يبدأ بمزود واحد. OpenAI أو Anthropic أو نموذج محلي. الـ SDK يدخل في الكود. ميزات خاصة بالمزود (صيغة Function Calling، وضع JSON، سلوك System Prompt) تنحفر في منطق العمل. بعد ستة شهور، تبي تغيّر المزود (تكاليف، أداء، امتثال) وتكتشف إن التغيير يتطلب إعادة كتابة نص التطبيق.

بنينا أنظمة ذكاء اصطناعي تستخدم عدة مزودين بنفس الوقت، وغيّرنا المزود بنص المشروع بدون ما نلمس منطق العمل. هالمقال يغطي البنية اللي تخلّي هالشي ممكن والـ trade-offs الحقيقية المرتبطة فيه.

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

شو اللي يسبب التقييد

مصدر التقييدمثالالخطورة
ربط SDKopenai.chat.completions.create() في 50 ملفعالية
صيغة البرومبتبرومبتات محسّنة لسلوك GPT-4، تفشل مع Claudeعالية
نماذج Fine-Tunedنموذج متدرب على بياناتك، مستضاف عند المزودعالية جدا
ميزات خاصةAssistants API، صيغة Function Calling، وضع JSONمتوسطة
تقييد الـ Embedding100K وثيقة مضمّنة بـ text-embedding-3-small، غير متوافقة مع نماذج ثانيةعالية جدا
بنية Rate Limitنظام مصمم حول Rate Limits و Batching الخاصة بـ OpenAIمنخفضة

طبقة التجريد اللي فعلا تشتغل

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

// واجهة LLM موحدة
interface LlmProvider {
    generate(request: LlmRequest): Promise<LlmResponse>;
    stream(request: LlmRequest): AsyncIterable<LlmChunk>;
    embed(texts: string[]): Promise<number[][]>;
}

interface LlmRequest {
    model: string;
    messages: Message[];
    temperature?: number;
    maxTokens?: number;
    responseFormat?: 'text' | 'json';
    tools?: ToolDefinition[];
}

interface LlmResponse {
    text: string;
    usage: { promptTokens: number; completionTokens: number };
    finishReason: string;
    model: string;
    provider: string;
    latencyMs: number;
}

كل مزود يطبّق هالواجهة:

class OpenAiProvider implements LlmProvider {
    async generate(request: LlmRequest): Promise<LlmResponse> {
        const response = await this.client.chat.completions.create({
            model: request.model,
            messages: this.convertMessages(request.messages),
            temperature: request.temperature,
            max_tokens: request.maxTokens,
            response_format: request.responseFormat === 'json'
                ? { type: 'json_object' } : undefined,
        });
        return this.convertResponse(response);
    }
}

class AnthropicProvider implements LlmProvider {
    async generate(request: LlmRequest): Promise<LlmResponse> {
        const response = await this.client.messages.create({
            model: request.model,
            system: this.extractSystemMessage(request.messages),
            messages: this.convertMessages(request.messages),
            temperature: request.temperature,
            max_tokens: request.maxTokens ?? 4096,
        });
        return this.convertResponse(response);
    }
}

منطق العمل يستخدم الواجهة، ما يستخدم SDK المزود مباشرة أبدا:

// منطق العمل: مستقل عن المزود
async function generateCustomerResponse(ctx: Context, ticket: string): Promise<string> {
    const provider = ctx.getLlmProvider(); // يتحدد من الإعدادات
    const response = await provider.generate({
        model: ctx.getModelForUseCase('customer-support'),
        messages: [
            { role: 'system', content: SUPPORT_SYSTEM_PROMPT },
            { role: 'user', content: ticket },
        ],
        temperature: 0.3,
    });
    return response.text;
}

التحويل من OpenAI لـ Anthropic: غيّر الإعدادات. صفر تعديلات كود في منطق العمل.

مشكلة نقل البرومبتات

البرومبتات مو قابلة للنقل بين النماذج. برومبت محسّن لـ GPT-4 ممكن يعطي نتائج أسوأ على Claude، والعكس صحيح. النماذج تفسر التعليمات بشكل مختلف، تتعامل مع الحالات الحدية بشكل مختلف، وعندها نقاط قوة مختلفة.

الحل: نسخ برومبت لكل عائلة نماذج.

const SUPPORT_PROMPTS: Record<string, string> = {
    'openai': `You are a customer support assistant. Be concise and helpful.
When you don't know the answer, say so clearly.
Always reference the customer's order number.`,

    'anthropic': `You are a customer support assistant.
<instructions>
- Be concise and helpful
- When you don't know the answer, say so clearly
- Always reference the customer's order number
</instructions>`,

    'local': `### System
You are a customer support assistant.
### Rules
1. Be concise and helpful
2. When you don't know the answer, say so clearly
3. Always reference the customer's order number`,
};

function getPrompt(useCase: string, provider: string): string {
    return PROMPTS[useCase][provider] || PROMPTS[useCase]['default'];
}

هالشي يضيف تكلفة صيانة (عدة نسخ من البرومبتات)، بس هي الواقع. برومبت واحد لكل النماذج يعني قبول تراجع بالجودة على نموذج واحد على الأقل.

التوجيه متعدد النماذج

المهام المختلفة عندها متطلبات مختلفة. التصنيف يحتاج سرعة. التوليد يحتاج جودة. الـ Embedding يحتاج ثبات. وجّه كل مهمة للنموذج المناسب.

const MODEL_ROUTING: Record<string, ModelConfig> = {
    'classification': {
        primary: { provider: 'openai', model: 'gpt-4o-mini' },
        fallback: { provider: 'anthropic', model: 'claude-haiku-4-5-20251001' },
        reason: 'سريع، رخيص، كافي للتصنيف',
    },
    'generation': {
        primary: { provider: 'anthropic', model: 'claude-sonnet-4-20250514' },
        fallback: { provider: 'openai', model: 'gpt-4o' },
        reason: 'أفضل جودة للتوليد الطويل',
    },
    'embedding': {
        primary: { provider: 'openai', model: 'text-embedding-3-small' },
        fallback: null, // ما تقدر تبدّل نماذج الـ Embedding بدون إعادة تضمين كل شي
        reason: 'الثبات: كل الـ Embeddings لازم تستخدم نفس النموذج',
    },
    'summarization': {
        primary: { provider: 'anthropic', model: 'claude-haiku-4-5-20251001' },
        fallback: { provider: 'openai', model: 'gpt-4o-mini' },
        reason: 'سريع ورخيص للتلخيص',
    },
};

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

تقييد الـ Embedding: الأصعب بالكسر

الـ Embeddings هي أقوى شكل من أشكال التقييد. بمجرد ما تضمّن 100K وثيقة بـ text-embedding-3-small، التحويل لنموذج Embedding ثاني يتطلب إعادة تضمين كل شي. المتجهات غير متوافقة بين النماذج (أبعاد مختلفة، فضاءات دلالية مختلفة).

التخفيفات:

  • خزّن الوثائق المصدرية جنب الـ Embeddings (عشان تقدر تعيد التضمين)
  • تتبّع أي نموذج Embedding استُخدم لكل وثيقة
  • خصص ميزانية لإعادة التضمين لما تخطط لتغيير المزود
  • فكّر بنماذج Embedding مفتوحة المصدر (sentence-transformers) للنقل

لمزيد عن بنيات الـ Embedding والبحث بالمتجهات، شوف دليل بنية البحث بالمتجهات ودليل أنظمة RAG للمؤسسات.

التطوير القائم على التقييم

المفتاح لتغيير مزود آمن: قيّم مقابل المهام، مو مقابل النماذج. إذا مجموعة التقييم عندك تختبر "هل الإجابة تعالج مشكلة العميل بشكل صحيح؟" بدل "هل المخرج يطابق صيغة GPT-4؟"، تقدر تغيّر النماذج بثقة.

interface EvalCase {
    input: string;
    expectedBehavior: string;  // شو لازم المخرج يسوي، مو شو لازم يقول
    criteria: EvalCriterion[];
}

interface EvalCriterion {
    name: string;
    check: (output: string, context: EvalCase) => boolean | number;
}

const SUPPORT_EVALS: EvalCase[] = [
    {
        input: "I haven't received my order #12345",
        expectedBehavior: "Acknowledge the issue, reference order number, offer next steps",
        criteria: [
            { name: 'references_order', check: (out) => out.includes('12345') },
            { name: 'acknowledges_issue', check: (out) => /sorry|apologize|understand/i.test(out) },
            { name: 'offers_action', check: (out) => /check|investigate|track|follow up/i.test(out) },
            { name: 'reasonable_length', check: (out) => out.length > 50 && out.length < 500 },
        ],
    },
];

// شغّل التقييمات مقابل أي نموذج
async function runEvalSuite(provider: LlmProvider, model: string): Promise<EvalResults> {
    const results = [];
    for (const evalCase of SUPPORT_EVALS) {
        const output = await provider.generate({
            model,
            messages: [
                { role: 'system', content: SUPPORT_SYSTEM_PROMPT },
                { role: 'user', content: evalCase.input },
            ],
        });
        const scores = evalCase.criteria.map(c => ({
            name: c.name,
            passed: c.check(output.text, evalCase),
        }));
        results.push({ input: evalCase.input, scores });
    }
    return summarize(results);
}

شغّل مجموعة التقييم قبل ما تغيّر النماذج. إذا النموذج الجديد نجح بدرجات مقبولة، التغيير آمن. إذا ما نجح، تعرف بالضبط أي سلوكيات تراجعت.

هالنمط مرتبط بشكل وثيق باللي نوصفه في دليل مراقبة الذكاء الاصطناعي ودليل حوكمة الذكاء الاصطناعي.

شو ما لازم تجرّد

مو كل شي لازم يتجرّد. التجريد الزائد يضيف تعقيد بدون فائدة.

لا تجرّدليش
نموذج الـ Embedding (داخل المشروع)المتجهات غير متوافقة. التجريد ما يساعد.
تفاصيل النماذج Fine-Tunedالـ Fine-Tuning بطبيعته خاص بالمزود
تحسينات خاصة بالمزودالـ Batching والـ Caching ومعالجة Rate Limit تختلف لكل مزود
تفاصيل صيغة الـ Streamingعالجها في adapter المزود، مو في التجريد

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

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

متى يكون التقييد بالمزود القرار الصحيح

أحيانا، التقييد بمزود واحد هو القرار التجاري الصحيح:

  • Fine-Tuning يتطلب التزام بمزود واحد. النموذج المتدرب مو قابل للنقل.
  • خصومات الحجم من مزود واحد ممكن تقلل التكاليف بشكل كبير.
  • الامتثال ممكن يتطلب مزود محدد (إقامة البيانات، الشهادات).
  • سرعة الوصول للسوق تعني استخدام ميزات خاصة بالمزود بدون حمل التجريد الزائد.

الأساس: اتخذ قرار التقييد بوعي، مو بالصدفة. إذا اخترت تستخدم Assistants API من OpenAI لأنها توفر 3 شهور تطوير، هذا trade-off صحيح. إذا استخدمتها لأنك ما فكرت بالتجريد، هذا دين تقني.

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

الأخطاء الشائعة

  1. SDK المزود في منطق العمل. openai.chat.completions.create() منتشر على 50 ملف يخلي التغيير مستحيل. استخدم طبقة تجريد.

  2. برومبت واحد لكل النماذج. البرومبتات خاصة بالنموذج. حافظ على نسخ لكل عائلة نماذج. اقبل تكلفة الصيانة.

  3. تجريد كل شي. التجريد الزائد يضيف تعقيد. جرّد الواجهة، مو الداخليات.

  4. بدون مجموعة تقييم. بدون تقييمات أوتوماتيكية، ما تقدر تعرف إذا تغيير النموذج خرّب الجودة لين المستخدمين يشتكون.

  5. تجاهل تقييد الـ Embedding. تغيير نماذج الـ Embedding يتطلب إعادة تضمين كل وثيقة. خطط لهالتكلفة.

  6. تقييد بالصدفة. أسوأ نوع. استخدام ميزات خاصة بالمزود بدون ما تنتبه إنك تخلق اعتمادية.

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

أهم النقاط

  • جرّد الواجهة، مو التطبيق. واجهة LlmProvider موحدة مع adapters خاصة بكل مزود. منطق العمل يستدعي الواجهة.

  • البرومبتات مو قابلة للنقل. حافظ على نسخ برومبت لكل عائلة نماذج. برومبت واحد لكل النماذج يعني تراجع بالجودة بمكان ما.

  • وجّه المهام المختلفة لنماذج مختلفة. التصنيف لنموذج سريع، التوليد لنموذج دقيق، الـ Embeddings لنموذج ثابت. مدفوع بالإعدادات، مو بالكود.

  • قيّم مقابل المهام، مو مقابل النماذج. إذا مجموعة التقييم تختبر السلوك ("هل يذكر رقم الطلب؟") بدل الصيغة، تقدر تغيّر النماذج بثقة.

  • الـ Embedding هو أصعب تقييد تكسره. خزّن الوثائق المصدرية. تتبّع أي نموذج استُخدم. خصص ميزانية لإعادة التضمين لما تغيّر.

  • أحيانا التقييد بالمزود هو الصح. Fine-Tuning وخصومات الحجم والامتثال ممكن يبررونه. خلّه قرار واعي.

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

المواضيع المغطاة

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

جاهز لبناء أنظمة ذكاء اصطناعي جاهزة للإنتاج؟

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

ابدأ محادثة