Passer au contenu principal

Documentation Index

Fetch the complete documentation index at: https://docs.fim.ai/llms.txt

Use this file to discover all available pages before exploring further.

Le problème

Les LLMs ont des fenêtres de contexte limitées. Un modèle de 128K tokens semble généreux jusqu’à ce que vous soustrayiez le budget de sortie, l’invite système, les descriptions d’outils et l’historique accumulé d’une conversation multi-tours. Les longues conversations, les résultats d’outils volumineux et les boucles d’agents multi-étapes poussent tous contre cette limite — souvent au cours d’une seule session. La solution naïve est la troncature : supprimer les anciens messages lorsque la fenêtre se remplit. C’est rapide et prévisible, mais cela détruit le contexte indiscriminément. L’intention originale de l’utilisateur, les décisions clés des tours précédents et les points de données critiques disparaissent tous lorsqu’une coupure de caractères brutale les atteint. L’extrême opposé — la synthèse alimentée par LLM à chaque tour — préserve le contenu sémantique mais est coûteux, lent et introduit ses propres modes de défaillance (résumés hallucés, perte de précision numérique). Le vrai défi n’est pas « tenir dans la fenêtre ». C’est : se dégrader gracieusement sans perdre les informations critiques, sans brûler de tokens sur une compaction inutile et sans ajouter de latence que l’utilisateur peut ressentir. FIM One résout ce problème avec une architecture de défense en profondeur à cinq couches. Chaque couche aborde une échelle différente du problème, et elles se composent proprement — aucune couche unique n’a besoin d’être parfaite car la suivante rattrape ce qu’elle manque.

Cinq couches de défense

La gestion du contexte n’est pas un mécanisme unique. C’est une pile, où chaque couche gère une préoccupation spécifique à une granularité spécifique :
CoucheComposantCe qu’il faitQuand il agit
5Budget ConfigurationCalcule le budget de jetons d’entrée utilisable à partir des spécifications du modèleAu démarrage / par requête
4DbMemoryCharge l’historique persisté, compacte au chargementUne fois par requête
3ContextGuardApplication du budget par itérationÀ chaque itération ReAct
2CompactUtilsEstimation des jetons, troncature intelligente, compaction LLMAppelé par les couches 3 et 4
1Memory ImplementationsInterface abstraite + stratégies concrètesNiveau du framework
Les couches sont numérotées de bas en haut car les couches supérieures dépendent des couches inférieures. La couche 5 définit le budget. La couche 4 effectue la compaction initiale au moment du chargement. La couche 3 applique le budget à chaque itération. Les couches 2 et 1 fournissent les primitives que les couches 3 et 4 utilisent.

Couche 5 — Configuration du budget

Le budget est calculé à partir de deux valeurs :
usable_input_tokens = context_size - max_output_tokens
Avec les valeurs par défaut : 128 000 - 64 000 = 64 000 tokens. Aucune réserve de message système statique n’est soustraite. Le message système (instructions de l’agent, descriptions des outils, surcharge de formatage) est inclus dans la liste des messages que ContextGuard estime dynamiquement — soustraire une réserve fixe en plus créerait un double comptage et gaspillerait le budget. Un message système typique consomme 3K-5K tokens ; les agents avec de nombreux outils ou des instructions longues peuvent en utiliser davantage. ContextGuard tient compte de la taille réelle automatiquement. Les valeurs de budget peuvent provenir de trois sources, résolues par ordre de priorité :
  1. ModelConfig de la base de donnéescontext_size et max_output_tokens par modèle définis par l’administrateur.
  2. Variables d’environnementLLM_CONTEXT_SIZE et LLM_MAX_OUTPUT_TOKENS.
  3. Valeurs par défaut codées en dur — contexte 128K, sortie 64K.
