Accueil / Articles

Arrêtez de faire re-lire la base de code à votre agent de codage : le modèle LLM Wiki

L’idée de wiki pour les LLM de Karpathy et openwiki de LangChain : sources brutes, un wiki adapté aux agents ainsi qu’un lien vers AGENTS.md, en plus de tâches de mise à jour, d’évaluation et de traçage.

Éditeur : REACTAPP.TOP12 min de lecture2695 mots

Sources: Stop Making Your AI Agent Re-Read Your Entire Codebase Every Session

La plupart des agents de codage ont une habitude agaçante : chaque session commence à zéro. Ils rouvrent les fichiers, découvrent à nouveau quels modules dépendent les uns des autres et apprennent à nouveau à quoi sert chaque partie du codebase. Vous payez en tokens, et donc en argent, pour des connaissances que l’agent possédait il y a cinq minutes à peine et a oubliées dès que la fenêtre de contexte s’est fermée.

Andrej Karpathy a décrit ce problème et esquissé une solution qu’il a appelée le modèle LLM Wiki. LangChain a publié une implémentation open source, openwiki. Ci-dessous, ce modèle et son implémentation sont examinés côte à côte : quelle est l’idée, pourquoi elle est importante et comment fonctionne le code derrière une application de démonstration. Le résultat est un modèle mental que vous pouvez appliquer à tout codebase que l’agent doit comprendre.

Agents amnésiques

Tout d’abord, le mode de défaillance en termes précis. La réponse habituelle à la question « comment un agent comprend-il une base de code ? » est le RAG. Il s’agit d’incorporer les fichiers sources, de les placer dans une base de données vectorielle et de récupérer les fragments pertinents au moment de la requête.

Le RAG convient bien pour répondre aux questions. Cependant, pour les agents de programmation, il présente trois limites.

La compréhension est recomputée à chaque fois. Supposons que l’agent ait besoin du lien entre agent.py et memory.py. Il récupère des fragments, le modèle en déduit à nouveau la réponse, puis ce résultat est jeté.

La récupération dépend des embeddings. Si celui qui a écrit le code utilise un vocabulaire différent de celui de la requête de l’agent, les bons fragments ne seront jamais trouvés. Les embeddings sont approximatifs, tandis que l’architecture est exacte.

Il devient obsolète sans qu’aucun son ne se fasse entendre. Le stockage vectoriel n’a aucune idée que memory.py a été refactorisé mardi dernier, et il sert des données anciennes avec une totale confiance.

L’idée de Karpathy était simple : faire en sorte que le modèle écrive lui-même la documentation, dans un format adapté à sa lecture, et de la mettre à jour automatiquement. Il s’agit donc de documentation destinée aux agents, et non aux humains.

Trois niveaux

Chaque élément de openwiki appartient à l’un des trois niveaux de la conception de Karpathy.

