Ollama: واجهة برمجة التطبيقات المتوافقة مع OpenAI

واجهة برمجة التطبيقات المتوافقة مع OpenAI هي جسر الترحيل — غيّر سطرين كود، انتقل من السحابة إلى المحلي.

💡 نصيحة: تجعل واجهة API المتوافقة مع OpenAI في Ollama الترحيل بسيطًا للغاية — فقط غيّر base_url إلى http://localhost:11434/v1، واضبط api_key على أي سلسلة غير فارغة (مثلاً، "ollama")، وغيّر اسم النموذج إلى نموذج محلي (مثلاً، "qwen2.5"). يمكنك بعد ذلك إعادة استخدام جميع كود مكتبة openai الحالي. هذا يعني أن تطبيقات ChatGPT الحالية ومشاريع LangChain وسير عمل AutoGen يمكنها التبديل إلى المحلي بدون تغيير كود.

📋 المتطلبات المسبقة: يجب أن تتقن ما يلي أولًا

1. ماذا ستتعلم


2. قصة حقيقية من مؤسس SaaS

⚠️ تحذير: واجهة API المتوافقة مع OpenAI في Ollama ليست مكتملة بنسبة 100% — لا تدعم Function Calling (Tools)، أو Streaming مع tool_calls، أو Assistants API، إلخ. قبل الترحيل، تأكد من فحص ما إذا كان تطبيقك يعتمد على هذه الميزات؛ وإلا ستحتاج للتبديل إلى وكيل ReAct أو استدعاء نقطة النهاية /api/chat مباشرة كبديل.

ℹ️ معلومة: api_key="ollama" هو اصطلاح عنصر نائب لنقطة النهاية المتوافقة في Ollama — لا تتحقق Ollama من محتوى API Key. هذا يعني أن api_key يمكن أن يكون أي سلسلة (مثلاً، "sk-1234"، "dummy")، بوظيفة متطابقة. المصادقة الحقيقية يجب تimplementها في طبقة الوكيل العكسي.

(1) المشكلة: تكلفة GPT-4 الشهرية 2,000 دولار

يستخدم SupportBot لأليس واجهة GPT-4 API، يعالج 5 ملايين رمز شهريًا بفاتورة 2,000 دولار. الشركة تطالب بتخفيض التكاليف، لكن الترحيل يتطلب إعادة كتابة الكثير من الكود — جميع الاستدعاءات مرتبطة بمكتبة openai في Python.

(2) الحل: التبديل إلى المحلي بسطرين كود

تتيح واجهة API المتوافقة مع OpenAI لأليس تغيير base_url وapi_key فقط، مُنجزةً الترحيل في 5 دقائق:

PYTHON
from openai import OpenAI

# قبل: OpenAI سحابي
# client = OpenAI(api_key="sk-xxx")

# بعد: Ollama محلي (سطران فقط تغيرا!)
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")

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

💡 نصيحة: api_key="ollama" هو اصطلاح عنصر نائب لواجهة API المتوافقة مع Ollama — لا تتحقق Ollama من API Key. هذا يعني أن أي شخص يعرف عنوان Ollama يمكنه استدعاءها. في الإنتاج، أضف دائمًا مصادقة Key حقيقية عبر وكيل عكسي مثل Nginx.

(1) مرجع نقاط نهاية API

نقطة نهاية OpenAI نقطة نهاية Ollama المتوافقة الحالة
/v1/chat/completions ✅ متوافقة بالكامل الاستخدام الرئيسي
/v1/completions ✅ متوافقة الإكمال القديم
/v1/embeddings ✅ متوافقة تضمين المتجهات
/v1/models ✅ متوافقة عرض النماذج
/v1/images/generations ❌ غير مدعومة توليد الصور
/v1/audio/transcriptions ❌ غير مدعومة نسخ الصوت
100%
flowchart LR
    A[OpenAI SDK] -->|base_url change| B[Ollama /v1/...]
    B --> C[/v1/chat/completions]
    B --> D[/v1/completions]
    B --> E[/v1/embeddings]
    B --> F[/v1/models]
    B --> G[/v1/images ❌]

