La boucle du prochain token : un modèle mental des LLM avant même d’aborder les agents
Apprenez comment fonctionnent les tokens, les fenêtres de contexte, l’échantillonnage et le cycle de génération, à travers de petits exemples en Python hors ligne qui expliquent pourquoi RAG, ReAct et LangGraph existent.
Les ingénieurs qui travaillent avec les LLM via un framework comme LangChain ont souvent du mal à déterminer, lorsque quelque chose ne fonctionne pas, si c’est le framework, l’API qu’il encapsule ou le modèle lui-même qui est en cause. La solution réside dans un modèle mental clair : un LLM est une fonction de prédiction du token suivant qui est appelée répétitivement en boucle. Avec cette vision en tête, RAG constitue un moyen de choisir ce qui est introduit dans la fonction, ReAct un moyen d’analyser ce qui en sort, et LangGraph un moyen d’orchestrer de nombreuses appels. Les exemples ci-dessous s’exécutent localement avec Python et une petite bibliothèque, sans besoin de clé API.
La génération consiste en une prédiction du token suivant en boucle
Étant donné une séquence de tokens, le modèle renvoie une probabilité pour chaque token suivant possible. Un token est choisi, ajouté à la séquence, et cette dernière est de nouveau utilisée. Ce processus se répète jusqu’à ce qu’un token d’arrêt ou une limite de longueur soit atteint. L’affichage en flux continu se contente d’exposer cette boucle token par token.
Deux conséquences en découlent :
- Il n’y a pas de plan. Chaque token ne dépend que des tokens qui le précèdent.
- Il n’y a pas de mémoire entre les appels. Chaque requête commence exactement avec les tokens que vous envoyez, c’est pourquoi la génération améliorée par le récupération existe.
Les tokens sont des fragments de sous-mots, pas des mots
o200k_base, l’encodage de la famille gpt-4o, via tiktoken, encode une phrase et affiche chaque ID accompagné de son fragment de texte. Notez que « LangGraph » devient deux tokens et que les espaces en tête appartiennent au token suivant.
import tiktoken
# tokenizer of the gpt-4o model family
enc = tiktoken.get_encoding("o200k_base")
ids = enc.encode("LangGraph orchestrates agents.")
print(ids)
# [30741, 9922, 109873, 1381, 19297, 13]
for i in ids:
print(i, repr(enc.decode([i])))
# 'Lang' 'Graph' ' orchestr' 'ates' ' agents' '.'
L’entraînement et l’inference sont des phases distinctes
L’entraînement ajuste les poids en prédisant les tokens suivants à partir de grandes quantités de texte. L’inference exécute le modèle déjà figé. Votre prompt ne modifie jamais les poids, il faut donc placer dans le prompt, au moment de l’inference, toutes les informations manquantes au modèle. C’est précisément ce que fait RAG.
La fenêtre de contexte représente tout le monde du modèle
Une seule appelation ne peut traiter qu’un certain nombre de tokens, et cette limite, la fenêtre de contexte, englobe à la fois le prompt et le résultat généré. Tout ce qui en est hors ne existe pas pour cette appelation. Le chat donne l’impression de continuité uniquement parce que l’application envoie à nouveau les messages précédents à chaque fois, tandis que RAG existe parce que les collections de documents ne tiennent pas dans la fenêtre de contexte, ce qui oblige à récupérer uniquement quelques extraits pertinents.
La budgétisation est une simple arithmétique. L’outil compte les tokens de réponse à l’aide du tokenizer mentionné précédemment et les soustrait, ainsi qu’une réserve pour la réponse, d’une fenêtre de 128 000 tokens, valeur indiquée pour gpt-4o-mini. Les limites varient selon les modèles et les versions, il convient donc de consulter la documentation actuelle de votre fournisseur.
def count_tokens(text: str) -> int:
return len(enc.encode(text))
prompt = "Summarize the attached design doc."
window = 128_000 # e.g. gpt-4o-mini
reserve = 1_000 # room for the answer
used = count_tokens(prompt)
print("left:", window - reserve - used)
La température contrôle le choix du prochain token
L’échantillonnage sélectionne un token parmi la distribution du modèle, et la température modifie cette distribution. Près de zéro, le token le plus probable l’emporte presque toujours. À 1,0 et au-delà, des tokens moins probables ont une réelle chance d’être choisis. Utilisez une température de 0 pour l’extraction, les sorties structurées et les agents d’appel d’outils, ainsi qu’environ 0,7 à 1,0 pour la rédaction et le brainstorming. Notre guide sur la température, top-k et top-p aborde les autres paramètres d’échantillonnage.
Création d’un cycle en miniature
L’exemple suivant associe un tokeniseur réel à un modèle fictif constitué d’une table de recherche. Il est très petit, mais il suit le même cycle qu’un LLM en production : encoder, prédire, ajouter, s’arrêter.
Tout d’abord, un petit corpus est tokenisé et chaque paire d’ID adjacente est comptée. Pour chaque token, la table ne conserve que son successeur le plus fréquent, ce qui en fait un prédicteur gourmand, équivalent à une température nulle.
CORPUS = (
"the agent calls the model. "
"the model returns a token. "
"the agent calls the tool. "
"the tool returns a result."
)
ids = enc.encode(CORPUS)
counts = {}
for a, b in zip(ids, ids[1:]):
counts.setdefault(a, {})
counts[a][b] = counts[a].get(b, 0) + 1
# greedy "model": token -> likeliest successor
table = {
a: max(s, key=s.get)
for a, s in counts.items()
}
Le cycle encode une requête, cherche le successeur le plus probable du dernier token, l’ajoute et affiche le texte décodé. L’absence de successeur renvoie None, ce qui remplace un token d’arrêt. L’étape d’ajout est cruciale : la sortie devient l’entrée.
seq = enc.encode("the agent calls")
for _ in range(3):
nxt = table.get(seq[-1])
if nxt is None: # our toy "stop token"
break
seq.append(nxt) # output becomes input
print(enc.decode(seq))
La séquence augmente d’un token à chaque étape :
the agent calls the
the agent calls the model
the agent calls the model.
Un détail est instructif. Dans le corpus, « the » est suivi de « model » et de « tool » avec la même fréquence, et max retient celui qu’il a vu en premier. Les modèles réels font face constamment à de tels empilages, d’où l’importance des paramètres de sampling.
Exécution des exemples
Rassemblez les extraits dans un seul script, par exemple examples/part01_tokens.py, accompagné d’un requirements.txt qui liste tiktoken. Ce dernier affiche les divisions en tokens, effectue les calculs liés au budget et exécute la boucle de simulation en environ une seconde, hors ligne une fois que les données du tokenizer sont mémorisées. Ensuite, modifiez le corpus et observez comment les résultats changent.
pip install -r requirements.txt
python examples/part01_tokens.py
Points clés
- Un LLM est une fonction figée qui transforme une séquence de tokens en une distribution des tokens suivants, exécutée dans un cycle de rétroaction.
Lectures complémentaires
- Comprendre les agents IA : objectifs, outils, mémoire et la boucle de l’agent — Une explication adaptée aux débutants sur les différences entre les agents IA et les chatbots, abordant les composants fondamentaux, la boucle de décision, les niveaux d’autonomie et les cas d’usage concrets.
- LangGraph vs Pydantic AI : Pourquoi c’est le modèle, et non le framework, qui a déterminé la précision — Un benchmark contrôlé comprenant 160 essais de LangGraph et Pydantic AI montre une précision identique dans l’appel des outils, et révèle que le choix du modèle ainsi que la conception de l’évaluation sont bien plus importants.
- Débogage d’un petit GPT en PyTorch : Des tests qui isolent chaque échec — Un processus étape par étape pour déboguer un GPT au niveau des caractères en PyTorch, allant des IDs de tokens et du décalage cible aux gradients, aux pertes NaN et aux points de contrôle.
- Budgets de tokens vs. frontières sémantiques : comment effectuer correctement les divisions en chunks dans RAG — Découvrez en quoi la tokenisation et le chunking diffèrent, quelle est leur place dans un pipeline RAG, et comment diviser les documents en fonction de leur sens tout en mesurant leur taille avec le tokeniseur réel.