bots-garden/mini-mepublic Fork
⑂ feature/improve-init
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. bed1738Unverified · on feature/improve-init · k33g · 10d ago
asking-the-user.md · 54 lines · 7.2 KBmarkdown
Blame HistoryOpen raw

Interroger l'utilisateur — explication

De quoi s'agit-il ?

Un agent arrive souvent à un point où l'étape suivante est une décision de l'utilisateur : quel framework, quel environnement, faut-il continuer. Sans moyen de demander, un modèle soit devine — et bâtit sur une supposition —, soit termine son tour par une question rédigée, ce qui perd le travail en cours et ne donne rien à cliquer à l'utilisateur. L'outil ask_user (ticket 84) permet au modèle de demander au milieu d'un tour et de continuer avec la réponse.

Le ticket nomme les formes habituelles d'une invite interactive : choisir une option, en choisir plusieurs, taper un texte, confirmer. Ce sont exactement les types de champ des formulaires d'elicitation d'ACP, d'où les quatre genres de l'outil : choice, multi, text, confirm.

Une question, quatre interfaces

mm s'utilise de quatre façons, et la question doit atteindre une personne dans chacune — ou dire clairement que personne n'est là. La conception regroupe tout ce qui ne dépend pas d'un écran dans un seul paquet, internal/elicit : les questions, les réponses, ce que le modèle relit, la façon dont une ligne tapée répond à une question, et la conversion depuis et vers un formulaire d'elicitation. Chaque interface ne fait que demander :

  • le mode ligne affiche des options numérotées et lit la ligne tapée suivante, par la même salle d'attente que l'invite de permission a/s/r/n — c'est pourquoi /abort et Ctrl-C fonctionnent toujours pendant qu'une question attend. Les menus aux flèches ont été envisagés et laissés à la fenêtre : le mode ligne lit des lignes entières, et passer le terminal en mode brut le temps d'une question entrerait en conflit avec son propre lecteur d'entrée ;
  • la fenêtre dessine les options par-dessus la conversation, comme sa fenêtre de complétion, et prend toutes les touches tant que le formulaire est là ;
  • un éditeur reçoit un formulaire ACP elicitation/create s'il a annoncé le mode formulaire ; sinon chaque question à choix ou oui/non devient un dialogue de permission dont les boutons sont les options, parce que tout éditeur ACP sait afficher ce dialogue. Un texte libre et des choix multiples ne peuvent pas être des boutons : le modèle apprend que personne ne peut être interrogé ;
  • personne : mm -p et mm --ask répondent aussitôt que l'utilisateur ne peut pas être interrogé, et il est demandé au modèle de faire une hypothèse et de la dire. Attendre une réponse qui ne peut pas venir bloquerait un script.

Une interface qui sait demander implémente une interface optionnelle, ui.Elicitor, à côté du ui.Sink qu'elle est déjà — le même principe que pour les comptes de tokens (UsageSink) et le nom de l'agent qui parle (Speaker).

Les agents interrogés par d'autres agents

Un agent appelé par /ask ou ask_agent est un mm -acp enfant, et ses demandes de permission remontent déjà à celui qui l'interroge. Ses questions prennent le même chemin : le client ACP de l'appelant annonce le mode formulaire, reçoit l'elicitation/create de l'enfant et interroge son propre utilisateur par sa propre interface, en nommant l'enfant (homer asks: …). Un coordinateur dans un éditeur montre donc les questions de son assistant sous forme de formulaires de l'éditeur, et un coordinateur dans le terminal sous forme de lignes numérotées.

Qu'un agent appelé puisse demander ou non est la décision de l'utilisateur, agent par agent : son propre askUserTool, ou le mmConfigs.<nom>.askUser de celui qui l'appelle, transmis au processus enfant par AGENT_ASK_USER comme yolo voyage par AGENT_YOLO. Un exécutant qui ne doit jamais interrompre personne n'a tout simplement pas l'outil.

Un schéma souple, exprès

L'outil déclare son entrée de façon souple : pas d'enum, rien d'obligatoire, et chaque question décrite en mots plutôt qu'avec des properties. Deux raisons mesurées :

  • Genkit valide un appel contre le schéma déclaré avant d'exécuter l'outil, et un appel rejeté termine le tour. Un petit modèle qui écrit "type": "single" perdrait son tour ; lu par elicit.Normalize, le synonyme fonctionne simplement, et une vraie erreur revient sous forme d'une phrase que le modèle corrige.
  • llama.cpp transforme le schéma en grammaire, et cette grammaire impose les propriétés d'un objet dans l'ordre alphabétique. Avec les properties déclarées, les champs d'une question devaient venir dans l'ordre default, options, question, type ; un modèle qui commence par question — tous le font — ne peut alors plus jamais écrire ses options. Mesuré avec Qwen3.5 9B : les options perdues 3 fois sur 3 avec les propriétés déclarées, écrites 3 fois sur 3 sans. La complétion brute, hors grammaire, les contenait toujours.