Le LLM principal et le LLM rapide ont des budgets indépendants. L’exécution des étapes du DAG utilise le budget du LLM rapide ; le mode ReAct utilise le budget du LLM principal. Cela importe car les opérateurs associent souvent un modèle à grand contexte pour ReAct (où l’historique s’accumule) avec un modèle plus petit et plus rapide pour les étapes du DAG (où chaque étape recommence à zéro). Un plancher de 4 000 tokens est appliqué — si les valeurs mal configurées produisaient un budget plus petit, le système se limite à 4K plutôt que d’échouer silencieusement.

Couche 4 — DbMemory

DbMemory est l’implémentation de mémoire en production. Elle charge l’historique de conversation persisté depuis la base de données et le compacte pour tenir dans le budget de tokens avant que l’agent ne le voie. La conception est intentionnellement en lecture seule. La persistance est gérée par chat.py — la couche API qui possède le cycle de vie complet des messages (y compris les métadonnées, le suivi d’utilisation et les pièces jointes d’images). DbMemory ne fait que lire. Ses méthodes add_message() et clear() sont des no-ops. Cette séparation prévient les écritures doubles et garde la logique de persistance au même endroit. Au chargement, DbMemory :
  1. Interroge tous les messages user et assistant pour la conversation, ordonnés par heure de création.
  2. Supprime le dernier message utilisateur (la requête actuelle, que l’agent réajoutera).
  3. Reconstruit les pièces jointes d’images — les messages utilisateur qui incluaient des images stockent les métadonnées (file_id, mime_type) dans la base de données, et DbMemory reconstruit les data-URLs en base64 à partir du disque pour que le LLM puisse « voir » les images des tours précédents.
  4. Compacte : si un compact_llm a été fourni, utilise CompactUtils.llm_compact(). Sinon, revient à CompactUtils.smart_truncate().
Après compaction, DbMemory définit les drapeaux de suivi (was_compacted, _original_count, _compacted_count) que la couche SSE utilise pour émettre un événement compact au frontend.

Couche 3 — ContextGuard

ContextGuard est l’applicateur de budget par itération. Il est appelé au début de chaque itération ReAct — à la fois en mode ReAct autonome et à l’intérieur de l’agent de chaque étape DAG. C’est la dernière ligne de défense avant que les messages ne frappent l’API LLM. L’application suit un processus en trois étapes :
  1. Tronquer les messages surdimensionnés. Tout message unique dépassant 50 000 caractères est tronqué brutalement avec un suffixe [Truncated]. Cela capture les sorties d’outils qui s’échappent — un scraping web qui retourne une page entière, une lecture de fichier qui déverse un grand ensemble de données.
  2. Estimer les jetons totaux. Si le total s’inscrit dans le budget, retourner immédiatement. La plupart des itérations passent ici — la compaction est l’exception, pas la norme.
  3. Compacter si dépassement du budget. Si un compact_llm est disponible, utiliser la compaction alimentée par LLM avec un prompt spécifique à l’indice. Sinon, revenir à smart_truncate.
Le système d’indice est ce qui rend ContextGuard conscient du contexte plutôt que générique. Différentes situations nécessitent différentes stratégies de compaction :
IndiceUtilisé parPréserveSupprime
react_iterationBoucle d’agent ReActChaîne de raisonnement récente, objectif actuel, données critiquesÉtapes anciennes redondantes, tentatives échouées, sorties d’outils verbeux
planner_inputRequête enrichie DAGÉvolution de l’intention utilisateur, décisions clés, contraintesDétails de dialogue, salutations, mécaniques d’appel d’outils
step_dependencyContexte d’étape DAGDonnées clés, nombres, conclusionsProcessus de raisonnement, tentatives échouées, formatage verbeux
generalRepli par défautFaits clés, décisions, résultats d’outilsSalutations, remplissage, allers-retours redondants
Chaque indice correspond à un prompt système soigneusement formulé qui indique au LLM de compaction ce qu’il faut conserver et ce qu’il faut supprimer. Les prompts se terminent par « Écrivez dans la même langue que la conversation » — un détail qui importe pour les utilisateurs CJK dont les résumés seraient autrement par défaut en anglais. Si la compaction LLM échoue (erreur réseau, réponse vide, toute exception), ContextGuard revient silencieusement à smart_truncate. L’agent ne voit jamais l’échec. C’est un choix de fiabilité délibéré : il est préférable de perdre un peu de contexte via une troncature heuristique que de faire échouer l’itération.

