Require destination evidence for ticket results
CI / test (push) Successful in 1m17s

This commit is contained in:
m
2026-08-26 04:17:12 +03:00
parent 4d1a579132
commit da5d06895b
4 changed files with 48 additions and 0 deletions
+17
View File
@@ -1818,6 +1818,9 @@ func researchFindingMatchesIntent(query string, finding epistemics.ResearchFindi
if !findingContainsRequestedIdentifier(query, finding) {
return false
}
if ticketSearchQuery(query) && !ticketFindingMatchesDestination(query, finding) {
return false
}
if !nearestFlightQuery(query) {
return true
}
@@ -1847,6 +1850,20 @@ func researchFindingMatchesIntent(query string, finding epistemics.ResearchFindi
return true
}
func ticketSearchQuery(query string) bool {
return interactiondomain.TicketSearchDestination(query) != ""
}
func ticketFindingMatchesDestination(query string, finding epistemics.ResearchFinding) bool {
destination := strings.ToLower(interactiondomain.TicketSearchDestination(query))
if destination == "" {
return true
}
// The source title or URL may describe a category; human-facing proof must
// mention the requested destination in the actual displayed excerpt.
return strings.Contains(strings.ToLower(finding.Excerpt.Text), destination)
}
// findingContainsRequestedIdentifier prevents a generic landing page from
// being presented as a result for a concrete model, article or brand request.
// Identifiers are detected by their syntax, not by a catalogue of providers or
+12
View File
@@ -58,6 +58,18 @@ func TestNearestFlightEvidenceRequiresBothRouteEndpointsWhenKnown(t *testing.T)
}
}
func TestTicketReplyRejectsGenericCategoryPageWithoutDestination(t *testing.T) {
query := "найди билеты до Ялты сегодня"
generic := epistemics.ResearchFinding{Source: epistemics.Source{Title: "Купить билеты", Locator: "https://tickets.example"}, Excerpt: epistemics.SourceExcerpt{Text: "Поиск авиабилетов среди множества направлений."}}
if researchFindingMatchesIntent(query, generic) {
t.Fatal("generic ticket category was presented as destination result")
}
relevant := epistemics.ResearchFinding{Source: epistemics.Source{Title: "Билеты"}, Excerpt: epistemics.SourceExcerpt{Text: "Билеты до Ялты: варианты на сегодня."}}
if !researchFindingMatchesIntent(query, relevant) {
t.Fatal("destination-specific ticket result was rejected")
}
}
func TestResearchReplyRejectsGenericLandingPageForConcreteIdentifier(t *testing.T) {
result := epistemics.ResearchResult{Findings: []epistemics.ResearchFinding{{
State: epistemics.FindingSupported,
@@ -85,6 +85,15 @@ func TestLocalSearchClarification(t *testing.T) {
}
}
func TestTicketSearchDestinationUsesRequestDestination(t *testing.T) {
if got := TicketSearchDestination("найди билеты до Ялты сегодня"); got != "Ялты" {
t.Fatalf("ticket destination = %q", got)
}
if got := TicketSearchDestination("найди билеты"); got != "" {
t.Fatalf("destination invented without one: %q", got)
}
}
func TestContinueLocalSearch(t *testing.T) {
history := []interaction.HumanUtterance{{Content: "Найди ближайший ресторан"}}
if got := ContinueLocalSearch("в Сочи", history); got != "Найди ближайший ресторан в Сочи" {
+10
View File
@@ -236,6 +236,16 @@ func ContinueTravelDate(message string, history []interaction.HumanUtterance) st
return strings.TrimSpace(latest + " " + current)
}
// TicketSearchDestination extracts the destination from a ticket request
// without assigning any city or travel-provider meaning in core code.
func TicketSearchDestination(message string) string {
tokens := semanticTokens(StripAssistantVocative(message))
if !containsStemInTokens(tokens, "билет") {
return ""
}
return travelContextDestinationFromTokens(tokens)
}
// 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 {