- Qu'est-ce qui fait d'un système un agent IA
- Étape 1 : Planification — la couche de raisonnement
- Étape 2 : Utilisation d'outils — connecter le modèle au monde
- Étape 3 : Exécution de code — exécuter du code en toute sécurité
- Étape 4 : Évaluation — savoir quand l'agent a raison
- Choisir un modèle pour le raisonnement de l'agent
- Assemblage — un agent fonctionnel minimal
- FAQ
- Articles recommandés
Construire un agent IA commence par une idée simple : donner à un modèle la capacité d’effectuer des actions, d’observer les résultats et de boucler. Le modèle raisonne sur un objectif, décide de la prochaine étape, appelle un outil, reçoit un résultat et continue jusqu’à ce que la tâche soit terminée. Cette boucle lire-évaluer-agir est le cœur de tout agent que vous construirez, qu’il s’agisse d’un simple appelant d’outil en une étape ou d’un système multi-étapes qui écrit du code, l’exécute et corrige ses propres erreurs.
Ce tutoriel parcourt les quatre composants qui font fonctionner un agent : la planification, l’utilisation d’outils, l’exécution de code et l’évaluation. Vous en ressortirez avec un modèle mental opérationnel et une implémentation minimale que vous pouvez exécuter dès aujourd’hui.
Qu’est-ce qui fait d’un système un agent IA
Un agent IA diffère d’un appel LLM de base d’une manière cruciale : il effectue des actions et observe leurs résultats.
Une complétion de chat simple produit du texte. Un agent utilise ce texte pour appeler une fonction, interroger une API, exécuter une commande ou lire un fichier — et réinjecte le résultat comme entrée pour l’étape suivante. Cette boucle de rétroaction est ce qui rend les agents utiles pour des tâches qui ne peuvent pas être résolues en une seule réponse.
Trois capacités définissent un agent :
- Appel d’outil — invoquer des fonctions définies par leur nom avec des arguments structurés
- Accumulation de contexte — transporter les résultats des outils au fur et à mesure que la conversation s’allonge
- Boucle orientée objectif — continuer jusqu’à ce qu’une condition d’arrêt soit satisfaite, pas seulement après une réponse
Si un système possède les trois, vous construisez un agent. Si l’accumulation de contexte ou la boucle est absente, il s’agit d’un chatbot augmenté par outil.
La même architecture gère un large éventail de tâches : des agents de recherche qui cherchent et synthétisent, des agents de codage qui écrivent et exécutent du code, des agents de données qui interrogent des bases de données et génèrent des rapports. Le schéma d’implémentation est presque identique dans chaque cas.
Étape 1 : Planification — la couche de raisonnement
La couche de planification est l’endroit où le modèle décide quoi faire ensuite. Vous n’avez pas besoin d’un modèle de planification séparé — le même modèle qui génère du texte peut raisonner sur un objectif et produire une action structurée (un appel d’outil) à chaque étape.
Le prompt système fait l’essentiel du travail. Un bon prompt de planification indique au modèle :
- Quel objectif il poursuit
- Quels outils sont disponibles et ce que chacun fait
- Quel format utiliser lors de l’appel à un outil
- Quand s’arrêter (la condition de fin)
Avec l’API LLM de Novita — qui est compatible OpenAI — cela ressemble à :
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.novita.ai/v3/openai",
api_key=os.environ["NOVITA_API_KEY"],
)
tools = [
{
"type": "function",
"function": {
"name": "run_code",
"description": "Execute Python code and return stdout/stderr.",
"parameters": {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "Python code to execute"
}
},
"required": ["code"]
}
}
}
]
messages = [
{
"role": "system",
"content": "You are a coding assistant. Use the run_code tool to execute Python and solve tasks. Stop when you have a final answer."
},
{
"role": "user",
"content": "Find all prime numbers under 50."
}
]
response = client.chat.completions.create(
model="meta-llama/llama-3.1-8b-instruct",
messages=messages,
tools=tools,
tool_choice="auto",
)
Le modèle renvoie un appel d’outil — un objet JSON avec le nom de la fonction et les arguments — au lieu d’une réponse textuelle simple. Votre code extrait cela, exécute la fonction et reboucle avec le résultat ajouté à messages.
Étape 2 : Utilisation d’outils — connecter le modèle au monde
Les outils sont ce que les agents font réellement. Sans eux, le modèle ne peut que produire du texte. Avec eux, il peut lire des fichiers, appeler des API, exécuter du code, rechercher dans des bases de données ou déclencher n’importe quelle fonction que vous définissez.
Définir les outils
Chaque outil est un schéma JSON décrivant une fonction. Le modèle lit ce schéma pour formater correctement son appel. Quelques règles pratiques :
- Gardez les descriptions courtes et précises. Le modèle les lit à chaque étape. Des descriptions verbeuses gaspillent des tokens.
- Faites correspondre les noms des paramètres à ce que la fonction accepte réellement. Les décalages entre le schéma et votre implémentation provoquent des échecs silencieux.
- Retournez une sortie structurée. Si votre outil renvoie du HTML brut ou de la prose non formatée, le modèle gaspille des tokens à l’analyser. Renvoyez des chaînes propres ou du JSON avec des étiquettes claires.
La boucle d’appel d’outil
Après que le modèle a effectué un appel d’outil, votre code :
- Analyse le nom de l’outil et les arguments à partir de la réponse
- Recherche la fonction Python correspondante
- L’appelle avec les arguments analysés
- Ajoute le résultat à
messagesavecrole: "tool" - Effectue une autre requête de complétion
La boucle continue jusqu’à ce que le modèle renvoie un message simple sans appel d’outil (la condition de fin).
import json
def run_agent_loop(client, model, messages, tools, tool_fns, max_steps=10):
for _ in range(max_steps):
response = client.chat.completions.create(
model=model,
messages=messages,
tools=tools,
tool_choice="auto",
)
choice = response.choices[0]
# Terminé — pas d'appel d'outil
if choice.finish_reason == "stop":
return choice.message.content
# Exécuter l'appel d'outil
tool_call = choice.message.tool_calls[0]
fn_name = tool_call.function.name
fn_args = json.loads(tool_call.function.arguments)
result = tool_fns[fn_name](**fn_args)
# Ajouter à la fois l'appel d'outil du modèle et le résultat
messages.append(choice.message)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": str(result),
})
return "Max steps reached without a final answer."
Ceci est la boucle d’agent complète. La garde max_steps empêche les boucles incontrôlées — si le modèle reste bloqué à appeler le même outil de manière répétée, la boucle se termine plutôt que de s’exécuter indéfiniment. Tout le reste — orchestration multi-outils, mémoire, streaming — se construit sur ce modèle.
Modèles d’outils courants
Accès aux fichiers : Lire et écrire des fichiers dans le répertoire de travail de l’agent. Renvoyer le contenu des fichiers sous forme de chaînes.
Recherche web : Renvoyer les résultats de recherche sous forme de liste d’objets {title, url, snippet}. Garder les extraits courts.
Exécution de code : Exécuter du code dans un environnement isolé et renvoyer stdout/stderr (couvert dans la section suivante).
Recherche de données structurées : Interroger une base de données ou une API et renvoyer les enregistrements au format JSON. Le modèle gère la synthèse.
Chaque outil doit faire une seule chose et renvoyer une sortie déterministe pour les mêmes entrées. Les agents qui essaient de mettre trop de logique dans un seul outil deviennent plus difficiles à déboguer et à évaluer.
Étape 3 : Exécution de code — exécuter du code en toute sécurité
Si votre agent génère et exécute du code, vous avez besoin d’un environnement isolé. Exécuter du code généré par un modèle directement sur votre machine est un risque de sécurité : le modèle pourrait produire des commandes shell destructrices, des boucles infinies ou du code qui lit des fichiers sensibles auxquels il ne devrait pas accéder.
L’approche standard est un bac à sable — un environnement Linux isolé et éphémère dans lequel l’agent peut exécuter du code librement sans affecter le système hôte. Le bac à sable d’agent de Novita est conçu exactement pour ce cas d’utilisation : un environnement d’exécution cloud qui se lance en moins de 200 ms, exécute tout code produit par l’agent et supprime l’environnement lorsque la session se termine.
Voici comment l’outil run_code de la section planification serait implémenté en utilisant le bac à sable :
from novita_sandbox.code_interpreter import Sandbox
sandbox = Sandbox.create()
def run_code(code: str) -> str:
result = sandbox.run_code(code)
output = ""
if result.logs.stdout:
output += "\n".join(result.logs.stdout)
if result.logs.stderr:
output += "\nSTDERR: " + "\n".join(result.logs.stderr)
return output or "(no output)"
Lorsque l’agent appelle run_code, cette fonction exécute le code dans l’environnement isolé et renvoie stdout/stderr sous forme de chaîne. L’agent voit le résultat et décide de la prochaine étape — que ce soit pour corriger un bug, continuer avec la sortie ou déclarer la tâche terminée.
Cette architecture sépare proprement la couche de raisonnement de la couche d’exécution. Le LLM planifie et appelle des outils ; le bac à sable exécute du code sans exposer quoi que ce soit en dehors du conteneur.
Ce que le bac à sable gère
- Commandes shell (
bash,git,python,node) - Opérations sur le système de fichiers dans le bac à sable
- Requêtes réseau depuis l’intérieur du conteneur
- Tâches d’arrière-plan longues avec récupération asynchrone des résultats
- Pause et reprise pour les workflows multi-sessions
Ce qu’il ne permet pas : l’accès au système hôte, à d’autres bacs à sable ou aux identifiants que vous n’avez pas explicitement transmis. Ce confinement est ce qui rend sûr de laisser le modèle écrire et exécuter du code arbitraire.
Étape 4 : Évaluation — savoir quand l’agent a raison
L’évaluation est l’étape la plus souvent ignorée dans le développement d’agents. Un agent qui produit une sortie n’est pas la même chose qu’un agent qui produit une sortie correcte.
Trois niveaux d’évaluation
1. Vérifications déterministes
Pour les tâches avec des réponses correctes connues — analyser une date, calculer une valeur, extraire un champ — écrivez des tests unitaires contre la sortie de l’agent. Ceux-ci sont peu coûteux à exécuter et détectent rapidement les régressions.
2. Vérifications basées sur l’exécution
Pour les agents de codage, le signal le plus fiable est : le code s’exécute-t-il réellement et produit-il le bon résultat ? Exécutez la sortie de l’agent dans le bac à sable et comparez stdout aux valeurs attendues. C’est plus fort que de comparer le texte du code car cela teste le comportement, pas la syntaxe.
def evaluate_code_task(agent_code: str, expected_stdout: str) -> bool:
result = run_code(agent_code)
return result.strip() == expected_stdout.strip()
3. LLM comme juge
Pour les tâches ouvertes — rédiger un résumé, répondre à une question de recherche — utilisez un deuxième appel LLM pour noter la sortie par rapport à une grille d’évaluation. Gardez la grille spécifique : “La réponse incluait-elle un exemple de code fonctionnel ?” est meilleur que “La réponse est-elle bonne ?” Des grilles vagues produisent des scores incohérents.
Suivi des modes de défaillance
Les agents échouent selon un ensemble récurrent de modèles :
| Échec | Cause | Correctif |
|---|---|---|
| Boucle d’appel d’outil | Le modèle continue d’appeler le même outil | Définir max_steps ; journaliser les étapes intermédiaires |
| Décalage d’argument | Le schéma ne correspond pas à l’implémentation | Valider les arguments de l’outil avant d’appeler |
| Débordement de contexte | La session dépasse la limite de contexte du modèle | Résumer ou tronquer les résultats d’outils plus anciens |
| Nom d’outil halluciné | Le modèle invente un outil qui n’existe pas | Utiliser une liste d’outils stricte avec tool_choice: "auto" |
L’évaluation n’est pas une phase séparée à la fin — elle fait partie du développement. Chaque fois que vous étendez les capacités d’un agent, écrivez au moins un test qui détecterait une régression dans le nouveau comportement.
Choisir un modèle pour le raisonnement de l’agent
La plupart des tutoriels utilisent par défaut des modèles closed-source pour le raisonnement de l’agent. Cela fonctionne bien pour le prototypage, mais en production — où un agent peut effectuer 20 appels LLM par tâche — le coût du modèle et les limites de débit deviennent des contraintes réelles.
Les modèles open-source hébergés via l’API LLM de Novita égalent de plus en plus les performances closed-source sur les tâches dont les agents ont réellement besoin : appel de fonction, sortie structurée et raisonnement multi-étapes. Des modèles comme Llama 3.3 70B et DeepSeek-V3 se classent au niveau GPT-4 sur le codage et le suivi d’instructions tout en étant exécutés à une fraction du coût. Pour les charges de travail agentiques à haut débit, cette différence s’additionne rapidement.
L’API Novita est entièrement compatible OpenAI, donc passer d’un modèle closed-source à un modèle open-source est un changement d’une seule ligne :
# Avant
model = "gpt-4o"
# Après — même contrat API, coût inférieur
model = "meta-llama/llama-3.3-70b-instruct"
Pour les boucles d’agent sensibles à la latence, des modèles plus petits comme meta-llama/llama-3.1-8b-instruct répondent plus rapidement et coûtent moins cher par étape, ce qui compte lorsque chaque tâche utilisateur implique de nombreux appels séquentiels. Un modèle pratique : utiliser un modèle plus rapide et plus petit pour les étapes de routage et de classification, et un modèle plus grand pour les étapes de raisonnement complexes qui nécessitent plus de capacité.
La bibliothèque de modèles Novita comprend également de bonnes options pour des sous-tâches agentiques spécifiques — des modèles de vision pour les résultats d’outils basés sur des images, des modèles d’embedding pour la recherche, et des modèles spécialisés dans le code pour les agents de codage — tous accessibles depuis le même point d’accès API.
Assemblage — un agent fonctionnel minimal
Voici un agent complet qui prend un problème mathématique, génère du code Python pour le résoudre, l’exécute dans un bac à sable et renvoie le résultat vérifié :
import os
import json
from openai import OpenAI
from novita_sandbox.code_interpreter import Sandbox
client = OpenAI(
base_url="https://api.novita.ai/v3/openai",
api_key=os.environ["NOVITA_API_KEY"],
)
sandbox = Sandbox.create()
def run_code(code: str) -> str:
result = sandbox.run_code(code)
stdout = "\n".join(result.logs.stdout)
stderr = "\n".join(result.logs.stderr)
return stdout or stderr or "(no output)"
tool_fns = {"run_code": run_code}
tools = [
{
"type": "function",
"function": {
"name": "run_code",
"description": "Execute Python code. Returns stdout or stderr.",
"parameters": {
"type": "object",
"properties": {
"code": {"type": "string"}
},
"required": ["code"]
}
}
}
]
messages = [
{
"role": "system",
"content": "Solve the task by writing and running Python code. Use run_code to execute it. Stop when you have a verified answer."
},
{
"role": "user",
"content": "What is the sum of all even Fibonacci numbers under 1,000,000?"
}
]
for _ in range(10):
response = client.chat.completions.create(
model="meta-llama/llama-3.1-8b-instruct",
messages=messages,
tools=tools,
tool_choice="auto",
)
choice = response.choices[0]
if choice.finish_reason == "stop":
print(choice.message.content)
break
tool_call = choice.message.tool_calls[0]
fn_args = json.loads(tool_call.function.arguments)
result = tool_fns[tool_call.function.name](**fn_args)
messages.append(choice.message)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result,
})
sandbox.kill()
Ceci couvre la boucle d’agent complète en environ 50 lignes : planification, appel d’outil, exécution, accumulation de contexte, garde de nombre d’étapes maximum et nettoyage propre du bac à sable. L’agent écrit du code, l’exécute, voit la sortie et soit continue, soit donne une réponse finale.
Pour l’étendre : ajoutez plus d’outils à tool_fns et tools, remplacez par un modèle plus grand pour les tâches plus difficiles, ou remplacez run_code du bac à sable par toute autre fonction — une recherche web, une requête de base de données, un appel API. La boucle reste la même.
FAQ
Quelle est la différence entre un agent IA et un chatbot ?
Un chatbot génère des réponses textuelles. Un agent effectue des actions — appeler des outils, exécuter du code, interroger des API — et utilise les résultats pour continuer à raisonner vers un objectif. La présence d’appels d’outils et d’une boucle de rétroaction est ce qui fait d’un système un agent.
Ai-je besoin d’un framework comme LangChain pour construire un agent IA ?
Non. La boucle centrale de l’agent est suffisamment simple à implémenter directement contre n’importe quelle API compatible OpenAI. Les frameworks apportent une valeur ajoutée pour l’orchestration complexe (systèmes multi-agents, mémoire persistante, sortie en streaming), mais ils ajoutent des couches d’abstraction qui peuvent rendre le débogage plus difficile. Commencez avec l’API brute, puis ajoutez un framework si vous avez réellement besoin de ce qu’il fournit.
Comment empêcher l’agent d’exécuter du code dangereux ?
Utilisez un environnement d’exécution en bac à sable. Le code de l’agent s’exécute à l’intérieur d’un conteneur isolé qui n’a pas accès à votre système hôte ni à d’autres processus. Le bac à sable est supprimé après la fin de la session, donc toute modification effectuée par l’agent est limitée à ce conteneur.
Les modèles open-source peuvent-ils gérer l’appel de fonction de manière fiable ?
Oui — avec des réserves sur la taille du modèle. Llama 3.3 70B, DeepSeek-V3 et Qwen 2.5 72B gèrent tous bien les appels d’outils structurés. Les modèles plus petits (7B–8B) fonctionnent pour des schémas d’outils simples et bien définis mais peuvent avoir du mal avec des instructions ambiguës ou des chaînes de raisonnement complexes en plusieurs étapes. Pour les agents de production, testez sur votre distribution de tâches réelle avant de vous engager sur un modèle.
Combien d’étapes un agent doit-il effectuer par tâche ?
Fixez une limite stricte — 10 à 20 étapes est typique pour la plupart des tâches. Journalisez les étapes intermédiaires afin de pouvoir voir exactement ce que l’agent a fait et déboguer les échecs. Les tâches qui nécessitent constamment plus d’étapes bénéficient généralement d’être divisées en sous-agents ou sous-tâches plutôt que d’étendre la limite de boucle.