(2) تفصيل فروق التوافق

الميزة OpenAI Ollama المتوافقة الفرق
المتدفقة صيغة SSE ✅ متوافقة مع SSE متسقة
Function Calling دعم كامل ⚠️ دعم جزئي بعض النماذج تدعم
وضع JSON response_format ✅ format=json متسقة
التضمينات text-embedding-3 ✅ تستخدم nomic-embed-text نموذج مختلف
الرؤية gpt-4o vision ✅ تستخدم llava نموذج مختلف
الضبط الدقيق مدعوم استخدم Modelfile بدلاً
تحديد المعدل RPM/TPM يتطلب تنفيذًا خارجيًا

▶ مثال 1: تبديل أساسي لمكتبة openai

PYTHON
from openai import OpenAI

# التوجيه إلى خادم Ollama المحلي
client = OpenAI(
    base_url="http://localhost:11434/v1",
    api_key="ollama"  # أي سلسلة غير فارغة تعمل
)

# إكمال المحادثة (استخدام مطابق لواجهة OpenAI API)
response = client.chat.completions.create(
    model="qwen2.5",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Explain RAG in 2 sentences"}
    ],
    temperature=0.3
)

print(response.choices[0].message.content)

الإخراج:

TEXT
# Execution successful

4. ترحيل الميزات الأساسية

(1) مرجع ترحيل الميزات

الميزة كود OpenAI تغيير Ollama الجهد
المحادثة model="gpt-4" model="qwen2.5" تغيير اسم النموذج
المتدفقة stream=True بدون تغيير بدون تغييرات
التضمينات model="text-embedding-3-small" model="nomic-embed-text" تغيير اسم النموذج
وضع JSON response_format={"type": "json_object"} نفسه بدون تغيير
موجه النظام messages=[{"role":"system"...}] نفسه بدون تغييرات
Function Calling tools=[...] ⚠️ دعم جزئي للنموذج يحتاج اختبار

▶ مثال 2: ترحيل الإخراج المتدفق

PYTHON
from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:11434/v1",
    api_key="ollama"
)

# المتدفقة تعمل بشكل متطابق
stream = client.chat.completions.create(
    model="qwen2.5",
    messages=[{"role": "user", "content": "Write a short poem about AI"}],
    stream=True,
    temperature=0.7
)

for chunk in stream:
    if chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)
print()

الإخراج:

TEXT
# Execution successful

▶ مثال 3: ترحيل التضمينات

PYTHON
from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:11434/v1",
    api_key="ollama"
)

# التضمينات: فقط غيّر اسم النموذج
response = client.embeddings.create(
    model="nomic-embed-text",  # كان: text-embedding-3-small
    input="What is the return policy for electronics?"
)

print(f"Embedding dimension: {len(response.data[0].embedding)}")
# 768 بُعد لـ nomic-embed-text

الإخراج:

TEXT
# Execution successful

▶ مثال 4: ترحيل وضع JSON

PYTHON
from openai import OpenAI
import json

client = OpenAI(
    base_url="http://localhost:11434/v1",
    api_key="ollama"
)

# وضع JSON: واجهة API متطابقة
response = client.chat.completions.create(
    model="qwen2.5",
    messages=[
        {"role": "system", "content": "You are a product catalog API."},
        {"role": "user", "content": "List 3 laptops under $500"}
    ],
    response_format={"type": "json_object"},
    temperature=0.3
)

data = json.loads(response.choices[0].message.content)
print(json.dumps(data, indent=2))

الإخراج:

TEXT
# Execution successful

5. الترحيل عمليًا وقيود التوافق

⚠️ ملاحظة: واجهة API المتوافقة مع OpenAI في Ollama ليست تطبيقًا مكتملًا بنسبة 100% — Function Calling (Tools) مدعوم جزئيًا فقط من بعض النماذج وأقل استقرارًا من GPT-4. Assistants API وFine-tuning API وBatch API غير مدعومة. قبل الترحيل، تأكد من فحص ما إذا كان تطبيقك يعتمد على هذه الميزات؛ وإلا ستحتاج لاستخدام وضع JSON + موجه لمحاكاة Function Calling، أو استدعاء نقطة النهاية /api/chat مباشرة كبديل.

