TL;DR — 3 paradigmes
- smolagents (code-first) — le LLM génère du code Python exécuté step by step. Variables persistantes entre étapes. ~1 000 lignes de core. Jusqu'à 30% moins d'étapes selon les benchmarks Hugging Face.
- Pydantic AI (type-safe) — DI explicite, type checking statique mypy/pyright, TestModel déterministe. Idéal pour les APIs de production.
- LangGraph (stateful) — graphe de nœuds, état persistant, cycles, human-in-the-loop natif. Pour les workflows orchestrés complexes.
- Ollama : smolagents utilise
LiteLLMModel(pas de classe OllamaModel native). - Les trois sont complémentaires — une archi hybride smolagents + LangGraph est courante en 2026.
Sommaire
- Les 3 paradigmes des agents IA Python
- smolagents — architecture et philosophie
- Installation
- CodeAgent — agents code-first
- ToolCallingAgent — agents JSON
- Définir des outils
- Intégration Ollama
- Exécution sécurisée du code
- Multi-agents avec smolagents
- Système de mémoire — ActionStep et AgentMemory
- Interface Gradio en une ligne
- smolagents vs Pydantic AI vs LangGraph
- Quand utiliser quoi
- Exemple complet — agent CSV + Ollama
- Limites de smolagents
- FAQ
1. Les 3 paradigmes des agents IA Python
Avant de choisir un framework, il faut comprendre que les trois approches dominant le paysage Python en 2026 ne font pas la même chose — elles répondent à des questions différentes.
smolagents — Code-first (Hugging Face)
Le LLM génère du code Python exécutable à chaque étape. Les variables persistent entre les steps. Minimaliste (~1 000 lignes), auditables, intégration native Hugging Face Hub. Question centrale : comment automatiser des tâches complexes avec le minimum de glue code ?
Pydantic AI — Type-safe (Samuel Colvin)
Injection de dépendances typée, validation compile-time mypy/pyright, TestModel déterministe, structured outputs en 3 modes. Question centrale : comment rendre les agents aussi maintenables que le reste du code Python de production ?
LangGraph — Stateful (LangChain Inc.)
Graphe de nœuds avec état partagé, cycles, checkpointing, human-in-the-loop natif, streaming. Question centrale : comment orchestrer des workflows IA complexes avec persistance d'état et contrôle précis du flux d'exécution ?
Le vrai problème des agents IA en Python
Beaucoup de développeurs choisissent leur framework d'agents comme une librairie classique — popularité GitHub, exemples rapides, promesses marketing. Mauvais réflexe. Un agent code-first, un agent type-safe et un graphe stateful ne répondent pas au même besoin. Le mauvais choix peut transformer un prototype impressionnant en système impossible à maintenir après trois semaines.
Ces trois paradigmes répondent à des niveaux d'abstraction différents, pas à des besoins identiques formulés différemment. C'est pourquoi les combiner est non seulement possible mais souvent optimal.
2. smolagents — architecture et philosophie
Si vous souhaitez d'abord approfondir l'une des deux autres approches : guide Pydantic AI Python 2026 (type-safe) et guide LangGraph 2026 (stateful). Pour les modèles locaux : démarrer avec Ollama.
smolagents a été lancé le 31 décembre 2024 par Hugging Face comme successeur de transformers.agents. Il est actuellement en version 1.24.0 (16 janvier 2026). Son argument fondateur tient en un principe : le code est un meilleur format d'action que le JSON.
Cette thèse est étayée par trois papiers de recherche :
- Executable Code Actions Elicit Better LLM Agents (arXiv:2402.01030)
- If LLM Is the Wizard, Then Code Is the Wand (arXiv:2401.00812)
- DynaSaur: Large Language Agents Beyond Predefined Actions (arXiv:2411.01747)
L'argument central : le code Python est composable (boucles, conditions, fonctions imbriquées), gère naturellement les objets intermédiaires (stocker le résultat d'une image dans une variable, passer un DataFrame d'un tool au suivant), et est sur-représenté dans les données d'entraînement des LLMs — ils y excellent.
La conséquence architecturale : le core de smolagents tient en ~1 000 lignes de code (src/smolagents/agents.py). Pas de surcharge d'abstraction, auditabilité complète, fork possible en une journée.
Résultat GAIA benchmark
L'architecture multi-agents Hugging Face basée sur smolagents a atteint 44,2% sur GAIA validation (Human Avg : ~92%, GPT-4 Turbo seul : ~7%). Ce résultat a été obtenu avec l'ancienne architecture transformers.agents migrée vers smolagents — les scores sont indicatifs d'une capacité réelle, pas d'une comparaison directe entre frameworks.
3. Installation
smolagents est organisé en extras PyPI selon les backends utilisés :
# Base (CodeAgent + ToolCallingAgent + outils natifs)
pip install smolagents
# + LiteLLM (Ollama, Anthropic, OpenAI, 100+ providers)
pip install 'smolagents[litellm]'
# + DuckDuckGoSearch + Whisper transcriber (outils courants)
pip install 'smolagents[toolkit]'
# + Interface Gradio
pip install 'smolagents[gradio]'
# + Sandbox E2B cloud (exécution sécurisée)
pip install 'smolagents[e2b]'
# + Inférence locale via Transformers
pip install 'smolagents[transformers]'
# Tout en une commande (dev)
pip install 'smolagents[litellm,toolkit,gradio]'
4. CodeAgent — agents code-first
CodeAgent est le cœur de smolagents. À chaque step, le LLM génère un bloc de code Python entre balises — ce code est exécuté, son output est observé, et le LLM décide du step suivant. Le cycle s'arrête quand le LLM appelle final_answer() — une fonction Python standard injectée automatiquement dans l'environnement.
from smolagents import CodeAgent, InferenceClientModel, DuckDuckGoSearchTool
# Modèle via Hugging Face Inference API (Qwen3-Next par défaut)
model = InferenceClientModel()
# Agent avec outils natifs (web search + calcul + code Python)
agent = CodeAgent(
tools=[DuckDuckGoSearchTool()],
model=model,
add_base_tools=True, # Ajoute PythonInterpreterTool, FinalAnswerTool
max_steps=10,
)
result = agent.run("Quelle est la racine carrée de la population de Paris en 2025 ?")
print(result)
Ce que le LLM génère à chaque step
# Step 1 : Le LLM génère ce code
results = web_search("population Paris 2025")
print(results)
# Observation : "Paris compte environ 2,1 millions d'habitants en 2025"
# Step 2 : Le LLM génère ce code (réutilise les variables du step 1)
import math
population = 2_100_000
racine = math.sqrt(population)
final_answer(f"La racine carrée de {population:,} est {racine:.1f}")
État stateful entre les steps
Les variables Python définies au step N sont disponibles au step N+1. C'est ce qui permet à un CodeAgent d'enchaîner des opérations complexes — charger un DataFrame, le filtrer, faire un calcul, générer un graphique — sans perdre les objets intermédiaires entre chaque appel LLM.
Paramètres clés du CodeAgent
agent = CodeAgent(
tools=[...],
model=model,
additional_authorized_imports=["pandas", "matplotlib", "numpy"],
# ^ Imports autorisés en plus de la liste blanche par défaut
use_structured_outputs_internally=True,
# ^ LLM génère {"thoughts": "...", "code": "..."} → 2-7 pts de gain
planning_interval=3,
# ^ Replanning tous les 3 steps : le LLM met à jour sa stratégie
max_steps=15,
executor_type="local", # ou "e2b", "docker", "modal", "wasm"
stream_outputs=True, # Affiche les prints en temps réel
)
5. ToolCallingAgent — agents JSON
ToolCallingAgent utilise le format standard OpenAI function-calling : le LLM génère un JSON structuré indiquant quel outil appeler et avec quels arguments. Aucun code Python n'est exécuté — c'est le framework qui dispatch l'appel.
from smolagents import ToolCallingAgent, InferenceClientModel
agent = ToolCallingAgent(
tools=[search_tool, calculator_tool],
model=model,
max_tool_threads=4, # Appels parallèles via ThreadPoolExecutor
)
result = agent.run("Compare les prix de l'or et de l'argent aujourd'hui")
# → Les deux recherches sont lancées en parallèle
CodeAgent vs ToolCallingAgent — quand choisir quoi
| Critère | CodeAgent | ToolCallingAgent |
|---|---|---|
| Format d'action | Code Python exécutable | JSON structuré (function calling) |
| Composabilité | Maximale (boucles, conditions, objets) | Nulle — chaque appel est atomique |
| Objets intermédiaires | Variables Python persistantes | Impossible nativement |
| Appels parallèles | Non | Oui via max_tool_threads |
| Exécution de code | Requis (executor) | Aucune exécution |
| Nombre d'étapes (benchmark) | Jusqu'à 30% moins (benchmarks HF) | Baseline |
| Risque sécurité | Exécution arbitraire possible | Pas d'exécution de code |
| Idéal pour | Analyse de données, automation, science | Chatbots, RAG, APIs structurées |
6. Définir des outils
Un outil smolagents est une fonction Python décorée — le framework génère automatiquement le schéma JSON depuis les type hints et la docstring.
from smolagents import tool
@tool
def get_stock_price(ticker: str, currency: str = "EUR") -> float:
"""
Retourne le prix actuel d'une action boursière.
Args:
ticker: Le symbole boursier (ex: AAPL, NVDA, MSFT).
currency: La devise de retour (EUR, USD, GBP).
"""
# type hints + docstring Args: obligatoires
return fetch_price_api(ticker, currency)
# Vérification de la définition générée
print(get_stock_price.inputs)
# {'ticker': {'type': 'string', 'description': 'Le symbole boursier (ex: AAPL, NVDA, MSFT).'},
# 'currency': {'type': 'string', 'description': 'La devise de retour (EUR, USD, GBP).'}}
Classe Tool (pour les outils lourds)
from smolagents import Tool
class EmbeddingSearchTool(Tool):
name = "embedding_search"
description = "Recherche sémantique dans la base de connaissances."
inputs = {
"query": {"type": "string", "description": "La requête de recherche."},
"k": {"type": "integer", "description": "Nombre de résultats à retourner."}
}
output_type = "string"
def setup(self):
# Appelé au premier usage — lazy init des ressources lourdes
self.index = load_vector_index("knowledge_base.faiss")
self.embedder = load_embedder()
def forward(self, query: str, k: int = 3) -> str:
vec = self.embedder.encode(query)
results = self.index.search(vec, k)
return "\n".join(results)
Outils depuis le Hub et MCP
from smolagents import ToolCollection
from mcp import StdioServerParameters
# Charger des outils depuis Hugging Face Hub
coll = ToolCollection.from_hub("huggingface-tools/diffusion-tools-6")
agent = CodeAgent(tools=[*coll.tools], model=model)
# Charger des outils depuis un serveur MCP (stdio)
server = StdioServerParameters(command="uvx", args=["pubmedmcp@0.1.3"])
with ToolCollection.from_mcp(server, trust_remote_code=True) as tools:
agent = CodeAgent(tools=[*tools.tools], model=model)
result = agent.run("Résume les 3 dernières publications sur les vaccins ARNm")
# Depuis un serveur MCP HTTP Streamable (production)
with ToolCollection.from_mcp({"url": "http://mcp-service:8000/mcp", "transport": "streamable-http"}) as tools:
agent = CodeAgent(tools=[*tools.tools], model=model)
7. Intégration Ollama
smolagents n'a pas de classe OllamaModel native — c'est une erreur fréquente dans les blogs et tutoriels. L'intégration Ollama passe par LiteLLMModel avec le préfixe ollama_chat/.
from smolagents import CodeAgent, LiteLLMModel, DuckDuckGoSearchTool
from smolagents.tools import PythonInterpreterTool
model = LiteLLMModel(
model_id="ollama_chat/qwen3:14b",
api_base="http://localhost:11434",
# api_key non requis pour Ollama local
num_ctx=8192, # CRITIQUE — défaut Ollama = 2048, insuffisant pour les agents
)
agent = CodeAgent(
tools=[DuckDuckGoSearchTool(), PythonInterpreterTool()],
model=model,
additional_authorized_imports=["pandas", "numpy", "matplotlib"],
max_steps=8,
)
result = agent.run(
"Télécharge les données EUR/USD des 30 derniers jours et calcule la volatilité"
)
print(result)
num_ctx=8192 — le paramètre critique avec Ollama
Le contexte par défaut d'Ollama est 2 048 tokens — largement insuffisant pour un agent qui accumule l'historique des steps. Avec num_ctx=8192 minimum (32 768 pour les tâches longues), les agents fonctionnent correctement. Sans ce paramètre, l'agent "oublie" ses steps précédents et boucle.
Pour aller plus loin avec les modèles locaux, voir notre guide Ollama Python complet et le guide RAG avec Ollama et ChromaDB.
LiteLLMModel active automatiquement flatten_messages_as_text=True pour les identifiants commençant par "ollama" — cela gère les limitations de format de certains modèles Ollama qui ne supportent pas nativement les rôles multiples dans l'historique.
8. Exécution sécurisée du code
L'exécution de code généré par un LLM est le risque principal du paradigme code-first. smolagents offre une progression en 3 niveaux.
Niveau 1 — LocalPythonExecutor (défaut)
Le LocalPythonExecutor ne fait pas d'eval() ou d'exec(). Il parse l'AST (Abstract Syntax Tree) et évalue chaque nœud indépendamment — ce qui permet de compter les opérations, intercepter les imports et détecter les abus.
Protections actives par défaut :
- Imports interdits sauf liste blanche explicite (
additional_authorized_imports) - Accès aux sous-modules bloqué —
random._os.systeméchoue même sirandomest autorisé - Compteur d'opérations maximum (1 000 000 itérations de boucle)
- Liste blanche par défaut :
statistics,itertools,math,random,re,datetime,collections, etc.
Limite du sandbox local
Aucun sandbox local ne peut être 100% sûr. Exemple documenté par Hugging Face : si PIL est autorisé, un LLM mal aligné pourrait générer des milliers d'images pour saturer le disque. Pour du code non fiable en production, utiliser les sandboxes distants.
Niveau 2 — Sandboxes distants
# E2B cloud sandbox — une ligne de changement
with CodeAgent(model=model, tools=tools, executor_type="e2b") as agent:
agent.run("Analyse ce CSV et génère un graphique")
# Le code s'exécute dans une VM isolée, pas sur le serveur local
# Docker local (nécessite Docker installé)
agent = CodeAgent(model=model, tools=tools, executor_type="docker")
# Modal cloud (scale-to-zero automatique)
agent = CodeAgent(model=model, tools=tools, executor_type="modal")
# WebAssembly via Pyodide (zéro installation cloud, via Deno)
agent = CodeAgent(model=model, tools=tools, executor_type="wasm")
Limite des sandboxes distants avec multi-agents
L'approche sandbox simple (executor_type seul) ne supporte pas le multi-agents car les clés API ne sont pas transférées dans le sandbox. Pour le multi-agents sécurisé, il faut déployer l'ensemble du système agent dans E2B ou Docker — plus complexe mais complet.
9. Multi-agents avec smolagents
Le système multi-agents de smolagents repose sur un principe élégant : tout agent avec name et description peut être passé dans managed_agents d'un agent parent. Il n'existe pas de classe ManagedAgent séparée dans l'API actuelle.
from smolagents import CodeAgent, ToolCallingAgent, InferenceClientModel, WebSearchTool
model = InferenceClientModel()
# Agent spécialisé (ToolCallingAgent pour les recherches JSON-structurées)
web_agent = ToolCallingAgent(
tools=[WebSearchTool()],
model=model,
name="web_search_agent", # Requis
description="Effectue des recherches web. Passe la requête complète en argument.",
max_steps=5,
)
# Agent spécialisé analyse données
data_agent = CodeAgent(
tools=[],
model=model,
name="data_analyst",
description="Analyse des données, calcule des statistiques, génère des graphiques.",
additional_authorized_imports=["pandas", "matplotlib", "scipy"],
)
# Agent manager — voit web_agent et data_agent comme des fonctions Python
manager = CodeAgent(
tools=[],
model=model,
managed_agents=[web_agent, data_agent],
planning_interval=2, # Replanning tous les 2 steps
)
manager.run("Collecte les prix des actions NVDA sur 30 jours et analyse leur volatilité")
Du point de vue du LLM manager, appeler un agent géré est strictement identique à appeler un outil. Dans le system prompt du manager, les agents gérés apparaissent comme des fonctions Python :
def web_search_agent(task: str, additional_args: dict[str, Any]) -> str:
"""Effectue des recherches web. Passe la requête complète en argument.
Args:
task: Description détaillée de la tâche.
additional_args: Variables supplémentaires (images, DataFrames...).
"""
10. Système de mémoire — ActionStep et AgentMemory
Chaque interaction est enregistrée dans un objet ActionStep. La mémoire complète de l'agent est accessible via agent.memory.
# Après un run, inspecter la mémoire
result = agent.run("Analyse ce fichier CSV", reset=False)
for step in agent.memory.steps:
if hasattr(step, 'code_action'):
print(f"Step {step.step_number} — Code généré :")
print(step.code_action)
print(f"Observation : {step.observations}")
print(f"Tokens : {step.token_usage}")
# Récupérer tout le code généré en un seul script
full_script = agent.memory.return_full_code()
# Rejouer la session en mode debug
agent.memory.replay(agent.logger, detailed=True)
# Continuer une conversation (reset=False conserve l'historique)
result2 = agent.run("Maintenant génère un graphique de ce résultat", reset=False)
11. Interface Gradio en une ligne
from smolagents import CodeAgent, InferenceClientModel, GradioUI
agent = CodeAgent(
tools=[DuckDuckGoSearchTool()],
model=InferenceClientModel(),
add_base_tools=True,
)
# Interface chat complète avec upload de fichiers
GradioUI(
agent,
file_upload_folder="uploads", # Dossier pour les fichiers uploadés
reset_agent_memory=False, # Conserve l'historique entre messages
).launch(share=True) # share=True → URL publique temporaire
# Publier sur Hugging Face Hub comme Space
agent.push_to_hub("user/mon-agent-ia")
agent2 = CodeAgent.from_hub("user/mon-agent-ia", trust_remote_code=True)
12. smolagents vs Pydantic AI vs LangGraph — comparaison complète
| Critère | smolagents | Pydantic AI | LangGraph |
|---|---|---|---|
| Paradigme | Code-first (Python exec) | Type-safe (DI + Pydantic) | Graph stateful |
| Type safety | Non | Native, compilateur | Partielle |
| Structured output | Via tool appel | 3 modes, auto-retry | Via Pydantic |
| État agent | Variables Python stateful | Message history | State dict partagé |
| Cycles / boucles | Natif (code Python) | Via output_retries | Natif (graphe) |
| Human-in-the-loop | Via step_callbacks | requires_approval | Natif (interrupt()) |
| Testing | Mocks manuels | TestModel, FunctionModel | Mocks manuels |
| Observabilité | AgentMemory + Logfire optionnel | OpenTelemetry standard | LangSmith |
| Ollama | Via LiteLLMModel | Via OllamaModel + OllamaProvider | Via OllamaChatModel |
| Interface UI | GradioUI natif | agent.to_web() | Non intégré |
| Lines of code core | ~1 000 | ~15 000 | ~8 000 |
| Courbe d'entrée | Facile | Raide (generics) | Intermédiaire |
13. Quand utiliser quoi
smolagents
- Automation de tâches exploratoires
- Analyse de données ad hoc
- Agents scientifiques / calcul
- Prototype rapide
- Intégration Hugging Face Hub
- Interface Gradio rapide
Pydantic AI
- APIs de production (FastAPI)
- Codebase grande équipe
- Structured output fiable
- Tests unitaires déterministes
- DI avec DB/HTTP pools
- Type checking CI/CD
LangGraph
- Workflows multi-étapes complexes
- État persistant cross-sessions
- Human-in-the-loop frequent
- Routing conditionnel complexe
- Self-RAG avec rebouclage
- Checkpointing / reprise
Architecture hybride — le pattern 2026
Une combinaison qui émerge sur les projets production : smolagents pour les agents exploratoires et l'analyse de données (code-first, rapide à prototyper) + Pydantic AI pour les micro-services IA (type safety, DI, tests) + LangGraph pour l'orchestration globale (routing, état, human-in-the-loop). Les trois s'appellent mutuellement — un nœud LangGraph peut instancier un CodeAgent smolagents.
14. Exemple complet — agent d'analyse CSV avec smolagents + Ollama
Un cas concret : un agent qui lit un fichier CSV, calcule des statistiques descriptives, détecte les valeurs aberrantes et produit une conclusion textuelle — entièrement local avec Ollama.
from smolagents import CodeAgent, LiteLLMModel, tool
import os
model = LiteLLMModel(
model_id="ollama_chat/qwen3:14b",
api_base="http://localhost:11434",
num_ctx=16384,
)
@tool
def read_csv_summary(filepath: str) -> str:
"""
Lit un fichier CSV et retourne un résumé de ses colonnes et premières lignes.
Args:
filepath: Chemin absolu vers le fichier CSV à analyser.
"""
import pandas as pd
df = pd.read_csv(filepath)
return f"Shape: {df.shape}\nColumns: {list(df.columns)}\nHead:\n{df.head(3).to_string()}\nDtypes:\n{df.dtypes.to_string()}"
agent = CodeAgent(
tools=[read_csv_summary],
model=model,
additional_authorized_imports=["pandas", "numpy", "scipy"],
max_steps=6,
)
result = agent.run(
"""Analyse le fichier /data/ventes_2025.csv :
1. Calcule les statistiques descriptives des colonnes numériques
2. Détecte les valeurs aberrantes (IQR method)
3. Identifie les 3 produits avec la plus forte variance de ventes
4. Rédige une conclusion en 3 points"""
)
print(result)
# L'agent génère et exécute du code pandas/numpy à chaque étape
# Les variables (df, stats, outliers...) persistent entre les steps
Ce qui se passe en interne : le CodeAgent génère du pandas au step 1 (load + describe), réutilise le DataFrame au step 2 (IQR outliers), puis au step 3 (variance par produit), et synthétise au step 4 avec final_answer(). Les variables df, stats, etc. sont disponibles tout au long du run — c'est l'avantage central du paradigme code-first sur le JSON tool-calling.
15. Limites de smolagents
Pour un article honnête, les limites méritent d'être nommées :
- Pas de type safety — les bugs se manifestent au runtime, pas à la compilation. Sur un codebase d'équipe, c'est un désavantage réel face à Pydantic AI.
- Sécurité d'exécution — le sandbox local n'est pas suffisant pour du code non fiable. En production multi-tenant, un sandbox cloud (E2B, Docker) est obligatoire.
- Pas de state management avancé — l'état d'un CodeAgent est dans les variables Python de l'exécuteur, pas dans un graphe structuré. Pas de checkpointing, pas de reprise sur panne.
- Appels parallèles limités — CodeAgent exécute les steps séquentiellement. Pour le parallélisme, ToolCallingAgent avec
max_tool_threadsest nécessaire. - Dépendance à la qualité du LLM — générer du bon code Python à chaque step requiert un modèle capable. Avec les petits modèles Ollama (<7B), les résultats sont variables.
- Écosystème plus restreint — moins d'intégrations natives que LangChain (1000+). Les connecteurs DB, API et services tiers doivent souvent être écrits manuellement.
16. FAQ
Conclusion
smolagents v1.24 est le choix le plus direct pour des agents code-first : minimal, auditable, intégré à l'écosystème Hugging Face. Pydantic AI est la référence pour les APIs de production qui exigent type safety et testabilité. LangGraph pour les workflows avec état persistant et orchestration complexe.
Le vrai take-away 2026 : ces trois frameworks ne se remplacent pas — ils se complètent. L'architecture hybride (smolagents pour l'exploration + Pydantic AI pour la prod + LangGraph pour l'orchestration) est ce qui émerge sur les projets sérieux.