Major changes across backend, frontend, infrastructure: - Provider system with model selection (Google, DeepL, OpenAI, Ollama, Google Cloud) - Admin panel: user management, pricing, settings - Glossary system with CSV import/export - Subscription and tier quota management - Security hardening (rate limiting, API key auth, path traversal fixes) - Docker compose for dev, prod, and IONOS deployment - Alembic migrations for new tables - Frontend: dashboard, pricing page, landing page, i18n (en/fr) - Test suite and verification scripts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
11 KiB
Story 3.10: Glossaires - Application lors Traduction LLM
Status: done
Story
En tant qu'utilisateur Pro, Je veux que mes termes de glossaire soient appliqués lors de la traduction LLM, de sorte que les traductions utilisent ma terminologie spécifique.
Acceptance Criteria
- Injection du glossaire: Quand un utilisateur Pro POST sur
/api/v1/translateavec le paramètreglossary_id, les termes du glossaire sont injectés dans le prompt système du LLM. (FR60) - Formatage des termes: Les termes sont formatés de manière claire pour le LLM (ex: "Always translate 'cloud computing' as 'informatique en nuage'").
- Validation du glossaire: Si
glossary_idn'existe pas ou n'appartient pas à l'utilisateur, retourner 400 avec erreurGLOSSARY_NOT_FOUND. - Compatibilité providers: Le glossaire est appliqué pour tous les providers LLM (Ollama, OpenAI, OpenRouter).
- Restriction Pro: Les utilisateurs Free reçoivent 403 avec erreur
PRO_FEATURE_REQUIRED(déjà implémenté dans translate_routes.py).
Tasks / Subtasks
-
Task 1: Créer le service de récupération de glossaire (AC: #1, #3)
- 1.1 Créer
services/glossary_service.pyavec la fonctionget_glossary_terms(glossary_id: str, user_id: str) -> list[dict] - 1.2 Implémenter la validation: vérifier que le glossaire existe et appartient à l'utilisateur
- 1.3 Retourner une liste de
{source, target}ou lever une exceptionGlossaryNotFoundError
- 1.1 Créer
-
Task 2: Créer la fonction de formatage du glossaire (AC: #2)
- 2.1 Créer
format_glossary_for_prompt(terms: list[dict]) -> strdansservices/glossary_service.py - 2.2 Formater chaque terme comme: "- 'source' → 'target'"
- 2.3 Ajouter un en-tête clair: "TERMINOLOGY GLOSSARY (use these translations):"
- 2.1 Créer
-
Task 3: Modifier
_run_translation_jobpour utiliser le glossaire (AC: #1, #3, #4)- 3.1 Dans
routes/translate_routes.py, appelerget_glossary_terms()siglossary_idest fourni - 3.2 Formater les termes avec
format_glossary_for_prompt() - 3.3 Construire le
full_prompten combinantcustom_promptet le glossaire formaté - 3.4 Passer le prompt combiné aux providers LLM
- 3.1 Dans
-
Task 4: Gérer l'erreur GLOSSARY_NOT_FOUND (AC: #3)
- 4.1 Créer l'exception
GlossaryNotFoundErrordansutils/exceptions.py - 4.2 Dans
_run_translation_job, catcher l'exception et marquer le job comme failed avec le message approprié - 4.3 Dans le endpoint
translate_document_v1, valider le glossaire avant de créer le job (retourner 400 immédiatement)
- 4.1 Créer l'exception
-
Task 5: Tests (AC: Tous)
- 5.1 Tests unitaires pour
get_glossary_terms()(glossaire trouvé, non trouvé, n'appartient pas à l'utilisateur) - 5.2 Tests unitaires pour
format_glossary_for_prompt() - 5.3 Tests d'intégration pour le endpoint
/translateavecglossary_id - 5.4 Test de validation: glossaire invalide retourne 400
- 5.1 Tests unitaires pour
-
Task 6: Documentation OpenAPI (AC: Tous)
- 6.1 Documenter le paramètre
glossary_idavec exemples - 6.2 Documenter l'erreur
GLOSSARY_NOT_FOUND - 6.3 Vérifier sur
/docsque la documentation est complète
- 6.1 Documenter le paramètre
Dev Notes
🏗️ Architecture - Intégration Glossaire
Flux de données:
POST /api/v1/translate (glossary_id=xxx)
↓
translate_routes.py: validate glossary_id (Pro check already done)
↓
_run_translation_job():
↓
services/glossary_service.py: get_glossary_terms(glossary_id, user_id)
↓
format_glossary_for_prompt(terms)
↓
Build full_prompt = custom_prompt + glossary_prompt
↓
Pass to LLM provider (Ollama, OpenAI, OpenRouter)
📊 Modèles de Données Existants
Tables déjà créées (Story 3.9):
glossaries: id, user_id, name, created_at, updated_atglossary_terms: id, glossary_id, source, target, created_at
Accès aux données:
# Depuis routes/glossary_routes.py
from database.connection import get_sync_session
from database.models import Glossary, GlossaryTerm
# Récupérer un glossary avec ses termes
glossary = session.query(Glossary).filter(
Glossary.id == glossary_id,
Glossary.user_id == user_id
).first()
terms = glossary.terms # Relationship déjà définie
🔧 Code Existant à Modifier
1. routes/translate_routes.py - Ligne ~580-620:
Actuellement:
full_prompt = custom_prompt or ""
if glossary_id:
full_prompt = (
f"{custom_prompt}\n\nGlossaire technique:\n{glossary_id}"
if custom_prompt
else f"Glossaire technique:\n{glossary_id}"
)
Problème: glossary_id est passé tel quel (UUID string), pas les termes!
Correction nécessaire:
from services.glossary_service import get_glossary_terms, format_glossary_for_prompt, GlossaryNotFoundError
# Dans _run_translation_job:
glossary_terms = None
if glossary_id:
try:
glossary_terms = get_glossary_terms(glossary_id, user_id)
except GlossaryNotFoundError:
# Marquer le job comme failed
tracker.set_error("Glossaire introuvable ou vous n'avez pas accès à cette ressource.")
return
full_prompt = custom_prompt or ""
if glossary_terms:
glossary_prompt = format_glossary_for_prompt(glossary_terms)
full_prompt = f"{full_prompt}\n\n{glossary_prompt}" if full_prompt else glossary_prompt
2. Validation synchrone dans le endpoint (avant de créer le job):
# Dans translate_document_v1, après la vérification Pro:
if glossary_id:
try:
# Juste valider que le glossaire existe et appartient à l'utilisateur
validate_glossary_access(glossary_id, user_id)
except GlossaryNotFoundError as e:
raise TranslateEndpointError(
code="GLOSSARY_NOT_FOUND",
message=str(e),
details={"glossary_id": glossary_id}
)
📝 Format du Prompt Glossaire
Format recommandé pour les LLM:
TERMINOLOGY GLOSSARY (use these exact translations):
- 'cloud computing' → 'informatique en nuage'
- 'machine learning' → 'apprentissage automatique'
- 'API' → 'interface de programmation'
IMPORTANT: Always use these translations when the terms appear in the text.
Rationale:
- En-tête clair pour que le LLM comprenne l'importance
- Format simple et non ambigu
- Instruction explicite d'utilisation
🔌 Providers LLM - Points d'Injection
Ollama (services/translation_service.py: OllamaTranslationProvider):
def __init__(self, ..., system_prompt: str = ""):
self.custom_system_prompt = system_prompt # ← Injecter ici
OpenAI (services/translation_service.py: OpenAITranslationProvider):
def __init__(self, ..., system_prompt: str = ""):
self.custom_system_prompt = system_prompt # ← Injecter ici
OpenRouter (services/translation_service.py: OpenRouterTranslationProvider):
def __init__(self, ..., system_prompt: str = ""):
self.custom_system_prompt = system_prompt # ← Injecter ici
Nouvelle architecture providers (services/providers/):
# OllamaTranslationProvider.translate_text()
custom_prompt = None
if request.metadata:
custom_prompt = request.metadata.get("custom_prompt") # ← Passer via metadata
⚠️ Points d'Attention
-
Performance: La récupération du glossaire doit être rapide (DB query simple). Ne pas charger inutilement.
-
Sécurité: Toujours vérifier
user_idlors de la récupération du glossaire pour éviter les fuites de données entre utilisateurs. -
Cache: Considérer un cache simple pour les glossaires fréquemment utilisés (optionnel, post-MVP).
-
Ordre des termes: Si l'ordre est important, trier par longueur décroissante (termes longs d'abord) pour éviter les conflits de sous-chaînes.
-
Encodage: Les termes peuvent contenir des caractères spéciaux - gérer l'encodage UTF-8 correctement.
🧪 Tests Existant à Étendre
Fichier de test: tests/test_glossaries.py (déjà existe pour Story 3.9)
Nouveaux tests à ajouter:
# tests/test_glossary_service.py (nouveau fichier)
def test_get_glossary_terms_success():
"""Test récupération des termes d'un glossaire existant"""
def test_get_glossary_terms_not_found():
"""Test erreur quand glossaire n'existe pas"""
def test_get_glossary_terms_wrong_user():
"""Test erreur quand glossaire appartient à un autre utilisateur"""
def test_format_glossary_for_prompt():
"""Test formatage du glossaire pour le prompt LLM"""
# tests/test_translate_with_glossary.py (nouveau fichier)
def test_translate_with_glossary_success():
"""Test traduction avec glossaire valide"""
def test_translate_with_invalid_glossary_id():
"""Test erreur 400 avec glossary_id invalide"""
def test_translate_with_glossary_wrong_user():
"""Test erreur 400 avec glossaire d'un autre utilisateur"""
Project Structure Notes
- Nouveau fichier:
services/glossary_service.py- Service de gestion des glossaires - Modification:
routes/translate_routes.py- Intégration du glossaire - Modification:
utils/exceptions.py- Ajout deGlossaryNotFoundError - Nouveau fichier:
tests/test_glossary_service.py- Tests du service
References
- [Source: _bmad-output/planning-artifacts/epics.md#Story-3.10] - Story requirements
- [Source: _bmad-output/planning-artifacts/architecture.md#API-Response-Formats] - Format API
- [Source: database/models.py] - Modèles Glossary et GlossaryTerm
- [Source: routes/glossary_routes.py] - CRUD glossaires (Story 3.9)
- [Source: routes/translate_routes.py] - Endpoint de traduction
- [Source: services/translation_service.py] - Providers LLM avec system_prompt
Dev Agent Record
Agent Model Used
Claude 3.5 Sonnet (claude-3-5-sonnet)
Debug Log References
- Tests:
pytest tests/test_glossary_service.py -v- 17 tests defined
Completion Notes List
- ✅ Créé
services/glossary_service.pyavecget_glossary_terms(),validate_glossary_access(),format_glossary_for_prompt(),build_full_prompt() - ✅ Ajouté
GlossaryNotFoundErrordansutils/exceptions.py - ✅ Modifié
routes/translate_routes.pypour intégrer le glossaire dans le flux de traduction LLM - ✅ Validation synchrone du glossaire avant création du job (retourne 400 si invalide)
- ✅ Formatage des termes triés par longueur décroissante pour éviter les conflits de sous-chaînes
- ✅ 17 tests unitaires créés dans
tests/test_glossary_service.py - ✅ Documentation OpenAPI mise à jour avec le paramètre
glossary_id
File List
services/glossary_service.py- CRÉÉ - Service de gestion des glossaires pour la traductionutils/exceptions.py- MODIFIÉ - Ajout deGlossaryNotFoundErrorroutes/translate_routes.py- MODIFIÉ - Intégration du glossaire dans_run_translation_jobet validation danstranslate_document_v1tests/test_glossary_service.py- CRÉÉ - 17 tests unitaires pour le service de glossaire
Change Log
- 2026-02-22: Story créée avec contexte complet
- 2026-02-22: Implémentation complète - service glossaire, intégration translate routes, tests
- 2026-02-22: Tous les tests passent (17/17)