Files
office_translator/_bmad-output/implementation-artifacts/3-10-glossaires-application-lors-traduction-llm.md
Sepehr Ramezani 26bd096a06 feat: production deployment - full update with providers, admin, glossaries, pricing, tests
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>
2026-04-25 15:01:47 +02:00

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

  1. Injection du glossaire: Quand un utilisateur Pro POST sur /api/v1/translate avec le paramètre glossary_id, les termes du glossaire sont injectés dans le prompt système du LLM. (FR60)
  2. Formatage des termes: Les termes sont formatés de manière claire pour le LLM (ex: "Always translate 'cloud computing' as 'informatique en nuage'").
  3. Validation du glossaire: Si glossary_id n'existe pas ou n'appartient pas à l'utilisateur, retourner 400 avec erreur GLOSSARY_NOT_FOUND.
  4. Compatibilité providers: Le glossaire est appliqué pour tous les providers LLM (Ollama, OpenAI, OpenRouter).
  5. 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.py avec la fonction get_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 exception GlossaryNotFoundError
  • Task 2: Créer la fonction de formatage du glossaire (AC: #2)

    • 2.1 Créer format_glossary_for_prompt(terms: list[dict]) -> str dans services/glossary_service.py
    • 2.2 Formater chaque terme comme: "- 'source' → 'target'"
    • 2.3 Ajouter un en-tête clair: "TERMINOLOGY GLOSSARY (use these translations):"
  • Task 3: Modifier _run_translation_job pour utiliser le glossaire (AC: #1, #3, #4)

    • 3.1 Dans routes/translate_routes.py, appeler get_glossary_terms() si glossary_id est fourni
    • 3.2 Formater les termes avec format_glossary_for_prompt()
    • 3.3 Construire le full_prompt en combinant custom_prompt et le glossaire formaté
    • 3.4 Passer le prompt combiné aux providers LLM
  • Task 4: Gérer l'erreur GLOSSARY_NOT_FOUND (AC: #3)

    • 4.1 Créer l'exception GlossaryNotFoundError dans utils/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)
  • 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 /translate avec glossary_id
    • 5.4 Test de validation: glossaire invalide retourne 400
  • Task 6: Documentation OpenAPI (AC: Tous)

    • 6.1 Documenter le paramètre glossary_id avec exemples
    • 6.2 Documenter l'erreur GLOSSARY_NOT_FOUND
    • 6.3 Vérifier sur /docs que la documentation est complète

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_at
  • glossary_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

  1. Performance: La récupération du glossaire doit être rapide (DB query simple). Ne pas charger inutilement.

  2. Sécurité: Toujours vérifier user_id lors de la récupération du glossaire pour éviter les fuites de données entre utilisateurs.

  3. Cache: Considérer un cache simple pour les glossaires fréquemment utilisés (optionnel, post-MVP).

  4. 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.

  5. 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 de GlossaryNotFoundError
  • 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.py avec get_glossary_terms(), validate_glossary_access(), format_glossary_for_prompt(), build_full_prompt()
  • Ajouté GlossaryNotFoundError dans utils/exceptions.py
  • Modifié routes/translate_routes.py pour 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 traduction
  • utils/exceptions.py - MODIFIÉ - Ajout de GlossaryNotFoundError
  • routes/translate_routes.py - MODIFIÉ - Intégration du glossaire dans _run_translation_job et validation dans translate_document_v1
  • tests/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)