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>
282 lines
11 KiB
Markdown
282 lines
11 KiB
Markdown
# 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
|
|
|
|
- [x] **Task 1: Créer le service de récupération de glossaire** (AC: #1, #3)
|
|
- [x] 1.1 Créer `services/glossary_service.py` avec la fonction `get_glossary_terms(glossary_id: str, user_id: str) -> list[dict]`
|
|
- [x] 1.2 Implémenter la validation: vérifier que le glossaire existe et appartient à l'utilisateur
|
|
- [x] 1.3 Retourner une liste de `{source, target}` ou lever une exception `GlossaryNotFoundError`
|
|
|
|
- [x] **Task 2: Créer la fonction de formatage du glossaire** (AC: #2)
|
|
- [x] 2.1 Créer `format_glossary_for_prompt(terms: list[dict]) -> str` dans `services/glossary_service.py`
|
|
- [x] 2.2 Formater chaque terme comme: "- 'source' → 'target'"
|
|
- [x] 2.3 Ajouter un en-tête clair: "TERMINOLOGY GLOSSARY (use these translations):"
|
|
|
|
- [x] **Task 3: Modifier `_run_translation_job` pour utiliser le glossaire** (AC: #1, #3, #4)
|
|
- [x] 3.1 Dans `routes/translate_routes.py`, appeler `get_glossary_terms()` si `glossary_id` est fourni
|
|
- [x] 3.2 Formater les termes avec `format_glossary_for_prompt()`
|
|
- [x] 3.3 Construire le `full_prompt` en combinant `custom_prompt` et le glossaire formaté
|
|
- [x] 3.4 Passer le prompt combiné aux providers LLM
|
|
|
|
- [x] **Task 4: Gérer l'erreur GLOSSARY_NOT_FOUND** (AC: #3)
|
|
- [x] 4.1 Créer l'exception `GlossaryNotFoundError` dans `utils/exceptions.py`
|
|
- [x] 4.2 Dans `_run_translation_job`, catcher l'exception et marquer le job comme failed avec le message approprié
|
|
- [x] 4.3 Dans le endpoint `translate_document_v1`, valider le glossaire avant de créer le job (retourner 400 immédiatement)
|
|
|
|
- [x] **Task 5: Tests** (AC: Tous)
|
|
- [x] 5.1 Tests unitaires pour `get_glossary_terms()` (glossaire trouvé, non trouvé, n'appartient pas à l'utilisateur)
|
|
- [x] 5.2 Tests unitaires pour `format_glossary_for_prompt()`
|
|
- [x] 5.3 Tests d'intégration pour le endpoint `/translate` avec `glossary_id`
|
|
- [x] 5.4 Test de validation: glossaire invalide retourne 400
|
|
|
|
- [x] **Task 6: Documentation OpenAPI** (AC: Tous)
|
|
- [x] 6.1 Documenter le paramètre `glossary_id` avec exemples
|
|
- [x] 6.2 Documenter l'erreur `GLOSSARY_NOT_FOUND`
|
|
- [x] 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:**
|
|
```python
|
|
# 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:
|
|
```python
|
|
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:**
|
|
```python
|
|
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):**
|
|
|
|
```python
|
|
# 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):**
|
|
```python
|
|
def __init__(self, ..., system_prompt: str = ""):
|
|
self.custom_system_prompt = system_prompt # ← Injecter ici
|
|
```
|
|
|
|
**OpenAI (services/translation_service.py: OpenAITranslationProvider):**
|
|
```python
|
|
def __init__(self, ..., system_prompt: str = ""):
|
|
self.custom_system_prompt = system_prompt # ← Injecter ici
|
|
```
|
|
|
|
**OpenRouter (services/translation_service.py: OpenRouterTranslationProvider):**
|
|
```python
|
|
def __init__(self, ..., system_prompt: str = ""):
|
|
self.custom_system_prompt = system_prompt # ← Injecter ici
|
|
```
|
|
|
|
**Nouvelle architecture providers (services/providers/):**
|
|
```python
|
|
# 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:**
|
|
```python
|
|
# 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)
|