Couche 2 — CompactUtils

CompactUtils est une classe utilitaire sans état — pas d’instances, pas d’état, juste des fonctions pures. Elle fournit trois capacités sur lesquelles les couches 3 et 4 s’appuient. L’estimation de jetons convertit le texte en un nombre de jetons approximatif sans importer de bibliothèque de tokeniseur. L’heuristique :
  • Caractères ASCII : ~4 caractères par jeton
  • Caractères CJK / non-ASCII : ~1,5 caractères par jeton
  • Images : 765 jetons par image (coût fixe)
  • Surcharge par message : 4 jetons (marqueur de rôle, délimiteurs)
smart_truncate est le recours heuristique. Il conserve les messages épinglés sans condition, puis parcourt les messages non épinglés en arrière, en accumulant jusqu’à épuisement du budget. Le résultat est un suffixe de la conversation qui s’adapte. Il garantit également que le résultat ne commence jamais par un message d’assistant — un tour d’assistant orphelin sans message utilisateur précédent confond les LLM. llm_compact est le chemin alimenté par LLM. Il divise les messages en trois groupes — messages système (toujours conservés), messages épinglés (toujours conservés) et messages compactables. Les messages compactables les plus anciens sont résumés en un seul message système [Conversation summary] ; les 4 messages les plus récents sont conservés textuellement. Si le résultat compacté est toujours trop long, il revient à smart_truncate sur la sortie compactée — double sécurité.

Couche 1 — Implémentations de mémoire

La couche de mémoire définit l’interface BaseMemory : add_message(), get_messages(), clear(). Trois implémentations existent :
  • WindowMemory — une fenêtre glissante basée sur le nombre. Conserve les N derniers messages non-système. Simple, prévisible, aucun appel LLM. Non utilisée en production ; utile pour les tests et les scénarios sans état.
  • SummaryMemory — déclenche une résumé LLM quand le nombre de messages dépasse un seuil. Compresse les anciens messages en un message système [Conversation summary]. Non utilisée en production ; antérieure à l’approche ContextGuard plus sophistiquée.
  • DbMemory — l’implémentation de production (décrite à la couche 4). Sauvegardée en base de données, en lecture seule, avec compaction LLM ou heuristique au chargement.
WindowMemory et SummaryMemory restent dans la base de code car elles servent de primitives utiles pour les tests et pour les utilisateurs qui intègrent la bibliothèque principale de FIM One sans la couche web. Ce ne sont pas du code mort — ce sont les cas simples dont l’architecture est issue.

Comment le contexte circule dans ReAct

L’agent ReAct utilise la gestion du contexte à deux phases distinctes : le temps de chargement et le temps d’itération. Les itérations d’outils utilisent chat() sans streaming pour la rapidité ; la synthèse de réponse utilise stream_chat() via stream_answer() avec streaming. Cette division en deux phases — boucle d’outils rapide suivie d’une synthèse en streaming — optimise à la fois la latence et l’expérience utilisateur. Pour l’architecture complète du moteur ReAct incluant l’exécution en mode dual et la sélection d’outils, voir Moteur ReAct. L’insight clé : DbMemory gère le problème du contexte historique (tours des requêtes précédentes), tandis que ContextGuard gère le problème de croissance intra-requête (résultats d’outils s’accumulant pendant une boucle d’agent). Ils opèrent à des échelles de temps différentes et capturent des modes de défaillance différents. La requête actuelle de l’utilisateur est toujours marquée comme pinned=True. Cela garantit qu’elle survit à toute compaction — à la fois smart_truncate et llm_compact préservent les messages épinglés sans condition. Peu importe la compression agressive de l’historique, la question réelle de l’utilisateur n’est jamais perdue.

