Lost in the Middle : pourquoi ton LLM ignore ce que tu lui donnes

Lost in the Middle : pourquoi ton LLM ignore ce que tu lui donnes

Article · 8 min de lecture
🇬🇧 Cet article est aussi disponible en English

Un million de tokens de contexte. C’est le chiffre qu’on cite désormais pour les grands modèles du marché. C’est le chiffre qui donne confiance : tu peux charger des dizaines de fichiers, des conversations entières, toute la documentation d’un projet. Le modèle aura “tout vu”.

Le problème, c’est que “avoir vu” et “avoir utilisé” ne sont pas synonymes.

Le contexte long est une illusion confortable

Scénario concret. Tu passes à un LLM un contexte de 80 000 tokens : des logs d’application, quelques fichiers de configuration, la documentation de l’API concernée. L’information critique (la valeur de timeout qui explique tout) se trouve à peu près au milieu. Le modèle te rend une réponse. Elle est confiante, bien formulée, techniquement cohérente. Elle est fausse.

Pas d’hésitation dans la réponse, pas de “je n’ai pas trouvé l’information dans le contexte fourni”. Le modèle a comblé les trous avec ce qu’il savait déjà, et il n’a pas marqué la différence.

C’est ça le vrai problème. Pas que le modèle échoue : c’est qu’il échoue sans signal d’erreur.

La fenêtre de contexte d’un LLM n’est pas un buffer de RAM où chaque octet a le même poids. C’est une distribution d’attention sur des tokens, et cette distribution n’est pas uniforme. Elle ne l’a jamais été. Les modèles lisent différemment selon où l’information se trouve dans le contexte, et on peut maintenant le mesurer précisément.

La mesure du phénomène : deux études, même conclusion

Liu et al. (2023) : la courbe en U

L’étude “Lost in the Middle” de Liu et al. (TACL, 2023) est devenue une référence sur le sujet. Le protocole : des tâches de Q&A multi-documents et de key-value retrieval, où la position du document pertinent dans le contexte varie systématiquement.

Le résultat est une courbe en U. Les performances sont maximales quand l’information est au début du contexte, ou à la fin. Quand elle est au milieu, elles s’effondrent, proportionnellement à la longueur du contexte.

graph LR
    D["📍 Début"] -->|"~75-85%"| P1(( ))
    M["📍 Milieu"] -->|"~42-55%"| P2(( ))
    F["📍 Fin"] -->|"~72-80%"| P3(( ))

    P1 --> PERF["Performance"]
    P2 --> PERF
    P3 --> PERF

    style P1 fill:#22c55e,stroke:#22c55e
    style P2 fill:#ef4444,stroke:#ef4444
    style P3 fill:#22c55e,stroke:#22c55e

Ce n’est pas un bug d’un modèle particulier. L’étude couvre plusieurs architectures. La courbe en U apparaît systématiquement.

Boytsov et al. : FirstP, le benchmark humiliant

Boytsov et al. (arXiv:2207.01262) testent plus de 20 modèles (dont GPT-4o-mini et Claude Haiku-3) sur une tâche simple : trouver l’information pertinente dans un long document.

Ils comparent ces modèles à une baseline qu’ils appellent FirstP : couper le document après les 512 premiers tokens et ignorer le reste. Pas de long-context, pas de fenêtre étendue, juste les 512 premiers tokens.

Résultat : aucun modèle long-context ne fait mieux que FirstP de façon significative. Écart maximum : 5%.

Pourquoi ? Parce que dans les données d’entraînement de ces modèles, l’information pertinente se trouve presque toujours dans les premiers tokens des documents. Les modèles ont appris à concentrer leur attention sur le début, et cette habitude persiste même quand on leur donne 100 000 tokens.

Attention

Un modèle avec 1M tokens de contexte disponibles peut être statistiquement équivalent à un modèle qui lit seulement les 512 premiers tokens. C’est le résultat de Boytsov et al. sur des benchmarks de ranking.

Pourquoi c’est structurel

Le biais positionnel n’est pas un bug d’implémentation. Il a deux causes architecturales distinctes, identifiées et formalisées dans la littérature récente.

