Context engineering – votre frontmatter est le point de départ
Le context engineering en pratique : comment des métadonnées de frontmatter structurées issues des content collections Astro deviennent une source de contexte précise et budgétée pour un LLM – avec des exemples TypeScript plutôt qu'une recherche plein texte naïve.

Le context engineering est la discipline consistant à remplir la fenêtre de contexte d’un LLM à l’exécution de sorte que le modèle ait devant lui exactement les informations nécessaires à une bonne réponse – ni plus, ni moins. Ça semble abstrait ? Ça ne l’est pas. Les fichiers Markdown dont ce blog est constitué en sont déjà une étude de cas. C’est le fil que suit ce billet.
Le prompt s’arrête où le contexte commence
Le prompt engineering optimise un message : formulation, ton, un bon exemple. Cela suffit pour des tâches bien délimitées. Mais dès qu’un système doit rester fiable à travers des sources de connaissances externes, un historique et des outils, la question passe de « Comment formuler ? » à « Que doit contenir la fenêtre de contexte pour que la requête soit traitable ? » – et sous quelle forme, dans quel ordre, avec quel budget.
Votre frontmatter est déjà du context engineering
Le blog utilise les content collections d’Astro. Le schéma dans content.config.ts n’est rien d’autre qu’un contrat de contexte : il force chaque billet dans une forme structurée et typée.
// src/content.config.ts (extrait)
const blogCollection = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
schema: z.object({
title: z.string(),
lang: z.enum(['de', 'en', 'fr']).default('de'),
translationKey: z.string().optional(),
description: z.string().optional(),
tags: z.array(z.string()).default([]),
publishDate: z.coerce.date(),
draft: z.boolean().default(false),
}),
});Et le frontmatter de ce billet même – c’est déjà du contexte curé et compressé :
---
title: 'Context engineering – votre frontmatter est le point de départ'
description: 'Le context engineering en pratique : comment …'
lang: 'fr'
translationKey: 'context-engineering'
tags: ['tutorial', 'Context Engineering', 'prompting', 'KI', 'LLM']
---title, description et tags compressent un article de 900 mots en ~30 tokens. translationKey relie les variantes linguistiques. lang permet le filtrage. Rien de tout cela n’est fortuit – c’est exactement la matière première dont une fonctionnalité LLM a besoin.
Des métadonnées structurées valent mieux que le texte intégral
Supposons que vous vouliez une fonction « billets similaires » ou un chat sur votre blog. Le réflexe naïf : embedder chaque corps MDX et tout balancer via recherche vectorielle. Faisons le calcul :
| Approche | Contexte par billet | 15 billets |
|---|---|---|
| Corps Markdown complet | ~1 200 tokens | ~18 000 tok |
| Frontmatter seul (titre + desc) | ~30 tokens | ~450 tok |
Un facteur 40. Et ces 450 tokens ne sont pas seulement moins chers mais plus précis : pas de boilerplate, pas de bruit, pas de dilution. Pour répondre à « quels billets correspondent à X ? », le modèle n’a pas besoin de prose – il lui faut l’essence curée qui figure déjà dans le frontmatter.
Assembler le contexte dans le code
Voici un assemblage de contexte budgété directement sur les content collections – sans base vectorielle, uniquement avec ce que le frontmatter fournit :
import { getCollection } from 'astro:content';
// Heuristique grossière mais exploitable : ~4 caractères par token.
const estimateTokens = (s: string) => Math.ceil(s.length / 4);
export async function buildBlogContext(query: string, budget = 1500) {
const posts = await getCollection('blog', ({ data }) => !data.draft && data.lang === 'fr');
// 1. Classer par recouvrement de tags – simple, transparent, sans embeddings.
const terms = new Set(query.toLowerCase().split(/\W+/).filter(Boolean));
const ranked = posts
.map((post) => ({
post,
score: post.data.tags.filter((t) => terms.has(t.toLowerCase())).length,
}))
.filter((r) => r.score > 0)
.sort((a, b) => b.score - a.score);
// 2. Construire le contexte UNIQUEMENT à partir du frontmatter – et respecter le budget.
const lines: string[] = [];
let used = 0;
for (const { post } of ranked) {
const line = `- ${post.data.title} [${post.data.tags.join(', ')}]: ${post.data.description ?? ''}`;
const tokens = estimateTokens(line);
if (used + tokens > budget) break; // une limite dure, pas un « on verra »
lines.push(line);
used += tokens;
}
return lines.join('\n');
}Les lignes décisives ne sont pas le classement mais if (used + tokens > budget) break. Le context engineering, c’est pour moitié retirer.
Dédupliquer les traductions
Chaque billet existe trois fois (de/en/fr) – même contenu, même translationKey. Sans filtrage, vous serviriez au modèle la même information trois fois : triple coût, zéro valeur ajoutée. Le frontmatter résout cela aussi :
function dedupeByTranslation<T extends { data: { translationKey?: string }; id: string }>(posts: T[]): T[] {
const seen = new Set<string>();
return posts.filter((p) => {
const key = p.data.translationKey ?? p.id;
if (seen.has(key)) return false;
seen.add(key);
return true;
});
}La structure vaut mieux que le texte continu : délimiteurs & ordre
Une fois le contexte assemblé, c’est l’emballage qui décide. Les modèles séparent instruction et données bien plus fiablement quand les deux sont clairement balisés – ce qui réduit au passage le risque d’injection de prompt depuis le contenu récupéré :
const prompt = `
<instructions>
Réponds à la question EN UTILISANT UNIQUEMENT <context>.
Si l'information y manque, dis-le explicitement – ne devine pas.
</instructions>
<context>
${await buildBlogContext(userQuery)}
</context>
<question>
${userQuery}
</question>
`;Deux principes y sont inscrits :
- Délimiteurs (
<context>,<question>) séparent proprement les blocs de données. - Positionnement : instructions tôt, question concrète tard. Les modèles prêtent attention au début et à la fin plus fiablement qu’au milieu – l’effet « lost in the middle » est mesurable, donc placez le critique aux extrémités.
Un budget de tokens plutôt que « tout mettre »
Pourquoi budgéter alors que les fenêtres sont énormes ? Parce que plus de contexte dégrade trois choses à la fois :
- Coût et latence croissent linéairement avec chaque token.
- Dilution – plus il y a d’éléments non pertinents, plus le modèle peine à pondérer l’essentiel.
- Chute du rappel – au-delà d’un certain remplissage, le taux de succès sur les faits situés au milieu du contexte baisse nettement.
Un budget dur comme ci-dessus (budget = 1500) n’est donc pas de la pingrerie, mais un gage de qualité.
Pour les agents : charger le contexte dynamiquement
Avec les agents à plusieurs étapes, résultats d’outils et étapes intermédiaires s’accumulent. Plutôt que tout charger d’avance, donnez au modèle un outil qui ne récupère que le corps d’un billet précisément choisi – frontmatter d’abord, texte intégral seulement à la demande :
// Définition d'outil : décider via le frontmatter d'abord, puis charger sélectivement.
const getPostBody = {
name: 'get_post_body',
description: "Charge le texte intégral d'un billet de blog par son id.",
input_schema: {
type: 'object',
properties: { id: { type: 'string' } },
required: ['id'],
},
};Ainsi la fenêtre reste légère : le modèle ne voit d’abord que l’aperçu compact du frontmatter et ne récupère le texte intégral coûteux que pour le seul billet dont il a réellement besoin (retrieval just-in-time). En complément, les scratchpads conservent les résultats intermédiaires hors de la fenêtre et les sous-agents encapsulent des sous-tâches avec leur propre contexte frais.
Erreurs fréquentes
- Trop de contexte – « au cas où, on met tout » dégrade à la fois qualité, coût et latence.
- Traductions non dédupliquées – la même info en de/en/fr, trois fois dans la fenêtre.
- Instruction et données mélangées – sans délimiteurs, on risque l’injection de prompt et le modèle confond instruction et contenu.
- Texte intégral là où les métadonnées suffisent – le frontmatter est souvent la meilleure source, la moins chère.
- Position ignorée – enterrer une information critique au milieu d’un long contexte.
Conclusion
Le context engineering est le passage de « bien demander » à « bien informer ». Et le point de départ est plus proche qu’on ne le croit : si vous structurez proprement votre contenu – schéma typé, description parlante, tags précis, translationKey stables –, vous avez déjà fait la moitié du travail. Le frontmatter que vous entretenez de toute façon est la source de contexte la plus compacte que vous possédez. Le reste, c’est de l’assemblage : classer, dédupliquer, budgéter, structurer.
À rapprocher : l’étude de cas du wiki LLM à la Karpathy montre le même principe à grande échelle – des notes structurées et accumulées plutôt qu’une recherche vectorielle naïve.