Accueil / Articles

L’IA agente — Permettez à Ollama d’écrire et d’exécuter votre Python, en local

Un générateur de code d’IA agent local : Ollama écrit du Python, le exécute et lit la sortie dans une boucle d’agent. Prérequis, structure du projet et processus de compilation complet.

Éditeur : REACTAPP.TOP11 min de lecture2446 mots

Sources: Agentic AI — Let Ollama Write and Run Your Python, A Local Code Generator

Ce guide reconstitue le parcours allant des matières premières à un système fonctionnel pour : l’IA agente — permettant à Ollama d’écrire et d’exécuter votre code Python, un générateur de code local. L’accent est mis sur des étapes opérationnelles, des vérifications explicites, ainsi que du code que vous pouvez intégrer directement dans un dépôt sans devoir deviner son intention. Pour l’étape d’aperçu, définissez les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans avoir à deviner l’état caché. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Nommez les artefacts, définissez des vérifications de succès, et refusez toute complétion partielle silencieuse.

Ce que nous construisons

Lors de la phase « Ce que nous construisons », notez d’abord les exigences du contrat : entrées requises, signal de succès et conséquences en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le projet passe de la démonstration aux environnements partagés. Enregistrez l’ID de la requête, l’ID du modèle et le temps de latence pour chaque appel. Sans ce suivi, les erreurs intermittentes du fournisseur sont prises pour des bugs de l’application.

Prérequis

Lors de la phase des Prérequis, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du système. Enregistrez l’ID de la requête, l’ID du modèle et le temps de réponse pour chaque appel. Sans ces traces, les erreurs intermittentes du fournisseur sont prises pour des bugs de l’application.

ollama serve
ollama pull llama3.1:8b
pip install ollama

Structure du projet

Lors de la phase d’organisation du projet, notez d’abord les conditions du contrat : entrées requises, signal de succès et comportement en cas d’échec partiel. Cette liste de contrôle permet de garantir l’honnêteté des modifications ultérieures du code. Documentez ensemble le parcours normal et les procédures de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’améliorations ultérieures. Enregistrez l’ID de la demande, l’ID du modèle et le temps de latence à chaque appel. Sans ce suivi, les erreurs intermittentes du fournisseur sont perçues comme des bugs de l’application. Lors de la phase d’organisation du projet, notez d’abord les conditions du contrat : entrées requises, signal de succès et comportement en cas d’échec partiel. Cette liste de contrôle permet de garantir l’honnêteté des modifications ultérieures du code. Considérez cette phase comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définez des vérifications de succès et refusez les terminations partielles silencieuses.

╔═══════════╦════════════════════════════════════════════════════╗
║   File    ║                         Role                       ║
╠═══════════╬════════════════════════════════════════════════════╣
║ main.py   ║ CLI, work-dir prompt / --dir, starts the loop      ║
║ agent.py  ║ Chat loop, tool execution, fake tool-call handling ║
║ tools.py  ║ Read / write / run, plus a soft ~/repo path limit  ║
║ config.py ║ Model name, iteration cap, system prompt           ║
╚═══════════╩════════════════════════════════════════════════════╝

Définir la configuration en premier (config.py)

La première étape de configuration de Config py fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de rollback avant d’élargir le périmètre. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le processus passe de la démo aux environnements partagés. Fixez l’interpréteur et le fichier de verrouillage des dépendances avant d’enseigner la boucle. Les différences entre l’ordinateur portable et les environnements CI constituent la cause la plus fréquente de dysfonctionnement silencieux dans les démos API.

MODEL_NAME = "llama3.1:8b"
MAX_ITERATIONS = 15

SYSTEM_PROMPT = (
    "You are an autonomous local AI assistant with file tools. "
    "When creating or editing code, you MUST call write_local_file with the COMPLETE source in content. "
    "Never write empty files. Never create a placeholder file first. "
    "Always use relative filenames (for example 'calculator.py'). "
    "Do not invent absolute paths like /repo/.... "
    "Never describe or simulate tool calls in text, XML, JSON, or markdown - only use real tool calls. "
    "Keep calling tools until the task is actually done; then reply with a short plain-text summary."
)

Les outils (tools.py)

Les outils py stage fonctionnent le mieux lorsqu’ils sont considérés comme une surface mesurable. Capturez un transcript parfait, un cas d’échec et la note de rollback avant d’élargir le périmètre. Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les flags fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du système. Fixez l’interpréteur ainsi que le fichier de verrouillage des dépendances avant d’utiliser la boucle. Les différences entre l’ordinateur portable et l’environnement CI sont la cause la plus fréquente de dysfonctionnements silencieux dans les démos API.

