
Je suis Jules Ginhac, Co-Founder & ingénieur IA chez Apogée Consult à Lyon. Je conçois et déploie des architectures IA génératives (RAG, agents, LLMOps) pour des PME, startups et organisations publiques.
Quand le prompt engineering atteint ses limites et qu'il faut accepter d'écrire du code
- ia-produit
Le prompt engineering est puissant mais il a des limites structurelles. À un moment, continuer à itérer sur le prompt coûte plus cher que d'écrire le code qui manque.
Quand le prompt engineering atteint ses limites et qu'il faut accepter d'écrire du code
Il y a un stade dans tout projet LLM où on passe plus de temps à raffiner le prompt qu'à construire le produit. On ajoute des instructions, on reformule, on ajoute des exemples, on teste, on observe que ça améliore un cas et casse un autre. On est dans une boucle de tuning sans fin qui ressemble de moins en moins à de l'ingénierie et de plus en plus à de la prestidigitation.
Ce n'est pas que le prompt engineering soit une mauvaise approche. C'est qu'il y a des classes de problèmes que les prompts ne peuvent pas résoudre de façon fiable, et qu'accepter cette limite plus tôt évite beaucoup de temps perdu.
Ce que le prompt engineering résout bien
Soyons honnêtes sur les forces avant de parler des limites.
Le prompt engineering est efficace pour :
- Cadrer le ton, le style et le format de sortie
- Contraindre le périmètre de réponse ("ne réponds qu'aux questions sur X")
- Injecter du contexte métier sans fine-tuning
- Guider le raisonnement avec des exemples (few-shot)
- Décomposer une tâche complexe en étapes (chain-of-thought)
Sur ces axes, un bon prompt peut faire une différence de qualité significative. On a des cas où passer d'un prompt vague à un prompt structuré avec rôle, contexte, contraintes et exemples a multiplié par deux le taux de réponses exploitables sans changer le modèle.
Le signal d'alarme : le prompt devient une base de règles
Quand on commence à écrire des phrases du type "Si l'utilisateur demande X, alors fais Y, mais si X contient aussi Z, alors fais W sauf si...", c'est le signal qu'on est en train d'écrire de la logique conditionnelle en langage naturel.
La logique conditionnelle en langage naturel est fragile par nature. Le modèle l'interprète, il ne l'exécute pas. Et cette interprétation varie selon le contexte, la formulation de la requête utilisateur, et des facteurs qui ne sont pas entièrement prévisibles.
Exemple réel : on avait un agent de routage qui devait rediriger les requêtes vers quatre handlers différents selon le type de demande. On a commencé avec un prompt qui décrivait les quatre cas. Puis on a ajouté des exceptions. Puis des règles de priorité entre les exceptions. Le prompt faisait 800 tokens. Le taux d'erreur de routage était à 8%, acceptable mais pas satisfaisant.
On a remplacé ce prompt par 40 lignes de Python :
from enum import Enum
import re
class RequestType(Enum):
FACTUAL = "factual"
CREATIVE = "creative"
STRUCTURED = "structured"
AMBIGUOUS = "ambiguous"
def classify_request(message: str) -> RequestType:
# Règles déterministes en premier
if re.search(r'\b(calcule|combien|quelle date|quel prix)\b', message.lower()):
return RequestType.FACTUAL
if re.search(r'\b(rédige|écris|génère|crée)\b', message.lower()):
if re.search(r'\b(tableau|liste|json|csv|structure)\b', message.lower()):
return RequestType.STRUCTURED
return RequestType.CREATIVE
# Fallback LLM seulement pour les cas ambigus
return classify_with_llm(message)
def classify_with_llm(message: str) -> RequestType:
# Appel LLM uniquement pour les cas non couverts par les règles
...Le taux d'erreur est passé à 1.2%. Les règles déterministes couvrent 80% des cas. Le modèle ne traite que les 20% ambigus.
Les trois limites structurelles du prompt engineering
Limite 1 : la fiabilité sur les tâches structurées
Extraire des données dans un format précis, JSON valide avec des champs obligatoires et des types corrects, est une tâche où le prompt seul n'est pas suffisamment fiable pour la production.
Le modèle hallucine parfois des champs, omet des propriétés required, ou génère du JSON avec des erreurs de syntaxe sur des cas limites.
La solution n'est pas un meilleur prompt. C'est une validation côté code et une structure de retry avec feedback d'erreur :
from pydantic import BaseModel, ValidationError
import json
class ExtractedData(BaseModel):
name: str
date: str
amount: float
currency: str
async def extract_with_retry(text: str, max_retries: int = 2) -> ExtractedData:
error_context = ""
for attempt in range(max_retries):
response = await call_llm(text, error_context)
try:
data = json.loads(response)
return ExtractedData(**data)
except (json.JSONDecodeError, ValidationError) as e:
error_context = f"\nTentative précédente invalide : {str(e)}"
raise ExtractionError("Extraction échouée après retries")Limite 2 : la mémoire et l'état dans les workflows longs
Un prompt ne peut pas maintenir un état entre plusieurs appels. Si un workflow nécessite de mémoriser des décisions intermédiaires, de modifier un état partagé, ou de brancher selon des conditions qui se construisent sur plusieurs tours, le prompt seul ne suffit pas.
La réponse est un orchestrateur en code qui gère l'état explicitement et appelle le LLM uniquement pour les parties qui nécessitent du langage naturel ou du raisonnement.
Limite 3 : la testabilité
Un prompt est difficile à tester de façon automatisée. On peut écrire des assertions sur les sorties, mais les sorties varient légèrement d'un appel à l'autre même avec temperature=0. On ne peut pas écrire un test unitaire sur un prompt comme on l'écrirait sur une fonction.
En revanche, une fonction Python qui implémente une règle de routage est entièrement testable. On peut couvrir tous les cas limites avec des tests déterministes, faire tourner ces tests en CI, et détecter les régressions immédiatement.
Le bon split prompt/code
Ce n'est pas un choix binaire. La bonne architecture est un pipeline où le code gère ce qui est déterministe et le LLM traite ce qui nécessite du langage naturel, de la compréhension sémantique, ou de la génération.
Un principe simple : si le comportement attendu peut être décrit par une règle métier précise, écrivez du code. Si le comportement attendu nécessite de l'interprétation, utilisez un LLM.
La pression à tout mettre dans le prompt vient souvent d'une sur-estimation des capacités du prompt engineering et d'une sous-estimation du coût de maintenance d'un prompt complexe. Un prompt de 1 500 tokens avec des dizaines de règles imbriquées est aussi difficile à maintenir qu'un module de code, sauf qu'il n'a pas de tests, pas de types, et pas de comportement garanti.
La question qu'on se pose maintenant avant d'allonger un prompt : est-ce que cette règle pourrait être exprimée en code de façon plus fiable ? Si oui, on écrit le code.