L’attention causale accumule du poids sur les premiers tokens. Dans un LLM, chaque token ne peut voir que les tokens qui le précèdent. Le token en position 1 est visible par tous les tokens suivants : il cumule de l’attention sur toute la longueur du contexte. Un token au milieu d’un contexte de 500 000 tokens n’est visible que par la moitié d’entre eux. Ce n’est pas un choix du modèle. C’est la mécanique de l’attention causale, par construction.

RoPE crée une zone morte au milieu. La plupart des modèles modernes utilisent Rotary Position Embedding (RoPE) pour encoder la position des tokens. RoPE introduit une décroissance des scores d’attention proportionnelle à la distance entre les tokens : plus deux tokens sont éloignés, moins ils s’influencent. Un token en fin de contexte bénéficie de la proximité avec les tokens adjacents (recency), et les tokens en tout début gardent une saillance particulière (primacy). Un token au milieu ? Trop loin du début pour la primacy, trop loin de la fin pour la recency. Zone morte.

Il y a une troisième dimension, plus formelle. Le softmax (la fonction qui normalise les scores d’attention en probabilités) distribue toujours la masse de probabilité sur l’ensemble des tokens visibles. Vasylenko et al. (2026) prouvent que l’entropie de cette distribution tend mathématiquement vers O(log n) quand n grandit : les poids convergent vers l’uniforme. En clair, plus le contexte est long, plus l’attention se dilue. Les signaux faibles se noient dans le bruit statistique.

graph LR
    subgraph CTX["Fenêtre de contexte"]
        D["📍 Début"]
        M["📍 Milieu"]
        F["📍 Fin"]
    end

    subgraph EFF["Effets positionnels"]
        E1["① Attention causale\nVisible par tous les tokens suivants"]
        E2["② RoPE decay\nZone morte - trop loin\ndu début ET de la fin"]
        E3["③ Softmax dilution\nProximité recency\n→ score préservé"]
    end

    D --> E1
    M --> E2
    F --> E3

    E1 --> OK1(["✓ Signal fort"])
    E2 --> KO(["✗ Signal noyé"])
    E3 --> OK2(["✓ Signal fort"])

    style D fill:#1e1e2e,stroke:#6b7280,color:#e2e8f0
    style M fill:#1e1e2e,stroke:#6b7280,color:#e2e8f0
    style F fill:#1e1e2e,stroke:#6b7280,color:#e2e8f0
    style E1 fill:#22c55e,stroke:#22c55e,color:#000
    style E2 fill:#ef4444,stroke:#ef4444,color:#fff
    style E3 fill:#22c55e,stroke:#22c55e,color:#000
    style OK1 fill:#22c55e,stroke:#22c55e,color:#000
    style KO fill:#ef4444,stroke:#ef4444,color:#fff
    style OK2 fill:#22c55e,stroke:#22c55e,color:#000

Ces trois effets se combinent. Ce n’est pas une question de capacité du modèle. Les 1M tokens n’effacent pas le biais, ils l’amplifient. Plus le contexte est long, plus la probabilité que l’information critique tombe dans une zone mal couverte est élevée.

Et le problème fondamental reste le même : tu ne sais pas ce que le modèle a utilisé pour construire sa réponse. Il n’y a pas de log d’attention, pas de trace des tokens réellement influents. La réponse arrive, confiante, sans que tu aies aucun moyen de savoir si elle repose sur l’information que tu as fournie ou sur les priors d’entraînement.

C’est une propriété fondamentale du non-déterminisme des LLMs. Le contexte surchargé n’est pas transparent. Il est opaque.

Ce que ça casse en pratique

Deux patterns qui semblent raisonnables mais qui deviennent fragiles dès qu’on intègre ce biais.

Charger un agent de beaucoup d’informations pour l’autonomiser. L’idée : si l’agent a accès à tout, il peut décider de lui-même ce qui est pertinent. En pratique, l’agent va surpondérer ce qui est en début de contexte, ignorer ce qui est au milieu, et combler les lacunes avec ses priors d’entraînement. Il sera autonome, mais pas fiable.

Multiplier les capacités dans un seul système. Regrouper dans un seul agent la capacité à lire du code, analyser des logs, appeler des APIs, interpréter des spécifications, et lui passer tout le contexte disponible à chaque fois. Le résultat : un système dont le comportement dépend de l’ordre dans lequel les informations arrivent dans le contexte, ce qui n’est presque jamais sous ton contrôle complet.

