Keep travel requests in semantic dialogue #312
@@ -508,7 +508,7 @@ func main() {
|
||||
http.Error(w, "project focus unavailable", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
var effectiveTaskMessage = utterance.Content
|
||||
var effectiveTaskMessage = interactiondomain.StripAssistantVocative(utterance.Content)
|
||||
var previousTask *interaction.HumanUtterance
|
||||
var conversationHistory []interaction.HumanUtterance
|
||||
if request.ConversationID != "" {
|
||||
@@ -536,7 +536,7 @@ func main() {
|
||||
}
|
||||
candidate := conversationHistory[index]
|
||||
previousTask = &candidate
|
||||
effectiveTaskMessage = candidate.Content
|
||||
effectiveTaskMessage = interactiondomain.StripAssistantVocative(candidate.Content)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -601,6 +601,13 @@ func main() {
|
||||
cognitiveAvailable = false
|
||||
}
|
||||
}
|
||||
if travelReply := interactiondomain.TravelSearchClarification(effectiveTaskMessage, conversationHistory); travelReply != "" {
|
||||
semantic.Interpretation.Clarification = interaction.ClarificationRequired
|
||||
semantic.Interpretation.Applicability = interaction.SemanticCandidateClarify
|
||||
semantic.Reply = travelReply
|
||||
semantic.NeedsCognition = false
|
||||
semantic.Interpretation.NeedsCognition = false
|
||||
}
|
||||
if err := interactionRepository.AppendUtterance(r.Context(), conversation, utterance, intent, relations); err != nil {
|
||||
http.Error(w, "save chat message", http.StatusInternalServerError)
|
||||
return
|
||||
|
||||
@@ -60,6 +60,28 @@ func TestStableConversationalReplySurvivesCognitiveRewrite(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStripAssistantVocative(t *testing.T) {
|
||||
if got := StripAssistantVocative("Дед, какая погода в Сочи?"); got != "какая погода в Сочи?" {
|
||||
t.Fatalf("unexpected cleaned query: %q", got)
|
||||
}
|
||||
if got := StripAssistantVocative("домовой: найди ресторан"); got != "найди ресторан" {
|
||||
t.Fatalf("unexpected cleaned query: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTravelSearchClarification(t *testing.T) {
|
||||
if got := TravelSearchClarification("Найди билеты", nil); got == "" {
|
||||
t.Fatal("underspecified travel request must ask for missing route and date")
|
||||
}
|
||||
if got := TravelSearchClarification("Найди билеты из Москвы в Сочи на 12 сентября", nil); got != "" {
|
||||
t.Fatalf("complete travel request must proceed, got %q", got)
|
||||
}
|
||||
history := []interaction.HumanUtterance{{Content: "Найди билеты"}}
|
||||
if got := TravelSearchClarification("а куда летим?", history); got == "" {
|
||||
t.Fatal("follow-up must remain attached to the travel goal")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInterpretUtteranceDirectiveRecordsOutcomeWithoutExecution(t *testing.T) {
|
||||
result := interpretForTest(t, "Поменяй базу на УПО1.", activeFocus("upo", "УПО"), "")
|
||||
if result.Interpretation.SpeechAct != interaction.SpeechActDirective {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/interaction"
|
||||
)
|
||||
|
||||
// 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.
|
||||
func StripAssistantVocative(message string) string {
|
||||
trimmed := strings.TrimSpace(message)
|
||||
lower := strings.ToLower(trimmed)
|
||||
for _, name := range []string{"дед", "домовой"} {
|
||||
if !strings.HasPrefix(lower, name) {
|
||||
continue
|
||||
}
|
||||
rest := trimmed[len(name):]
|
||||
if rest == "" || !strings.ContainsRune(" \t,;:!?—-", []rune(rest)[0]) {
|
||||
continue
|
||||
}
|
||||
return strings.TrimSpace(strings.TrimLeft(rest, " \t,;:!?—-"))
|
||||
}
|
||||
return trimmed
|
||||
}
|
||||
|
||||
// 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 {
|
||||
current := semanticTokens(StripAssistantVocative(message))
|
||||
travel := travelCue(current)
|
||||
if !travel {
|
||||
for index := len(history) - 1; index >= 0; index-- {
|
||||
if travelCue(semanticTokens(history[index].Content)) {
|
||||
travel = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !travel {
|
||||
return ""
|
||||
}
|
||||
origin := containsStemInTokens(current, "из", "откуда", "от")
|
||||
destination := containsStemInTokens(current, "в", "куда", "до")
|
||||
date := containsStemInTokens(current, "сегодня", "завтра", "послезавтр", "январ", "феврал", "март", "апрел", "мая", "июн", "июл", "август", "сентябр", "октябр", "ноябр", "декабр")
|
||||
if !date {
|
||||
for _, token := range current {
|
||||
if hasDigit(token) {
|
||||
date = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if origin && destination && date {
|
||||
return ""
|
||||
}
|
||||
return "Уточните маршрут и дату: откуда, куда и на какой день нужны билеты?"
|
||||
}
|
||||
|
||||
func travelCue(tokens []string) bool {
|
||||
return containsStemInTokens(tokens, "билет", "рейс", "поезд", "автобус", "перелет", "перелёт")
|
||||
}
|
||||
|
||||
func hasDigit(value string) bool {
|
||||
for _, runeValue := range value {
|
||||
if runeValue >= '0' && runeValue <= '9' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user