diff --git a/cmd/mavend/clarify.go b/cmd/mavend/clarify.go index 75d6691..81d271b 100644 --- a/cmd/mavend/clarify.go +++ b/cmd/mavend/clarify.go @@ -348,9 +348,18 @@ func (h *reactiveHandler) rememberTurn(prev *dialogue.Session, dec router.Decisi if dec.Intent == router.IntentChat { ttl = 15 * time.Minute // conversational turns should last longer } + // A system or query turn often carries no Text slot at all — a stage-0 + // grammar fills none. The next turn may be an ellipsis ("а завтра?"), + // which knows the day but not what was asked ABOUT, so keep the raw + // utterance where continuation.go can find it. Only these two intents: + // everywhere else Text is a payload and must stay what the router put in. + slots := toDialogueSlots(dec.Slots) + if slots.Text == "" && (dec.Intent == router.IntentSystem || dec.Intent == router.IntentQuery) { + slots.Text = dec.Utterance + } h.dialogueSessions.Put(voiceDialogueID, &dialogue.Session{ Intent: dialogue.Intent(dec.Intent), - Slots: toDialogueSlots(dec.Slots), + Slots: slots, Timestamp: now, TTL: ttl, History: history, diff --git a/cmd/mavend/continuation_test.go b/cmd/mavend/continuation_test.go index 931bcff..f4816f9 100644 --- a/cmd/mavend/continuation_test.go +++ b/cmd/mavend/continuation_test.go @@ -101,3 +101,18 @@ func TestContinuationNeverCarriesAnFn(t *testing.T) { t.Fatalf("carried fn %q into a continuation", dec.Slots.Fn) } } + +// TestContinuationCarriesTheTopic — the ellipsis names the day; what he is +// asking ABOUT has to come from the previous turn, or replySystem keyword- +// matches "а завтра?" and finds nothing. Caught on the deployed daemon. +func TestContinuationCarriesTheTopic(t *testing.T) { + prev := contSession(dialogue.IntentSystem, "") + prev.Slots.Text = "какой сегодня день" + dec, ok := continuationDecision(prev, "а завтра?", contNow) + if !ok { + t.Fatal("want a decision") + } + if dec.Slots.Text != "какой сегодня день" { + t.Fatalf("Slots.Text = %q, want the previous turn's topic", dec.Slots.Text) + } +} diff --git a/cmd/mavend/voice.go b/cmd/mavend/voice.go index 1428ef1..138b0d8 100644 --- a/cmd/mavend/voice.go +++ b/cmd/mavend/voice.go @@ -369,6 +369,17 @@ func (h *reactiveHandler) replySystem(ctx context.Context, dec router.Decision) u := strings.ToLower(dec.Utterance) now := h.now() + // The topic and the day come from different places on a continuation. + // "а завтра?" names the day and nothing else; what he is asking ABOUT + // lives in the previous turn, which continuation.go copied into + // Slots.Text. Dates keep parsing from the utterance — that is the part + // the ellipsis actually restates — and only the keyword match widens. + // On an ordinary turn Slots.Text is the utterance, so nothing changes. + topic := u + if t := strings.ToLower(dec.Slots.Text); t != "" && t != u { + topic = u + " " + t + } + // stage-0 grammars catch the exact time/date patterns, but duration // queries ("сколько времени прошло") bypass the grammar's build filter // and can reach replySystem via the classifier path. Guard against them. @@ -377,14 +388,14 @@ func (h *reactiveHandler) replySystem(ctx context.Context, dec router.Decision) } switch { - case strings.Contains(u, "час") || strings.Contains(u, "врем"): + case strings.Contains(topic, "час") || strings.Contains(topic, "врем"): // "который час в киеве" — she keeps one clock, so any named place gets // the honest answer. Never local time dressed up as the city's. if mentionsUnknownPlace(u) { return onlyLocalTimeReply } return "сейчас " + ruClock(now) - case strings.Contains(u, "день") || strings.Contains(u, "числ"): + case strings.Contains(topic, "день") || strings.Contains(topic, "числ"): // "какое число завтра" — answer for the day the user asked about, // not today. Reuses the router's calendar day-word parser. day := now