DeepSeek Harness: محول LLM

آخر تحديث: 2026-08-31

نهج DSH "المستقل عن النموذج" ليس مجرد شعار — إنه تصميم معماري. محولات LLM، كـ Providers في نظام قدرات Cordis، تجعل تبديل النماذج سهلاً كتبديل بطاريات. هذا الدرس يتعمق في تسجيل محولات LLM وتنفيذها وإدارة النماذج المتعددة.

💡 نصيحة: جميع محولات LLM تنفذ نفس Definition — LLMCapability. الوكلاء والأدوات تعتمد فقط على هذه الواجهة، لا على ما إذا كان النموذج الأساسي DeepSeek أو GPT-4o.

📋 المتطلبات المسبقة: أكمل 22-capability.md، تفهم أدوار القدرات الثلاثة

1. ما ستتعلمه

بنية محول LLM


2. تسجيل محول ctx.llm

(1) واجهة قدرة LLM

محول LLM في DSH ينفذ واجهة LLMCapability:

TYPESCRIPT
interface LLMCapability {
  complete(request: LLMRequest): Promise<LLMResponse>
  stream(request: LLMRequest): AsyncIterable<StreamChunk>
  getModels(): ModelInfo[]
  getModelCapabilities(model: string): ModelCapabilities
}

(2) ▶ مثال 2

TYPESCRIPT
import { Service, Context } from '@deepseek-ai/cordis'

export default class MyLLMAdapter extends Service {
  constructor(ctx: Context) {
    super(ctx, 'llm')
  }
}

super(ctx, 'llm') يُسجّل المحول كخدمة llm.

(3) المحولات المدمجة

DSH يأتي مع محولي LLM:

المحول اسم الخدمة النماذج المدعومة
DeepSeek llm deepseek-chat, deepseek-reasoner
OpenAI Compatible llm أي API متوافق مع OpenAI

(4) ▶ مثال 4

YAML
# cordis.yml
plugins:
  llm:
    $replace: ./adapters/my-custom-llm
    config:
      apiKey: sk-xxx
      endpoint: https://api.example.com/v1

3. محول نقطة نهاية متوافق مع OpenAI

(1) نظرة عامة على البروتوكول

OpenAI Chat Completions API هو المعيار الفعلي في الصناعة. المحول المدمج في DSH متوافق مع هذا البروتوكول:

TYPESCRIPT
interface OpenAIRequest {
  model: string
  messages: { role: string; content: string }[]
  temperature?: number
  max_tokens?: number
  stream?: boolean
  tools?: ToolDefinition[]
}

(2) ▶ مثال 2

YAML
# cordis.yml
plugins:
  llm:
    config:
      provider: openai-compatible
      apiKey: sk-xxx
      endpoint: https://api.openai.com/v1
      models:
        - id: gpt-4o
          capabilities: [chat, tool-use, vision]
        - id: gpt-4o-mini
          capabilities: [chat, tool-use]

(3) نقاط نهاية مخصصة

أي API متوافق مع OpenAI يمكن توصيله:

YAML
# الاتصال بـ Ollama المحلي
plugins:
  llm:
    config:
      provider: openai-compatible
      endpoint: http://localhost:11434/v1
      apiKey: ollama
      models:
        - id: llama3
          capabilities: [chat]

# الاتصال بـ Azure OpenAI
plugins:
  llm:
    config:
      provider: openai-compatible
      endpoint: https://my-resource.openai.azure.com/openai/deployments/my-deployment
      apiKey: xxx
      headers:
        api-key: xxx

(4) كتابة محول مخصص

عندما لا يلبي المحول المدمج احتياجاتك، اكتب واحداً مخصصاً:

TYPESCRIPT
import { Service, Context } from '@deepseek-ai/cordis'
import { LLMCapability, LLMRequest, LLMResponse, StreamChunk } from '@deepseek-ai/dsh'

export const Config = Schema.object({
  apiKey: Schema.string().required().description('API key'),
  endpoint: Schema.string().required().description('API endpoint'),
  defaultModel: Schema.string().default('custom-model').description('Default model ID')
})

export default class CustomLLMAdapter extends Service implements LLMCapability {
  static inject = []

  private apiKey: string
  private endpoint: string
  private defaultModel: string

  constructor(ctx: Context) {
    super(ctx, 'llm')
    this.apiKey = ctx.config.apiKey
    this.endpoint = ctx.config.endpoint
    this.defaultModel = ctx.config.defaultModel
  }