┌─────────────────────────────────────────────────┐
│  Layer 3: The Schema                            │
│  AGENTS.md / CLAUDE.md — "the wiki exists here" │
├─────────────────────────────────────────────────┤
│  Layer 2: The Wiki                              │
│  openwiki/*.md — LLM-generated structured docs  │
├─────────────────────────────────────────────────┤
│  Layer 1: Raw Sources                           │
│  Your actual codebase — immutable to the wiki   │
└─────────────────────────────────────────────────┘

Chaque couche repose sur celle qui se trouve en dessous d’elle.

Couche 1 : les sources brutes

Les sources brutes ne sont rien d’autre que votre base de code. Du point de vue du wiki, elles sont immuables : OpenWiki les lit mais ne les modifie jamais.

Le projet de démonstration conserve ses sources dans un répertoire sample_agent/, un agent LangChain ReAct réaliste que la plupart des ingénieurs en IA reconnaîtront.

# sample_agent/agent.py
from langchain.agents import AgentExecutor, create_react_agent
from langchain_openai import ChatOpenAI
from .memory import build_memory
from .prompts import build_prompt
from .tools import TOOLS
def build_agent(
    model: str = "openai/gpt-4o-mini",
    base_url: str = "https://openrouter.ai/api/v1",
    api_key: str | None = None,
    temperature: float = 0.0,
) -> AgentExecutor:
    llm = ChatOpenAI(model=model, base_url=base_url, api_key=api_key, temperature=temperature)
    prompt = build_prompt()
    memory = build_memory()
    agent = create_react_agent(llm=llm, tools=TOOLS, prompt=prompt)
    return AgentExecutor(
        agent=agent,
        tools=TOOLS,
        memory=memory,
        verbose=True,
        handle_parsing_errors=True,
        max_iterations=10,
    )

Quatre composants sont reliés entre eux : un client modèle (ChatOpenAI), un modèle de prompt, une mémoire de conversation et les outils. Il s’agit du ReAct classique, le type de code pour lequel OpenWiki a été conçu. Les outils sont délibérément restreints : une recherche simulée et une calculatrice sécurisée :

# sample_agent/tools.py
@tool
def calculator(expression: str) -> str:
    """Evaluate a safe mathematical expression and return the result."""
    allowed_names: dict = {}
    try:
        result = eval(expression, {"__builtins__": {}}, allowed_names)
        return str(result)
    except Exception as exc:
        return f"Error evaluating '{expression}': {exc}"

Regardez {"__builtins__": {}}. Il supprime tous les éléments intégrés de Python du contexte de eval afin qu’un utilisateur ne puisse pas passer quelque chose comme __import__('os').system('rm -rf /') pour le faire exécuter. Un petit détail ayant des conséquences de sécurité réelles.

Pour le schéma, ce qui compte, c’est que OpenWiki lira chacun de ces fichiers, déterminera comment ils sont liés et générera une page wiki expliquant tout cela une seule fois, et non à chaque démarrage d’un agent.

Niveau 2 : le wiki lui-même

C’est ici que réside l’idée centrale. Les documents manuscrits se détériorent, tandis que RAG sur du code brut réduit tout à nouveau à partir de chaque requête ; donc le schéma fait en sorte que le modèle génère du markdown structuré adapté à la manière dont les modèles traitent le contexte.

Comment OpenWiki le génère

Dans l’application Streamlit de la démonstration, un bouton « Générer le wiki » exécute ce processus :

# app.py
def run_openwiki(
    args: list[str],
    provider: str,
    api_key: str,
    model: str,
    langsmith: str,
    cf_account_id: str = "",
) -> tuple[str, int]:
    import sys
    env = _build_openwiki_env(provider, api_key, model, langsmith, cf_account_id)
    result = subprocess.run(
        ["openwiki", *args],
        cwd=str(SAMPLE_AGENT_DIR),
        env=env,
        capture_output=True,
        text=True,
        timeout=300,
        shell=sys.platform == "win32",  # npm .cmd files need shell=True on Windows
    )
    return result.stdout + result.stderr, result.returncode

Deux détails sont importants.

Tout d’abord, cwd=SAMPLE_AGENT_DIR est choisi délibérément. L’interface en ligne de commande openwiki parcourt le répertoire depuis lequel elle est exécutée. En définissant le répertoire de travail sur sample_agent/, on limite le wiki à cette base de code et non à l’ensemble du projet. Si l’on oublie ce paramètre, elle parcourt la racine du projet, ce qui génère un wiki couvrant les tests, la configuration CI et node_modules.

Deuxièmement, shell=True sous Windows. Les packages npm globaux s’installent sous forme de scripts d’enveloppe .cmd sur cette plateforme. L’appel à subprocess.run(["openwiki", ...]) cherche un binaire nommé openwiki, n’en trouve aucun et génère une erreur FileNotFoundError: [WinError 2]. En passant shell=sys.platform == "win32", l’appel est redirigé vers cmd.exe, qui traite correctement les fichiers .cmd. Cela peut prêter à confusion la première fois, mais devient évident par la suite.

Que signifie le formatage « agent-native »

La documentation destinée aux humains est écrite pour être lue de haut en bas. On ouvre README.md, on commence par le haut et on construit le contexte progressivement. Les agents ne lisent pas de cette manière : ils scannent les titres pour trouver les sections pertinentes, extraient les informations nécessaires et passent à autre chose.

La documentation « agent-native » est conçue en fonction de ce comportement :

  • En-têtes denses à chaque niveau, permettant de parcourir la structure sans avoir à lire du texte narratif.
  • Références croisées explicites telles que See: patterns.md#memory-strategy, que l’agent suit mécaniquement et non par intuition.
  • Résumés en un paragraphe par composant : suffisants pour décider s’il faut continuer à lire, sans être destinés à raconter l’histoire dans son intégralité.
  • Aucun texte superflu. Omettre l’introduction type « Dans ce document, nous allons explorer » ainsi que tout résumé de conclusion.

Un openwiki/architecture.md généré pour l’agent de exemple pourrait ressembler à ceci :

# Architecture — sample_agent
## Overview
ReAct agent built on LangChain AgentExecutor. Entry point: agent.py.
Four components: LLM (ChatOpenAI via OpenRouter), tools, memory, prompt.
## Components
- agent.py - AgentExecutor wiring. See: components.md#agent
- tools.py - search (simulated) + calculator (sandboxed eval). See: components.md#tools
- memory.py - ConversationBufferWindowMemory, k=5 turns
- prompts.py - ReAct ChatPromptTemplate with agent_scratchpad placeholder
- evaluator.py - LLM-as-judge chain. See: patterns.md#evaluation
## Data Flow
question → AgentExecutor.invoke() → ReAct loop (max 10 iter)
→ tool calls → final answer → evaluator.evaluate() (optional)

Placez cela à côté d’un README typique destiné aux humains, composé de trois paragraphes d’introduction avant que des informations concrètes n’apparaissent. Les agents n’ont pas besoin de ces introductions ; ils ont besoin des faits.

Niveau 3 : le schéma, qui clôt la boucle

Générer un wiki représente la moitié du travail. L’autre moitié consiste à s’assurer que l’agent de codage connaît l’existence du wiki.

C’est là le rôle de AGENTS.md et CLAUDE.md, les fichiers d’instructions que Claude Code, Cursor, GitHub Copilot et la plupart des agents de codage modernes lisent au début d’une session. OpenWiki insère une courte référence dans celui d’entre eux que votre projet utilise. Avant :

# sample_agent/AGENTS.md (before OpenWiki)
This repository contains a LangChain ReAct agent.
## Architecture
- agent.py - AgentExecutor entry point
- tools.py - available tools (search, calculator)
- memory.py - conversation memory strategy
- prompts.py - system prompt and ReAct template
- evaluator.py - LLM-as-judge evaluation chain

Et après avoir exécuté openwiki --init :

# sample_agent/AGENTS.md (after OpenWiki injection)
This repository contains a LangChain ReAct agent.
## Architecture
- agent.py - AgentExecutor entry point
...
## OpenWiki Context
When searching for codebase context, check `openwiki/` first.
Key files:
- `openwiki/architecture.md` - overall structure and file responsibilities
- `openwiki/patterns.md` - coding patterns and conventions
- `openwiki/components.md` - detailed component documentation
The wiki is auto-updated daily. Always prefer wiki context over re-reading source files.

L’injection est délibérément courte. Placer toute la documentation du wiki dans le fichier d’instructions épuiserait la fenêtre de contexte au début de chaque session sur une grande base de code. Un pointeur suffit. L’agent le lit, n’ouvre openwiki/architecture.md que lorsque le contexte de la base de code est nécessaire, et apprend ce dont il a besoin sans toucher à un seul fichier source.

La quatrième onglet de la démonstration montre cela avant et après, en simulant l’injection afin que le mécanisme soit visible :

# app.py — Tab 4
agents_md_content = AGENTS_MD.read_text(encoding="utf-8")
openwiki_injection = """
## OpenWiki Context
When searching for codebase context, check `openwiki/` first.
...
"""
col_before, col_after = st.columns(2)
with col_before:
    st.code(agents_md_content, language="markdown")
with col_after:
    st.code(agents_md_content + openwiki_injection, language="markdown")

Choix du fournisseur de modèle

OpenWiki se fiche du LLM qui se trouve derrière lui. Il utilise ChatOpenAI de LangChain, qui lit OPENAI_BASE_URL pour obtenir une adresse d’extrémité personnalisée, de sorte que n’importe quel fournisseur compatible avec OpenAI fonctionne. La démonstration prend en charge deux d’entre eux :

# app.py
def _build_openwiki_env(
    provider: str,
    api_key: str,
    model: str,
    langsmith: str,
    cf_account_id: str = "",
) -> dict:
    env = {**os.environ, "OPENWIKI_MODEL_ID": model}
if provider == "Cloudflare Workers AI" and cf_account_id:
        cf_base = f"https://api.cloudflare.com/client/v4/accounts/{cf_account_id}/ai/v1"
        env["OPENAI_API_KEY"] = api_key
        env["OPENAI_BASE_URL"] = cf_base      # LangChain reads this automatically
    else:
        env["OPENROUTER_API_KEY"] = api_key   # openwiki's native key name
    return env

OpenRouter est l’option la plus simple. Une seule clé permet d’accéder à la plupart des modèles majeurs via une seule interface, et la définition de OPENWIKI_MODEL_ID=openai/gpt-4o-mini suffit pour finaliser la configuration.

Cloudflare Workers AI met à disposition des modèles depuis le réseau périphérique. Le modèle GLM de ZhipuAI (@cf/zai-org/glm-5.2) s’exécute sur l’infrastructure propre à Cloudflare, ce qui réduit la latence lorsque l’application est déployée en production et que l’on souhaite utiliser un seul fournisseur de services cloud. Comme Cloudflare propose une interface compatible OpenAI à l’adresse /ai/v1, aucun client personnalisé n’est nécessaire. Il suffit de pointer OPENAI_BASE_URL vers cette adresse, et l’intégration LangChain déjà en place s’occupe du reste.

Mettre à jour la wiki

Tout système de documentation finit par rencontrer les mêmes problèmes : les utilisateurs oublient de le mettre à jour lorsque le code change.

Selon Karpathy, il ne s’agit pas d’un problème de discipline mais d’un problème économique. Le coût de maintenance augmente avec la taille du code, tandis que la personne qui s’en occupe ne reçoit aucune récompense immédiate. Finalement, les documents s’éloignent tellement de la réalité que plus personne ne leur fait confiance, et le cycle recommence.

La solution dans OpenWiki est openwiki --update. Au lieu de rescanner tout le contenu, il interroge git log pour identifier les commits effectués après la dernière exécution, utilise git diff pour ne lire que les fichiers modifiés, et réécrit uniquement les pages concernées par ces changements. Une action GitHub intégrée au projet la lance selon un calendrier prédéfini :

# .github/workflows/openwiki-update.yml
name: OpenWiki Update
on:
  schedule:
    - cron: "0 8 * * *"   # 08:00 UTC daily
  workflow_dispatch:
jobs:
  update-wiki:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0   # full git history so openwiki can read diffs
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
      - run: npm install -g openwiki
      - name: Update wiki
        working-directory: sample_agent     # scope to our codebase
        run: openwiki --update --print
        env:
          OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
          OPENWIKI_MODEL_ID: openai/gpt-4o-mini
      - uses: peter-evans/create-pull-request@v7
        with:
          commit-message: "docs: update openwiki pages"
          title: "OpenWiki: daily documentation update"
          branch: openwiki/auto-update

Notez working-directory: sample_agent : il s’agit de la même option de cadrage que dans l’appel de subprocess en Python. Où que vous déployiez ce système, exécutez --update depuis la racine du codebase et non depuis la racine du projet.

Ouvrir une demande de fusion est la meilleure approche. Un commit automatique dans la branche main permettrait aux documents de changer sans aucune revue, tandis qu’une demande de fusion vous offre un diff à examiner ainsi que la possibilité de rejeter des résultats de modèle incorrects. La cinquième onglet de la démonstration simule la mise à jour localement :

# app.py — Tab 5
if st.button("🔄 Run openwiki --update", type="primary"):
    with st.spinner("Checking for changes and updating wiki…"):
        output, returncode = run_openwiki(
            ["--update", "--print"],
            provider,
            api_key if provider == "Cloudflare Workers AI" else openrouter_key,
            model_id,
            langsmith_key,
            cf_account_id,
        )
    st.code(output, language="bash")

Un évaluateur LLM-en-juge

À côté de l’agent se trouve un évaluateur LLM-en-juge, une technique qui mérite d’être examinée en détail.

# sample_agent/evaluator.py
def evaluate(
    question: str,
    response: str,
    model: str = "openai/gpt-4o-mini",
    base_url: str = "https://openrouter.ai/api/v1",
    api_key: str | None = None,
) -> dict[str, int | str]:
    llm = ChatOpenAI(model=model, base_url=base_url, api_key=api_key, temperature=0)
    chain = _JUDGE_PROMPT | llm
    result = chain.invoke({"question": question, "response": response})
    try:
        return json.loads(result.content)
    except json.JSONDecodeError as exc:
        raise ValueError(
            f"LLM returned non-JSON response: {result.content!r}"
        ) from exc

temperature=0 n’est pas optionnel ici. Le juge doit être déterministe, afin que la même question et réponse obtiennent toujours la même note. Une évaluation aléatoire contredit l’objectif même de l’attribution de notes.

json.loads est encadré dans try/except JSONDecodeError car les modèles placent parfois leur JSON à l’intérieur de balises de code markdown, ajoutent une phrase introductive ou interrompent leurs réponses trop longues. L’exception explicite avec result.content!r dans le message montre exactement ce que le modèle a retourné, ce qui facilite le débogage.

Le prompt du juge évalue séparément la précision, l’utilité et la clarté, puisque ces critères échouent de manière indépendante. Une réponse peut être correcte sans répondre à la vraie question, ou indiquer la bonne direction tout en confusant le lecteur. En les séparant, on obtient des notes sur lesquelles on peut agir.

Explorer l’intérieur avec LangSmith

En définissant LANGSMITH_API_KEY, chaque étape effectuée par l’agent openwiki est traçée : les fichiers qu’il lit, la requête qu’il envoie, la réponse du modèle et la durée de chaque appel.

# app.py
def _build_openwiki_env(...) -> dict:
    ...
    if langsmith:
        env["LANGSMITH_API_KEY"] = langsmith
        env["LANGCHAIN_TRACING_V2"] = "true"
        env["LANGCHAIN_PROJECT"] = "openwiki-demo"
    return env

Il suffit de trois variables d’environnement. LangChain les détecte automatiquement sans aucune modification de code dans openwiki ou l’agent. C’est le modèle d’observabilité idéal pour les projets LangChain : utiliser l’environnement plutôt que le code pour l’instrumentation, afin de pouvoir activer le suivi en production sans toucher à l’application.

Le suivi révèle également des informations utiles. L’appel --init n’est pas une seule requête vers un LLM. Il s’agit d’un cycle d’agent comprenant des appels de planification, des appels de lecture de fichiers et des appels d’écriture sur le wiki, et le suivi affiche le nombre de tokens et la latence pour chacun d’eux. C’est ainsi que l’on peut répondre à la question « pourquoi --init prend-il 90 secondes » : en consultant le suivi, on voit trois appels au modèle de 30 secondes chacun s’exécuter les uns après les autres.

Le flux complet

En résumé :

Developer commits code change
    ↓
GitHub Action runs openwiki --update at 08:00 UTC
    ↓
CLI reads git diff → only changed files
    ↓
LLM regenerates affected wiki pages
    ↓
PR opened with updated openwiki/*.md
    ↓
Developer merges PR
    ↓
Agent starts next session → reads AGENTS.md → sees wiki reference
    ↓
Agent fetches openwiki/architecture.md when it needs codebase context
    ↓
Agent makes informed change without reading source files

Le wiki s’enrichit avec le temps. Six mois d’actualisations quotidiennes font que openwiki/patterns.md contient désormais plus que la structure actuelle : il enregistre également l’évolution du code, y compris les schémas qui sont apparus lors des refacteursings et les raisons sous-jacentes aux conventions. Un nouvel agent ou un nouveau collaborateur dispose immédiatement de toutes ces informations.

La véritable réflexion

En réalité, les schémas ne concernent pas la documentation. Il s’agit plutôt de savoir qui assume le coût de la compréhension d’un codebase.

Au sein du modèle RAG, chaque session d’agent entraîne des coûts : il faut redéterminer les relations, réapprendre les conventions, relire les fichiers déjà consultés. Ces coûts augmentent en fonction de la fréquence des sessions.

Dans le modèle wiki, le coût est payé une seule fois par modification de code, par le biais d’une tâche planifiée qui s’exécute sans que personne n’attende, pour un montant inférieur à un centime par page, et ce qu’il produit gagne constamment en valeur.

Les modèles ne se lassent jamais de maintenir les wikis à jour. Aucune échéance ne fait baisser la documentation dans leur liste de priorités, et ils n’écrivent jamais « TODO : documenter plus tard » pour l’oublier ensuite. Cet déséquilibre est justement l’essentiel du problème.

Pour essayer, installez Node.js 18 ou une version ultérieure pour la CLI openwiki et obtenez une clé API auprès d’OpenRouter ou de Cloudflare Workers AI.