From a54ebac0cb4b0f4cd607ed1b15c31b785427378c Mon Sep 17 00:00:00 2001 From: kami Date: Fri, 31 Jul 2026 02:22:18 +0400 Subject: [PATCH] Work out which slot is missing and phrase one short question MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A table per intent (reminder needs a time, fact needs a key, act needs a fn) plus one fixed Russian question per slot. Templates, not model output: a 0.8B would wander and a question that rewords itself is harder to answer. Note, query, chat and system get no question — for those a clarify decision keeps the canned reply rather than inventing a question for noise. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01CGeSZxh1DCtRxmFVSYVGvJ --- cmd/mavend/clarify.go | 66 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 cmd/mavend/clarify.go diff --git a/cmd/mavend/clarify.go b/cmd/mavend/clarify.go new file mode 100644 index 0000000..eff681f --- /dev/null +++ b/cmd/mavend/clarify.go @@ -0,0 +1,66 @@ +package main + +import ( + "time" + + "github.com/kami/maven/internal/dialogue" + "github.com/kami/maven/internal/router" +) + +// clarifyTTL — how long a parked question stays answerable. Same 90s as the +// confirm gate, for the same reason: an answer is a same-breath gesture, and a +// stale question must not eat an unrelated later utterance. +const clarifyTTL = 90 * time.Second + +// wantedSlots — what each intent needs before she can act on it. First entry is +// the one she asks about; the rest are only used to decide act-vs-drop. +// +// Intents not listed here are never worth a question: note and query act on the +// raw utterance, chat and system have nothing to fill in. For those a clarify +// decision keeps the canned "не поняла" reply — inventing a question for noise +// is worse than admitting she missed it. +var wantedSlots = map[router.Intent][]dialogue.Slot{ + router.IntentReminder: {dialogue.SlotTime}, + router.IntentFact: {dialogue.SlotKey}, + router.IntentAct: {dialogue.SlotFn}, +} + +// clarifyQuestions — one short question per missing slot. +// +// These are fixed templates, not model output. The resident model is a 0.8B; it +// would wander, and a question whose wording changes every time is harder to +// answer than a blunt one that always reads the same. They are infinitive +// questions, so there is no gender agreement to get wrong; the feminine +// self-reference lives in the reply she gives when she drops the request. +var clarifyQuestions = map[dialogue.Slot]string{ + dialogue.SlotTime: "На когда напомнить?", + dialogue.SlotKey: "Что записать?", + dialogue.SlotFn: "Что сделать?", +} + +// clarifyDropped — she asked once, the answer still did not fill the gap, so +// the request is gone. Said plainly, once, with no second question. +const clarifyDropped = "Не разобрала — скажи целиком, пожалуйста." + +// missingFor returns the slots a decision still needs, most important first. +// Empty ⇒ there is nothing identifiable to ask about. +func missingFor(dec router.Decision) []dialogue.Slot { + return dialogue.StillMissing(wantedSlots[dec.Intent], toDialogueSlots(dec.Slots)) +} + +// clarifyQuestion picks the one question to ask for a clarify decision. Returns +// ("", false) when she has no idea what is missing. +// +// One question about one thing: if two slots are missing she asks about the +// first and lets the rest go. Two questions in a row is an interrogation. +func clarifyQuestion(dec router.Decision) (dialogue.Slot, string, bool) { + missing := missingFor(dec) + if len(missing) == 0 { + return "", "", false + } + q, ok := clarifyQuestions[missing[0]] + if !ok { + return "", "", false + } + return missing[0], q, true +}