انتقل إلى المحتوى

خطّ الذكاء الاصطناعي (Gemini)

خطّ REST بلا SDK: محادثة وصوت وترجمة فورية — مع أدوات تُنفَّذ في جانب الخادم (function calling).

الصورة العامة

المتصفّح (/me/ai, /me/translate)
   │  fetch من المصدر نفسه (ترويسة CSRF)
BFF بـ Next.js  /api/ai/{chat,stt,tts,translate}   ← يتحقّق من البنية ويرفق JWT
   │  خادم←خادم
واجهة Go  /api/v1/ai/*   (JWT + تحديد معدّل ~20/دقيقة)
usecases/ai ──────────► pkg/gemini ──────► واجهة Gemini REST
   │   ▲                 (3 محاولات بتراجع تدريجي عند 429/5xx/أخطاء الشبكة)
   │   └─ functionResponse
ToolDef.Execute()  ← يعمل في الواجهة الخلفية بسياق الطلب
   ├─ search_knowledge ← جدول ai_knowledge
   └─ get_server_time  ← أداة توضيحية

المبدأ الأساسي

النموذج يقرّر أيّ أداة يستدعي، والواجهة الخلفية تنفّذها. ولا ينفّذ النموذج شيفرةً أبدًا. وتعمل الأدوات بسياق الطلب، فتنطبق سياسات RLS والمهل على كلّ ما تلمسه.

مسار المحادثة (حلقة function calling)

  1. بناء contents من السجلّ (20 دورًا كحدّ أقصى) مع الرسالة الجديدة. وتصل الرسالة الصوتية جزءًا صوتيًّا مضمّنًا بترميز base64 — ويفهمه Gemini مباشرةً، فلا حاجة إلى خطوة تحويل كلام إلى نصّ منفصلة.
  2. استدعاء Gemini بتعليمة النظام متعدّدة الطبقات وبتعريفات الأدوات.
  3. إذا تضمّنت الاستجابة استدعاءات دوالّ: تُنفَّذ كلّ أداة، ويُضاف دور النموذج ثمّ دور functionResponse، وتتكرّر الحلقة (حتى MaxSteps، وقيمته الافتراضية 4). ويُسجَّل كلّ استدعاء منفَّذ على هيئة Step{Tool, Args, Result} ويُعاد إلى العميل، لتتمكّن الواجهة من عرض «ما فعله الذكاء الاصطناعي».
  4. وإذا كانت الاستجابة نصًّا: يُعاد كما هو.

دلالات الإخفاق

الحالة النتيجة
إخفاق عابر في Gemini (بعد محاولات العميل الثلاث) ليس خطأ 5xx — بل ردّ احتياطي بلغة المستخدم مع degraded: true
غياب GEMINI_API_KEY خطأ حقيقي — 500، مع تسجيل السبب
أداة مجهولة أو فاشلة يُبلَّغ بها النموذج على هيئة {"error": …} — ولا تصل إلى العميل مباشرةً أبدًا

لا تغيّر هذا السلوك

يجب أن تتراجع المحادثة بلطف إلى الردّ الاحتياطي عند الإخفاقات العابرة في Gemini — فلا تحوّل ذلك إلى خطأ 5xx.

طبقات التعليمة

تُجمَّع تعليمة النظام في كلّ طلب من ثلاث طبقات:

  1. ضوابط مكتوبة في الشيفرة — ثابتة وغير قابلة للضبط إطلاقًا.
  2. النطاق — من جدول ai_prompts، ويحرّره المشرفون.
  3. التعليمات — تُضبط كذلك من قاعدة البيانات.

لغة الردّ

ترسل الواجهة الأمامية لغةَ واجهتها (mn/en/ar/zh/fr/ru/es) في الحقل lang، ويردّ المساعد بها وحدها — فلا تتجاوزها اللغةُ التي كتب بها المستخدم، ولا سجلّ المحادثة، ولا قاعدة المعرفة، ولا نتائج الأدوات (وتُترجَم المصادر بلغات أخرى). ويُترجَم الردّ الاحتياطي degraded بالطريقة نفسها.

لا تجعل طبقة الضوابط قابلة للضبط أبدًا

مكان هذه الطبقة في الشيفرة فقط. أمّا scope وinstructions وحدهما فتُقادان من قاعدة البيانات.

إضافة أداة

ai.ToolDef{
    Declaration: gemini.FunctionDeclaration{
        Name:        "my_tool",
        Description: "متى ينبغي للنموذج استدعاء هذه الأداة…",
        Parameters:  map[string]any{ /* JSON Schema */ },
    },
    Execute: func(ctx context.Context, args map[string]any) (map[string]any, error) {
        // يعمل في الواجهة الخلفية؛ ويحمل ctx هويّة الطلب (فتنطبق RLS)
        return map[string]any{"result": "…"}, nil
    },
}

سجّلها في cmd/api/server/server.go:

aiTools := append(ai.DefaultTools(), ai.KnowledgeSearchTool(aiRepo), myTool)

الأدوات المرفقة

البحث الدلالي (RAG)

