feat: support Gemma 4 chat and agent trials

This commit is contained in:
Hamza Ayed
2026-09-29 23:19:25 +03:00
parent 18d17eba50
commit 874c7152d1
3 changed files with 28 additions and 4 deletions
+11 -1
View File
@@ -1,6 +1,6 @@
# 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 محلية.
@@ -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"
```
لتشغيل 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`):
@@ -48,6 +57,7 @@ python -m uvicorn app.main:app --reload --host 127.0.0.1 --port 8000
- `POST /v1/chat/completions` لإرسال رسالة.
- `POST /v1/agent/run` لتجربة الوكيل والأداة الحسابية.
- `POST /v1/chat/completions` يمرر الطلب إلى الخادم المحلي عند ضبط المتغيرين أعلاه.
- يمكن التبديل في Swagger بين النموذجين بإضافة `model` إلى جسم الطلب، مثل `gemma4:e2b`.
الخدمة مربوطة بـ `127.0.0.1` عمدًا، فلا تعرضها على الشبكة أو الإنترنت الآن. الوكيل الحالي يمرر السؤال النصي للنموذج، وينفذ الحسابات البسيطة بأداة حساب محلية محدودة. لا يقرأ ملفات ولا ينفذ أوامر النظام.
+12 -2
View File
@@ -47,6 +47,10 @@ class ChatRequest(BaseModel):
class AgentRequest(BaseModel):
task: str = Field(description="مهمة قصيرة للوكيل المحلي")
model: str | None = Field(
default=None,
description="اسم نموذج Ollama؛ اتركه فارغًا لاستخدام النموذج الافتراضي",
)
class StoredMessage(BaseModel):
@@ -106,7 +110,7 @@ def get_local_user() -> dict[str, str]:
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")
return {
payload: dict[str, Any] = {
"model": model,
"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,
}
# 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")
@@ -283,7 +291,7 @@ async def run_agent(request: AgentRequest) -> dict[str, Any]:
pass
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 = {
"model": model,
"messages": [
@@ -292,6 +300,8 @@ async def run_agent(request: AgentRequest) -> dict[str, Any]:
],
"stream": False,
}
if model.lower().startswith("gemma4"):
payload["reasoning_effort"] = "none"
completion = await get_completion(payload, base_url)
return {
"task": request.task,
+5 -1
View File
@@ -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.'
}
$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)) {
throw "Ollama is running, but model '$model' is missing. Run: ollama pull $model"
}