nathanrenting.dev
Pattern · routage LLM

Routage LLM multi-cerveaux

Tout faire passer par Sonnet coûte plus cher que nécessaire. Tout faire passer par Haiku casse dès que ça devient un peu plus qu'un bonjour. Quelque part entre les deux, tu veux un routeur qui choisit, par requête, le modèle adapté. Ma préférence : des heuristiques peu coûteuses d'abord, un classifieur LLM en fallback — pas par défaut.

Croquis dessiné à la main du routeur : REQUEST va vers HEURISTICS (greet / keyword / deep / shell), en l'absence de correspondance il passe au CLASSIFIER (petit LLM), et aboutit à un choix de palier FAST / MAIN / THINK.

Croquis de tableau blanc · le flux de routage

La forme

async def decide(text: str, *, force: Brain | None = None) -> RouterDecision:
    if force is not None:
        return RouterDecision(force, "manual", "user-override")

    # Couche 1 : heuristiques regex bon marché (microsecondes, gratuit)
    h = _heuristic(text)
    if h is not None:
        return h

    # Couche 2 : classifieur LLM (Haiku, ~30 tokens d'entrée, sub-second)
    return await _llm_classify(text)

La couche 1 intercepte le gros du trafic gratuitement. « Salut » part vers le palier fast. Tout ce qui touche un mot-clé pour l'usage d'outils, le raisonnement profond ou un domaine spécifique part directement vers le bon palier. La couche 2 ne se déclenche que lorsque les heuristiques n'ont sincèrement aucune idée.

Le gain n'est pas dans une heuristique astucieuse. Il est dans la structure en couches : bon marché d'abord, cher seulement quand il le faut, les deux visibles via le même objet RouterDecision pour que tu puisses voir après coup exactement ce qui a été choisi et pourquoi.

Ce que les heuristiques interceptent

Quatre catégories couvrent en général 70 à 80 % des décisions :

  1. Bonjours courts et questions sur l'heure — des entrées de moins de 20 caractères qui matchent un petit jeu de regex. Vers le palier le moins cher et le plus rapide. Quelqu'un qui dit « salut » n'a pas besoin de Sonnet.

  2. Mots-clés de domaine — des termes qui pointent vers un produit ou un contexte précis. Vers le palier qui charge le bon contexte de system-prompt. Essentiel dans un orchestrateur multi-produits — sinon l'agent se met à raisonner depuis le mauvais contexte.

  3. Mots-clés de raisonnement profond — « design », « architect », « refactor », « review », « approfondi ». Vers le haut, vers Opus / Sonnet 4.6 / quel que soit ton palier le plus élevé. Bon marché à détecter, cher à manquer.

  4. Signaux d'usage d'outils — chemins de fichiers, verbes shell (« scan », « check », « lis »), marqueurs de code-fence. Vers le palier où les outils shell sont disponibles.

Cette dernière est un tueur silencieux. Sans cette vérification, un petit modèle écrit la commande shell en Markdown au lieu d'appeler l'outil. Le correctif se situe sur la couche de routage, pas dans le prompt.

Ce que couvre le classifieur

Quand les heuristiques renvoient None, tu sollicites un petit LLM (classe Haiku) avec une instruction fixe :

You are a routing classifier. Given a user message, output exactly ONE word:
- 'fast' for trivial greetings, time questions, simple confirmations
- 'main' for normal conversation, document Q&A, summaries, smart-home
- 'deep' for multi-step reasoning, code review, complex analysis
Output ONLY the single word, nothing else.

Coût par classification : quelques dixièmes de centime. Latence : moins d'une seconde. Robustesse : élevée — Haiku est assez cohérent sur un choix à trois voies comme celui-ci pour que tu n'aies pas besoin d'un modèle plus grand.

Un petit gain mais bien réel : dis au classifieur de pencher vers main en cas de doute plutôt que vers fast. Une requête triviale via main coûte peu de plus. Une requête réclamant un outil qui part par erreur vers fast (et n'appelle ensuite pas l'outil) coûte bien plus cher.

Quand tu n'as pas besoin d'un routeur

Si chaque requête qui arrive a à peu près la même forme — un chatbot qui fait une seule chose, par exemple — c'est surdimensionné. Le routeur se rentabilise dès que :

Deux de ces quatre sont vrais ? Alors le routeur se rentabilise en une semaine.

L'observabilité compte plus que le routeur

Renvoie un objet RouterDecision structuré avec le palier choisi, la couche qui a décidé (heuristic / haiku / fallback), la raison et le temps écoulé. Ça rend l'ensemble inspectable. Après 200 requêtes, tu fais défiler le log et tu vois exactement où les heuristiques se trompent, où le classifieur hésite, et où tu paies pour main alors que fast aurait suffi.

Sans ce log, le routeur devient une boîte noire que tu finis par jeter parce que « c'est la faute du routeur ». Avec ce log, le routeur est un bouton que tu ajustes.