تعيش معرفة المنصّة في ai_knowledge على هيئة نحو 58 مقطعًا. وتُحوَّل الأسئلة إلى متّجهات عبر Gemini وتُطابَق بتشابه جيب التمام في pgvector، فيعثر السؤال المصاغ بطريقة مختلفة على المقطع الصحيح. وتُحسب المتّجهات تلقائيًّا عند الإقلاع؛ كما يوفّر مسار الإدارة ← الإعدادات زرًّا لإعادة الفهرسة يدويًّا.

  • search_knowledge — بحث دلالي في ai_knowledge: يُحوَّل السؤال إلى متّجه، وتُجلب أفضل 8 نتائج من pgvector، ثمّ تُصفّى نسبةً إلى أفضل نتيجة (يُستبعد كلّ ما يقلّ عنها بأكثر من 0.03، فيبقى من 2 إلى 4). ولا تصلح هنا عتبة ثابتة — إذ تبلغ حتى المقاطع غير ذات الصلة تشابهًا قدره 0.64 فأكثر في هذا المتن. وعند تعذّر المتّجهات يرتدّ إلى ILIKE، فيقسّم السؤال كلماتٍ ويبحث في أطولها حسب الجذر. وتوجّه الضوابطُ الأساسية النموذجَ إلى استدعائها قبل الإجابة عن أسئلة المنصّة، وإلى قول «لا أعرف» بدلًا من التخمين عند عدم العثور على شيء. ووسّع المتن بإدراج صفوف جديدة — إذ تُحوَّل الصفوف الجديدة إلى متّجهات تلقائيًّا.
  • get_server_time — أداة توضيحية بسيطة (توقيت أولان باتور) بلا أيّ اعتماديات.

محادثة عامّة في الصفحة الرئيسية (دون تسجيل دخول)

تستدعي أداةٌ عائمة في الزاوية السفلية POST /public/ai/chat دون رمز. وتعمل على نسخة حالة استخدام منفصلة موصولة بأداة قاعدة المعرفة فقط، فلا تستطيع الوصول إلى بيانات المستخدمين. نحو 6 طلبات/دقيقة لكلّ IP، والرسالة 1000 محرف كحدّ أقصى، والسجلّ 6 أدوار؛ وتحصل تعليمة النظام على ضابط إضافي خاصّ بـ«الزائر المجهول». وتخاطب الأداةُ POST /public/ai/chat/stream (عبر SSE)، فتظهر الإجابة أثناء كتابتها. ويرسل الضغط للتحدّث (بإبقاء الزرّ الدائري مضغوطًا) مقطعًا بترميز base64 بحجم نحو 250 كيلوبايت (≈ 15 ثانية)، ويعيد استدعاءٌ واحد للنموذج النصَّ المنسوخ (عبر حدث transcript) والإجابةَ معًا. وتُجاب الأسئلة الصوتية بصوت مسموع جملةً جملة عبر POST /public/ai/tts، فيبدأ النطق بعد الجملة الأولى.

الصوت

القدرة نقطة النهاية آلية العمل
رسالة محادثة صوتية POST /ai/chat مع audio يدخل الصوت مباشرةً في دور المستخدم كبيانات مضمّنة — فنموذج المحادثة متعدّد الوسائط
تحويل الكلام إلى نصّ POST /ai/stt استدعاء واحد بتعليمة صارمة «انسخ حرفيًّا»؛ والنصّ الفارغ يعني غياب الكلام
تحويل النصّ إلى كلام POST /ai/tts نموذج TTS منفصل بـ responseModalities: ["AUDIO"]؛ ويُغلَّف الـ PCM الخام (L16/24 كيلوهرتز) بترويسة WAV ليشغّله المتصفّح
الترجمة الفورية POST /ai/translate نصّ ← ترجمة؛ وصوت ← خطوتان: تحويل إلى نصّ ثمّ ترجمة؛ ويضيف speak: true نُطقًا بـ TTS (وإخفاق TTS يتراجع بصمت — فيُعاد النصّ على أيّ حال)

يُصفّى الدخل الصوتي حسب نوع MIME (webm/ogg/wav/mpeg/mp3/mp4/m4a/aac/flac)، ويُحدَّد بنحو 700 كيلوبايت بترميز base64 (~30 ثانية من opus) في طبقة BFF وفي DTO الواجهة الخلفية معًا.

تفصيل الترجمة الفورية

يسجّل الميكروفون مقاطع مدّتها نحو 7 ثوانٍ باستخدام MediaRecorder جديد لكلّ مقطع — إذ لا تحمل أجزاء الـ timeslice ترويسةَ الحاوية إلّا في الجزء الأوّل، وهذا ما يجعل كلّ مقطع صالحًا بذاته. أمّا المقاطع الصامتة فتعيد حقولًا فارغة وتُهمَل بدل أن تُرفع بوصفها أخطاء.

تحديد المعدّل

يُحدَّد /ai/* بنحو 20 طلبًا في الدقيقة لكلّ عنوان IP. وتبثّ الترجمةُ الفورية قرابة 8 مقاطع في الدقيقة، فاخفض ذلك الحدّ بحذر.

التفصيل الكامل في المستودع: backend/docs/AI_PIPELINE.md.