feat: support Gemma 4 chat and agent trials
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# SovereignAI Starter
|
# SovereignAI Starter
|
||||||
|
|
||||||
مشروع تعليمي لبناء مساعد ذكاء اصطناعي محلي قابل للتوسع. النموذج المحمّل حاليًا: `qwen2.5:1.5b-instruct-q4_K_M` (نحو 986 ميغابايت).
|
مشروع تعليمي لبناء مساعد ذكاء اصطناعي محلي قابل للتوسع. النموذج الافتراضي `qwen2.5:1.5b-instruct-q4_K_M` (نحو 986 ميغابايت)، ونموذج `gemma4:e2b` (نحو 7.16 غيغابايت) مثبت للتجربة والمقارنة.
|
||||||
|
|
||||||
## مكونات النسخة الأولى
|
## مكونات النسخة الأولى
|
||||||
- FastAPI كواجهة HTTP محلية.
|
- FastAPI كواجهة HTTP محلية.
|
||||||
@@ -33,6 +33,15 @@ $env:LOCAL_LLM_BASE_URL = "http://127.0.0.1:11434/v1"
|
|||||||
$env:LOCAL_MODEL = "qwen2.5:1.5b-instruct-q4_K_M"
|
$env:LOCAL_MODEL = "qwen2.5:1.5b-instruct-q4_K_M"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
لتشغيل API على Gemma 4 بدل النموذج الافتراضي في نافذة PowerShell الحالية:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$env:LOCAL_MODEL = "gemma4:e2b"
|
||||||
|
.\start-api.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
يمكن أيضًا اختيار النموذج لكل طلب من `POST /v1/chat/completions` بإرسال `model`، أو من `POST /v1/agent/run` بإرسال `model` مع `task`. يبقى النموذج الافتراضي Qwen ما لم تغيّره من البيئة. على جهاز الاختبار، Gemma 4 E2B اشتغلت محليًا على CPU، لكنها استخدمت قرابة 3.5 غيغابايت من الذاكرة أثناء التحميل؛ قد تختلف السرعة حسب الجهاز.
|
||||||
|
|
||||||
راجع رخصة النموذج المختار وشروطه الحالية قبل الاستخدام التجاري. تنزيل النموذج يحتاج اتصال إنترنت ومساحة تخزين.
|
راجع رخصة النموذج المختار وشروطه الحالية قبل الاستخدام التجاري. تنزيل النموذج يحتاج اتصال إنترنت ومساحة تخزين.
|
||||||
|
|
||||||
5. شغل الـ API على جهازك فقط (أو شغل `start-api.ps1`):
|
5. شغل الـ API على جهازك فقط (أو شغل `start-api.ps1`):
|
||||||
@@ -48,6 +57,7 @@ python -m uvicorn app.main:app --reload --host 127.0.0.1 --port 8000
|
|||||||
- `POST /v1/chat/completions` لإرسال رسالة.
|
- `POST /v1/chat/completions` لإرسال رسالة.
|
||||||
- `POST /v1/agent/run` لتجربة الوكيل والأداة الحسابية.
|
- `POST /v1/agent/run` لتجربة الوكيل والأداة الحسابية.
|
||||||
- `POST /v1/chat/completions` يمرر الطلب إلى الخادم المحلي عند ضبط المتغيرين أعلاه.
|
- `POST /v1/chat/completions` يمرر الطلب إلى الخادم المحلي عند ضبط المتغيرين أعلاه.
|
||||||
|
- يمكن التبديل في Swagger بين النموذجين بإضافة `model` إلى جسم الطلب، مثل `gemma4:e2b`.
|
||||||
|
|
||||||
الخدمة مربوطة بـ `127.0.0.1` عمدًا، فلا تعرضها على الشبكة أو الإنترنت الآن. الوكيل الحالي يمرر السؤال النصي للنموذج، وينفذ الحسابات البسيطة بأداة حساب محلية محدودة. لا يقرأ ملفات ولا ينفذ أوامر النظام.
|
الخدمة مربوطة بـ `127.0.0.1` عمدًا، فلا تعرضها على الشبكة أو الإنترنت الآن. الوكيل الحالي يمرر السؤال النصي للنموذج، وينفذ الحسابات البسيطة بأداة حساب محلية محدودة. لا يقرأ ملفات ولا ينفذ أوامر النظام.
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ class ChatRequest(BaseModel):
|
|||||||
|
|
||||||
class AgentRequest(BaseModel):
|
class AgentRequest(BaseModel):
|
||||||
task: str = Field(description="مهمة قصيرة للوكيل المحلي")
|
task: str = Field(description="مهمة قصيرة للوكيل المحلي")
|
||||||
|
model: str | None = Field(
|
||||||
|
default=None,
|
||||||
|
description="اسم نموذج Ollama؛ اتركه فارغًا لاستخدام النموذج الافتراضي",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class StoredMessage(BaseModel):
|
class StoredMessage(BaseModel):
|
||||||
@@ -106,7 +110,7 @@ def get_local_user() -> dict[str, str]:
|
|||||||
|
|
||||||
def chat_payload(request: ChatRequest, *, stream: bool) -> dict[str, Any]:
|
def chat_payload(request: ChatRequest, *, stream: bool) -> dict[str, Any]:
|
||||||
model = request.model or os.getenv("LOCAL_MODEL", "qwen2.5:1.5b-instruct-q4_K_M")
|
model = request.model or os.getenv("LOCAL_MODEL", "qwen2.5:1.5b-instruct-q4_K_M")
|
||||||
return {
|
payload: dict[str, Any] = {
|
||||||
"model": model,
|
"model": model,
|
||||||
"messages": (
|
"messages": (
|
||||||
[message.model_dump() for message in request.messages]
|
[message.model_dump() for message in request.messages]
|
||||||
@@ -130,6 +134,10 @@ def chat_payload(request: ChatRequest, *, stream: bool) -> dict[str, Any]:
|
|||||||
),
|
),
|
||||||
"stream": stream,
|
"stream": stream,
|
||||||
}
|
}
|
||||||
|
# Disable Gemma 4 thinking so Ollama places the answer in `content` for clients.
|
||||||
|
if model.lower().startswith("gemma4"):
|
||||||
|
payload["reasoning_effort"] = "none"
|
||||||
|
return payload
|
||||||
|
|
||||||
|
|
||||||
@app.post("/v1/chat/completions")
|
@app.post("/v1/chat/completions")
|
||||||
@@ -283,7 +291,7 @@ async def run_agent(request: AgentRequest) -> dict[str, Any]:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
base_url = os.getenv("LOCAL_LLM_BASE_URL", "http://127.0.0.1:11434/v1").rstrip("/")
|
base_url = os.getenv("LOCAL_LLM_BASE_URL", "http://127.0.0.1:11434/v1").rstrip("/")
|
||||||
model = os.getenv("LOCAL_MODEL", "qwen2.5:1.5b-instruct-q4_K_M")
|
model = request.model or os.getenv("LOCAL_MODEL", "qwen2.5:1.5b-instruct-q4_K_M")
|
||||||
payload = {
|
payload = {
|
||||||
"model": model,
|
"model": model,
|
||||||
"messages": [
|
"messages": [
|
||||||
@@ -292,6 +300,8 @@ async def run_agent(request: AgentRequest) -> dict[str, Any]:
|
|||||||
],
|
],
|
||||||
"stream": False,
|
"stream": False,
|
||||||
}
|
}
|
||||||
|
if model.lower().startswith("gemma4"):
|
||||||
|
payload["reasoning_effort"] = "none"
|
||||||
completion = await get_completion(payload, base_url)
|
completion = await get_completion(payload, base_url)
|
||||||
return {
|
return {
|
||||||
"task": request.task,
|
"task": request.task,
|
||||||
|
|||||||
@@ -11,7 +11,11 @@ if ($env:GROQ_API_KEY) {
|
|||||||
Write-Warning 'GROQ_API_KEY is not configured; local chat works, but voice transcription will return 503.'
|
Write-Warning 'GROQ_API_KEY is not configured; local chat works, but voice transcription will return 503.'
|
||||||
}
|
}
|
||||||
$ollama = Invoke-RestMethod -Uri 'http://127.0.0.1:11434/api/tags' -TimeoutSec 5
|
$ollama = Invoke-RestMethod -Uri 'http://127.0.0.1:11434/api/tags' -TimeoutSec 5
|
||||||
$model = 'qwen2.5:1.5b-instruct-q4_K_M'
|
$model = if ($env:LOCAL_MODEL) {
|
||||||
|
$env:LOCAL_MODEL
|
||||||
|
} else {
|
||||||
|
'qwen2.5:1.5b-instruct-q4_K_M'
|
||||||
|
}
|
||||||
if (-not ($ollama.models.name -contains $model)) {
|
if (-not ($ollama.models.name -contains $model)) {
|
||||||
throw "Ollama is running, but model '$model' is missing. Run: ollama pull $model"
|
throw "Ollama is running, but model '$model' is missing. Run: ollama pull $model"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user