Comment un agent utilise les outils (Bases) — Python (implémentation complète)

Exemple runnable complet de tool calling avec allowlist, exécution d'outils et boucle d'agent.
Sur cette page
  1. Ce que cet exemple démontre
  2. Structure du projet
  3. Comment lancer
  4. Code
  5. tools.py — outils réellement exécutés
  6. executor.py — frontière de sécurité et exécution
  7. llm.py — appel modèle et description des outils disponibles
  8. main.py — boucle d'agent (model → tool → model)
  9. requirements.txt
  10. Exemple de sortie
  11. Pourquoi c'est un agent, et pas un seul model call
  12. Ce qu'il faut modifier dans cet exemple
  13. Code complet sur GitHub

Ceci est une implémentation complète de l'exemple de l'article Comment un agent utilise les outils (Bases).

Si tu n'as pas encore lu l'article, commence par lui. Ici, le focus est uniquement sur le code.


Ce que cet exemple démontre

  • Comment LLM décide quand il faut appeler un outil
  • Comment le système vérifie si l'outil est autorisé (allowlist)
  • Comment exécuter un appel d'outil et renvoyer le résultat au modèle
  • Comment l'agent termine la boucle quand les données sont suffisantes

Structure du projet

TEXT
examples/
└── foundations/
    └── tool-calling-basics/
        └── python/
            ├── main.py           # agent loop
            ├── llm.py            # model call + tool definitions
            ├── executor.py       # allowlist check + tool execution
            ├── tools.py          # tools (business logic)
            └── requirements.txt

Cette séparation est pratique en projet réel : modèle, policy et exécution des outils ne sont pas mélangés dans un seul fichier.


Comment lancer

1. Clone le dépôt et va dans le dossier :

BASH
git clone https://github.com/AgentPatterns-tech/agentpatterns.git
cd examples/foundations/tool-calling-basics/python

2. Installe les dépendances :

BASH
pip install -r requirements.txt

3. Définis la clé API :

BASH
export OPENAI_API_KEY="sk-..."

4. Lance l'exemple :

BASH
python main.py

⚠️ Si tu oublies de définir la clé, l'agent te le dira immédiatement avec une indication sur quoi faire.


Code

tools.py — outils réellement exécutés

PYTHON
from typing import Any

USERS = {
    42: {"id": 42, "name": "Anna", "tier": "pro"},
    7: {"id": 7, "name": "Max", "tier": "free"},
}

BALANCES = {
    42: {"currency": "USD", "value": 128.40},
    7: {"currency": "USD", "value": 0.0},
}


def get_user_profile(user_id: int) -> dict[str, Any]:
    user = USERS.get(user_id)
    if not user:
        return {"error": f"user {user_id} not found"}
    return {"user": user}


def get_user_balance(user_id: int) -> dict[str, Any]:
    balance = BALANCES.get(user_id)
    if not balance:
        return {"error": f"balance for user {user_id} not found"}
    return {"balance": balance}

Le modèle n'a pas d'accès direct aux dictionnaires. Il peut seulement demander à appeler ces fonctions.


executor.py — frontière de sécurité et exécution

PYTHON
import json
from typing import Any

from tools import get_user_balance, get_user_profile

TOOL_REGISTRY = {
    "get_user_profile": get_user_profile,
    "get_user_balance": get_user_balance,
}

ALLOWED_TOOLS = {"get_user_profile", "get_user_balance"}


def execute_tool_call(tool_name: str, arguments_json: str) -> dict[str, Any]:
    if tool_name not in ALLOWED_TOOLS:
        return {"error": f"tool '{tool_name}' is not allowed"}

    tool = TOOL_REGISTRY.get(tool_name)
    if tool is None:
        return {"error": f"tool '{tool_name}' not found"}

    try:
        args = json.loads(arguments_json or "{}")
    except json.JSONDecodeError:
        return {"error": "invalid JSON arguments"}

    try:
        result = tool(**args)
    except TypeError as exc:
        return {"error": f"invalid arguments: {exc}"}

    return {"tool": tool_name, "result": result}

Idée clé ici : même si le modèle demande quelque chose d'étrange, le système exécute uniquement ce qui est explicitement autorisé.


llm.py — appel modèle et description des outils disponibles

PYTHON
import os
from openai import OpenAI

api_key = os.environ.get("OPENAI_API_KEY")

if not api_key:
    raise EnvironmentError(
        "OPENAI_API_KEY is not set.\n"
        "Run: export OPENAI_API_KEY='sk-...'"
    )

client = OpenAI(api_key=api_key)

SYSTEM_PROMPT = """
You are an AI support agent. When you need data, call the available tools.
Once you have enough information, give a short answer.
""".strip()

TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "get_user_profile",
            "description": "Returns user profile by user_id",
            "parameters": {
                "type": "object",
                "properties": {"user_id": {"type": "integer"}},
                "required": ["user_id"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "get_user_balance",
            "description": "Returns user balance by user_id",
            "parameters": {
                "type": "object",
                "properties": {"user_id": {"type": "integer"}},
                "required": ["user_id"],
            },
        },
    },
]