(1) قائمة مراجعة الترحيل

عنصر الفحص الوصف المخاطر
اسم النموذج gpt-4 → qwen2.5/llama3.1 يحتاج تقييم جودة
Function Calling اختبر إن كان يعمل بشكل صحيح قد يفشل جزئيًا
أقصى سياق gpt-4: 128K → يختلف حسب النموذج المحلي انتبه لـ num_ctx
حد رموز الإخراج معلمة max_tokens يحتاج اختبار
تحديد المعدل OpenAI RPM → محلي بدون حد يجب بناؤه بنفسك
التزامن OpenAI تزامن عالٍ → محلي محدود يحتاج توسعة

(2) بدائل الميزات غير المتوافقة

ميزة OpenAI بديل Ollama التنفيذ
الضبط الدقيق Modelfile مفصل في الدرس 8
Function Calling موجه + تحليل JSON تنفيذ يدوي
توليد الصور llava (فهم فقط) التوليد غير مدعوم
Batch API سكربتات Shell/Python تنسيق ذاتي
Assistants API وكيل LangChain مفصل في الدرس 13

▶ مثال 5: بديل Function Calling

PYTHON
from openai import OpenAI
import json

client = OpenAI(
    base_url="http://localhost:11434/v1",
    api_key="ollama"
)

# بدلاً من Function Calling، استخدم وضع JSON + موجه
tools = {
    "get_order_status": {"order_id": "string"},
    "process_refund": {"order_id": "string", "amount": "number"},
    "search_products": {"query": "string", "category": "string"}
}

response = client.chat.completions.create(
    model="qwen2.5",
    messages=[
        {"role": "system", "content": f"""You are a customer service bot.
Available tools: {json.dumps(tools)}
If you need to call a tool, respond with JSON: {{"tool": "name", "args": {{...}}}}
Otherwise, respond normally."""},
        {"role": "user", "content": "Where is my order #12345?"}
    ],
    response_format={"type": "json_object"},
    temperature=0.2
)

result = json.loads(response.choices[0].message.content)
if "tool" in result:
    print(f"Call tool: {result['tool']} with args: {result['args']}")
    # Call: get_order_status with {"order_id": "12345"}
else:
    print("Direct response:", result)

الإخراج:

TEXT
Direct response:

6. مثال شامل: ترحيل SupportBot من GPT-4

PYTHON
# ============================================
# شامل: ترحيل SupportBot
# من GPT-4 إلى Ollama المحلي باستخدام OpenAI SDK
# ============================================

from openai import OpenAI
import json
from dataclasses import dataclass
from typing import Optional

@dataclass
class SupportBotMigrator:
    """SupportBot مع تبديل سهل بين OpenAI/Ollama."""

    # غيّر هذين السطرين للتبديل بين السحابة والمحلي
    base_url: str = "http://localhost:11434/v1"
    api_key: str = "ollama"
    model: str = "qwen2.5"
    temperature: float = 0.4

    def __post_init__(self):
        self.client = OpenAI(
            base_url=self.base_url,
            api_key=self.api_key
        )
        self.system_prompt = (
            "You are SupportBot, an e-commerce customer service agent. "
            "Be polite, concise, and helpful. "
            "For order queries, ask for order number. "
            "If unsure, say 'Let me connect you with a human agent.'"
        )
        self.messages = [
            {"role": "system", "content": self.system_prompt}
        ]

    def chat(self, user_input: str) -> str:
        self.messages.append({"role": "user", "content": user_input})
        try:
            response = self.client.chat.completions.create(
                model=self.model,
                messages=self.messages[-10:],  # نافذة منزلقة
                temperature=self.temperature
            )
            reply = response.choices[0].message.content
            self.messages.append({"role": "assistant", "content": reply})
            return reply
        except Exception as e:
            self.messages.pop()
            return f"Error: {e}"

    def classify_intent(self, user_input: str) -> dict:
        response = self.client.chat.completions.create(
            model=self.model,
            messages=[
                {"role": "system", "content": """Classify the customer intent.
Return JSON: {"intent": "order_status|refund|product_query|shipping|other", "confidence": 0.0-1.0}"""},
                {"role": "user", "content": user_input}
            ],
            response_format={"type": "json_object"},
            temperature=0.1
        )
        return json.loads(response.choices[0].message.content)

    def generate_embedding(self, text: str) -> list[float]:
        response = self.client.embeddings.create(
            model="nomic-embed-text",
            input=text
        )
        return response.data[0].embedding

