يتم اختصار الترجمة بصمت عند فشل تحليل تدفق JSON (بدون رفع خطأ)

ملخص
DiscourseAi::Translation::PostLocalizer (توطين المحتوى) ينتج أحيانًا ترجمات مبتورة دون تسجيل أي خطأ في أي مكان (سجل Rails، سجل Sidekiq، طوابير Sidekiq للمهام المتوقفة/إعادة المحاولة). يحدث الاختفاء في منتصف الجملة، بصمت، ويتم حفظ النتيجة الجزئية وكأنها ترجمة كاملة وناجحة.

تم ملاحظته على: Discourse core + discourse-ai 46bea6613 (2026-07-09)، نموذج الذكاء الاصطناعي:
Gemini 2.5 Flash عبر مزود Google.

أدلة التكرار
حالتان حقيقيتان على مثيلنا:

  • منشور ألماني بطول ~2090 حرفًا تمت ترجمته إلى الإسبانية منتجًا 50 حرفًا فقط،
    مبتورًا مباشرة قبل كلمة مقتبسة (“aufwendiger”) في النص المصدر.
  • منشور ألماني بطول ~1830 حرفًا تمت ترجمته إلى الإنجليزية (83 حرفًا) والسلوفاكية
    (97 حرفًا) — كلاهما مبتور عند نقطة واحدة بالضبط في الجملة،
    مباشرة قبل اختصار إيموجي (:collision:) في النص المصدر.
    جميع اللغات الأخرى (fr, it, pt, ru, he) لنفس المنشور تمت ترجمتها
    بشكل كامل (1600–1950 حرفًا).

السبب الجذري (حسب تتبعي له)
في lib/completions/structured_output.rb، تعتمد StructuredOutput#read_buffered_property
على DiscourseAi::Utils::BestEffortJsonParser.extract_key كلما
كان @partial_json_tracker.broken? صحيحًا. يعيد هذا الحل البديل أي شيء تم تحليله قبل أن ينقطع تدفق JSON — دون استثناء، دون تحذير، دون
أي مؤشر في أي مكان على أن المخرجات غير مكتملة. في
lib/translation/base_translator.rb#get_translation، يتم استخدام هذه القيمة كما هي
كترجمة نهائية ويتم حفظها عبر PostLocalization#save!.

تقع نقطتا الاختفاء في بياناتنا مباشرة قبل “حرف خاص”
في النص المصدر (علامة اقتباس مستقيمة، اختصار إيموجي)،
مما يشير إلى أن متتبع تدفق JSON يصبح مشوشًا بسبب بعض
المتتبعين/التسلسلات في المخرجات المتدفقة من النموذج ويحدد أن التدفق
منقطع? بشكل مبكر.

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

الإصلاحات المقترحة

  • إظهار تحذير/خطأ (Rails.logger + Discourse-error-log) كلما
    كان StructuredOutput#broken? صحيحًا وتم استخدام الحل البديل قصير المدى، بحيث
    يمكن للمسؤولين اكتشاف ذلك بدلاً من شحن محتوى جزئي بصمت.
  • النظر في التحقق من طول النص المترجم مقابل عتبة معقولة نسبيًا
    مقارنة بطول النص المصدر قبل حفظ PostLocalization.

سعيد بتوفير أزواج النصوص الخام/المترجمة الكاملة بشكل خاص إذا كان ذلك مفيدًا لـ
تصحيح تتبع JSON.

إعجابَين (2)

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

مُلاحَظ على: نواة Discourse + discourse-ai، نموذج اللغات الكبير (LLM): GPT-5.1 عبر OpenAI (اتصال API مخصص، وليس نموذجًا مُزَوَّدًا مسبقًا).

أدلة التكرار

نفس المنشور المصدر (بالروسية، ~2000 حرف، عناوين/نص عريض/قوائم نقطية/روابط)،
مُترجم إلى عدة لغات محلية عبر الترجمة بالذكاء الاصطناعي الأصلي:

  • البولندية (pl): فقد نص الترميز (Markdown) للصورة المضمنة هيكله — تحول من
    ![alt text|690x460](upload://...) إلى رابط مشوه يفتقد بادئة !
    وفاصل |، مع دمج نص البديل والأبعاد كـ نص عادي. تم عرضه كرابط قابل للنقر بدلاً من
    صورة مضمنة.
  • الأوكرانية (uk): تظهر متتابعات \n\n الحرفية كنص عادي في جميع أنحاء
    المنشور بأكمله، بدلاً من فواصل الفقرات. كان نص الترميز للصورة في هذا
    الترجمة نفسه سليماً — لذا فإن التلف غير مرتبط بأعراض ثابتة واحدة، بل يختلف حسب التشغيل.

على عكس الحذف الموصوف أعلاه، لا تُظهر حالتنا أي محتوى مفقود —
النص الكامل موجود، ولكن مع متتابعات الهروب / صيغة الترميز (Markdown) التالفة بدلاً من إخراج مختصر. مزود مختلف (OpenAI مقابل Google
هنا)، عرض مختلف (تلف مقابل حذف)، نفس الجذر المشتبه به:
StructuredOutput#read_buffered_property الذي يلجأ إلى
BestEffortJsonParser#extract_key، والذي لا يزيل الهروب من متتابعات السلاسل النصية في JSON (\n يبقى حرفياً) ويبدو أيضاً أنه يتعامل بشكل خاطئ مع الأحرف الخاصة
المجاورة لصيغة الترميز (Markdown) عند تفعيل اللجوء أثناء التحليل.

لا توجد أخطاء في سجلات Sidekiq أو Rails من جانبنا أيضاً — نفس سلوك الفشل الصامت.