Continue ticket search with date follow-up #380

Merged
m merged 2 commits from codex/household-travel-query-fix into main 2026-08-25 18:21:31 +00:00
3 changed files with 50 additions and 1 deletions
+9 -1
View File
@@ -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
}
}
@@ -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")
+28
View File
@@ -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 {