Comment le contexte circule dans le DAG

Le mode DAG a une forme de contexte fondamentalement différente de ReAct. Au lieu d’un long fil de conversation unique, il a une arborescence : une phase de planification, plusieurs étapes d’exécution parallèles et une phase d’analyse. Chaque phase a sa propre stratégie de gestion du contexte. Phase 1 — Chargement de l’historique. DbMemory charge et compacte l’historique de conversation, comme dans ReAct. L’historique compacté est formaté dans un bloc de texte préfixé par "Previous conversation:". Phase 2 — Construction de requête enrichie. L’historique et la requête actuelle sont combinés dans une enriched_query. Si cela dépasse 16K tokens, il est résumé par LLM en utilisant l’invite planner_input. Le seuil de 16K est choisi parce que le planificateur doit lire l’intégralité de la requête en une seule passe — contrairement à ReAct, il n’y a pas de compaction itérative pendant la planification. Phase 3 — Planification. Le planificateur reçoit une invite à 2 messages : invite système plus requête enrichie. Pas de ContextGuard ici — la requête enrichie est déjà contrôlée en taille par la vérification de 16K. Phase 4 — Exécution des étapes. Chaque étape DAG s’exécute comme un agent ReAct indépendant avec son propre ContextGuard. De manière critique, ces agents d’étape n’ont pas de mémoire — ils commencent à zéro avec seulement leur description de tâche et le contexte de dépendance. C’est intentionnel : les étapes DAG doivent être des unités de travail autonomes. Les résultats de dépendance sont injectés via _build_step_context, qui tronque les caractères à 50K (la limite max_message_chars du ContextGuard). Phase 5 — Analyse. Les résultats des étapes sont formatés pour l’LLM analyseur avec troncature par étape à 10K caractères. Cela empêche la sortie détaillée d’une seule étape de dominer le contexte d’analyse. Phase 6 — Replanification. Quand l’analyseur détermine que l’objectif n’a pas été atteint et que la confiance est en dessous du seuil, les résultats des étapes sont tronqués à seulement 500 caractères chacun pour le contexte de replanification. La replanification doit savoir ce qui s’est passé et ce qui a mal tourné, pas le détail complet de la sortie de chaque étape. Cette troncature agressive garde l’invite de replan suffisamment compacte pour que le planificateur la traite efficacement. Pour l’architecture complète du pipeline DAG incluant la Carte des Appels LLM et la logique de replanification, voir Moteur DAG.

Messages épinglées

Le mécanisme d’épinglage empêche la compaction de détruire les messages qui doivent persister. Deux catégories de messages sont épinglées :
  1. La requête utilisateur actuelle — toujours épinglée. Si l’utilisateur pose une question et l’historique est trop long, le système compresse l’historique, pas la question.
  2. Messages injectés en cours de flux — quand un utilisateur envoie un suivi pendant que l’agent est encore en cours d’exécution, le message injecté est marqué comme épinglé pour que l’agent le voie à l’itération suivante.
Le risque avec l’épinglage est l’accumulation. Dans une longue session avec de nombreux messages injectés, le contenu épinglé peut croître et consommer la majorité du budget, ne laissant aucune place pour l’historique de conversation réel. ContextGuard résout ce problème avec un plafond strict : quand les jetons épinglés dépassent 50% du budget, les messages injectés les plus anciens sont désépinglés et déplacés vers le pool compactable. Seul le message épinglé le plus récent (la requête actuelle) est préservé. C’est un compromis. Désépingler les anciens messages injectés signifie qu’ils pourraient être résumés ou tronqués. Mais l’alternative — laisser les messages épinglés évincer tout autre contexte — est pire. Le système privilégie la préservation du contexte le plus récent, qui est presque toujours plus pertinent que les injections plus anciennes.

