19242ef73b
Every clarify turn said one sentence per gap, and a re-ask repeated it word for word. A question he already failed to answer is the worst one to ask again unchanged: the second wording is what tells him which part she missed. clarifytemplates.go holds three wordings per slot, picked by attempt rather than at random — short first, then naming the gap, then spelling it out with an example. Past the end she keeps the most explicit one instead of wrapping back to the short question he has already not answered. The intents with nothing identifiable to ask about (note, query, chat, system) kept the stub's single "не совсем поняла — можешь переформулировать?", which is the line he hears whenever she misses him completely. Four wordings now, picked by a hash of the utterance so one question asked twice reads the same and two different misses do not. Still no model call on this path: the resident model would wander, and this text has to be right every time. No schema_version either, unlike the nudge templates — these are Go constants, so no file can drift out of step with the code that reads it. The persona test already in clarify_test.go covers the new lines. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
110 lines
4.4 KiB
Go
110 lines
4.4 KiB
Go
package main
|
|
|
|
import (
|
|
"github.com/kami/maven/internal/dialogue"
|
|
"github.com/kami/maven/internal/router"
|
|
)
|
|
|
|
// The clarify copy deck (Vikunja #457).
|
|
//
|
|
// Every clarify turn used to say one sentence per gap, and a re-ask repeated
|
|
// that sentence word for word. A question he already failed to answer is the
|
|
// worst one to ask again unchanged: the second wording is the one that tells
|
|
// him which part she missed.
|
|
//
|
|
// Fixed templates, not model output, for the reason clarifyQuestions has always
|
|
// given: the resident model would wander, and a question whose wording changes
|
|
// at random is harder to answer than a blunt one. What changes here is that the
|
|
// wording varies with the attempt rather than with a die roll — the first ask is
|
|
// short, the second names the gap, the third spells it out.
|
|
//
|
|
// No schema_version, unlike internal/phraser/nudge_templates.go. These are Go
|
|
// constants compiled into the daemon, so there is no file that can drift out of
|
|
// step with the code that reads it.
|
|
//
|
|
// Persona holds: infinitive and imperative questions, so there is no gender
|
|
// agreement to get wrong, "ты" throughout, and no pet names.
|
|
var clarifyQuestionVariants = map[dialogue.Slot][]string{
|
|
dialogue.SlotTime: {
|
|
"Когда?",
|
|
"Во сколько напомнить?",
|
|
"Скажи время — например, «в семь вечера» или «через час».",
|
|
},
|
|
dialogue.SlotText: {
|
|
"О чём напомнить?",
|
|
"Что сказать тебе в это время?",
|
|
"Скажи одной фразой, о чём напомнить.",
|
|
},
|
|
dialogue.SlotKey: {
|
|
"Что записать?",
|
|
"Что именно отметить?",
|
|
"Назови, что записать — например, «выпил воды».",
|
|
},
|
|
dialogue.SlotFn: {
|
|
"Что сделать?",
|
|
"Какое действие выполнить?",
|
|
"Назови действие — я умею только то, что ты мне разрешил.",
|
|
},
|
|
}
|
|
|
|
// clarifyQuestionFor picks the wording for this attempt. attempt is 1-based, as
|
|
// PendingQuestion.Attempts counts it; anything past the list uses the last and
|
|
// most explicit phrasing rather than wrapping round to the short one, because
|
|
// wrapping would ask the same short question he has already not answered.
|
|
//
|
|
// Deterministic on purpose, unlike clarifyExpiredLine: an expiry notice is the
|
|
// same statement however it is worded, and a re-ask is not.
|
|
func clarifyQuestionFor(slot dialogue.Slot, attempt int) (string, bool) {
|
|
variants, ok := clarifyQuestionVariants[slot]
|
|
if !ok || len(variants) == 0 {
|
|
return "", false
|
|
}
|
|
i := attempt - 1
|
|
if i < 0 {
|
|
i = 0
|
|
}
|
|
if i >= len(variants) {
|
|
i = len(variants) - 1
|
|
}
|
|
return variants[i], true
|
|
}
|
|
|
|
// clarifyMissedVariants — she is asking for the whole utterance again, because
|
|
// the gate fired on an intent with nothing identifiable to ask about (note,
|
|
// query, chat, system are not in wantedSlots).
|
|
//
|
|
// Rotated like the expiry lines and for the same reason: this is the line he
|
|
// hears whenever she misses him completely, so it is a line that repeats, and
|
|
// the same sentence every time is what makes a house assistant sound like a
|
|
// kiosk. All of them say the same two things — she did not catch it, and he
|
|
// should say it again — because the wording may vary and the meaning may not.
|
|
var clarifyMissedVariants = []string{
|
|
"Не совсем поняла — скажи, пожалуйста, ещё раз.",
|
|
"Я тебя не разобрала. Повтори, пожалуйста.",
|
|
"Не уловила. Скажи это по-другому?",
|
|
"Прости, не поняла — попробуй сказать иначе.",
|
|
}
|
|
|
|
// clarifyMissedFor picks a wording by the utterance itself, so the same words
|
|
// asked twice get the same answer and two different misses sound different.
|
|
//
|
|
// A hash, not rand: a test that drives an utterance twice must not depend on a
|
|
// die roll, and the point of rotating is only that consecutive misses differ.
|
|
func clarifyMissedFor(utterance string) string {
|
|
var sum int
|
|
for _, r := range utterance {
|
|
sum += int(r)
|
|
}
|
|
return clarifyMissedVariants[sum%len(clarifyMissedVariants)]
|
|
}
|
|
|
|
// clarifyMissedLine is the canned reply for a clarify decision she cannot turn
|
|
// into a question. Returns "" for a decision that is not a clarify, so the
|
|
// caller keeps its own reply.
|
|
func clarifyMissedLine(dec router.Decision) string {
|
|
if !dec.Clarify {
|
|
return ""
|
|
}
|
|
return clarifyMissedFor(dec.Utterance)
|
|
}
|