Diagnose, build, and repair the CogniForge Math Pipeline — the LangGraph 3-node StateGraph that handles BAC math questions. Use when: math responses are in wrong language (Russian/English mixed with Arabic), missing LaTeX or $$\boxed{}$$, LaTeX uses \[...\] instead of $$...$$, no step-by-step structure, a new math question type needs adding, system prompts need updating, or the pipeline fails silently. Triggers on: "إجابات رياضيات كارثية", "خلط لغات", "بدون LaTeX", "بدون boxed", "\\[...\\] بدلاً من $$", "نوع مسألة جديد", "math pipeline", "conversation_graph", "math_pipeline.py", "subject detection", "system prompt رياضيات", "pipeline لا يعمل", "تصنيف المسائل", "BAC math", "شرح رياضيات", "normalize latex", "ISS-071", "ISS-072".
Resources
1Install
npx skillscat add bakabala27-svg/naas-agentic-core/cogniforge-math-pipeline Install via the SkillsCat registry.
CogniForge Math Pipeline
Law: A math response is correct only when it contains Arabic text + LaTeX
$$...$$
$$\boxed{...}$$+ numbered steps. Anything less is a broken response.ISS-071 Law (2026-05-15): The model ALWAYS uses
\[...\]regardless of instructions.normalize_nodeis mandatory — never remove it from the pipeline.
ISS-072 Law (2026-05-15):temperature=0.2for math,temperature=0.3for general education.
Never usetemperature > 0.3for math responses.
1. Quick Diagnosis (30 seconds)
cd /workspaces/NAAS-Agentic-Core
export OPENROUTER_API_KEY="..."
export PYTHONPATH="/workspaces/NAAS-Agentic-Core"
# Test Math Pipeline directly
python3 -c "
import asyncio, sys
sys.path.insert(0, '.')
from microservices.conversation_service.src.math_pipeline import invoke_math_pipeline
async def test():
r = await invoke_math_pipeline('احسب مشتق f(x) = x²·e^(3x)', 'diag-001')
resp = r['final_response']
print(f'type={r[\"math_type\"]} | chars={len(resp)} | boxed={\"boxed\" in resp}')
ar = sum(1 for c in resp if \"\\u0600\" <= c <= \"\\u06ff\")
bad = any(w in resp for w in ['We need', 'Must', 'это', 'что'])
print(f'AR_chars={ar} | foreign_meta={bad}')
print(resp[:400])
asyncio.run(test())
"Expected healthy output:
type=derivative | chars>500 | boxed=TrueAR_chars > 200(عربية كافية)foreign_meta=False(لا خلط لغات)- يحتوي على
$$\boxed{...}$$
2. Pipeline Architecture
ConversationGraph (3 nodes):
intent_node → context_node → response_node
↓
subject detection
(math/physics/chemistry/general)
↓
subject=="math" AND intent=="educational"?
YES ↓ NO ↓
Math Pipeline Direct LLM
(3 nodes) (system prompt + _normalize_latex_response)
Math Pipeline (3 nodes — ISS-071 updated 2026-05-15):
classify_node → deterministic — تصنيف 11 نوع (لا LLM)
solve_node → LLM واحد — الحل الكامل + LaTeX + $$\boxed{}$$
normalize_node → deterministic — تحويل \[...\] → $$...$$ (لا LLM)Files:
microservices/conversation_service/src/math_pipeline.py— Math Pipelinemicroservices/conversation_service/src/conversation_graph.py— ConversationGraph + routingmicroservices/conversation_service/main.py— ChatResponse includessubject
ISS-071 Critical Note: normalize_node is Node 3 — it converts \[...\] to $$...$$
after every LLM response. The model ALWAYS ignores the $$...$$ instruction in system prompt.
This is a known behavior of nvidia/nemotron-3-nano-30b-a3b:free — post-processing is the only fix.
3. Symptom → Root Cause → Fix
\[...\] في الإجابة بدلاً من $$...$$ (ISS-071)
Root cause (VERIFIED LIVE 2026-05-15): nvidia/nemotron-3-nano-30b-a3b:free يتجاهل قاعدة $$...$$ في system prompt ويستخدم \[...\] بشكل افتراضي — هذا سلوك ثابت لا يتغير بتغيير الـ prompt.
Fix: normalize_node في Math Pipeline + _normalize_latex_response() في conversation_graph:
# math_pipeline.py — normalize_node (Node 3)
async def normalize_node(state: MathPipelineState) -> MathPipelineState:
solution = state.get("solution", "")
if solution:
normalized = _normalize_latex(solution)
label = _TYPE_LABELS.get(state["math_type"], "📚 رياضيات")
final = f"## {label}\n\n{normalized}"
return {**state, "solution": normalized, "final_response": final}
return state
# _normalize_latex() — التحويلات:
# \[ ... \] → $$ ... $$
# \begin{equation} ... \end{equation} → $$ ... $$
# \begin{align} ... \end{align} → $$ ... $$Verification:
from microservices.conversation_service.src.math_pipeline import _normalize_latex
text = r"الحل: \[ f'(x) = 2x \]"
result = _normalize_latex(text)
assert r"\[" not in result # ✅
assert "$$" in result # ✅إجابات بالروسية أو الإنجليزية
Root cause: system prompt لا يُطبِّق قاعدة اللغة بشكل صريح.
Fix: تحقق من _BASE_RULES في math_pipeline.py:
_BASE_RULES = (
"## قواعد اللغة — صارمة لا تُخرق\n"
"- اكتب بالعربية الفصحى الواضحة فقط\n"
"- لا تخلط مع الروسية أو الإنجليزية أو الفرنسية إلا للمصطلحات التقنية\n"
...
)يجب أن يكون _BASE_RULES مُضمَّناً في كل system prompt.
لا $$\boxed{}$$ في الإجابة
Root cause: step_by_step_node لا يطلب \boxed صراحةً في الـ prompt.
Fix: تحقق من نهاية user_prompt في step_by_step_node:
"يجب أن تنتهي بـ: $$\\boxed{النتيجة النهائية}$$"foreign_meta=True — النموذج يُعيد meta-text
Root cause: النموذج يُعيد وصفاً لما سيفعله بدلاً من الفعل. يحدث مع verification_node عند استخدام prompts معقدة.
Fix: بسِّط user_prompt — اطلب الأقسام مباشرة بدون شرح:
user_prompt=(
"اكتب الأقسام التالية بالعربية مباشرة:\n"
"**✅ التحقق:**\n"
"**🔭 التفسير:**\n"
# لا تضف: "Must", "Should", "Provide", "output exactly"
)math_type=general_math لمسألة واضحة
Root cause: الـ pattern لا يُطابق — تعارض بين أنواع (مثلاً محدد يُطابق حد).
Fix: راجع ترتيب _MATH_TYPES — الأكثر تحديداً أولاً:
_MATH_TYPES = {
"differential_eq": [...], # أولاً — الأطول
"derivative": [...],
"matrix": ["مصفوفة", "محدد المصفوفة"], # "محدد المصفوفة" لا "محدد" وحده
"limit": ["نهاية الدالة", "lim("], # لا "حد" وحده
...
}Pipeline لا يُوجَّه إليه (يذهب للـ LLM المباشر)
Root cause: subject لا يُكتشف كـ "math" أو intent ليس "educational".
Debug:
from microservices.conversation_service.src.conversation_graph import (
_detect_subject, _classify_intent
)
print(_detect_subject("احسب مشتق الدالة")) # يجب: "math"
print(_classify_intent("احسب مشتق الدالة")) # يجب: "educational"Fix: أضف الـ pattern المفقود في _SUBJECT_PATTERNS["math"] أو _INTENT_PATTERNS["educational"].
content=None من النموذج (ISS-069)
Root cause: نموذج reasoning-only يضع الإجابة في message.reasoning لا message.content.
Fix: تحقق من _call_openrouter في math_pipeline.py:
content = msg.get("content") or msg.get("reasoning") or ""يجب أن يكون هذا الـ guard موجوداً. إذا لم يكن — أضفه.
4. Adding a New Math Type
لإضافة نوع مسألة جديد (مثلاً trigonometry):
Step 1: أضف في _MATH_TYPES (بترتيب صحيح — الأكثر تحديداً أولاً):
"trigonometry": [
"مثلثات", "جيب", "جتا", "ظل", "sin", "cos", "tan",
"trigonométrie", "sinusoïde",
],Step 2: أضف سياقاً تعليمياً في _get_math_context:
"trigonometry": (
"مسألة مثلثات — قوانين: sin²x + cos²x = 1، "
"صيغ التحويل، المعادلات المثلثية، الدوائر المثلثية"
),Step 3: أضف header في verification_node:
"trigonometry": "📐 مسألة مثلثات",Step 4: أضف اختباراً في test_math_pipeline.py:
def test_trigonometry(self):
assert _classify_math_type("احسب sin(30°)") == "trigonometry"Step 5: شغِّل الاختبارات:
cd /workspaces/NAAS-Agentic-Core
PYTHONPATH=. python -m pytest tests/microservices/conversation_service/test_math_pipeline.py -v5. Live Model Benchmark
قبل تغيير النموذج الافتراضي، شغِّل البنشمارك الحي:
cd /workspaces/NAAS-Agentic-Core
export OPENROUTER_API_KEY="..."
python3 -c "
import asyncio, httpx, time, os
SYSTEM = '''أنت أستاذ رياضيات. اكتب بالعربية فقط. LaTeX: \$\$...\$\$. النتيجة: \$\$\\\\boxed{...}\$\$'''
Q = 'اشرح مشتق f(x) = x²·e^(3x) خطوة بخطوة'
async def test(model):
t0 = time.time()
try:
async with httpx.AsyncClient(timeout=25) as c:
r = await c.post('https://openrouter.ai/api/v1/chat/completions',
headers={'Authorization': f'Bearer {os.environ[\"OPENROUTER_API_KEY\"]}'},
json={'model': model, 'messages': [
{'role': 'system', 'content': SYSTEM},
{'role': 'user', 'content': Q}
], 'max_tokens': 400})
d = r.json()
elapsed = time.time() - t0
choices = d.get('choices', [])
if choices:
content = choices[0].get('message', {}).get('content', '') or ''
ar = sum(1 for c in content if '\u0600' <= c <= '\u06ff')
latex = '\$\$' in content or '\\\\[' in content
bad = any(w in content for w in ['это', 'что', ' the ', ' is '])
q = '✅' if ar > 50 and not bad else '⚠️'
print(f'{q} {elapsed:.1f}s AR:{ar} LaTeX:{latex} | {model[:50]}')
else:
print(f'❌ {model[:50]}: {d.get(\"error\",{}).get(\"message\",\"?\")[:50]}')
except Exception as e:
print(f'💥 {model[:50]}: {e}')
async def main():
models = [
'nvidia/nemotron-3-nano-30b-a3b:free',
'openai/gpt-oss-20b:free',
'google/gemma-4-26b-a4b-it:free',
'nvidia/nemotron-3-super-120b-a12b:free',
]
await asyncio.gather(*[test(m) for m in models])
asyncio.run(main())
"Verified live (2026-05-15):
| Model | TTFT | Arabic | LaTeX | Status |
|---|---|---|---|---|
nvidia/nemotron-3-nano-30b-a3b:free |
2.4s | ✅ | ✅ | PRIMARY |
nvidia/nemotron-3-super-120b-a12b:free |
10.3s | ✅ | ✅ | FALLBACK-1 |
openai/gpt-oss-120b:free |
41.8s | ✅ | ✅ | FALLBACK-2 |
openai/gpt-oss-20b:free |
10.4s | ✅ | ✅ | FALLBACK-3 |
google/gemma-4-26b-a4b-it:free |
15.5s | ✅ | ✅ | FALLBACK-4 |
6. System Prompt Rules
Load references/system-prompt-templates.md for canonical templates.
Mandatory elements in every math system prompt:
- قاعدة اللغة الصارمة: "اكتب بالعربية الفصحى فقط — لا تخلط مع الروسية أو الإنجليزية"
- LaTeX rules:
$$...$$للمعادلات،\(...\)للرموز المضمّنة $$\boxed{...}$$للنتيجة النهائية- "ابدأ بـ لماذا قبل الحساب"
- "لا تختصر — الطالب يحتاج كل التفاصيل"
Anti-pattern — prompt يُسبب meta-text:
# ❌ خاطئ — يُسبب "We need to output..."
"Must follow format exactly. Provide verification. Output sections in order."
# ✅ صحيح — اطلب مباشرة
"اكتب الأقسام التالية بالعربية:\n**التحقق:**\n**التفسير:**"7. Anti-Patterns (مُكتشَفة حياً)
Load references/anti-patterns.md for the full catalogue.
Critical — never do these:
| Anti-pattern | السبب | البديل |
|---|---|---|
google/gemini-2.0-flash-exp:free في fallback |
DEAD — No endpoints | google/gemma-4-26b-a4b-it:free |
meta-llama/llama-3.2-11b-vision-instruct:free |
DEAD — No endpoints | openai/gpt-oss-20b:free |
nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free كـ PRIMARY |
content=None مع system prompt | nvidia/nemotron-3-nano-30b-a3b:free |
inclusionai/ring-2.6-1t:free |
rate-limited دائماً على Novita | أي نموذج آخر |
| MCTS depth > 1 مع نماذج مجانية | 6+ LLM calls → rate limiting | depth=1 دائماً |
| verification_node يستدعي LLM بـ prompt معقد | meta-text بدلاً من إجابة | بسِّط الـ prompt أو اجعله deterministic |
content=None صامت |
إجابات فارغة للطالب | msg.get("content") or msg.get("reasoning") or "" |
8. Run Tests
cd /workspaces/NAAS-Agentic-Core
PYTHONPATH=. python -m pytest \
tests/microservices/conversation_service/test_math_pipeline.py \
-v --tb=short
# Expected: 36 passed9. Reference Files
references/system-prompt-templates.md— قوالب system prompts الكاملة لكل نوع مسألةreferences/anti-patterns.md— كتالوج كامل للـ anti-patterns مع الأدلة الحيةreferences/math-types-registry.md— سجل 11 نوع مسألة مع patterns + سياق تعليمي