From f121445f24195f9c705e11110ecd84f805f71069 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Tue, 25 Aug 2026 18:08:48 +0300 Subject: [PATCH] Preserve media failures and enrich nearest flight searches --- cmd/core-runtime/main.go | 11 +++++--- modules/interaction/domain/semantics_test.go | 10 +++++++ modules/interaction/domain/travel.go | 29 +++++++++++++++++++- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/cmd/core-runtime/main.go b/cmd/core-runtime/main.go index 3bf0976..c72df82 100644 --- a/cmd/core-runtime/main.go +++ b/cmd/core-runtime/main.go @@ -771,10 +771,11 @@ func main() { replies = append(replies, "По запросу «"+part+"»: "+clarification) continue } + searchQuery := interactiondomain.TravelSearchQuery(part, now) researchCtx, cancelResearch := context.WithTimeout(r.Context(), 8*time.Second) result, researchErr := researchClient.SearchWeb(researchCtx, epistemics.WebSearchRequest{ - Plan: epistemics.ResearchPlan{ID: epistemics.ResearchPlanID(fmt.Sprintf("plan:%s:%d", planID, index)), Question: part, SourceClasses: []string{"web-search"}, FreshnessPolicy: "current"}, - PropositionKey: fmt.Sprintf("question:%s:%d", utterance.ID, index), Query: part, + Plan: epistemics.ResearchPlan{ID: epistemics.ResearchPlanID(fmt.Sprintf("plan:%s:%d", planID, index)), Question: searchQuery, SourceClasses: []string{"web-search"}, FreshnessPolicy: "current"}, + PropositionKey: fmt.Sprintf("question:%s:%d", utterance.ID, index), Query: searchQuery, }) cancelResearch() reply, backed := renderResearchReply(part, result, researchErr) @@ -821,7 +822,9 @@ func main() { var reminder *continuity.ProspectiveItem var generatedImage capability.MultimodalResponse mediaHandled := false + mediaAttempted := false if hasChatRoute(routePlan, chatRouteMedia) && semantic.Interpretation.Clarification != interaction.ClarificationRequired { + mediaAttempted = true imageCtx, cancelImage := context.WithTimeout(r.Context(), 15*time.Minute) generatedImage, err = cognitiveClient.GenerateImage(imageCtx, effectiveTaskMessage, "") cancelImage() @@ -920,7 +923,7 @@ func main() { ConfidenceMillis: semantic.Interpretation.ConfidenceMillis, }) cancelReview() - stableSemanticReply := mediaHandled || identityReply != "" || creativeReply != "" || (creativeFallbackReply != "" && !cognitiveAvailable) || semantic.InformationRequestKind != "" || locationReply != "" || deterministicSpeechAct == interaction.SpeechActConversational || deterministicSpeechAct == interaction.SpeechActConfirmation || deterministicSpeechAct == interaction.SpeechActRejection + stableSemanticReply := mediaAttempted || identityReply != "" || creativeReply != "" || (creativeFallbackReply != "" && !cognitiveAvailable) || semantic.InformationRequestKind != "" || locationReply != "" || deterministicSpeechAct == interaction.SpeechActConversational || deterministicSpeechAct == interaction.SpeechActConfirmation || deterministicSpeechAct == interaction.SpeechActRejection if reviewErr != nil { log.Printf("cognitive response review unavailable: %v", reviewErr) } else if !stableSemanticReply && (!researchBacked || reviewPreservesResearchEvidence(reviewedReply, planningReply)) { @@ -929,7 +932,7 @@ func main() { log.Printf("cognitive response review proposed stable semantic rewrite; preserving semantic reply") } } - if !mediaHandled && identityReply == "" && creativeReply == "" && creativeFallbackReply == "" { + if !mediaAttempted && !mediaHandled && identityReply == "" && creativeReply == "" && creativeFallbackReply == "" { stableAct := interactiondomain.ClassifySpeechAct(utterance.Content, utterance.CorrectsUtteranceID) travelClarification := "" if !creativeRequest { diff --git a/modules/interaction/domain/semantics_test.go b/modules/interaction/domain/semantics_test.go index ae1162e..aa4eb65 100644 --- a/modules/interaction/domain/semantics_test.go +++ b/modules/interaction/domain/semantics_test.go @@ -149,6 +149,16 @@ func TestTravelSearchClarification(t *testing.T) { } } +func TestTravelSearchQueryCarriesNearestFlightDate(t *testing.T) { + now := time.Date(2026, time.August, 25, 18, 0, 0, 0, time.UTC) + if got := TravelSearchQuery("какой ближайший рейс из Москвы в Казань", now); got != "какой ближайший рейс из Москвы в Казань сегодня 25.08.2026" { + t.Fatalf("nearest flight query=%q", got) + } + if got := TravelSearchQuery("рейс из Москвы в Казань завтра", now); got != "рейс из Москвы в Казань завтра" { + t.Fatalf("explicit date was changed: %q", got) + } +} + func TestInterpretUtteranceDirectiveRecordsOutcomeWithoutExecution(t *testing.T) { result := interpretForTest(t, "Поменяй базу на УПО1.", activeFocus("upo", "УПО"), "") if result.Interpretation.SpeechAct != interaction.SpeechActDirective { diff --git a/modules/interaction/domain/travel.go b/modules/interaction/domain/travel.go index fe3b8a1..05a9725 100644 --- a/modules/interaction/domain/travel.go +++ b/modules/interaction/domain/travel.go @@ -2,10 +2,25 @@ package domain import ( "strings" + "time" "git.cin.su/m/Assistant/contracts/interaction" ) +// TravelSearchQuery carries the implicit current-day meaning of a nearest +// flight request into the research adapter. +func TravelSearchQuery(message string, now time.Time) string { + cleaned := strings.TrimSpace(StripAssistantVocative(message)) + tokens := semanticTokens(cleaned) + if !travelCue(tokens) || !containsStemInTokens(tokens, "ближ") || isTravelDateSpecified(tokens) { + return cleaned + } + if now.IsZero() { + return cleaned + " сегодня" + } + return cleaned + " сегодня " + now.Format("02.01.2006") +} + // StripAssistantVocative removes a leading assistant name used as an address. // The original utterance remains the historical source; this value is only for // interpretation, cognitive prompts and external queries. @@ -159,7 +174,7 @@ func TravelSearchClarification(message string, history []interaction.HumanUttera } origin := containsStemInTokens(current, "из", "откуда", "от") destination := containsStemInTokens(current, "в", "куда", "до") - date := containsStemInTokens(current, "сегодня", "завтра", "послезавтр", "январ", "феврал", "март", "апрел", "мая", "июн", "июл", "август", "сентябр", "октябр", "ноябр", "декабр") + date := isTravelDateSpecified(current) if containsStemInTokens(current, "ближ") { date = true } @@ -209,6 +224,18 @@ func TravelSearchClarification(message string, history []interaction.HumanUttera return "Уточните маршрут и дату: откуда, куда и на какой день нужны билеты?" } +func isTravelDateSpecified(tokens []string) bool { + if containsStemInTokens(tokens, "сегодня", "завтра", "послезавтр", "январ", "феврал", "март", "апрел", "мая", "июн", "июл", "август", "сентябр", "октябр", "ноябр", "декабр") { + return true + } + for _, token := range tokens { + if hasDigit(token) { + return true + } + } + return false +} + func isTravelDateWord(value string) bool { return containsStem(value, "сегодня", "завтра", "послезавтр", "январ", "феврал", "март", "апрел", "мая", "июн", "июл", "август", "сентябр", "октябр", "ноябр", "декабр") }