diff --git a/cmd/core-runtime/main.go b/cmd/core-runtime/main.go index 13d16ec..2725c02 100644 --- a/cmd/core-runtime/main.go +++ b/cmd/core-runtime/main.go @@ -541,6 +541,10 @@ func main() { continuationMessage = continuation travelContinuation = true effectiveTaskMessage = continuation + } else if continuation := interactiondomain.ContinueTravelDate(effectiveTaskMessage, conversationHistory); continuation != "" { + continuationMessage = continuation + travelContinuation = true + effectiveTaskMessage = continuation } } if request.ConversationID != "" && interactiondomain.IsRetryRequest(utterance.Content) { @@ -567,7 +571,11 @@ func main() { continuedSemantic, continuedErr := interactiondomain.InterpretUtterance(continuedUtterance, projects, previousFocus, now) if continuedErr == nil { continuedSemantic.Interpretation.Source = "CONVERSATION_CONTINUATION" - continuedSemantic.Interpretation.SelectionReason = "location follow-up completed the previous local-search task" + if travelContinuation { + continuedSemantic.Interpretation.SelectionReason = "travel follow-up completed the previous travel task" + } else { + continuedSemantic.Interpretation.SelectionReason = "location follow-up completed the previous local-search task" + } semantic = continuedSemantic } } diff --git a/modules/interaction/domain/semantics_test.go b/modules/interaction/domain/semantics_test.go index 93b1cb8..f22d48a 100644 --- a/modules/interaction/domain/semantics_test.go +++ b/modules/interaction/domain/semantics_test.go @@ -108,6 +108,19 @@ func TestContinueTravelOrigin(t *testing.T) { } } +func TestContinueTravelDate(t *testing.T) { + history := []interaction.HumanUtterance{{Content: "Найди билеты до Ялты", ReceivedAtUnixMillis: 100}} + if got := ContinueTravelDate("сегодня", history); got != "Найди билеты до Ялты сегодня" { + t.Fatalf("date answer did not complete ticket search: %q", got) + } + if got := ContinueTravelDate("завтра", []interaction.HumanUtterance{{Content: "Найди билеты до Ялты сегодня", ReceivedAtUnixMillis: 100}}); got != "" { + t.Fatalf("dated ticket search must not be continued again: %q", got) + } + if got := ContinueTravelDate("сегодня", []interaction.HumanUtterance{{Content: "Какая погода в Ялте?", ReceivedAtUnixMillis: 100}}); got != "" { + t.Fatalf("date answer must not create travel work without travel context: %q", got) + } +} + func TestTravelSearchClarification(t *testing.T) { if got := TravelSearchClarification("Найди билеты", nil); got == "" { t.Fatal("underspecified travel request must ask for missing route and date") diff --git a/modules/interaction/domain/travel.go b/modules/interaction/domain/travel.go index d9d15da..4713edd 100644 --- a/modules/interaction/domain/travel.go +++ b/modules/interaction/domain/travel.go @@ -186,6 +186,34 @@ func ContinueTravelOrigin(message string, history []interaction.HumanUtterance) return strings.TrimSpace(previous + preposition + location) } +// ContinueTravelDate completes the most recent unfinished ticket search with +// a short date answer. The date is still the human's original wording, so the +// later route and research stages retain its provenance and can interpret it +// relative to the current time. +func ContinueTravelDate(message string, history []interaction.HumanUtterance) string { + current := strings.TrimSpace(StripAssistantVocative(message)) + currentTokens := semanticTokens(current) + if len(currentTokens) == 0 || len(currentTokens) > 6 || !isTravelDateSpecified(currentTokens) { + return "" + } + latestAt := int64(-1) + latest := "" + for _, utterance := range history { + tokens := semanticTokens(utterance.Content) + if !travelCue(tokens) || isTravelDateSpecified(tokens) || containsStemInTokens(tokens, "ближ") { + continue + } + if utterance.ReceivedAtUnixMillis >= latestAt { + latestAt = utterance.ReceivedAtUnixMillis + latest = strings.TrimSpace(StripAssistantVocative(utterance.Content)) + } + } + if latest == "" { + return "" + } + return strings.TrimSpace(latest + " " + current) +} + // TravelSearchClarification keeps an incomplete travel request in dialogue // instead of reducing it to an unrelated generic web search. func TravelSearchClarification(message string, history []interaction.HumanUtterance) string {