Complète les 3 phrases initiales de TEST_SENTENCES avec 4 cas cherchant
volontairement le point de rupture (au lieu de juste confirmer le cas
courant) : actions simultanées plutôt que séquentielles ("pendant que..."),
action conditionnelle noyée dans des actions fermes, négation explicite
d'action ("sans jamais laisser bouillir"), fin de cuisson par état/test de
résultat plutôt que par durée, et un champ température qui désigne un seuil
de cuisson à cœur plutôt qu'un réglage de feu. README mis à jour (7 phrases,
4 FR + 3 EN).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
110 lines
5.2 KiB
Markdown
110 lines
5.2 KiB
Markdown
# 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`](https://node-llama-cpp.withcat.ai/), avec sortie JSON
|
|
strictement contrainte par un schéma (GBNF grammar), sur trois axes :
|
|
précision, robustesse multilingue FR/EN, latence.
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
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"](https://node-llama-cpp.withcat.ai/guide/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". |
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
pnpm bench
|
|
```
|
|
|
|
Charge le modèle, puis lance 3 répétitions sur chacune des 7 phrases de test
|
|
(4 FR + 3 EN, voir `TEST_SENTENCES` dans
|
|
[`src/llm-tech-step-poc.ts`](./src/llm-tech-step-poc.ts)) — les 3 premières
|
|
couvrent le cas courant (actions enchaînées, dont le cas piège documenté dans
|
|
`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`),
|
|
les 4 suivantes poussent délibérément plus loin pour chercher le point de
|
|
rupture : actions simultanées plutôt que séquentielles, action conditionnelle
|
|
("if the batter looks too thick..."), négation explicite d'action ("sans
|
|
jamais laisser bouillir"), fin de cuisson par état/test de résultat plutôt
|
|
que par durée fixe, et un champ température qui désigne un seuil de cuisson
|
|
à cœur plutôt qu'un réglage de feu. 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 :
|
|
|
|
```ts
|
|
// 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 7 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_SCHEMA` dans 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 (`gpuLayers` dans les
|
|
options `loadModel`) si la cible dispose d'un GPU.
|