def ask_model(messages: list[dict]):
    completion = client.chat.completions.create(
        model="gpt-4.1-mini",
        messages=[{"role": "system", "content": SYSTEM_PROMPT}, *messages],
        tools=TOOLS,
        tool_choice="auto",
    )
    return completion.choices[0].message

Les outils sont décrits ici en schema. Le modèle voit cette liste et choisit dedans.


main.py — boucle d'agent (model → tool → model)

PYTHON
import json

from executor import execute_tool_call
from llm import ask_model

MAX_STEPS = 6

TASK = "Prepare a short account summary for user_id=42: name, tier, and balance."


def to_assistant_message(message) -> dict:
    tool_calls = []
    for tc in message.tool_calls or []:
        tool_calls.append(
            {
                "id": tc.id,
                "type": "function",
                "function": {
                    "name": tc.function.name,
                    "arguments": tc.function.arguments,
                },
            }
        )
    return {
        "role": "assistant",
        "content": message.content or "",
        "tool_calls": tool_calls,
    }


def run():
    messages: list[dict] = [{"role": "user", "content": TASK}]

    for step in range(1, MAX_STEPS + 1):
        print(f"\n=== STEP {step} ===")
        assistant = ask_model(messages)
        messages.append(to_assistant_message(assistant))

        text = assistant.content or ""
        if text.strip():
            print(f"Assistant: {text.strip()}")

        tool_calls = assistant.tool_calls or []
        if not tool_calls:
            print("\nDone: model finished without a new tool call.")
            return

        for tc in tool_calls:
            print(f"Tool call: {tc.function.name}({tc.function.arguments})")
            execution = execute_tool_call(
                tool_name=tc.function.name,
                arguments_json=tc.function.arguments,
            )
            print(f"Tool result: {execution}")

            messages.append(
                {
                    "role": "tool",
                    "tool_call_id": tc.id,
                    "content": json.dumps(execution, ensure_ascii=False),
                }
            )

    print("\nStop: MAX_STEPS reached.")


if __name__ == "__main__":
    run()

C'est la boucle classique : le modèle demande un outil, le système exécute, le résultat revient au modèle, puis la décision suivante.


requirements.txt

TEXT
openai>=1.0.0

Exemple de sortie

TEXT
=== STEP 1 ===
Tool call: get_user_profile({"user_id": 42})
Tool result: {'tool': 'get_user_profile', 'result': {'user': {'id': 42, 'name': 'Anna', 'tier': 'pro'}}}

=== STEP 2 ===
Tool call: get_user_balance({"user_id": 42})
Tool result: {'tool': 'get_user_balance', 'result': {'balance': {'currency': 'USD', 'value': 128.4}}}

=== STEP 3 ===
Assistant: User Anna is a pro tier member with a current balance of 128.4 USD.

Done: model finished without a new tool call.

Pourquoi c'est un agent, et pas un seul model call

Un model callAgent avec tools
Travaille uniquement avec le texte déjà disponible
Peut demander de nouvelles données via un tool
A une frontière d'exécution explicite (allowlist)
Fait plusieurs étapes vers une réponse finale

Ce qu'il faut modifier dans cet exemple

  • Bloque get_user_balance dans ALLOWED_TOOLS — que renverra l'agent à l'utilisateur ?
  • Remplace user_id=42 par user_id=999 — comment l'agent traitera-t-il l'erreur ?
  • Ajoute un outil get_user_orders mais ne l'ajoute pas dans ALLOWED_TOOLS — le modèle va-t-il essayer de l'appeler ?
  • Ajoute une limite du nombre de tool calls séparément de MAX_STEPS

Code complet sur GitHub

Le dépôt contient la version complète de cette démo : avec ALLOWED_TOOLS, boucle d'étapes et gestion des erreurs. Si tu veux la lancer rapidement ou parcourir le code ligne par ligne, ouvre l'exemple complet.

Voir le code complet sur GitHub ↗
⏱️ 6 min de lectureMis à jour Mars, 2026Difficulté: ★☆☆
Intégré : contrôle en productionOnceOnly
Ajoutez des garde-fous aux agents tool-calling
Livrez ce pattern avec de la gouvernance :
  • Budgets (steps / plafonds de coût)
  • Permissions outils (allowlist / blocklist)
  • Kill switch & arrêt incident
  • Idempotence & déduplication
  • Audit logs & traçabilité
Mention intégrée : OnceOnly est une couche de contrôle pour des systèmes d’agents en prod.
Auteur

Cette documentation est organisée et maintenue par des ingénieurs qui déploient des agents IA en production.

Le contenu est assisté par l’IA, avec une responsabilité éditoriale humaine quant à l’exactitude, la clarté et la pertinence en production.

Les patterns et recommandations s’appuient sur des post-mortems, des modes de défaillance et des incidents opérationnels dans des systèmes déployés, notamment lors du développement et de l’exploitation d’une infrastructure de gouvernance pour les agents chez OnceOnly.