Dans les deux cas, tu délègues la priorisation de l’information au modèle. Et le modèle fait cette priorisation selon des critères positionnels que tu ne maîtrises pas.

La réponse : des contextes ciblés, pas plus larges

L’intuition qui découle naturellement des études : si le modèle est meilleur sur les informations en début de contexte, mets les informations importantes en début de contexte.

Ça aide. Ça ne résout pas le fond du problème.

La vraie réponse, c’est de réduire la charge cognitive par agent, pas de réorganiser ce qu’on met dedans. Chaque agent doit savoir exactement ce qu’il sait, et uniquement ça.

graph TB
    subgraph "Fat context - un seul agent"
        FC_IN["Tous les fichiers<br/>Tous les logs<br/>Toute la doc<br/>Toutes les specs"] --> FC_A["Agent unique"]
        FC_A --> FC_OUT["Réponse (opaque)"]
    end

    subgraph "Contextes ciblés - orchestration"
        ORCH["Orchestrateur"] --> A1["Agent A<br/>(contexte : logs seuls)"]
        ORCH --> A2["Agent B<br/>(contexte : code seul)"]
        ORCH --> A3["Agent C<br/>(contexte : specs seules)"]
        A1 --> ORCH
        A2 --> ORCH
        A3 --> ORCH
        ORCH --> OUT["Synthèse"]
    end

Dans le modèle orchestré, chaque agent a un contexte petit, précis, dont tu contrôles le contenu. La position de l’information dans ce contexte est déterministe. L’orchestrateur synthétise les résultats : il reçoit des conclusions, pas des masses de données brutes.

Ce n’est pas juste une bonne pratique d’architecture. C’est une réponse directe au biais positionnel. Si le contexte est assez court et ciblé pour que toute l’information utile soit en zone haute-attention, le problème disparaît.

Conseil

La question à se poser : est-ce que je sais précisément ce que mon agent a comme contexte ? Si la réponse est “oui, et c’est court”, le biais positionnel est maîtrisé. Si la réponse est “il a accès à beaucoup de choses”, tu as un problème de fiabilité latent.

Une implémentation concrète : le pattern team-lead

Le pattern team-lead est une illustration directe de ce principe. L’orchestrateur, le “team lead”, ne lit jamais le code directement. Il reçoit des résumés courts des agents spécialisés, synthétise, et donne des directives.

Les agents spécialisés ont des contextes chirurgicaux : un fichier, une fonction, un périmètre bien défini. Ils n’ont pas besoin de connaître l’état global du système pour faire leur travail correctement. Et les reviewers arrivent sans contexte de production. Ils n’ont pas écrit le code, ils n’ont pas d’investissement dans les choix faits. Ce qui élimine un autre biais : la familiarité.

L’intérêt architectural : le scratchpad de l’orchestrateur peut être déterministe (une liste de conclusions factuelles) là où un résumé automatique généré par le modèle serait probabiliste. On contrôle ce que l’orchestrateur “sait” à chaque étape.

Ce pattern est documenté en détail dans cet article dédié.

Ce que ça implique pour concevoir tes systèmes

La question à garder en tête quand tu conçois un système qui implique des LLMs : est-ce que je sais ce que mon modèle a utilisé pour répondre ?

Si la réponse est non, si tu ne peux pas tracer quelles parties du contexte ont influencé la réponse, tu as une boîte noire. Ce n’est pas forcément un problème pour un assistant conversationnel. C’en est un pour un système où la fiabilité compte.

La taille du contexte n’est pas un proxy de la qualité. Un contexte de 2 000 tokens soigneusement sélectionnés vaut mieux qu’un contexte de 200 000 tokens injecté en vrac. Les études le confirment. FirstP le confirme presque ironiquement : dans beaucoup de cas, lire seulement le début suffit.

Ce n’est pas une limite qu’on va corriger dans la prochaine version des modèles. C’est une propriété structurelle de l’attention sur les longs contextes. Concevoir des systèmes fiables avec des LLMs, c’est concevoir des systèmes qui ne dépendent pas de la capacité du modèle à s’y retrouver dans une masse d’informations, parce que cette capacité est plus limitée qu’elle n’en a l’air.

← Retour aux articles