bots-garden/mini-mepublic Fork
⑂ fix/tool-order
Commits
⬇ Clone ▾
git clone https://git.rickub.com/bots-garden/mini-me.git
git clone ssh://git@rickub.com/bots-garden/mini-me.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP
🛟 Updated. 015bd7a · on fix/tool-order · k33g · 23d ago
providers.md · 38 lines · 6.8 KBmarkdown
Blame HistoryOpen raw

Fournisseurs : une implémentation par protocole — explication

De quoi s'agit-il ?

mm dialogue avec un serveur de modèle local via l'API chat-completions d'OpenAI. Deux serveurs sont pris en charge d'origine, Docker Model Runner et llama-server de llama.cpp, sous les noms dmr et llamacpp. Le paquet moteur était autrefois lié à Docker Model Runner ; l'abstraction de fournisseur est ce qui l'a généralisé.

Pourquoi c'est conçu ainsi

Un type de fournisseur par protocole réseau, pas par éditeur. Quand le moteur a été extrait, exactement une fonction et deux constantes de chaîne étaient spécifiques à Docker Model Runner. Tout le reste, le streaming, le watchdog, la nouvelle tentative, le compteur de commandes, se comportait de la même façon face à un faux moteur qui n'est pas DMR non plus. Donc dmr et llamacpp sont deux entrées de données d'un seul type openaiCompat : une URL de base par défaut, une URL de repli optionnelle, une politique de clé, et les mots employés dans les messages d'erreur. Ajouter un autre serveur compatible OpenAI est une entrée de registre ; ajouter un protocole différent serait un nouveau type.

Un fournisseur possède quatre choses. Il résout la configuration en un backend concret (URL après surcharges d'environnement et sonde de repli, clé lue dans l'environnement, nom du modèle) ; il ouvre Genkit avec son plugin ; il sonde le serveur avant la première question ; et il explique une erreur de transport en une ligne.

Le repli existe pour les conteneurs. Sur l'hôte, Docker Model Runner écoute sur localhost:12434 ; depuis un conteneur ou une sandbox, le même serveur est joignable en host.docker.internal. Si l'URL principale ne répond pas sur /models en deux secondes, le repli est utilisé. Une surcharge explicite par l'environnement saute la sonde : qui l'a posée sait où est le serveur. Un fallback: "" explicite le désactive, ce dont une configuration de test a besoin pour rester épinglée à une seule URL.

La clé ne va jamais dans le YAML. Le fichier nomme la variable d'environnement qui contient la clé, ce qui permet de le committer et de le montrer à l'écran. Les serveurs qui ignorent l'en-tête reçoivent une valeur factice, parce que le client OpenAI veut quelque chose à cet endroit. Un 401 est alors expliqué en nommant la variable que le fournisseur a réellement lue, ou, quand aucune n'est configurée, en pointant l'URL.

La sonde est un conseil, jamais un échec. Sur une machine de démo, le serveur est souvent démarré après l'agent. La sonde vérifie la joignabilité, avertit quand le modèle configuré n'est pas dans la liste de DMR, et sur llama.cpp lit la taille de contexte servie sur /props, un niveau au-dessus de /v1, parce que ce point d'accès rapporte ce que le serveur sert, pas ce sur quoi le modèle a été entraîné. Quand la fenêtre est encore inconnue et que la compression est activée, le REPL sonde à nouveau avant la première question.

Les erreurs sont réécrites dans les mots du serveur. Une pile de messages imbriqués est du bruit ; « tool calls need llama-server started with --jinja » est tout le diagnostic. Le fournisseur reconnaît les refus de connexion, l'option --jinja manquante, les 401/403, les 404 avec une indication de pull ou d'alias, les 429 et les 5xx. Les erreurs inconnues reviennent inchangées : un message brut vaut mieux qu'une mauvaise piste.

Un flux que le serveur interrompt est relancé, et ses mots sont gardés. llama.cpp signale un échec au milieu d'une réponse diffusée par un événement SSE error: (le statut 200 est déjà parti). openai-go ne lit que les lignes data: : le message disparaissait derrière unexpected end of JSON input. Le tap qui lit déjà le flux pour le raisonnement récupère aussi ces événements, et l'appel au modèle qui a échoué est relancé une fois — cet appel seulement : les outils déjà exécutés dans le tour ne sont pas rejoués, et le texte déjà diffusé par la tentative interrompue n'est pas retiré. S'il échoue encore, l'erreur donne le message du serveur (the model server reported an error while generating: Failed to parse input at pos …), ou indique que la connexion a été coupée. Dans les deux cas l'historique est conservé : demander de continuer reprend le travail.

