system-prompts-and-models-o.../dealix/core/config/models.py
2026-05-01 14:03:52 +03:00

247 lines
8.1 KiB
Python

"""
Model routing configuration — maps tasks to the best LLM provider.
توجيه النماذج — يربط كل مهمة بأفضل مزود نموذج.
"""
from __future__ import annotations
from dataclasses import dataclass
from enum import StrEnum
class Provider(StrEnum):
"""Supported LLM providers | المزودون المدعومون."""
ANTHROPIC = "anthropic"
DEEPSEEK = "deepseek"
GLM = "glm"
GEMINI = "gemini"
GROQ = "groq"
OPENAI = "openai"
class Task(StrEnum):
"""Task categories — route each to the best provider | أنواع المهام."""
# Reasoning / writing → Claude
REASONING = "reasoning"
SUMMARY = "summary"
PROPOSAL = "proposal"
PAGE_COPY = "page_copy"
ORCHESTRATION = "orchestration"
# Research / multimodal → Gemini
RESEARCH = "research"
MULTIMODAL = "multimodal"
SOURCE_ANALYSIS = "source_analysis"
# Fast classification → Groq
CLASSIFICATION = "classification"
TAGGING = "tagging"
FAST_VARIANTS = "fast_variants"
TRIAGE = "triage"
# Code → DeepSeek
CODE = "code"
IMPLEMENTATION = "implementation"
DEBUG = "debug"
# Arabic / bulk → GLM
ARABIC_TASKS = "arabic_tasks"
CHINESE_TASKS = "chinese_tasks"
BULK_TASKS = "bulk_tasks"
@dataclass(frozen=True)
class ModelConfig:
"""Immutable model configuration | إعدادات نموذج ثابتة."""
provider: Provider
model_id: str
max_tokens: int = 4096
temperature: float = 0.7
timeout: int = 60
# ═══════════════════════════════════════════════════════════════
# TASK → PROVIDER ROUTING TABLE
# جدول توجيه المهام إلى المزودين
# ═══════════════════════════════════════════════════════════════
TASK_ROUTING: dict[Task, Provider] = {
# Claude — reasoning, writing, orchestration
Task.REASONING: Provider.ANTHROPIC,
Task.SUMMARY: Provider.ANTHROPIC,
Task.PROPOSAL: Provider.ANTHROPIC,
Task.PAGE_COPY: Provider.ANTHROPIC,
Task.ORCHESTRATION: Provider.ANTHROPIC,
# Gemini — research, multimodal, sources
Task.RESEARCH: Provider.GEMINI,
Task.MULTIMODAL: Provider.GEMINI,
Task.SOURCE_ANALYSIS: Provider.GEMINI,
# Groq — fast, cheap
Task.CLASSIFICATION: Provider.GROQ,
Task.TAGGING: Provider.GROQ,
Task.FAST_VARIANTS: Provider.GROQ,
Task.TRIAGE: Provider.GROQ,
# DeepSeek — code
Task.CODE: Provider.DEEPSEEK,
Task.IMPLEMENTATION: Provider.DEEPSEEK,
Task.DEBUG: Provider.DEEPSEEK,
# GLM — Arabic, Chinese, bulk
Task.ARABIC_TASKS: Provider.GLM,
Task.CHINESE_TASKS: Provider.GLM,
Task.BULK_TASKS: Provider.GLM,
}
# Fallback chain — if primary provider fails, try these in order
# سلسلة الاحتياط — إذا فشل المزود الرئيسي جرّب هؤلاء
FALLBACK_CHAIN: dict[Provider, list[Provider]] = {
Provider.ANTHROPIC: [Provider.OPENAI, Provider.GLM],
Provider.DEEPSEEK: [Provider.ANTHROPIC, Provider.OPENAI],
Provider.GLM: [Provider.ANTHROPIC, Provider.GROQ],
Provider.GEMINI: [Provider.ANTHROPIC, Provider.OPENAI],
Provider.GROQ: [Provider.GLM, Provider.DEEPSEEK],
Provider.OPENAI: [Provider.ANTHROPIC, Provider.GLM],
}
def get_provider_for_task(task: Task) -> Provider:
"""Get primary provider for a task | المزود الرئيسي للمهمة."""
return TASK_ROUTING.get(task, Provider.ANTHROPIC)
def get_fallbacks(provider: Provider) -> list[Provider]:
"""Get fallback chain for a provider | سلسلة الاحتياط للمزود."""
return FALLBACK_CHAIN.get(provider, [Provider.ANTHROPIC])
# ═══════════════════════════════════════════════════════════════
# SMART MODEL ROUTING — cost-aware exact model per task
# توجيه ذكي يراعي التكلفة — نموذج محدد لكل مهمة
# ═══════════════════════════════════════════════════════════════
# Concrete model IDs per provider (cost-optimized picks)
PROVIDER_MODELS: dict[Provider, ModelConfig] = {
Provider.ANTHROPIC: ModelConfig(
provider=Provider.ANTHROPIC,
model_id="claude-sonnet-4-5",
max_tokens=4096,
temperature=0.3,
),
Provider.DEEPSEEK: ModelConfig(
provider=Provider.DEEPSEEK,
model_id="deepseek-chat",
max_tokens=4096,
temperature=0.2,
),
Provider.GLM: ModelConfig(
provider=Provider.GLM,
model_id="glm-4",
max_tokens=4096,
temperature=0.3,
),
Provider.GEMINI: ModelConfig(
provider=Provider.GEMINI,
model_id="gemini-2.5-flash",
max_tokens=8192,
temperature=0.3,
),
Provider.GROQ: ModelConfig(
provider=Provider.GROQ,
model_id="llama-3.3-70b-versatile",
max_tokens=2048,
temperature=0.1,
),
Provider.OPENAI: ModelConfig(
provider=Provider.OPENAI,
model_id="gpt-4o-mini",
max_tokens=4096,
temperature=0.3,
),
}
# Cost hints (USD per 1M tokens) — input/output. Used by smart router.
COST_HINTS: dict[Provider, tuple[float, float]] = {
Provider.ANTHROPIC: (3.00, 15.00),
Provider.DEEPSEEK: (0.14, 0.28),
Provider.GLM: (0.14, 0.28),
Provider.GEMINI: (0.075, 0.30),
Provider.GROQ: (0.00, 0.00),
Provider.OPENAI: (0.15, 0.60),
}
# Feature flags for Arabic-heavy content and token size thresholds
ARABIC_THRESHOLD = 0.30 # ratio of Arabic chars → prefer GLM
SHORT_EXTRACTION_TOKENS = 2000 # below this for code/extraction → DeepSeek
CRITICAL_TASKS = {
Task.REASONING,
Task.PROPOSAL,
Task.ORCHESTRATION,
}
def _arabic_ratio(text: str) -> float:
if not text:
return 0.0
arabic = sum(1 for c in text if "\u0600" <= c <= "\u06ff")
return arabic / max(len(text), 1)
def smart_route(
task: Task,
*,
text_sample: str = "",
est_tokens: int = 0,
critical: bool = False,
) -> ModelConfig:
"""
Cost-aware router | توجيه ذكي يراعي التكلفة.
Rules:
1. CLASSIFICATION/TRIAGE/TAGGING → Groq (free)
2. Arabic content (>30%) for non-critical → GLM
3. Short extraction/code → DeepSeek
4. Research → Gemini Flash
5. Critical reasoning/proposals → Anthropic (+ caching)
6. Else → provider from TASK_ROUTING
"""
# 1. Free tier for classification
if task in {Task.CLASSIFICATION, Task.TRIAGE, Task.TAGGING, Task.FAST_VARIANTS}:
return PROVIDER_MODELS[Provider.GROQ]
# 2. Critical reasoning always Anthropic
if critical or task in CRITICAL_TASKS:
return PROVIDER_MODELS[Provider.ANTHROPIC]
# 3. Arabic-heavy → GLM (keeps cost low while handling Arabic well)
if text_sample and _arabic_ratio(text_sample) >= ARABIC_THRESHOLD:
if task not in {Task.RESEARCH, Task.MULTIMODAL}:
return PROVIDER_MODELS[Provider.GLM]
# 4. Short code/extraction → DeepSeek
if task in {Task.CODE, Task.IMPLEMENTATION, Task.DEBUG}:
return PROVIDER_MODELS[Provider.DEEPSEEK]
if task == Task.SUMMARY and 0 < est_tokens <= SHORT_EXTRACTION_TOKENS:
return PROVIDER_MODELS[Provider.DEEPSEEK]
# 5. Research → Gemini Flash
if task in {Task.RESEARCH, Task.MULTIMODAL, Task.SOURCE_ANALYSIS}:
return PROVIDER_MODELS[Provider.GEMINI]
# 6. Default — use static routing table
provider = get_provider_for_task(task)
return PROVIDER_MODELS[provider]
def ordered_providers(
task: Task, *, text_sample: str = "", critical: bool = False
) -> list[Provider]:
"""Return primary + fallback chain after smart routing."""
primary = smart_route(task, text_sample=text_sample, critical=critical).provider
chain = [primary] + [p for p in get_fallbacks(primary) if p != primary]
return chain