Translation quality & format preservation: - Word: merge adjacent same-format runs into one unit (sentence-level coherence like inline-tag handling); translate comments/balloons; dedupe textbox collection (was translated twice); RTL no longer overrides center/justify alignment; CJK/Arabic font hints (eastAsia/cs) - PPTX: chart translations now actually reach the output file (ChartPart.blob is read-only — rewrite chart XML in the saved ZIP); CJK typeface hints (a:ea) - Excel: sheet renames no longer break references — rewrite cell formulas (3D/quoted), defined names, data validations, cond. formats - PDF: bold/italic honored (hebo/heit/hebi); table cells never merge; unchanged blocks left untouched (typography preserved, fixes duplicate hyperlinks); attempted/changed stats + route gate now cover PDF; CJK font paths; scanned PDFs via Mistral OCR (detection + admin settings) Features: - formality param (formal/informal) + automatic regional-variant prompts - output_mode=bilingual docx (source above translation) - per-user translation memory on Redis (falls back to LRU), context-hashed - QA report + 0-100 confidence score in job status; L0 on by default - OpenAI-compatible providers: whole chunk in ONE numbered-JSON request (~15x fewer calls) with per-item fallback; base prompt always present (custom prompt no longer replaces translation instructions) Infra & marketing alignment: - plan-based engine gating + vision gating (closes paid-engine leak); /providers/available filtered per plan; 107 languages exposed - zh-CN/zh-TW validation fixed; libmagic disabled on Windows (native crash) - admin: Mistral OCR settings + engine status dashboard; httpx<0.28 pin (TestClient breakage); Prometheus test fixture fixed - marketing docs aligned with code (PDF+OCR, retention, engines, pricing) - security: .env.ionos/.env.production/provider_settings.json removed Tests: 1173 passed / 0 failed (6 network tests deselected: free Google endpoint temporarily blocked from this machine)
6.7 KiB
title, type, created, status, context
| title | type | created | status | context |
|---|---|---|---|---|
| Quick wins pipeline LLM : prompt, connexions, cache, event loop | bugfix | 2026-08-26 | draft |
Intent
Problem: Le pipeline de traduction LLM souffre de 4 défauts vérifiés : (1) dans les 3 providers LLM, un prompt personnalisé/glossaire remplace le prompt par défaut — la paire de langues et les règles métier disparaissent de la requête ; (2) chaque segment traduit ouvre une nouvelle connexion TCP+TLS (requests.post nu) ; (3) le cache de traduction Redis/LRU (services/translation_cache.py) est écrit et testé mais jamais branché — re-traduire un fichier re-facture 100 % des appels ; (4) 7 appels bloquants (SHA-256 de fichiers, DB, disque, sonde réseau Google) s'exécutent directement dans l'event loop et peuvent geler tout le serveur.
Approach: 4 correctifs chirurgicaux dans la couche providers (openai/minimax/deepseek) et le runner de jobs de translate_routes.py, sans changement d'interface publique ni des translators.
Boundaries & Constraints
Always: L'interface TranslationProvider / TranslationRequest / TranslationResponse reste inchangée ; la paire de langues doit toujours figurer dans le system prompt ; le cache reste piloté par env (REDIS_CACHE_TTL, LRU_CACHE_MAXSIZE, REDIS_URL) ; tout échec du cache est silencieux (fallback LRU, puis poursuite sans cache) ; from_cache=True sur les réponses servies du cache.
Ask First: modifier build_full_prompt (services/glossary_service.py) ; modifier les schémas providers ; introduire un nouveau backend de cache.
Never: Pas de batch multi-segments LLM (chantier suivant) ; pas de refonte de la double couche providers legacy/nouvelle ; pas de modification des translators/ ; pas de nouvel endpoint ; pas de persistance des jobs.
I/O & Edge-Case Matrix
| Scenario | Input / State | Expected Output / Behavior | Error Handling |
|---|---|---|---|
| Prompt custom actif | glossaire et/ou prompt utilisateur | System prompt = prompt par défaut formaté (avec paire de langues) + contexte custom en complément | N/A |
| Segment déjà en cache | même texte + langues + provider + hash prompt | Réponse from_cache=True, 0 requête HTTP |
N/A |
| Segment répété dans un même document | 2e occurrence après succès de la 1ʳᵉ | Servie par le cache (le batch est séquentiel) | N/A |
| Redis indisponible | get_cache() init ou set() en échec |
Fallback LRU RAM puis non-bloquant ; traduction réussie quand même | Log warning, jamais d'exception |
| Provider en erreur | échec API après retries | Rien n'est écrit dans le cache | Erreur existante propagée |
| Fichier 50 Mo + glossaire configuré | job lancé pendant que d'autres requêtes arrivent | SHA-256, zip-safety, DB, settings et sonde Google hors event loop (asyncio.to_thread) |
Erreurs existantes propagées |
Code Map
services/providers/openai_provider.py--_build_system_prompt:120-128 (bug du remplacement),requests.post:263,translate_text:426-455 (point d'entrée cache)services/providers/minimax_provider.py-- prompt inline :181-183,requests.post:117,translate_text:168+services/providers/deepseek_provider.py-- prompt inline :164-166,requests.post:111,translate_text:151+services/translation_cache.py-- API prête et testée :make_cache_key:45,hash_prompt:77,get_cache():402 (Redis auto + fallback LRU) — aucun appelant hors testsroutes/translate_routes.py-- appels bloquants dans coroutines :calculate_sha256:858/:871,validate_zip_safety:884,_load_admin_settings:1174,get_glossary_terms:1191,get_prompt_content:1206,_google_cloud_key_valid:1242 (fait une traduction HTTP de test synchrone)tests/test_providers/-- 8 fichiers existants, donttest_minimax_provider.py(récent) : patterns de mock à réutiliser
Tasks & Acceptance
Execution:
services/providers/openai_provider.py-- (1)_build_system_prompt: concaténer le prompt par défaut formaté + le custom au lieu dereturn custom_prompt; (2)requests.Sessiond'instance réutilisée dans_make_api_request; (3)translate_text: lookupget_cache()+make_cache_key(..., custom_prompt_hash=hash_prompt(custom_prompt))avant l'appel,cache.setaprès succès -- corrige le bug qualité, la latence et le coûtservices/providers/minimax_provider.py-- mêmes 3 changements (prompt construit inline :181-183)services/providers/deepseek_provider.py-- mêmes 3 changements (prompt construit inline :164-166)routes/translate_routes.py-- envelopper les 7 appels bloquants listés dans la Code Map dansasyncio.to_thread-- l'event loop reste réactif pendant les jobstests/test_providers/-- nouveaux tests couvrant la matrice I/O : le system prompt contient la paire de langues et le custom (×3 providers) ; cache hit → 0 appel HTTP etfrom_cache=True; erreur provider → rien mis en cache ; Redis down → traduction quand même
Acceptance Criteria:
- Given un custom_prompt sans mention de langue, when traduction via openai/minimax/deepseek, then le payload contient « from {source} to {target} » et le contenu custom.
- Given un segment déjà en cache (même clé), when re-traduit, then aucune requête HTTP n'est émise vers l'API LLM.
- Given Redis down, when traduction, then réponse normale via LRU ou sans cache, sans exception remontée au job.
- Given la suite de tests existante, when
pytest -x, then 0 régression.
Spec Change Log
Design Notes
Concaténation du prompt (les 3 providers) :
system_prompt = DEFAULT_TRANSLATION_PROMPT.format(
source_lang=source_lang_name, target_lang=target_lang_name
)
if custom_prompt:
system_prompt += (
"\n\nAdditional context and instructions from the user "
f"(comply without overriding the language pair above):\n{custom_prompt}"
)
La requests.Session est créée dans __init__ du provider (le pooling urllib3 est thread-safe ; les translators appellent translate_text depuis 6 threads). Clé de cache : réutiliser make_cache_key(text, target_language, source_language, self._provider_name, custom_prompt_hash=hash_prompt(custom_prompt)) — le hash du prompt isole déjà les glossaires différents ; user_id absent des metadata aujourd'hui → valeur par défaut « anon » (partage inter-utilisateurs acceptable : mémoire de traduction standard, TTL 24 h).
Verification
Commands:
pytest tests/test_providers/ -x-- expected: succès, nouveaux tests incluspytest -x-- expected: succès complet, 0 régression