read_local_file

La phase readlocalfile fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un enregistrement idéal, un cas d’échec et une note de rollback avant d’élargir le périmètre. Documentez ensemble le parcours réussi et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’une mise en forme ultérieure. Fixez l’interpréteur ainsi que le fichier de verrouillage des dépendances avant d’expliquer la boucle. Les différences entre l’ordinateur portable et l’environnement CI sont la cause la plus fréquente de dysfonctionnements silencieux dans les démos API. La phase readlocalfile fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un enregistrement idéal, un cas d’échec et une note de rollback avant d’élargir le périmètre. Considérez cette phase comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définez des vérifications de succès et refusez toute complétion partielle silencieuse.

def read_local_file(filepath: str) -> str:
    """Reads and returns the contents of a local file.

    Args:
        filepath: The path to the file to read. Must be under ~/repo.
    """
    try:
        with open(_resolve_tool_path(filepath), "r") as f:
            return f.read()
    except Exception as e:
        return f"Error reading file: {str(e)}"

write_local_file

Pour l’étape writelocalfile, définissez les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Enregistrez les temps d’exécution ainsi que le coût des jetons ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce du coût évite les factures inattendues lorsque le parcours passe d’un environnement de démonstration à des environnements partagés. Séparez la construction du client de la boucle de messages afin que les fournisseurs puissent être remplacés sans avoir à réécrire la machine d’état de la conversation.

def write_local_file(filepath: str, content: str) -> str:
    """Writes or overwrites text content to a local file.

    Args:
        filepath: Target filename or path. Must be under ~/repo.
        content: The text content to write inside the file. Must not be empty.
    """
    try:
        if content is None or not str(content).strip():
            return (
                "Error writing file: content is empty. "
                "Call write_local_file again with the full file source in content."
            )
        path = _resolve_tool_path(filepath)
        path.parent.mkdir(parents=True, exist_ok=True)
        with open(path, "w") as f:
            f.write(content)
        return f"Successfully wrote file to {path} ({len(content)} bytes)"
    except Exception as e:
        return f"Error writing file: {str(e)}"

run_python_script

Pour l’étape runpythonscript, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du système. Séparez la construction du client de la boucle de messages afin que les fournisseurs puissent être remplacés sans avoir à réécrire la machine à états de la conversation.

def run_python_script(filepath: str) -> str:
    """Executes a local Python file and returns STDOUT and STDERR.

    Args:
        filepath: The path to the Python file to run. Must be under ~/repo.
    """
    try:
        path = _resolve_tool_path(filepath)
        result = subprocess.run(
            ["python3", str(path)],
            capture_output=True,
            text=True,
            timeout=10,
            cwd=str(_work_dir),
        )
        return f"STDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}"
    except Exception as e:
        return f"Execution error: {str(e)}"

Inscription

Pour l’étape d’inscription, définissez les entrées, le responsable de l’étape et les critères de sortie avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Documentez ensemble le parcours normal et les scénarios de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non traités font partie intégrante du produit, et non d’améliorations ultérieures. Séparez la construction des messages par le client du cycle de traitement pour que les fournisseurs puissent être remplacés sans avoir à réécrire la machine à états de la conversation. Pour l’étape d’inscription, définissez les entrées, le responsable de l’étape et les critères de sortie avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définissez des vérifications de succès et refusez les terminations partielles silencieuses.

REGISTERED_TOOLS = {
    "read_local_file": read_local_file,
    "write_local_file": write_local_file,
    "run_python_script": run_python_script,
}

Limits des chemins souples sous ~/repo

Lorsque vous travaillez sur les limits des chemins souples au niveau de l’étape, notez d’abord le contrat : les entrées requises, le signal de succès, ainsi que ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le chemin passe d’un environnement de démonstration à des environnements partagés. Enregistrez l’ID de la requête, l’ID du modèle et le temps de latence pour chaque appel. Sans ce suivi, les erreurs intermittentes du fournisseur ressemblent à des bugs de l’application.

ALLOWED_ROOT = (Path.home() / "repo").resolve()
_work_dir = ALLOWED_ROOT

def _under_repo(path: Path) -> Path:
    resolved = path.expanduser().resolve()
    try:
        resolved.relative_to(ALLOWED_ROOT)
    except ValueError as exc:
        raise PermissionError(
            f"Path '{resolved}' is outside allowed root '{ALLOWED_ROOT}'."
        ) from exc
    return resolved

La boucle de l’agent (agent.py)

Lorsque vous travaillez sur l’étape de l’agent dans le cycle de l’agent, notez d’abord les exigences : entrées requises, signal de succès, et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du système. Enregistrez l’ID de la requête, l’ID du modèle et le temps de réponse pour chaque appel. Sans ces traces, les erreurs intermittentes du fournisseur sont prises pour des bugs de l’application.

