Fichier TypeScript autonome (hors du workspace pnpm) qui compare le pipeline node-nlp existant (tech-step-matcher.ts) à un mini LLM instruct local via node-llama-cpp : sortie JSON strictement contrainte par schéma (grammaire GBNF, createGrammarForJsonSchema), interfaces RecipeStepAnalysis/ KitchenAction, recommandation de modèle (Qwen2.5-1.5B-Instruct Q4_K_M par défaut, Llama-3.2-1B-Instruct Q4_K_M en alternative), et un benchmark simple (performance.now() + delta RSS) sur 3 phrases complexes FR/EN, dont le cas piège sans verbe littéral déjà documenté dans tech-step-matcher.ts. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
4.8 KiB
PoC — détection d'actions culinaires par mini LLM local
Expérimentation autonome, hors du monorepo pnpm (pnpm-workspace.yaml ne
référence que apps/*/packages/*) : ce dossier a son propre
package.json/tsconfig.json et ne pollue ni les dépendances ni le build
Docker de apps/api.
Objectif : comparer, sur la même tâche (structurer une étape de recette en
séquence ordonnée d'actions), le pipeline node-nlp déjà en place
(apps/api/src/lib/recipe-matching/tech-step-matcher.ts —
TechStepClassifierService) à un mini LLM instruct tournant 100 % en local
via node-llama-cpp, avec sortie JSON
strictement contrainte par un schéma (GBNF grammar), sur trois axes :
précision, robustesse multilingue FR/EN, latence.
Installation
cd experiments/llm-tech-step-poc
pnpm install
node-llama-cpp télécharge/compile son binding natif llama.cpp à
l'installation (binaire prébuilt pour les plateformes courantes, sinon
compilation locale — nécessite alors un toolchain C++, voir sa doc
"Troubleshooting"
en cas d'échec).
Modèle
Le script télécharge automatiquement (une seule fois, mis en cache dans
experiments/llm-tech-step-poc/models/, jamais commité) le GGUF choisi via
LLM_TECH_STEP_MODEL :
| Valeur (défaut en gras) | Modèle | Pourquoi |
|---|---|---|
qwen2.5-1.5b |
Qwen2.5-1.5B-Instruct, Q4_K_M |
Meilleure robustesse multilingue FR/EN et meilleur suivi d'instructions de structuration JSON à taille comparable — recommandation par défaut vu que "robustesse FR/EN" est un critère explicite de ce PoC. |
llama-3.2-1b |
Llama-3.2-1B-Instruct, Q4_K_M |
~35 % de paramètres en moins (plus rapide/plus léger), FR officiellement supporté, mais structuration JSON moins fiable à 1B — utile en comparaison "latence d'abord". |
LLM_TECH_STEP_MODEL=llama-3.2-1b pnpm bench
Hors-ligne / CI : LLM_TECH_STEP_MODEL_PATH=/chemin/vers/un.gguf pnpm bench
pointe directement vers un fichier déjà téléchargé, sans passer par la
résolution/téléchargement Hugging Face.
Lancer le benchmark
pnpm bench
Charge le modèle, puis lance 3 répétitions sur chacune des 3 phrases de test
(2 FR + 1 EN, voir TEST_SENTENCES dans
src/llm-tech-step-poc.ts — l'une d'elles est
délibérément le cas piège documenté dans le commentaire de
tech-step-matcher.ts lui-même : "jusqu'à ce que le beurre ait disparu dans
la poêle", aucun verbe de cuisson littéral, seul le sens implique COOK).
Imprime, par phrase : le JSON détaillé de chaque action détectée, puis un
tableau récapitulatif (latence moyenne/min/max, delta RSS moyen, nombre
d'actions détectées).
Méthodologie de comparaison avec le pipeline node-nlp
Ce script reste volontairement autonome (aucune dépendance vers apps/api,
donc pas de connexion Postgres requise pour le faire tourner). Pour comparer
manuellement sur les mêmes phrases :
// Dans apps/api, un script ponctuel (ou un REPL tsx) :
import { techStepClassifier } from "./src/lib/recipe-matching/tech-step-matcher.js";
console.log(await techStepClassifier.matchTechStepSpans(
"Émincez finement les oignons puis faites-les revenir 10 minutes à feu moyen dans une poêle avec un filet d'huile d'olive, puis réservez.",
"fr",
));
(nécessite une base Postgres accessible et TechStep seedée — voir
apps/api/prisma/seed.ts — puisque matchTechStepSpans résout ses uid
vers de vrais TechStep.id).
Les deux sorties ne sont pas directement isomorphes (TechStepMatch renvoie
un techStepId + des spans de caractères contre un KitchenAction
structuré avec ingrédients/durée/température/ustensiles) — la comparaison
porte sur : le nombre d'actions/techniques détectées par phrase, si la
catégorie/technique choisie est correcte, et le comportement sur la phrase
piège FR sans verbe littéral.
Limites de ce PoC
- Pas de jeu d'évaluation étiqueté ni de métrique de précision automatisée — les 3 phrases sont inspectées à l'œil, pas notées.
- La grammaire GBNF ne garantit qu'une syntaxe JSON conforme au schéma,
jamais la justesse sémantique du contenu (catégorie choisie, durée
correctement extraite...) — voir le commentaire sur
KITCHEN_ACTION_JSON_SCHEMAdans le script. - Le delta de RSS process est une approximation de la RAM réellement utilisée par l'inférence (le binding natif alloue dans le même process, donc le RSS la capture, mais au bruit du GC/de l'allocateur près) — pas une mesure isolée du seul processus llama.cpp.
- Latence mesurée en CPU pur (pas de configuration GPU dans ce PoC) — un
déploiement réel voudrait évaluer l'offload GPU (
gpuLayersdans les optionsloadModel) si la cible dispose d'un GPU.