| .streamlit | ||
| tests | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| api.py | ||
| app_streamlit.py | ||
| db.py | ||
| docker-compose.yml | ||
| Dockerfile | ||
| enrich.py | ||
| llm.py | ||
| products.csv | ||
| README.md | ||
| requirements-dev.txt | ||
| requirements.txt | ||
Product Intelligence Platform — Exo Data/IA Engineer
Enrichissement de descriptions produits par IA (SEO e-commerce) + API de recherche.
Stack : Streamlit (UI) · FastAPI (API) · DuckDB (BDD) · DeepSeek (LLM, clé fournie par l'entreprise) · Docker.
✅ Prérequis
Mode Docker (recommandé)
- Docker + Docker Compose installés :
- Linux :
sudo apt install docker.io docker-compose-v2(ou via votre distribution) - Windows / macOS : Docker Desktop
- Vérifier :
docker --versionetdocker compose version
- Linux :
Mode sans Docker (dev local)
- Python 3.10+ (python.org) — vérifier :
python3 --version - pip inclus avec Python — vérifier :
pip --version venv(module standard, inclus avec Python)- Une clé API LLM (DeepSeek fournie par l'entreprise, ou autre provider — voir Configuration)
Les deux modes utilisent le même code et la même BDD DuckDB (un simple fichier) : rien d'autre à installer.
⚙️ Configuration
Toute la configuration se fait dans .env (copie de .env.example) :
cp .env.example .env
Variables générales
| Variable | Description | Requis | Défaut |
|---|---|---|---|
LLM_PROVIDER |
Fournisseur LLM : deepseek | ollama | openai | anthropic |
Oui | deepseek |
DATASET_PATH |
Chemin du fichier CSV de données | Non | products.csv |
DB_PATH |
Chemin du fichier BDD DuckDB | Non | data/products.duckdb |
Provider : DeepSeek (défaut)
Clé fournie par l'entreprise. API compatible OpenAI, très bon français, coût négligeable.
| Variable | Description | Requis | Défaut |
|---|---|---|---|
DEEPSEEK_API_KEY |
Clé API DeepSeek (tableau de bord DeepSeek) | Oui si LLM_PROVIDER=deepseek |
— |
DEEPSEEK_MODEL |
Nom du modèle DeepSeek | Non | deepseek-chat |
Provider : Ollama (modèle local, gratuit)
Aucune clé requise. Nécessite Ollama installé et lancé sur la machine (
ollama serve), avec le modèle téléchargé (ollama pull llama3.2:3b).
| Variable | Description | Requis | Défaut |
|---|---|---|---|
OLLAMA_MODEL |
Nom du modèle local à utiliser | Non | llama3.2:3b |
OLLAMA_BASE_URL |
URL de l'API locale Ollama | Non | http://localhost:11434 |
Provider : OpenAI
| Variable | Description | Requis | Défaut |
|---|---|---|---|
OPENAI_API_KEY |
Clé API OpenAI (platform.openai.com) | Oui si LLM_PROVIDER=openai |
— |
OPENAI_MODEL |
Nom du modèle OpenAI | Non | gpt-4o-mini |
Provider : Anthropic
| Variable | Description | Requis | Défaut |
|---|---|---|---|
ANTHROPIC_API_KEY |
Clé API Anthropic (console.anthropic.com) | Oui si LLM_PROVIDER=anthropic |
— |
ANTHROPIC_MODEL |
Nom du modèle Anthropic | Non | claude-sonnet-4-5 |
Résumé : seules
LLM_PROVIDER+ la clé du provider choisi sont obligatoires. Tout le reste a une valeur par défaut. L'abstractionllm.pyretourne toujours la même fonctioncomplete(prompt)quel que soit le provider : le reste du code n'a pas besoin de changer.
🚀 Lancement rapide (Docker)
# 1. Configurer la clé API (voir section Configuration)
cp .env.example .env
# → éditer .env et mettre DEEPSEEK_API_KEY=ta_clé
# 2. Lancer l'app Streamlit (interface d'enrichissement)
docker compose up --build app
# → http://localhost:8501
# 3. (autre terminal) Lancer l'API de recherche
docker compose up --build api
# → http://localhost:8000/docs (Swagger auto)
Le dataset products.csv est chargé automatiquement dans DuckDB au premier lancement (data/products.duckdb).
🧰 Sans Docker (dev local)
# 1. Créer et activer l'environnement virtuel
python -m venv .venv
source .venv/bin/activate # Windows : .venv\Scripts\activate
# 2. Installer les dépendances
pip install -r requirements.txt
pip install -r requirements-dev.txt # optionnel : pour lancer les tests
# 3. Configurer la clé API (voir section Configuration)
cp .env.example .env # + éditer .env
# 4. Lancer les services
streamlit run app_streamlit.py # UI sur :8501
uvicorn api:app --reload # API sur :8000
📁 Structure du projet
├── app_streamlit.py # UI Streamlit : stats, filtres, enrichissement avec progression, avant/après, export
├── api.py # API FastAPI : /search, /products/{id}, /stats, /health (+ Swagger /docs)
├── db.py # Couche DuckDB : chargement, stats, filtres, recherche, export
├── enrich.py # Enrichissement LLM : prompt, retry/backoff, batch avec progression
├── llm.py # Abstraction LLM : deepseek | ollama | openai | anthropic
├── products.csv # Dataset (1000 produits)
├── requirements.txt
├── Dockerfile
├── docker-compose.yml
└── .env.example # Modèle de configuration (copier en .env)
🔍 Utilisation de l'API
# Recherche par mots-clés
curl "http://localhost:8000/search?q=robe+coton"
# Recherche + filtres
curl "http://localhost:8000/search?q=maillot&product_type=Maillots&vendor=NAIA"
# Un produit précis
curl "http://localhost:8000/products/4659395625056"
# Stats du catalogue
curl "http://localhost:8000/stats"
La documentation interactive (Swagger UI) est disponible sur http://localhost:8000/docs.
🧪 Tests
Suite de tests pytest (20 tests) couvrant la couche BDD et l'API — sans appel LLM, sur un petit dataset isolé (tests/products_test.csv, BDD temporaire) pour ne jamais toucher aux vraies données.
pip install -r requirements.txt pytest httpx
pytest tests/ -v
Ce qui est testé :
- BDD (test_db.py) : chargement du dataset, stats catalogue, filtres (qualité / catégorie / marque / combinés), recherche par mots-clés + filtres, recherche sans résultat, parsing des images
- API (test_api.py) :
/health,/stats, recherche par mots-clés, filtres, limite de résultats,/searchsans critère → 422, produit inexistant → 404, route inconnue → 404
Résultat attendu : 20 passed.
📊 Le dataset en bref
- 1000 produits (export Shopify), 446 marques, 402 catégories
- 136 descriptions vides + 366 courtes (1-49 caractères) + 76 entre 50 et 149 → 578 produits à enrichir (58%) (critère : description < 150 caractères)
- Le champ
images_arraycontient les URLs d'images en JSON (utilisé pour l'affichage)
⚙️ Choix techniques & architecture
Architecture
products.csv ──► DuckDB (products_enriched) ◄── Streamlit (enrichissement via LLM)
│
└──► FastAPI /search (mots-clés + filtres) ──► JSON
Pourquoi ces choix
| Choix | Raison |
|---|---|
| DuckDB | Zéro config, lit le CSV natif, SQL complet, idéal pour une démo locale ; la BDD est un simple fichier (data/products.duckdb) |
| DeepSeek (API) | Clé fournie par l'entreprise ; qualité FR excellente ; coût négligeable ; pas de GPU requis |
Abstraction LLM (llm.py) |
LLM_PROVIDER permet de basculer entre deepseek / ollama / openai / anthropic sans toucher au reste du code |
Retry + backoff (enrich.py) |
Les API LLM échouent parfois (timeout, rate limit) : 2 retries avec pause croissante, et le batch continue même si un produit échoue |
| Pydantic (FastAPI) | Validation automatique des réponses API + génération de la doc Swagger gratuite |
| Docker | Reproductible partout (démo sans souci d'environnement) |
Gestion des erreurs
- Côté enrichissement : chaque produit renvoie
{ok, product_id, enriched_description, error}— un échec est loggé, le batch continue. - Côté API : réponses standardisées —
422si aucun critère de recherche,404si produit introuvable, toujours au format{"detail": "..."}.
🗓️ Exemple de démo (entretien)
- Ouvrir l'app Streamlit → montrer les stats (58% à enrichir)
- Filtrer "Descriptions vides" → lancer l'enrichissement de ~10 produits en direct (barre de progression)
- Montrer l'avant/après dans l'aperçu
- Interroger l'API :
/search?q=maillot→ les résultats avec descriptions enrichies - Ouvrir
/docs→ montrer que l'API est auto-documentée