La description de l'outil se termine par un exemple d'appel complet, que le schéma ne peut plus montrer.

Attendre un humain

Le chien de garde de mm continue de compter pendant qu'un outil s'exécute (ticket 57) : une question restée 20 secondes sans réponse aurait terminé le tour comme un serveur bloqué. Tant qu'une question attend, l'outil relance le chien de garde chaque seconde ; un tour arrêté annule toujours la question. Pour la question d'un agent appelé, le chien de garde de l'appelant est relancé de la même façon.

Un manque du SDK

ACP rattache un formulaire à sa session et à son appel d'outil par sessionId et toolCallId au premier niveau, mais acp-go-sdk v0.13.5 n'a ces champs d'aucun côté. mm range ce rattachement dans le _meta du formulaire et écrit les champs de premier niveau quand la ligne part ; un client mm qui relaie la question relit l'appel d'outil dans _meta. Les deux moitiés sont marquées pour disparaître quand le SDK portera ces champs.

Solutions écartées

  • Une question par appel. Plus simple, mais un formulaire de questions liées (framework, TypeScript, gestionnaire de paquets) est l'exemple même du ticket, et un éditeur l'affiche comme un seul formulaire.
  • Une question rédigée, à laquelle répond le message suivant. Elle termine le tour, et rien ne distingue la réponse d'une nouvelle demande.
  • Refuser quand personne ne peut répondre. « Refusé » dit au modèle que l'utilisateur a dit non ; « impossible de demander » lui dit de faire une hypothèse. La différence change ce que fait le modèle ensuite.
  • Déclarer l'outil à côté d'un structuredOutput. Tout outil déclaré empêche un tel agent de démarrer, et un agent qui répond une seule fois, en JSON, ne peut de toute façon pas tenir de conversation.

Lien avec le reste

  • Les permissions (yolo) décident si un outil peut s'exécuter ; ask_user recueille des informations. Les deux ne se mélangent pas : demander ne modifie rien, donc l'outil n'est pas soumis à yolo, et le dialogue de permission de l'éditeur n'est emprunté qu'en repli quand aucun formulaire ne peut être affiché. Voir yolo et permissions.
  • Agents appelés : interroger un autre agent.
  • Référence : l'outil, ACP ; mode d'emploi : laisser l'agent vous poser des questions.
 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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# Interroger l'utilisateur — explication

## De quoi s'agit-il ?

Un agent arrive souvent à un point où l'étape suivante est une décision de l'utilisateur : quel framework, quel environnement, faut-il continuer. Sans moyen de demander, un modèle soit devine — et bâtit sur une supposition —, soit termine son tour par une question rédigée, ce qui perd le travail en cours et ne donne rien à cliquer à l'utilisateur. L'outil `ask_user` (ticket 84) permet au modèle de demander au milieu d'un tour et de continuer avec la réponse.

Le ticket nomme les formes habituelles d'une invite interactive : choisir une option, en choisir plusieurs, taper un texte, confirmer. Ce sont exactement les types de champ des formulaires d'elicitation d'ACP, d'où les quatre genres de l'outil : `choice`, `multi`, `text`, `confirm`.

## Une question, quatre interfaces

mm s'utilise de quatre façons, et la question doit atteindre une personne dans chacune — ou dire clairement que personne n'est là. La conception regroupe tout ce qui ne dépend pas d'un écran dans un seul paquet, `internal/elicit` : les questions, les réponses, ce que le modèle relit, la façon dont une ligne tapée répond à une question, et la conversion depuis et vers un formulaire d'elicitation. Chaque interface ne fait que demander :

- le **mode ligne** affiche des options numérotées et lit la ligne tapée suivante, par la même salle d'attente que l'invite de permission a/s/r/n — c'est pourquoi `/abort` et Ctrl-C fonctionnent toujours pendant qu'une question attend. Les menus aux flèches ont été envisagés et laissés à la fenêtre : le mode ligne lit des lignes entières, et passer le terminal en mode brut le temps d'une question entrerait en conflit avec son propre lecteur d'entrée ;
- la **fenêtre** dessine les options par-dessus la conversation, comme sa fenêtre de complétion, et prend toutes les touches tant que le formulaire est là ;
- un **éditeur** reçoit un formulaire ACP `elicitation/create` s'il a annoncé le mode formulaire ; sinon chaque question à choix ou oui/non devient un dialogue de permission dont les boutons sont les options, parce que tout éditeur ACP sait afficher ce dialogue. Un texte libre et des choix multiples ne peuvent pas être des boutons : le modèle apprend que personne ne peut être interrogé ;
- **personne** : `mm -p` et `mm --ask` répondent aussitôt que l'utilisateur ne peut pas être interrogé, et il est demandé au modèle de faire une hypothèse et de la dire. Attendre une réponse qui ne peut pas venir bloquerait un script.