Les outils partent dans le même ordre à chaque requête. Un serveur llama.cpp garde le prompt qu'il vient de lire et, à la requête suivante, ne relit que ce qui a changé après le plus long début commun. Le template de chat place les outils vers le haut — dans le bloc système pour les templates de type Qwen — donc leur ordre décide de la part de l'historique qui survit. Genkit construit la liste des outils de la requête à partir d'une map Go, dont l'ordre est aléatoire : chaque question d'une session envoyait les mêmes outils dans un autre ordre, et le serveur relisait le prompt entier (ticket 53 ; toujours le cas dans Genkit v1.13.1). mm trie désormais les outils par nom juste avant chaque appel au modèle. Mesuré avec Mellum 12B sur Docker Model Runner : à partir de la deuxième question, une question lit une vingtaine de nouveaux tokens en un quart de seconde, là où elle relisait environ 3 400 tokens en sept secondes chaque fois que l'ordre changeait.

Alternatives écartées

  • Un fournisseur par éditeur avec du code moteur dupliqué. Écarté : mesurée, la partie spécifique à l'éditeur tenait en trois identifiants.
  • Mettre la clé d'API dans le fichier de configuration. Écarté parce que le fichier est fait pour être committé et montré.
  • Échouer au démarrage quand le serveur est arrêté. Écarté parce que démarrer le serveur en second est la situation normale d'une démo.
  • Garder l'ordre dans lequel main.go déclare les outils. Écarté : Genkit perd cet ordre avant de construire la requête, et un ordre qui dépend de la déclaration changerait de toute façon avec la configuration et les serveurs MCP. Le nom est la seule clé qui ne dépend que des outils eux-mêmes.
  • Attendre une version de Genkit qui corrige le problème. Écarté pour l'instant : la v1.13.1 et la branche principale construisent toujours la liste à partir d'une map.

Liens avec le reste

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# Fournisseurs : une implémentation par protocole — explication

## De quoi s'agit-il ?

`mm` dialogue avec un serveur de modèle local via l'API chat-completions d'OpenAI. Deux serveurs sont pris en charge d'origine, Docker Model Runner et `llama-server` de llama.cpp, sous les noms `dmr` et `llamacpp`. Le paquet moteur était autrefois lié à Docker Model Runner ; l'abstraction de fournisseur est ce qui l'a généralisé.

## Pourquoi c'est conçu ainsi

**Un type de fournisseur par protocole réseau, pas par éditeur.** Quand le moteur a été extrait, exactement une fonction et deux constantes de chaîne étaient spécifiques à Docker Model Runner. Tout le reste, le streaming, le watchdog, la nouvelle tentative, le compteur de commandes, se comportait de la même façon face à un faux moteur qui n'est pas DMR non plus. Donc `dmr` et `llamacpp` sont deux entrées de données d'un seul type `openaiCompat` : une URL de base par défaut, une URL de repli optionnelle, une politique de clé, et les mots employés dans les messages d'erreur. Ajouter un autre serveur compatible OpenAI est une entrée de registre ; ajouter un protocole différent serait un nouveau type.

**Un fournisseur possède quatre choses.** Il résout la configuration en un backend concret (URL après surcharges d'environnement et sonde de repli, clé lue dans l'environnement, nom du modèle) ; il ouvre Genkit avec son plugin ; il sonde le serveur avant la première question ; et il explique une erreur de transport en une ligne.

**Le repli existe pour les conteneurs.** Sur l'hôte, Docker Model Runner écoute sur `localhost:12434` ; depuis un conteneur ou une sandbox, le même serveur est joignable en `host.docker.internal`. Si l'URL principale ne répond pas sur `/models` en deux secondes, le repli est utilisé. Une surcharge explicite par l'environnement saute la sonde : qui l'a posée sait où est le serveur. Un `fallback: ""` explicite le désactive, ce dont une configuration de test a besoin pour rester épinglée à une seule URL.

**La clé ne va jamais dans le YAML.** Le fichier nomme la variable d'environnement qui contient la clé, ce qui permet de le committer et de le montrer à l'écran. Les serveurs qui ignorent l'en-tête reçoivent une valeur factice, parce que le client OpenAI veut quelque chose à cet endroit. Un 401 est alors expliqué en nommant la variable que le fournisseur a réellement lue, ou, quand aucune n'est configurée, en pointant l'URL.