  async complete(request: LLMRequest): Promise<LLMResponse> {
    const response = await fetch(`${this.endpoint}/chat/completions`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.apiKey}`
      },
      body: JSON.stringify({
        model: request.model || this.defaultModel,
        messages: request.messages,
        temperature: request.temperature,
        max_tokens: request.maxTokens,
        stream: false
      })
    })

    const data = await response.json()
    return {
      content: data.choices[0].message.content,
      model: data.model,
      usage: data.usage
    }
  }

  async *stream(request: LLMRequest): AsyncIterable<StreamChunk> {
    const response = await fetch(`${this.endpoint}/chat/completions`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.apiKey}`
      },
      body: JSON.stringify({
        model: request.model || this.defaultModel,
        messages: request.messages,
        temperature: request.temperature,
        max_tokens: request.maxTokens,
        stream: true
      })
    })

    const reader = response.body!.getReader()
    const decoder = new TextDecoder()
    let buffer = ''

    while (true) {
      const { done, value } = await reader.read()
      if (done) break

      buffer += decoder.decode(value, { stream: true })
      const lines = buffer.split('\n')
      buffer = lines.pop() || ''

      for (const line of lines) {
        if (line.startsWith('data: ')) {
          const data = line.slice(6)
          if (data === '[DONE]') return
          try {
            const parsed = JSON.parse(data)
            const delta = parsed.choices[0].delta
            if (delta.content) {
              yield { type: 'text', content: delta.content }
            }
          } catch {}
        }
      }
    }
  }

  getModels() {
    return [{ id: this.defaultModel, capabilities: ['chat', 'tool-use'] }]
  }

  getModelCapabilities(model: string) {
    return { chat: true, toolUse: true, vision: false }
  }
}

4. توجيه النماذج المخصص

(1) مفهوم التوجيه

توجيه النماذج يختار محولات LLM مختلفة بناءً على خصائص الطلب:

100%
graph TB
    REQ[طلب LLM] --> ROUTER{مُوجّه النماذج}
    ROUTER -->|مهمة كود| CODE[DeepSeek Coder]
    ROUTER -->|استدلال| REASON[DeepSeek Reasoner]
    ROUTER -->|محادثة بسيطة| CHAT[GPT-4o-mini]

(2) إعداد التوجيه

YAML
plugins:
  llm:
    config:
      routing:
        default: deepseek-chat
        rules:
          - match:
              mode: ptc
            model: deepseek-reasoner
          - match:
              mode: creative
            model: deepseek-chat
          - match:
              tools: [shell]
            model: deepseek-coder

(3) منطق توجيه مخصص

TYPESCRIPT
export default class RoutingLLMAdapter extends Service {
  private adapters: Map<string, LLMCapability> = new Map()

  constructor(ctx: Context) {
    super(ctx, 'llm')
  }

  async complete(request: LLMRequest): Promise<LLMResponse> {
    const model = this._selectModel(request)
    const adapter = this._getAdapter(model)
    return adapter.complete({ ...request, model })
  }

  private _selectModel(request: LLMRequest): string {
    if (request.tools?.some(t => t.name === 'shell')) {
      return 'deepseek-coder'
    }
    if (request.metadata?.mode === 'ptc') {
      return 'deepseek-reasoner'
    }
    return 'deepseek-chat'
  }

  private _getAdapter(model: string): LLMCapability {
    const prefix = model.split('-')[0]
    return this.adapters.get(prefix) || this.adapters.get('default')!
  }
}

5. بروتوكول StreamChunk

(1) تعريف البروتوكول

StreamChunk هو بروتوكول DSH الموحد للمخرجات المتدفقة:

TYPESCRIPT
type StreamChunk =
  | { type: 'text'; content: string }
  | { type: 'tool_call'; id: string; name: string; arguments: string }
  | { type: 'tool_result'; id: string; result: any }
  | { type: 'error'; error: Error }
  | { type: 'done'; reason: 'stop' | 'tool_use' | 'length' }

(2) شرح أنواع القطع

النوع الوصف المصدر
text جزء محتوى نصي مخرجات LLM المتدفقة
tool_call طلب استدعاء أداة LLM يقرر استدعاء أداة
tool_result نتيجة تنفيذ الأداة بعد تنفيذ الأداة
error معلومات خطأ أخطاء في أي مرحلة
done نهاية التدفق انتهت مخرجات LLM

(3) استهلاك StreamChunks

TYPESCRIPT
for await (const chunk of ctx.llm.stream(request)) {
  switch (chunk.type) {
    case 'text':
      process.stdout.write(chunk.content)
      break
    case 'tool_call':
      console.log(`\nTool call: ${chunk.name}`)
      break
    case 'tool_result':
      console.log(`Tool result:`, chunk.result)
      break
    case 'error':
      console.error(`Error:`, chunk.error)
      break
    case 'done':
      console.log(`\nDone (${chunk.reason})`)
      break
  }
}