Une interface qui sait demander implémente une interface optionnelle, `ui.Elicitor`, à côté du `ui.Sink` qu'elle est déjà — le même principe que pour les comptes de tokens (`UsageSink`) et le nom de l'agent qui parle (`Speaker`).

## Les agents interrogés par d'autres agents

Un agent appelé par `/ask` ou `ask_agent` est un `mm -acp` enfant, et ses demandes de permission remontent déjà à celui qui l'interroge. Ses questions prennent le même chemin : le client ACP de l'appelant annonce le mode formulaire, reçoit l'`elicitation/create` de l'enfant et interroge son propre utilisateur par sa propre interface, en nommant l'enfant (`homer asks: …`). Un coordinateur dans un éditeur montre donc les questions de son assistant sous forme de formulaires de l'éditeur, et un coordinateur dans le terminal sous forme de lignes numérotées.

Qu'un agent appelé puisse demander ou non est la décision de l'utilisateur, agent par agent : son propre `askUserTool`, ou le `mmConfigs.<nom>.askUser` de celui qui l'appelle, transmis au processus enfant par `AGENT_ASK_USER` comme `yolo` voyage par `AGENT_YOLO`. Un exécutant qui ne doit jamais interrompre personne n'a tout simplement pas l'outil.

## Un schéma souple, exprès

L'outil déclare son entrée de façon souple : pas d'enum, rien d'obligatoire, et chaque question décrite en mots plutôt qu'avec des `properties`. Deux raisons mesurées :

- Genkit valide un appel contre le schéma déclaré avant d'exécuter l'outil, et un appel rejeté termine le tour. Un petit modèle qui écrit `"type": "single"` perdrait son tour ; lu par `elicit.Normalize`, le synonyme fonctionne simplement, et une vraie erreur revient sous forme d'une phrase que le modèle corrige.
- llama.cpp transforme le schéma en grammaire, et cette grammaire impose les propriétés d'un objet dans l'**ordre alphabétique**. Avec les `properties` déclarées, les champs d'une question devaient venir dans l'ordre `default`, `options`, `question`, `type` ; un modèle qui commence par `question` — tous le font — ne peut alors plus jamais écrire ses `options`. Mesuré avec Qwen3.5 9B : les options perdues 3 fois sur 3 avec les propriétés déclarées, écrites 3 fois sur 3 sans. La complétion brute, hors grammaire, les contenait toujours.

La description de l'outil se termine par un exemple d'appel complet, que le schéma ne peut plus montrer.

## Attendre un humain

Le chien de garde de mm continue de compter pendant qu'un outil s'exécute (ticket 57) : une question restée 20 secondes sans réponse aurait terminé le tour comme un serveur bloqué. Tant qu'une question attend, l'outil relance le chien de garde chaque seconde ; un tour arrêté annule toujours la question. Pour la question d'un agent appelé, le chien de garde de l'appelant est relancé de la même façon.

## Un manque du SDK

ACP rattache un formulaire à sa session et à son appel d'outil par `sessionId` et `toolCallId` au premier niveau, mais acp-go-sdk v0.13.5 n'a ces champs d'aucun côté. mm range ce rattachement dans le `_meta` du formulaire et écrit les champs de premier niveau quand la ligne part ; un client mm qui relaie la question relit l'appel d'outil dans `_meta`. Les deux moitiés sont marquées pour disparaître quand le SDK portera ces champs.

## Solutions écartées

- **Une question par appel.** Plus simple, mais un formulaire de questions liées (framework, TypeScript, gestionnaire de paquets) est l'exemple même du ticket, et un éditeur l'affiche comme un seul formulaire.
- **Une question rédigée, à laquelle répond le message suivant.** Elle termine le tour, et rien ne distingue la réponse d'une nouvelle demande.
- **Refuser quand personne ne peut répondre.** « Refusé » dit au modèle que l'utilisateur a dit non ; « impossible de demander » lui dit de faire une hypothèse. La différence change ce que fait le modèle ensuite.
- **Déclarer l'outil à côté d'un `structuredOutput`.** Tout outil déclaré empêche un tel agent de démarrer, et un agent qui répond une seule fois, en JSON, ne peut de toute façon pas tenir de conversation.

## Lien avec le reste

- Les permissions (`yolo`) décident si un outil peut s'exécuter ; `ask_user` recueille des informations. Les deux ne se mélangent pas : demander ne modifie rien, donc l'outil n'est pas soumis à `yolo`, et le dialogue de permission de l'éditeur n'est emprunté qu'en repli quand aucun formulaire ne peut être affiché. Voir [yolo et permissions](yolo-and-permissions.md).
- Agents appelés : [interroger un autre agent](asking-another-agent.md).
- Référence : [l'outil](../reference/tools.md#ask_user), [ACP](../reference/acp.md) ; mode d'emploi : [laisser l'agent vous poser des questions](../how-to/let-the-agent-ask-you.md).