Continue local search from conversation context #321

Merged
m merged 2 commits from codex/chat-semantic-inventory into main 2026-08-25 02:57:40 +00:00
3 changed files with 33 additions and 0 deletions
+5
View File
@@ -511,6 +511,7 @@ func main() {
var effectiveTaskMessage = interactiondomain.StripAssistantVocative(utterance.Content)
var previousTask *interaction.HumanUtterance
var conversationHistory []interaction.HumanUtterance
var conversationOnlyHistory []interaction.HumanUtterance
if request.ConversationID != "" {
var historyErr error
conversationHistory, historyErr = interactionRepository.Utterances(r.Context(), conversation.ID)
@@ -518,6 +519,7 @@ func main() {
log.Printf("interaction conversation history unavailable: %v", historyErr)
}
}
conversationOnlyHistory = append(conversationOnlyHistory, conversationHistory...)
// Keep durable user context available even when the web client carries a
// conversation ID; a new or rotated conversation must not erase learned
// preferences.
@@ -528,6 +530,9 @@ func main() {
log.Printf("interaction principal history loaded: %d utterances", len(recentUserHistory))
}
conversationHistory = append(conversationHistory, recentUserHistory...)
if continuation := interactiondomain.ContinueLocalSearch(effectiveTaskMessage, conversationOnlyHistory); continuation != "" {
effectiveTaskMessage = continuation
}
if request.ConversationID != "" && interactiondomain.IsRetryRequest(utterance.Content) {
if len(conversationHistory) > 0 {
for index := len(conversationHistory) - 1; index >= 0; index-- {
@@ -85,6 +85,16 @@ func TestLocalSearchClarification(t *testing.T) {
}
}
func TestContinueLocalSearch(t *testing.T) {
history := []interaction.HumanUtterance{{Content: "Найди ближайший ресторан"}}
if got := ContinueLocalSearch("в Сочи", history); got != "Найди ближайший ресторан в Сочи" {
t.Fatalf("unexpected continuation: %q", got)
}
if got := ContinueLocalSearch("в Сочи", nil); got != "" {
t.Fatalf("location must not create a goal without conversation 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")
+18
View File
@@ -65,6 +65,24 @@ func LocalSearchClarification(message string) string {
return "В каком городе или районе искать ресторан?"
}
// ContinueLocalSearch turns a location-only follow-up into the previous local
// search request, but only when that request is present in this conversation.
func ContinueLocalSearch(message string, history []interaction.HumanUtterance) string {
current := strings.TrimSpace(StripAssistantVocative(message))
tokens := semanticTokens(current)
if len(tokens) == 0 || len(tokens) > 3 || (tokens[0] != "в" && tokens[0] != "на" && tokens[0] != "около") {
return ""
}
for index := len(history) - 1; index >= 0; index-- {
previous := strings.TrimSpace(StripAssistantVocative(history[index].Content))
previousTokens := semanticTokens(previous)
if containsStemInTokens(previousTokens, "ресторан", "кафе") && containsStemInTokens(previousTokens, "ближ", "рядом") {
return strings.TrimSpace(previous + " " + current)
}
}
return ""
}
// 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 {