# ollama api call
response = ollama.chat(
    model=MODEL_NAME,
    messages=messages,
    tools=tools_list,  # the Python functions from REGISTERED_TOOLS
)
message = response["message"]
messages.append(message)
tool_calls = message.get("tool_calls")

À quoi ressemblent les vraies appels

Lors de l’étape « What a real toolcalls », notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’intégrité des modifications ultérieures du code. Documentez ensemble le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’améliorations ultérieures. Enregistrez l’ID de la demande, l’ID du modèle et le temps de latence pour chaque appel. Sans ces traces, les erreurs intermittentes du fournisseur sont perçues comme des bugs de l’application. Lors de l’étape « What a real toolcalls », notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’intégrité des modifications ultérieures du code. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définites des vérifications de succès et refusez les terminations partielles silencieuses.

{
  "message": {
    "role": "assistant",
    "content": "",
    "tool_calls": [
      {
        "function": {
          "name": "write_local_file",
          "arguments": {
            "filepath": "hello.py",
            "content": "print('hi')\n"
          }
        }
      }
    ]
  }
}
for call in tool_calls:
    func_name = call["function"]["name"]
    func_args = call["function"]["arguments"]

if func_name in REGISTERED_TOOLS:
        observation = REGISTERED_TOOLS[func_name](**func_args)
    else:
        observation = f"Error: Tool '{func_name}' is not registered."
    messages.append({
        "role": "tool",
        "name": func_name,
        "content": str(observation),
    })

À quoi ressemble une fausse tool_call

L’étape « À quoi ressemble une fausse tool_call » fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de rollback avant d’élargir le périmètre. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le processus passe de la démonstration à des environnements partagés. Fixez l’interpréteur et le fichier de verrouillage des dépendances avant d’enseigner la boucle. Les différences entre l’ordinateur portable et les environnements CI constituent la cause la plus fréquente de dysfonctionnement silencieux dans les démonstrations API.

I'll create the file now.
<tool_call>
{"name": "write_local_file", "arguments": {"filepath": "hello.py", "content": "print('hi')"}}
</tool_call>
CONTINUE_NUDGE = (
    "That response was not a valid tool call. "
    "Call write_local_file now with the full file contents. "
    "Do not write empty content. Do not describe the tool call in text."
)

if not tool_calls:
    content = message.get("content") or ""
    if _looks_like_fake_tool_call(content):
        messages.append({"role": "user", "content": CONTINUE_NUDGE})
        continue  # next iteration; yes, this burns a turn
    print(f"\n[Agent Completed Task]:\n{content}")
    return

Élagage du contexte

La phase de suppression du contexte fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un enregistrement idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Conservez les configurations en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du système. Fixez l’interpréteur ainsi que le fichier de verrouillage des dépendances avant d’enseigner la boucle. Les différences entre l’ordinateur portable et l’environnement de test continu sont la cause la plus fréquente de dysfonctionnements silencieux dans les démos API.

def prune_context(messages: list, max_history: int = 6) -> list:
    if len(messages) <= max_history + 2:
        return messages
    return messages[:2] + messages[-max_history:]p

La CLI (main.py)

La phase CLI main py fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un enregistrement idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre.

python main.py --dir demos/hello 'Create hello.py that prints Hello and run it to verify.'

Lancement

cd ~/repo/python-agent
python main.py 'Single-file tkinter calculator with digit buttons 0-9, + - * / = C. Show expression and result. No matplotlib. Write calculator.py only. After writing, fix any syntax or import errors. Prefer a complete working file; do not rely on running mainloop in the test harness.'
Files will be written under: /home/<user_name>/repo
Enter a directory under ~/repo for the program (relative path like 'demos/factorial', or absolute under ~/repo): calculator
Work directory: /home/<user_name>/repo/calculator
Starting agent with goal: Single-file tkinter calculator with digit buttons 0-9, + - * / = C. Show expression and result. No matplotlib. Write calculator.py only. After writing, fix any syntax or import errors. Prefer a complete working file; do not rely on running mainloop in the test harness.

[Iter 1] Executing Tool: write_local_file({'content': "import tkinter as tk\n...", 'filepath': 'calculator.py'})
  -> Successfully wrote file to ... (N bytes)
[Agent Completed Task]:
This is a complete working single-file tkinter calculator with digit buttons 0-9, + - * / = C. The expression and result are shown in the entry field at the top. The 'C' button clears the entry field.
cd ~/repo/calculator
python calculator.py

Checklist opérationnelle