Allow research source recovery to complete #390

Merged
m merged 1 commits from codex/household-travel-query-fix into main 2026-08-25 21:25:27 +00:00
2 changed files with 29 additions and 4 deletions
+7 -1
View File
@@ -214,11 +214,17 @@ func TestDecomposeUsesRememberedAliasesAcrossWordings(t *testing.T) {
func TestTaskLexiconFromHistoryFeedsSearchQueryWithoutCoreAlias(t *testing.T) {
history := []interaction.HumanUtterance{{Content: "запомни: WB — это Wildberries, источник для поиска"}}
lexicon := TaskLexiconFromHistory(history)
if got := SearchQueryWithLexicon("найди на WB бренд marka-wear", lexicon); got != "найди на WB бренд marka-wear wildberries" {
if got := SearchQueryWithLexicon("найди на WB бренд marka-wear", lexicon); got != "на WB бренд marka-wear wildberries" {
t.Fatalf("learned source was not applied to search: %q", got)
}
}
func TestSearchQueryWithoutLearnedSourceOmitsImperative(t *testing.T) {
if got := SearchQueryWithLexicon("найди на WB бренд marka-wear", TaskLexicon{}); got != "на WB бренд marka-wear" {
t.Fatalf("search imperative leaked into external query: %q", got)
}
}
func TestDecomposeCreativeRequestDoesNotInventResearch(t *testing.T) {
lexicon := TaskLexicon{ObjectAliases: []TaskLexiconEntry{{Alias: "медвед", Value: "IMAGE"}}}
got := DecomposeTaskWithLexicon("нарисуй бурого медведя", lexicon)
@@ -72,19 +72,38 @@ func TaskLexiconFromHistory(history []interaction.HumanUtterance) TaskLexicon {
return lexicon
}
// SearchQueryWithLexicon keeps a learned source in the external query without
// turning its alias into core vocabulary.
// SearchQueryWithLexicon prepares a search-engine query without turning a
// learned source alias into core vocabulary. The imperative that expresses the
// user's intent is deliberately omitted: search engines must receive the
// subject of the request, not mistake a Russian command such as "найди" for a
// brand or site name.
func SearchQueryWithLexicon(message string, lexicon TaskLexicon) string {
decomposition := DecomposeTaskWithLexicon(message, lexicon)
if decomposition.Intent == TaskIntentSearch && decomposition.Source != "" {
if decomposition.Target != "" {
return decomposition.Target + " " + decomposition.Source
}
return strings.TrimSpace(message) + " " + decomposition.Source
return searchQueryWithoutImperative(message) + " " + decomposition.Source
}
if decomposition.Intent == TaskIntentSearch {
return searchQueryWithoutImperative(message)
}
return strings.TrimSpace(message)
}
func searchQueryWithoutImperative(message string) string {
cleaned := strings.TrimSpace(message)
fields := strings.Fields(cleaned)
if len(fields) < 2 {
return cleaned
}
first := strings.Trim(strings.ToLower(fields[0]), ".,!?:;")
if !containsStem(first, "найд", "поищ", "подбер", "проверь") {
return cleaned
}
return strings.TrimSpace(strings.TrimPrefix(cleaned, fields[0]))
}
// DecomposeTaskWithLexicon performs a cheap semantic pass. The lexicon is
// intentionally an input: a remembered alias may be revalidated or replaced
// without changing the parser or shipping a new hard-coded branch.