# الاستخدام - مقارنة السحابة مقابل المحلي
if __name__ == "__main__":
    # Ollama محلي (الإعداد الحالي)
    bot = SupportBotMigrator()

    # التبديل إلى OpenAI سحابي (أزل التعليق للاستخدام)
    # bot = SupportBotMigrator(
    #     base_url="https://api.openai.com/v1",
    #     api_key="sk-your-key",
    #     model="gpt-4"
    # )

    queries = [
        "Where is my order #88765?",
        "I want a refund for damaged goods",
        "Does this laptop have HDMI port?"
    ]

    for q in queries:
        intent = bot.classify_intent(q)
        reply = bot.chat(q)
        print(f"Q: {q}")
        print(f"Intent: {intent}")
        print(f"A: {reply[:100]}...")
        print()

❓ أسئلة شائعة

س ماذا أملأ في api_key؟
ج لا تتحقق Ollama من API Key — أي سلسلة غير فارغة تعمل (مثلاً، "ollama"). لكن يجب توفير واحدة، وإلا ستُطلق مكتبة openai خطأً.
س هل يمكن استخدام Function Calling؟
ج مدعوم جزئيًا من بعض النماذج (مثلاً، qwen2.5، llama3.1)، لكنه أقل استقرارًا من GPT-4. نوصي باستخدام وضع JSON + موجه لمحاكاة Function Calling لمزيد من التحكم.
س ماذا لو انخفضت الجودة بعد الترحيل؟
ج استخدم استراتيجية هجينة — الأسئلة البسيطة تستخدم النموذج المحلي 8B، والأسئلة المعقدة ترجع إلى GPT-4. نموذج 8B يغطي 80% من السيناريوهات ويمكنه بالفعل تقليل التكاليف بشكل كبير.
س هل تؤثر أبعاد التضمين المختلفة على RAG؟
ج نعم. nomic-embed-text يُخرج 768 بُعدًا؛ OpenAI text-embedding-3-small يُخرج 1536 بُعدًا. ترحيل نظام RAG يتطلب إعادة بناء فهرس المتجهات.
س كيف أستخدم OpenAI وOllama معًا في وقت واحد؟
ج أنشئ عميلين — واحد يشير إلى OpenAI وواحد إلى Ollama. وجّه الطلبات إلى عميل مختلف حسب التعقيد أو الميزانية.
س هل هناك فرق أداء بين نقطتي /v1 و/api في Ollama؟
ج لا. نقطة النهاية /v1 هي طبقة توافق فوق نقطة النهاية /api، تستدعي نفس محرك الاستنتاج في الأسفل. الأداء متطابق.

📖 ملخص


📝 تمارين

  1. أساسي (صعوبة ⭐): اتصل بـ Ollama باستخدام مكتبة openai في Python، أكمل استدعاء chat.completions، وتحقق من التوافق.
  2. متوسط (صعوبة ⭐⭐): رحّل سكربت OpenAI موجود (مع إخراج متدفق ووضع JSON) إلى Ollama، ووثّق التغييرات المطلوبة.
  3. متقدم (صعوبة ⭐⭐⭐): نفّذ موجه ثنائي الخلفية يوجّه تلقائيًا إلى Ollama المحلي أو GPT-4 السحابي حسب تعقيد السؤال، ويتتبع وفورات التكلفة.
Web-Tutorial.com

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

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

100%