fix: route model inventory by semantic intent
CI / test (push) Successful in 22s
CI / test (pull_request) Successful in 19s

This commit is contained in:
m
2026-08-22 02:30:23 +03:00
parent d286fbb234
commit 01f4fa27c2
5 changed files with 56 additions and 25 deletions
+3 -9
View File
@@ -394,7 +394,7 @@ func main() {
}
needsCognition = false
}
if isModelInventoryQuestion(utterance.Content) {
if isModelInventoryQuestion(semantic) {
candidates, inventoryErr := capabilityRepository.LoadCognitiveCandidates(r.Context(), capability.CapabilityID("creator-advisory"), session.PrincipalID)
planningReply = humanCognitiveModelInventory(candidates, inventoryErr)
needsCognition = false
@@ -1158,14 +1158,8 @@ func isCapabilityQuestion(message string) bool {
return strings.Contains(lower, "что ты умеешь") || strings.Contains(lower, "что умеешь") || strings.Contains(lower, "какие возможности")
}
func isModelInventoryQuestion(message string) bool {
lower := strings.ToLower(strings.TrimSpace(message))
if lower == "" {
return false
}
asksAboutModels := strings.Contains(lower, "модел") || strings.Contains(lower, "ии") && (strings.Contains(lower, "внешн") || strings.Contains(lower, "настро") || strings.Contains(lower, "подключ"))
asksForInventory := strings.Contains(lower, "какие") || strings.Contains(lower, "какая") || strings.Contains(lower, "список") || strings.Contains(lower, "доступ") || strings.Contains(lower, "настро")
return asksAboutModels && asksForInventory
func isModelInventoryQuestion(result interactiondomain.SemanticResult) bool {
return result.InformationRequestKind == interactiondomain.InformationRequestCognitiveModelInventory
}
func humanCognitiveModelInventory(candidates []capability.ResolvedCognitiveBinding, inventoryErr error) string {
+15 -2
View File
@@ -11,6 +11,8 @@ import (
"time"
"git.cin.su/m/Assistant/contracts/capability"
"git.cin.su/m/Assistant/contracts/interaction"
interactiondomain "git.cin.su/m/Assistant/modules/interaction/domain"
)
func TestBearerTokenRequiresBearerScheme(t *testing.T) {
@@ -51,9 +53,20 @@ func TestCognitiveDegradedReplyKeepsDomovoyAlive(t *testing.T) {
func TestModelInventoryQuestionUsesReadOnlyFastPath(t *testing.T) {
for _, message := range []string{"какие модели доступны?", "напиши список настроек ИИ", "какие есть подключения ИИ"} {
if !isModelInventoryQuestion(message) {
t.Fatalf("message %q was not recognized as model inventory question", message)
result, err := interactiondomain.InterpretUtterance(interaction.HumanUtterance{Content: message}, nil, nil, time.Now())
if err != nil {
t.Fatalf("interpretation failed for %q: %v", message, err)
}
if !isModelInventoryQuestion(result) {
t.Fatalf("semantic inventory request %q was not recognized: kind=%q act=%q hints=%v", message, result.InformationRequestKind, result.Interpretation.SpeechAct, result.CapabilityHints)
}
}
ordinary, err := interactiondomain.InterpretUtterance(interaction.HumanUtterance{Content: "настрой локальную модель"}, nil, nil, time.Now())
if err != nil {
t.Fatal(err)
}
if isModelInventoryQuestion(ordinary) {
t.Fatal("configuration mutation must not use read-only model inventory path")
}
}
@@ -4,11 +4,11 @@
"architecture_revision": "DEV-000073-baseline",
"base_repo_revision": "2306c8d6b31fbf5ac0933ecc72b4f76bec7218f2",
"risk_tier": "E",
"intent": "Answer model and external-AI settings questions from the capability registry through a deterministic read-only chat fast path instead of treating them as unavailable cognition.",
"intent": "Answer semantically identified model and external-AI inventory requests from the capability registry through a deterministic read-only chat fast path instead of treating them as unavailable cognition.",
"requirement_ids": ["Model inventory is readable from chat", "No secret material is shown", "Inventory does not require model reasoning"],
"allowed_change_scope": {
"modules": ["core-runtime", "development-governance"],
"paths": ["cmd/core-runtime/main.go", "cmd/core-runtime/main_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000092-cognitive-model-inventory-fast-path.json"]
"modules": ["core-runtime", "interaction", "development-governance"],
"paths": ["cmd/core-runtime/main.go", "cmd/core-runtime/main_test.go", "modules/interaction/domain/chat.go", "modules/interaction/domain/chat_test.go", "modules/interaction/domain/semantics.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000092-cognitive-model-inventory-fast-path.json"]
},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
+18
View File
@@ -194,6 +194,9 @@ func semanticScores(tokens []string, hasQuestionMark bool, explicitCorrection bo
if hasQuestionMark {
scores[interaction.SpeechActQuestion]++
}
if containsMeaningfulInterrogative(tokens) {
scores[interaction.SpeechActQuestion] += 2
}
if explicitCorrection {
scores[interaction.SpeechActCorrection] += 6
}
@@ -432,6 +435,9 @@ func capabilityHintsFor(primary, secondary interaction.SpeechAct, tokens []strin
if containsToken(tokens, "свободно") || containsToken(tokens, "гб") || containsToken(tokens, "модель") {
add("VERIFY_FACT")
}
if containsStemInTokens(tokens, "модел", "ии") && containsStemInTokens(tokens, "настро", "список", "доступ", "подключ") {
add("VERIFY_FACT")
}
values := make([]string, 0, len(seen))
for kind := range seen {
values = append(values, kind)
@@ -439,6 +445,18 @@ func capabilityHintsFor(primary, secondary interaction.SpeechAct, tokens []strin
return values
}
func informationRequestKind(message string) InformationRequestKind {
tokens := semanticTokens(message)
if len(tokens) == 0 || containsStem(tokens[0], "настро") || !containsStemInTokens(tokens, "модел", "ии") {
return ""
}
inventoryCue := containsStemInTokens(tokens, "список", "доступ", "подключ") || (containsMeaningfulInterrogative(tokens) && containsStemInTokens(tokens, "настро"))
if !inventoryCue {
return ""
}
return InformationRequestCognitiveModelInventory
}
func boundedConfidence(primaryScore, secondaryScore int, scores map[interaction.SpeechAct]int) int64 {
total := 0
for _, score := range scores {
+17 -11
View File
@@ -11,16 +11,21 @@ import (
)
type SemanticResult struct {
Interpretation interaction.UtteranceInterpretation
Candidates []interaction.SemanticCandidate
Projects []interaction.ProjectReference
Relations []interaction.InteractionRelation
ActiveFocus *situation.ActiveProjectFocus
Reply string
NeedsCognition bool
CapabilityHints []string
Interpretation interaction.UtteranceInterpretation
Candidates []interaction.SemanticCandidate
Projects []interaction.ProjectReference
Relations []interaction.InteractionRelation
ActiveFocus *situation.ActiveProjectFocus
Reply string
NeedsCognition bool
CapabilityHints []string
InformationRequestKind InformationRequestKind
}
type InformationRequestKind string
const InformationRequestCognitiveModelInventory InformationRequestKind = "COGNITIVE_MODEL_INVENTORY"
func InterpretUtterance(utterance interaction.HumanUtterance, projects []situation.ProjectDescriptor, previousFocus *situation.ActiveProjectFocus, now time.Time) (SemanticResult, error) {
interpretationID, err := ids.New()
if err != nil {
@@ -46,9 +51,10 @@ func InterpretUtterance(utterance interaction.HumanUtterance, projects []situati
SelectionReason: selectedCandidate.SelectionReason,
RecordedAtUnixMillis: millis,
},
Candidates: []interaction.SemanticCandidate{deterministicCandidate},
NeedsCognition: selectedCandidate.NeedsCognition,
CapabilityHints: append([]string(nil), selectedCandidate.CapabilityHints...),
Candidates: []interaction.SemanticCandidate{deterministicCandidate},
NeedsCognition: selectedCandidate.NeedsCognition,
CapabilityHints: append([]string(nil), selectedCandidate.CapabilityHints...),
InformationRequestKind: informationRequestKind(utterance.Content),
}
explicitFocus := explicitProjectFocus(utterance.Content, projects)
projectRefs, err := projectReferences(utterance, projects, explicitFocus, previousFocus, millis)