Exo Data/IA Engineer — Product Intelligence Platform (Streamlit + FastAPI + DuckDB + LLM)
Find a file
2026-08-09 16:20:53 +00:00
.streamlit Style: lignes < 100 chars (PEP8), commentaires déplacés hors ligne de code, config Streamlit (cacher bouton Deploy) 2026-08-09 16:20:53 +00:00
tests test: ajouter 20 tests pytest (BDD + API) + section Tests dans le README 2026-08-06 13:28:13 +00:00
.dockerignore Init Docker : conteneurisation de l'app Streamlit et de l'API FastAPI 2026-08-05 11:57:13 +00:00
.env.example Init Docker : conteneurisation de l'app Streamlit et de l'API FastAPI 2026-08-05 11:57:13 +00:00
.gitignore Init Docker : conteneurisation de l'app Streamlit et de l'API FastAPI 2026-08-05 11:57:13 +00:00
api.py fix: crash Streamlit quand 0 résultat aux filtres (section enrichissement masquée) + handler 500 JSON propre dans l'API 2026-08-09 15:47:46 +00:00
app_streamlit.py Style: lignes < 100 chars (PEP8), commentaires déplacés hors ligne de code, config Streamlit (cacher bouton Deploy) 2026-08-09 16:20:53 +00:00
db.py Style: lignes < 100 chars (PEP8), commentaires déplacés hors ligne de code, config Streamlit (cacher bouton Deploy) 2026-08-09 16:20:53 +00:00
docker-compose.yml Init Docker : conteneurisation de l'app Streamlit et de l'API FastAPI 2026-08-05 11:57:13 +00:00
Dockerfile Init Docker : conteneurisation de l'app Streamlit et de l'API FastAPI 2026-08-05 11:57:13 +00:00
enrich.py fix: gestion des valeurs NaN (DuckDB) dans le skip des produits enrichis 2026-08-09 14:58:29 +00:00
llm.py feat: robustesse des appels LLM (timeouts, retry ciblé + jitter, skip produits déjà enrichis) + pin starlette<1.4.0 2026-08-09 16:49:26 +02:00
products.csv Dataset : products.csv (1000 produits, export Shopify) 2026-08-05 18:24:58 +00:00
README.md README: harmoniser les stats catalogue (578 à enrichir, 58%) 2026-08-09 16:00:55 +00:00
requirements-dev.txt test: ajouter 20 tests pytest (BDD + API) + section Tests dans le README 2026-08-06 13:28:13 +00:00
requirements.txt feat: robustesse des appels LLM (timeouts, retry ciblé + jitter, skip produits déjà enrichis) + pin starlette<1.4.0 2026-08-09 16:49:26 +02:00

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 --version et docker compose version

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'abstraction llm.py retourne toujours la même fonction complete(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, /search sans 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_array contient 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 — 422 si aucun critère de recherche, 404 si produit introuvable, toujours au format {"detail": "..."}.

🗓️ Exemple de démo (entretien)

  1. Ouvrir l'app Streamlit → montrer les stats (58% à enrichir)
  2. Filtrer "Descriptions vides" → lancer l'enrichissement de ~10 produits en direct (barre de progression)
  3. Montrer l'avant/après dans l'aperçu
  4. Interroger l'API : /search?q=maillot → les résultats avec descriptions enrichies
  5. Ouvrir /docs → montrer que l'API est auto-documentée