6. تصريحات قدرات النموذج

(1) واجهة ModelCapabilities

TYPESCRIPT
interface ModelCapabilities {
  chat: boolean
  toolUse: boolean
  vision: boolean
  maxTokens: number
  supportedModes: string[]
}

(2) التصريح بقدرات النموذج

TYPESCRIPT
getModels(): ModelInfo[] {
  return [
    {
      id: 'deepseek-chat',
      capabilities: {
        chat: true,
        toolUse: true,
        vision: false,
        maxTokens: 65536,
        supportedModes: ['standard', 'ptc', 'minimal', 'creative']
      }
    },
    {
      id: 'deepseek-reasoner',
      capabilities: {
        chat: true,
        toolUse: true,
        vision: false,
        maxTokens: 65536,
        supportedModes: ['ptc']
      }
    }
  ]
}

(3) غرض تصريحات القدرات

الإطار يستخدم تصريحات القدرات ليقرر:


7. موازنة تحميل النماذج المتعددة

(1) الإعدادات

YAML
plugins:
  llm:
    config:
      loadBalancing:
        strategy: round-robin
        endpoints:
          - model: deepseek-chat
            endpoint: https://api.deepseek.com/v1
            apiKey: sk-key1
            weight: 3
          - model: gpt-4o
            endpoint: https://api.openai.com/v1
            apiKey: sk-key2
            weight: 1

(2) استراتيجيات الموازنة

الاستراتيجية الوصف
round-robin تناوب دائري
random اختيار عشوائي
weighted توزيع مرجح
least-latency اختيار أقل زمن استجابة

(3) موازنة تحميل مخصصة

TYPESCRIPT
export default class LoadBalancedLLM extends Service {
  private endpoints: Endpoint[]
  private currentIndex = 0

  async complete(request: LLMRequest): Promise<LLMResponse> {
    const endpoint = this._nextEndpoint()
    return endpoint.complete(request)
  }

  private _nextEndpoint(): Endpoint {
    const endpoint = this.endpoints[this.currentIndex]
    this.currentIndex = (this.currentIndex + 1) % this.endpoints.length
    return endpoint
  }
}

❓ أسئلة شائعة

س هل يمكن تسجيل محولات LLM متعددة في نفس الوقت؟
ج ليس افتراضياً — التسجيلات اللاحقة تتجاوز السابقة. استخدم عزل realm أو محول توجيه لإدارة النماذج المتعددة.
س أي الدوال يجب أن ينفذها المحول المخصص؟
ج يجب تنفيذ complete، stream، getModels، getModelCapabilities. غياب أي دالة يُسبب خطأ ترجمة TypeScript.
س كيف أختبر محولاً مخصصاً؟
ج استخدم نمط InMemoryProvider — لا ترسل طلبات HTTP حقيقية، أعد استجابات مُعدة مسبقاً. استبدل خدمة llm في اختبارات الوحدات.
س ماذا لو انقطعت المخرجات المتدفقة؟
ج بروتوكول StreamChunk يتضمن نوع error. المستهلكون يجب أن يعالجوا قطع الأخطاء، يسجلوها، ويقرروا إن كان يجب إعادة المحاولة.
س ما الفرق بين توجيه النماذج وموازنة التحميل؟
ج التوجيه يختار نموذجاً بناءً على خصائص الطلب (كود→coder)، بينما موازنة التحميل توزع الطلبات عبر نقاط نهاية متعددة لنفس النموذج.
س كيف أُخزّن مفاتيح API بأمان في المحولات؟
ج استخدم متغيرات بيئة أو ملفات .env؛ لا تُرمّز بثبات في الإعدادات. Schemastery يدعم .hidden() لتمييز الحقول الحساسة.

📖 ملخص


📝 تمارين

1. ⭐ أساسي: هيّئ DSH لاستخدام نقطة نهاية متوافقة مع OpenAI لـ GPT-4o، عيّن API Key ومُعرّف النموذج، واجعل الوكيل يُكمل محادثة بنجاح.

2. ⭐⭐ متوسط: اكتب محول LLM مخصص يُوجّه الطلبات لخدمة Ollama محلية (http://localhost:11434/v1). نفّذ دوال complete و stream، اختبر المخرجات المتدفقة.

3. ⭐⭐⭐ تحدٍ: نفّذ محول توجيه يختار النماذج بناءً على وضع تشغيل الوكيل — وضع PTC يستخدم reasoner، الأوضاع الأخرى تستخدم chat. أنشئ جلستين تستخدمان أوضاعاً مختلفة وتحقق أنهما تستخدمان نماذج مختلفة.

Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%