Load task aliases from semantic memory
CI / test (push) Failing after 1m0s
CI / test (pull_request) Failing after 1m2s

This commit is contained in:
m
2026-08-25 14:48:32 +03:00
parent c94a96c47e
commit ca45df3c62
3 changed files with 51 additions and 21 deletions
+6 -2
View File
@@ -38,8 +38,8 @@ func TestHouseholdCreativeRouteDoesNotUseResearch(t *testing.T) {
func TestHouseholdMarketplaceBrandSearchUsesResearchRoute(t *testing.T) {
candidate := interactiondomain.BuildDeterministicCandidate("найди на WB бренд marka-wear", "", time.Unix(10, 0).UnixMilli())
decomposition := interactiondomain.DecomposeTask("найди на WB бренд marka-wear")
if decomposition.Intent != interactiondomain.TaskIntentSearch || decomposition.Object != interactiondomain.TaskObjectBrand || decomposition.Source != interactiondomain.TaskSourceWB || decomposition.Target != "marka-wear" || len(decomposition.Missing) != 0 {
decomposition := interactiondomain.DecomposeTaskWithLexicon("найди на WB бренд marka-wear", interactiondomain.TaskLexicon{ObjectAliases: []interactiondomain.TaskLexiconEntry{{Alias: "бренд", Value: "BRAND"}}, SourceAliases: []interactiondomain.TaskLexiconEntry{{Alias: "wb", Value: "WILDBERRIES"}}})
if decomposition.Intent != interactiondomain.TaskIntentSearch || decomposition.Object != "BRAND" || decomposition.Source != "WILDBERRIES" || decomposition.Target != "marka-wear" || len(decomposition.Missing) != 0 {
t.Fatalf("marketplace request was not split into executable atoms: %+v", decomposition)
}
routes := buildChatRoutePlan(interactiondomain.SemanticResult{NeedsCognition: candidate.NeedsCognition, CapabilityHints: candidate.CapabilityHints})
@@ -50,6 +50,10 @@ func TestHouseholdMarketplaceBrandSearchUsesResearchRoute(t *testing.T) {
func TestHouseholdBearDrawingUsesCreativeLocalRoute(t *testing.T) {
candidate := interactiondomain.BuildDeterministicCandidate("нарисуй бурого медведя", "", time.Unix(10, 0).UnixMilli())
decomposition := interactiondomain.DecomposeTaskWithLexicon("нарисуй бурого медведя", interactiondomain.TaskLexicon{ObjectAliases: []interactiondomain.TaskLexiconEntry{{Alias: "медвед", Value: "IMAGE"}}})
if decomposition.Intent != interactiondomain.TaskIntentCreate || decomposition.Object != "IMAGE" || decomposition.Target != "бурого медведя" || decomposition.NeedsResearch {
t.Fatalf("creative request was not split into local execution atoms: %+v", decomposition)
}
for _, hint := range candidate.CapabilityHints {
if capability.CapabilityKind(hint) == capability.CapabilityKindWebResearch {
t.Fatalf("bear drawing selected web research: %+v", candidate)
+6 -4
View File
@@ -171,8 +171,9 @@ func TestSourceDependentQuestionToleratesTyposAndPlaceSearch(t *testing.T) {
}
func TestDecomposeMarketplaceBrandRequestIntoExecutionAtoms(t *testing.T) {
got := DecomposeTask("найди на WB бренд marka-wear")
if got.Intent != TaskIntentSearch || got.Object != TaskObjectBrand || got.Source != TaskSourceWB || got.Target != "marka-wear" || !got.NeedsResearch {
lexicon := TaskLexicon{ObjectAliases: []TaskLexiconEntry{{Alias: "бренд", Value: "BRAND"}}, SourceAliases: []TaskLexiconEntry{{Alias: "wb", Value: "WILDBERRIES"}}}
got := DecomposeTaskWithLexicon("найди на WB бренд marka-wear", lexicon)
if got.Intent != TaskIntentSearch || got.Object != "BRAND" || got.Source != "WILDBERRIES" || got.Target != "marka-wear" || !got.NeedsResearch {
t.Fatalf("unexpected marketplace decomposition: %+v", got)
}
if len(got.Missing) != 0 {
@@ -181,8 +182,9 @@ func TestDecomposeMarketplaceBrandRequestIntoExecutionAtoms(t *testing.T) {
}
func TestDecomposeCreativeRequestDoesNotInventResearch(t *testing.T) {
got := DecomposeTask("нарисуй бурого медведя")
if got.Intent != TaskIntentCreate || got.Object != TaskObjectImage || got.Target != "бурого медведя" || got.NeedsResearch {
lexicon := TaskLexicon{ObjectAliases: []TaskLexiconEntry{{Alias: "медвед", Value: "IMAGE"}}}
got := DecomposeTaskWithLexicon("нарисуй бурого медведя", lexicon)
if got.Intent != TaskIntentCreate || got.Object != "IMAGE" || got.Target != "бурого медведя" || got.NeedsResearch {
t.Fatalf("unexpected creative decomposition: %+v", got)
}
}
@@ -16,18 +16,32 @@ type TaskDecomposition struct {
NeedsResearch bool
}
type TaskLexiconEntry struct {
Alias string
Value string
}
type TaskLexicon struct {
ObjectAliases []TaskLexiconEntry
SourceAliases []TaskLexiconEntry
}
const (
TaskIntentSearch = "SEARCH"
TaskIntentCreate = "CREATE"
TaskObjectBrand = "BRAND"
TaskObjectImage = "IMAGE"
TaskSourceWB = "WILDBERRIES"
)
// DecomposeTask performs a cheap semantic pass. It is deliberately a
// vocabulary-assisted parser, not a complete language grammar: the result is
// an execution plan seed for the appropriate agent, not the final answer.
// DecomposeTask performs the structural pass without assuming a mutable
// world vocabulary. Learned names, aliases and source meanings are supplied
// by memory through DecomposeTaskWithLexicon.
func DecomposeTask(message string) TaskDecomposition {
return DecomposeTaskWithLexicon(message, TaskLexicon{})
}
// 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.
func DecomposeTaskWithLexicon(message string, lexicon TaskLexicon) TaskDecomposition {
lower := strings.ToLower(strings.TrimSpace(message))
tokens := semanticTokens(message)
result := TaskDecomposition{}
@@ -38,20 +52,30 @@ func DecomposeTask(message string) TaskDecomposition {
if containsStemInTokens(tokens, "нарис", "рису", "изобраз", "создай") {
result.Intent = TaskIntentCreate
}
if containsStemInTokens(tokens, "бренд") {
result.Object = TaskObjectBrand
for _, entry := range lexicon.ObjectAliases {
if containsStemInTokens(tokens, entry.Alias) {
result.Object = entry.Value
break
}
}
if containsStemInTokens(tokens, "медвед", "мишк", "картин") {
result.Object = TaskObjectImage
for _, entry := range lexicon.SourceAliases {
if containsStemInTokens(tokens, entry.Alias) {
result.Source = entry.Value
break
}
}
if containsToken(tokens, "wb") || containsStemInTokens(tokens, "wildberr", "маркетплейс") {
result.Source = TaskSourceWB
if result.Source != "" {
result.NeedsResearch = result.Intent == TaskIntentSearch
}
if result.Object == TaskObjectBrand {
result.Target = valueAfterCue(lower, "бренд")
if result.Object != "" {
for _, entry := range lexicon.ObjectAliases {
if entry.Value == result.Object {
result.Target = valueAfterCue(lower, entry.Alias)
break
}
}
}
if result.Object == TaskObjectImage && result.Intent == TaskIntentCreate {
if result.Intent == TaskIntentCreate && result.Target == "" {
result.Target = valueAfterAnyCue(lower, "нарисуй", "нарисовать", "изобрази", "создай")
}
if result.Intent == TaskIntentSearch && result.Source == "" {