**La sonde est un conseil, jamais un échec.** Sur une machine de démo, le serveur est souvent démarré après l'agent. La sonde vérifie la joignabilité, avertit quand le modèle configuré n'est pas dans la liste de DMR, et sur llama.cpp lit la taille de contexte servie sur `/props`, un niveau au-dessus de `/v1`, parce que ce point d'accès rapporte ce que le serveur sert, pas ce sur quoi le modèle a été entraîné. Quand la fenêtre est encore inconnue et que la compression est activée, le REPL sonde à nouveau avant la première question.

**Les erreurs sont réécrites dans les mots du serveur.** Une pile de messages imbriqués est du bruit ; « tool calls need llama-server started with --jinja » est tout le diagnostic. Le fournisseur reconnaît les refus de connexion, l'option `--jinja` manquante, les 401/403, les 404 avec une indication de pull ou d'alias, les 429 et les 5xx. Les erreurs inconnues reviennent inchangées : un message brut vaut mieux qu'une mauvaise piste.

**Un flux que le serveur interrompt est relancé, et ses mots sont gardés.** llama.cpp signale un échec au milieu d'une réponse diffusée par un événement SSE `error:` (le statut 200 est déjà parti). openai-go ne lit que les lignes `data:` : le message disparaissait derrière `unexpected end of JSON input`. Le tap qui lit déjà le flux pour le raisonnement récupère aussi ces événements, et l'appel au modèle qui a échoué est relancé une fois — cet appel seulement : les outils déjà exécutés dans le tour ne sont pas rejoués, et le texte déjà diffusé par la tentative interrompue n'est pas retiré. S'il échoue encore, l'erreur donne le message du serveur (`the model server reported an error while generating: Failed to parse input at pos …`), ou indique que la connexion a été coupée. Dans les deux cas l'historique est conservé : demander de continuer reprend le travail.

**Les outils partent dans le même ordre à chaque requête.** Un serveur llama.cpp garde le prompt qu'il vient de lire et, à la requête suivante, ne relit que ce qui a changé après le plus long début commun. Le template de chat place les outils vers le haut — dans le bloc système pour les templates de type Qwen — donc leur ordre décide de la part de l'historique qui survit. Genkit construit la liste des outils de la requête à partir d'une map Go, dont l'ordre est aléatoire : chaque question d'une session envoyait les mêmes outils dans un autre ordre, et le serveur relisait le prompt entier (ticket 53 ; toujours le cas dans Genkit v1.13.1). `mm` trie désormais les outils par nom juste avant chaque appel au modèle. Mesuré avec Mellum 12B sur Docker Model Runner : à partir de la deuxième question, une question lit une vingtaine de nouveaux tokens en un quart de seconde, là où elle relisait environ 3 400 tokens en sept secondes chaque fois que l'ordre changeait.

## Alternatives écartées

- **Un fournisseur par éditeur avec du code moteur dupliqué.** Écarté : mesurée, la partie spécifique à l'éditeur tenait en trois identifiants.
- **Mettre la clé d'API dans le fichier de configuration.** Écarté parce que le fichier est fait pour être committé et montré.
- **Échouer au démarrage quand le serveur est arrêté.** Écarté parce que démarrer le serveur en second est la situation normale d'une démo.
- **Garder l'ordre dans lequel `main.go` déclare les outils.** Écarté : Genkit perd cet ordre avant de construire la requête, et un ordre qui dépend de la déclaration changerait de toute façon avec la configuration et les serveurs MCP. Le nom est la seule clé qui ne dépend que des outils eux-mêmes.
- **Attendre une version de Genkit qui corrige le problème.** Écarté pour l'instant : la v1.13.1 et la branche principale construisent toujours la liste à partir d'une map.

## Liens avec le reste

- Les deux fichiers de configuration qui paramètrent chaque fournisseur sont listés dans la [référence de la configuration](../reference/configuration.md).
- La fenêtre apprise par la sonde est ce contre quoi la [compression du contexte](context-compression.md) mesure.
- Changer de serveur pour une exécution est couvert dans [comment faire tourner sur llama.cpp](../how-to/run-with-llama-cpp.md).
- Vérifier l'ordre des outils et mesurer le gain de cache sur votre propre serveur : [vérifier que les outils partent dans un ordre stable](../how-to/verify-tool-order.md).