Merge pull request 'Expand semantic decomposition checks' (#352) from codex/household-scenario-audit into main
CI / test (push) Successful in 1m32s

Merge PR #352
This commit was merged in pull request #352.
This commit is contained in:
m
2026-08-25 12:08:40 +00:00
4 changed files with 84 additions and 26 deletions
@@ -48,6 +48,20 @@ func TestHouseholdMarketplaceBrandSearchUsesResearchRoute(t *testing.T) {
}
}
func TestHouseholdSemanticMemoryControlsMarketplaceAlias(t *testing.T) {
withoutMemory := interactiondomain.DecomposeTask("найди на WB бренд marka-wear")
if withoutMemory.Source != "" || withoutMemory.Object != "" {
t.Fatalf("core invented marketplace meaning without memory: %+v", withoutMemory)
}
withMemory := interactiondomain.DecomposeTaskWithLexicon("поищи марку marka-wear в Wildberries", interactiondomain.TaskLexicon{
ObjectAliases: []interactiondomain.TaskLexiconEntry{{Alias: "марку", Value: "BRAND"}},
SourceAliases: []interactiondomain.TaskLexiconEntry{{Alias: "wildberries", Value: "WILDBERRIES"}},
})
if withMemory.Intent != interactiondomain.TaskIntentSearch || withMemory.Object != "BRAND" || withMemory.Source != "WILDBERRIES" || withMemory.Target != "marka-wear" || !withMemory.NeedsResearch {
t.Fatalf("semantic memory did not control marketplace decomposition: %+v", withMemory)
}
}
func TestHouseholdBearDrawingUsesCreativeLocalRoute(t *testing.T) {
candidate := interactiondomain.BuildDeterministicCandidate("нарисуй бурого медведя", "", time.Unix(10, 0).UnixMilli())
decomposition := interactiondomain.DecomposeTaskWithLexicon("нарисуй бурого медведя", interactiondomain.TaskLexicon{ObjectAliases: []interactiondomain.TaskLexiconEntry{{Alias: "медвед", Value: "IMAGE"}}})
+30 -20
View File
@@ -510,6 +510,7 @@ func main() {
return
}
var effectiveTaskMessage = interactiondomain.StripAssistantVocative(utterance.Content)
creativeUserRequest := interactiondomain.IsCreativeRequest(utterance.Content)
continuationMessage := ""
travelContinuation := false
var previousTask *interaction.HumanUtterance
@@ -531,13 +532,15 @@ func main() {
log.Printf("interaction principal history loaded: %d utterances", len(recentUserHistory))
}
conversationHistory = append(conversationHistory, recentUserHistory...)
if continuation := interactiondomain.ContinueLocalSearch(effectiveTaskMessage, conversationHistory); continuation != "" {
continuationMessage = continuation
effectiveTaskMessage = continuation
} else if continuation := interactiondomain.ContinueTravelOrigin(effectiveTaskMessage, conversationHistory); continuation != "" {
continuationMessage = continuation
travelContinuation = true
effectiveTaskMessage = continuation
if !creativeUserRequest {
if continuation := interactiondomain.ContinueLocalSearch(effectiveTaskMessage, conversationHistory); continuation != "" {
continuationMessage = continuation
effectiveTaskMessage = continuation
} else if continuation := interactiondomain.ContinueTravelOrigin(effectiveTaskMessage, conversationHistory); continuation != "" {
continuationMessage = continuation
travelContinuation = true
effectiveTaskMessage = continuation
}
}
if request.ConversationID != "" && interactiondomain.IsRetryRequest(utterance.Content) {
if len(conversationHistory) > 0 {
@@ -567,12 +570,12 @@ func main() {
semantic = continuedSemantic
}
}
if travelContinuation && interactiondomain.TravelSearchClarification(effectiveTaskMessage, conversationHistory) == "" {
if !creativeUserRequest && travelContinuation && interactiondomain.TravelSearchClarification(effectiveTaskMessage, conversationHistory) == "" {
semantic.CapabilityHints = append(semantic.CapabilityHints, string(capability.CapabilityKindWebResearch))
semantic.NeedsCognition = false
semantic.Interpretation.NeedsCognition = false
}
creativeRequest := interactiondomain.IsCreativeRequest(utterance.Content) || interactiondomain.IsCreativeRequest(effectiveTaskMessage)
creativeRequest := creativeUserRequest || interactiondomain.IsCreativeRequest(effectiveTaskMessage)
creativeReply := interactiondomain.CreativeDeterministicReply(utterance.Content)
if creativeReply == "" {
creativeReply = interactiondomain.CreativeDeterministicReply(effectiveTaskMessage)
@@ -652,13 +655,15 @@ func main() {
cognitiveAvailable = false
}
}
if travelReply := interactiondomain.TravelSearchClarification(effectiveTaskMessage, conversationHistory); travelReply != "" {
semantic.Interpretation.Clarification = interaction.ClarificationRequired
semantic.Interpretation.Applicability = interaction.SemanticCandidateClarify
semantic.Reply = travelReply
deterministicReply = travelReply
semantic.NeedsCognition = false
semantic.Interpretation.NeedsCognition = false
if !creativeRequest {
if travelReply := interactiondomain.TravelSearchClarification(effectiveTaskMessage, conversationHistory); travelReply != "" {
semantic.Interpretation.Clarification = interaction.ClarificationRequired
semantic.Interpretation.Applicability = interaction.SemanticCandidateClarify
semantic.Reply = travelReply
deterministicReply = 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)
@@ -794,9 +799,11 @@ func main() {
planningReply = helpPath.Reply
needsCognition = false
}
if travelReply := interactiondomain.TravelSearchClarification(effectiveTaskMessage, conversationHistory); travelReply != "" {
planningReply = travelReply
needsCognition = false
if !creativeRequest {
if travelReply := interactiondomain.TravelSearchClarification(effectiveTaskMessage, conversationHistory); travelReply != "" {
planningReply = travelReply
needsCognition = false
}
}
if planningReply == "" && needsCognition && cognitiveAttempted && !cognitiveAvailable {
planningReply = humanCognitiveDegradedReply(utterance.Content)
@@ -907,7 +914,10 @@ func main() {
}
if identityReply == "" && creativeReply == "" && creativeFallbackReply == "" {
stableAct := interactiondomain.ClassifySpeechAct(utterance.Content, utterance.CorrectsUtteranceID)
travelClarification := interactiondomain.TravelSearchClarification(effectiveTaskMessage, conversationHistory)
travelClarification := ""
if !creativeRequest {
travelClarification = interactiondomain.TravelSearchClarification(effectiveTaskMessage, conversationHistory)
}
if stableReply, stable := interactiondomain.StableConversationalReply(utterance.Content, stableAct); stable && travelClarification == "" {
planningReply = stableReply
needsCognition = false
+20
View File
@@ -181,6 +181,26 @@ func TestDecomposeMarketplaceBrandRequestIntoExecutionAtoms(t *testing.T) {
}
}
func TestDecomposeDoesNotInventMutableAliases(t *testing.T) {
got := DecomposeTask("найди на WB бренд marka-wear")
if got.Source != "" || got.Object != "" || got.NeedsResearch {
t.Fatalf("parser invented a mutable alias without memory: %+v", got)
}
}
func TestDecomposeUsesRememberedAliasesAcrossWordings(t *testing.T) {
lexicon := TaskLexicon{
ObjectAliases: []TaskLexiconEntry{{Alias: "бренд", Value: "BRAND"}, {Alias: "марку", Value: "BRAND"}},
SourceAliases: []TaskLexiconEntry{{Alias: "wb", Value: "WILDBERRIES"}, {Alias: "wildberries", Value: "WILDBERRIES"}},
}
for _, message := range []string{"найди на WB бренд marka-wear", "поищи марку marka-wear в Wildberries"} {
got := DecomposeTaskWithLexicon(message, lexicon)
if got.Intent != TaskIntentSearch || got.Object != "BRAND" || got.Source != "WILDBERRIES" || got.Target != "marka-wear" || !got.NeedsResearch {
t.Fatalf("remembered aliases failed for %q: %+v", message, got)
}
}
}
func TestDecomposeCreativeRequestDoesNotInventResearch(t *testing.T) {
lexicon := TaskLexicon{ObjectAliases: []TaskLexiconEntry{{Alias: "медвед", Value: "IMAGE"}}}
got := DecomposeTaskWithLexicon("нарисуй бурого медведя", lexicon)
@@ -53,13 +53,13 @@ func DecomposeTaskWithLexicon(message string, lexicon TaskLexicon) TaskDecomposi
result.Intent = TaskIntentCreate
}
for _, entry := range lexicon.ObjectAliases {
if containsStemInTokens(tokens, entry.Alias) {
if lexiconAliasMatches(tokens, entry.Alias) {
result.Object = entry.Value
break
}
}
for _, entry := range lexicon.SourceAliases {
if containsStemInTokens(tokens, entry.Alias) {
if lexiconAliasMatches(tokens, entry.Alias) {
result.Source = entry.Value
break
}
@@ -72,7 +72,7 @@ func DecomposeTaskWithLexicon(message string, lexicon TaskLexicon) TaskDecomposi
}
if result.Target == "" && result.Object != "" {
for _, entry := range lexicon.ObjectAliases {
if entry.Value == result.Object {
if entry.Value == result.Object && lexiconAliasMatches(tokens, entry.Alias) {
result.Target = valueAfterCue(lower, entry.Alias)
break
}
@@ -87,6 +87,14 @@ func DecomposeTaskWithLexicon(message string, lexicon TaskLexicon) TaskDecomposi
return result
}
func lexiconAliasMatches(tokens []string, alias string) bool {
alias = strings.TrimSpace(strings.ToLower(alias))
if len([]rune(alias)) < 2 {
return false
}
return containsStemInTokens(tokens, alias)
}
func valueAfterAnyCue(message string, cues ...string) string {
for _, cue := range cues {
if value := valueAfterCue(message, cue); value != "" {
@@ -103,8 +111,14 @@ func valueAfterCue(message, cue string) string {
}
value := strings.TrimSpace(message[index+len(cue):])
value = strings.Trim(value, " \t:,-")
if end := strings.IndexAny(value, "?!,.;"); end >= 0 {
value = value[:end]
end := len(value)
if punctuation := strings.IndexAny(value, "?!,.;"); punctuation >= 0 && punctuation < end {
end = punctuation
}
return strings.TrimSpace(value)
for _, boundary := range []string{" в ", " на ", " из ", " для ", " около "} {
if index := strings.Index(value, boundary); index >= 0 && index < end {
end = index
}
}
return strings.TrimSpace(value[:end])
}