Un agent multi-étapes qui répond à des questions en générant et exécutant du SQL, en calculant, et en cherchant sur le web — avec mémoire court/long terme et gestion d'erreurs (retry + fallback). La boucle d'orchestration est écrite à la main (pas de LangChain/LangGraph en dépendance principale) pour prouver la compréhension du mécanisme sous-jacent, avec une implémentation LangGraph fournie en parallèle à titre de comparaison explicite.
📄 Pourquoi ces choix techniques ? — la partie la plus importante : pourquoi une boucle maison plutôt que directement un framework, ce que LangGraph automatise réellement une fois qu'on l'a comparé ligne à ligne, et la sécurité de l'outil SQL.
Question utilisateur
│
▼
┌─────────────────────┐ ┌──────────────────────┐
│ Mémoire long terme │──────▶│ System prompt + │
│ (Chroma, persistant) │ │ historique court terme │
└─────────────────────┘ └──────────────────────┘
│
▼
┌─────────────────────┐
┌───────▶│ LLM (Claude) │
│ │ décide : répondre │
│ │ ou appeler un outil │
│ └─────────────────────┘
│ │
│ tool_use ?│ non ─────────▶ Réponse finale
│ │ oui
│ ▼
│ ┌─────────────────────┐
│ │ Exécution de l'outil │
│ │ (retry + fallback) │
│ └─────────────────────┘
│ │
└───────────────────┘
résultat réinjecté dans l'historique
Outils disponibles :
| Outil | Ce qu'il fait | Gestion d'erreur |
|---|---|---|
query_database |
Génère et exécute du SQL sur une base SQLite (clients/commandes/produits), avec correction en boucle si la requête échoue, et vérification de plausibilité du résultat | Retry sémantique (renvoie l'erreur SQL au LLM pour correction) + validation statique SELECT-only |
calculator |
Évalue une expression arithmétique via un parseur AST restreint (pas d'eval brut) |
Erreur permanente si expression invalide |
web_search |
Recherche web (DuckDuckGo par défaut, gratuit) | Fallback automatique vers un second provider (Tavily) si configuré |
- Boucle d'agent maison (
src/agent/core.py) plutôt que LangGraph par défaut : montre le mécanisme (historique + tool specs → LLM → exécution → réinjection → boucle) sans boîte noire.src/agent/graph.pyréimplémente exactement la même logique avec LangGraph pour comparaison directe. - Deux niveaux de retry : mécanique (relancer un appel identique après
une erreur réseau) et sémantique (corriger une requête SQL à partir de
son message d'erreur) — voir
docs/DESIGN_CHOICES.md§2. - Sécurité SQL en profondeur : validation statique SELECT-only
(
sqlparse) + vérification de plausibilité du résultat par un second appel LLM.
git clone <ce-repo>
cd agent-sql-tool-use
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
# éditer .env : renseigner ANTHROPIC_API_KEY
python src/db/setup_sample_db.py # crée data/sample.dbpython app/cli.pyQuestion > Quel client a le plus dépensé sur des commandes livrées ?
Étape 1 ✅ outil=query_database input={'question': 'Quel client a le plus dépensé sur des commandes livrées ?'}
-> {"sql_query": "SELECT c.name, SUM(...) ...", "rows": [["Amina Belkacem", 512.8], ...]}
Étape 2 — réponse finale
Réponse : Amina Belkacem a le plus dépensé, avec un total de 512,80€ sur ses commandes livrées.
python scripts/compare_implementations.py "Combien de commandes sont en cours ?"pytest tests/ -vagent-sql-tool-use/
├── src/
│ ├── agent/
│ │ ├── core.py # boucle d'agent écrite à la main
│ │ ├── graph.py # même logique en LangGraph (comparaison)
│ │ └── state.py # état de l'agent (traces d'étapes)
│ ├── tools/
│ │ ├── base.py # interface Tool + retry/fallback
│ │ ├── sql_tool.py # génération SQL + validation + exécution + plausibilité
│ │ ├── calculator.py # calculatrice sécurisée (AST, pas d'eval)
│ │ ├── web_search.py # recherche web, DuckDuckGo + fallback Tavily
│ │ └── registry.py # registre central des outils
│ ├── memory/
│ │ ├── short_term.py # historique de conversation, troncature sûre
│ │ └── long_term.py # mémoire persistante par similarité (Chroma)
│ ├── llm/client.py # wrapper Anthropic (tool-use natif)
│ ├── db/setup_sample_db.py # base SQLite d'exemple (schéma e-commerce)
│ └── config.py
├── app/cli.py # interface interactive avec trace
├── scripts/compare_implementations.py # maison vs LangGraph, côte à côte
├── docs/DESIGN_CHOICES.md # justification de chaque choix technique
└── tests/ # tests unitaires (sécurité SQL, retry, mémoire)
- La mémoire long terme n'est pas alimentée automatiquement (pas de résumé
de session) — voir
docs/DESIGN_CHOICES.md§5 pour l'extension prévue. - Le sandboxing SQL est purement applicatif (
validate_select_only), pas au niveau base de données (pas d'utilisateur SQLite en lecture seule dédié) — acceptable pour une base d'exemple locale, à durcir en production. max_steps=8par défaut : un agent bloqué dans une boucle de correction SQL répétée épuisera ce budget avant de conclure — un compromis entre robustesse et coût en appels API.
MIT — voir LICENSE.