Ollama: واجهة برمجة التطبيقات المتوافقة مع OpenAI
واجهة برمجة التطبيقات المتوافقة مع OpenAI هي جسر الترحيل — غيّر سطرين كود، انتقل من السحابة إلى المحلي.
💡 نصيحة: تجعل واجهة API المتوافقة مع OpenAI في Ollama الترحيل بسيطًا للغاية — فقط غيّر
base_url إلى http://localhost:11434/v1، واضبط api_key على أي سلسلة غير فارغة (مثلاً، "ollama")، وغيّر اسم النموذج إلى نموذج محلي (مثلاً، "qwen2.5"). يمكنك بعد ذلك إعادة استخدام جميع كود مكتبة openai الحالي. هذا يعني أن تطبيقات ChatGPT الحالية ومشاريع LangChain وسير عمل AutoGen يمكنها التبديل إلى المحلي بدون تغيير كود.
📋 المتطلبات المسبقة: يجب أن تتقن ما يلي أولًا
- الدرس 5: أساسيات REST API
1. ماذا ستتعلم
- تعيين نقاط نهاية
/v1/chat/completionsو/v1/embeddings - تبديل مكتبة openai في Python إلى Ollama
- فروق التوافق وقيود الميزات
- الترحيل عمليًا: تحويل تطبيق ChatGPT إلى خلفية Ollama
- دراسة حالة أليس: توفير 2,000 دولار شهريًا
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 |
❌ غير مدعومة | نسخ الصوت |
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، تستدعي نفس محرك الاستنتاج في الأسفل. الأداء متطابق.
📖 ملخص
- توفر Ollama نقاط نهاية متوافقة مع OpenAI مثل /v1/chat/completions، تغطي المحادثة/التضمينات/النماذج
- الترحيل يتطلب تغيير base_url وapi_key فقط — سطران كود
- Function Calling مدعوم جزئيًا؛ يُوصى بوضع JSON كبديل
- نماذج التضمين تختلف؛ ترحيل RAG يتطلب إعادة بناء فهرس المتجهات
- استراتيجية هجينة: 80% محلي + 20% سحابي لأقصى فعالية من حيث التكلفة
- أليس توفر 2,000 دولار شهريًا، تُنجز الترحيل في 5 دقائق
📝 تمارين
- أساسي (صعوبة ⭐): اتصل بـ Ollama باستخدام مكتبة openai في Python، أكمل استدعاء chat.completions، وتحقق من التوافق.
- متوسط (صعوبة ⭐⭐): رحّل سكربت OpenAI موجود (مع إخراج متدفق ووضع JSON) إلى Ollama، ووثّق التغييرات المطلوبة.
- متقدم (صعوبة ⭐⭐⭐): نفّذ موجه ثنائي الخلفية يوجّه تلقائيًا إلى Ollama المحلي أو GPT-4 السحابي حسب تعقيد السؤال، ويتتبع وفورات التكلفة.