Estimation des jetons

FIM One utilise une estimation heuristique des jetons plutôt qu’un vrai tokeniseur. C’est un choix délibéré avec des compromis clairs. Pourquoi pas un vrai tokeniseur ? Trois raisons :
  1. Coût de dépendance. tiktoken (le tokeniseur d’OpenAI) représente 15 Mo de liaisons Rust compilées. sentencepiece (utilisé par certains modèles open-source) a ses propres exigences de compilation. Pour un framework qui cible plusieurs fournisseurs de LLM, il n’existe pas de tokeniseur unique correct — chaque famille de modèles en utilise un différent.
  2. Vitesse. L’estimation heuristique est un seul passage sur la chaîne. La vrai tokenisation implique une recherche dans le vocabulaire, des opérations de fusion BPE et la gestion des jetons spéciaux. ContextGuard appelle l’estimation à chaque itération, parfois plusieurs fois — la différence de vitesse compte.
  3. Suffisant. L’heuristique est ajustée pour le texte multilingue (la division ASCII/CJK couvre les deux cas majeurs). Elle peut être 1,5 à 2 fois décalée pour les cas limites (code fortement ponctué, Unicode inhabituel), mais la gestion du contexte est intrinsèquement approximative. Être décalé de 30 % sur un budget de 64 K laisse encore une marge confortable.
Les heuristiques concrètes :
Type de contenuRatioJustification
Texte ASCII~4 caractères/jetonLa prose anglaise et le code font en moyenne 3,5-4,5 caractères/jeton sur les tokeniseurs GPT/Claude
CJK / non-ASCII~1,5 caractères/jetonChaque caractère CJK représente généralement 1-2 jetons ; 1,5 est la moyenne géométrique
Images765 jetons/imageCoût approximatif d’une image encodée en base64 dans l’API de vision
Surcharge par message4 jetonsMarqueur de rôle, délimiteurs, formatage
L’estimation retourne toujours au moins 1 jeton pour un contenu non vide. Cela évite les cas limites de division par zéro dans l’arithmétique du budget.

Ce que l’utilisateur voit

La gestion du contexte est conçue pour être invisible dans les cas courants et minimalement intrusive lorsqu’elle s’active. Les signaux visibles pour l’utilisateur sont : CompactDivider. Quand DbMemory compacte l’historique au chargement, l’interface affiche un séparateur pointillé avec le texte « Earlier context (N messages) was summarized by AI. » Cela apparaît entre le résumé et les messages récents conservés, donnant à l’utilisateur un indice visuel que le contexte ancien a été compressé sans interrompre le flux de la conversation. Affichage de l’utilisation des tokens. La carte done à la fin de chaque réponse affiche « X.Xk in / X.Xk out » — le total des tokens d’entrée et de sortie consommés. Cela inclut les tokens dépensés pour la compaction (les appels LLM rapides pour la résumé). Les utilisateurs qui surveillent la consommation de tokens peuvent voir quand la compaction ajoute une surcharge. Gestion gracieuse des erreurs. Si la gestion du contexte échoue complètement — un scénario qui ne devrait pas se produire compte tenu de la chaîne de secours, mais pourrait théoriquement — l’erreur s’affiche sous forme de texte d’erreur d’agent dans la réponse, et non comme un plantage système. La conversation continue ; l’utilisateur peut réessayer ou reformuler. L’objectif est que la plupart des utilisateurs ne pensent jamais à la gestion du contexte. Ils ont de longues conversations, le système gère le budget de manière transparente, et le seul artefact visible est un séparateur de compaction occasionnel. Pour les utilisateurs avancés et les opérateurs qui se soucient de l’efficacité des tokens, l’affichage de l’utilisation et les paramètres de budget configurables leur fournissent le contrôle dont ils ont besoin.