Load task aliases from semantic memory
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 == "" {
|
||||
|
||||
Reference in New Issue
Block a user