refactor: prioritize cognitive interpretation over deterministic rules
CI / test (push) Failing after 14s

This commit is contained in:
m
2026-08-18 17:46:14 +03:00
parent 2e0c0236aa
commit d50d709d69
4 changed files with 198 additions and 46 deletions
+116 -30
View File
@@ -175,7 +175,8 @@ func semanticScores(tokens []string, hasQuestionMark bool, explicitCorrection bo
interaction.SpeechActConversational: 0,
}
if hasQuestionMark {
scores[interaction.SpeechActQuestion] += 3
scores[interaction.SpeechActQuestion] += 4
scores[interaction.SpeechActRequestToInvestigate]++
}
if explicitCorrection {
scores[interaction.SpeechActCorrection] += 6
@@ -192,14 +193,17 @@ func semanticScores(tokens []string, hasQuestionMark bool, explicitCorrection bo
scores[interaction.SpeechActConfirmation] += 2
}
questionWords := map[string]struct{}{"кто": {}, "что": {}, "как": {}, "почему": {}, "зачем": {}, "где": {}, "когда": {}, "сколько": {}, "какой": {}, "какая": {}, "какие": {}, "чей": {}}
conversationWords := []string{"привет", "здравствуйте", "спасибо", "ку", "здравствуй", "добрый", "день", "добрый день"}
investigateStems := []string{"разбер", "проверь", "проанализ", "исслед", "выясн", "посмотри", "узнай", "расскаж", "объясн", "поясн"}
directiveStems := []string{"сдел", "запуст", "созд", "настро", "добав", "измени", "помен", "поменя", "измен", "использ", "удал", "напом", "подключ", "исправ", "обрати", "поправ", "обнов", "ответь", "посмотри"}
conversationWords := []string{"привет", "здравствуйте", "спасибо", "ку", "здравствуй", "добрый"}
investigateStems := []string{"разбер", "проверь", "провери", "проанализ", "исслед", "выясн", "узнай"}
directiveStems := []string{"сдел", "запуст", "созд", "настро", "добав", "измени", "помен", "поменя", "измен", "использ", "удал", "напом", "подключ", "исправ", "обрати", "поправ", "обнов", "ответь", "посмотри", "проверь"}
preferenceStems := []string{"предпоч", "хочу", "лучше", "не используй"}
hypothesisStems := []string{"думаю", "кажется", "гипотез", "похоже"}
decisionStems := []string{"решил", "решение", "выбираю"}
arithmeticMarker := false
for index, token := range tokens {
if firstPersonPastReport(tokens, index) && containsStem(token, directiveStems...) {
continue
}
if _, ok := questionWords[token]; ok && !(token == "что" && index+1 < len(tokens) && tokens[index+1] == "нибудь") {
scores[interaction.SpeechActQuestion] += 4
}
@@ -226,37 +230,24 @@ func semanticScores(tokens []string, hasQuestionMark bool, explicitCorrection bo
arithmeticMarker = true
}
}
if len(tokens) >= 2 && len(tokens[0]) >= 4 && (strings.HasSuffix(tokens[0], "й") || strings.HasSuffix(tokens[0], "и")) && tokens[0] != "ты" {
if containsStem(tokens[1], "как", "почему", "что", "какой", "какая", "какие", "зачем", "когда", "где", "об") {
scores[interaction.SpeechActQuestion] += 3
scores[interaction.SpeechActRequestToInvestigate] += 3
}
}
if len(tokens) >= 2 && tokens[0] == "про" {
if containsStemInTokens(tokens[1:], "расска", "объясн", "поясн", "разбер", "исслед", "проверь", "выясн", "посмотри") || containsStemInTokens(tokens[1:], "как", "почему", "что", "какой", "какая", "какие", "зачем", "когда", "где", "об") {
scores[interaction.SpeechActQuestion] += 4
scores[interaction.SpeechActRequestToInvestigate] += 3
}
}
if len(tokens) >= 2 && tokens[0] == "я" && containsStemInTokens(tokens[1:], "настро", "сдела", "исправ", "удал", "созда", "перезапуст", "поменя", "добави") {
scores[interaction.SpeechActAssertion] += 5
if looksLikeInstructionWithoutPunctuationAfterLead(tokens) {
scores[interaction.SpeechActQuestion] += 4
scores[interaction.SpeechActRequestToInvestigate] += 2
}
if arithmeticMarker {
scores[interaction.SpeechActQuestion] += 2
}
if len(tokens) > 0 && strings.HasSuffix(tokens[0], "ешь") {
scores[interaction.SpeechActQuestion] += 1
}
if len(tokens) > 0 && startsWithModalRequest(tokens) {
scores[interaction.SpeechActQuestion] += 2
scores[interaction.SpeechActRequestToInvestigate] += 2
}
if len(tokens) > 0 && containsStem(tokens[0], "разбер", "проверь", "проанализ", "исслед", "выясн") {
if containsMeaningfulInterrogative(tokens) {
scores[interaction.SpeechActRequestToInvestigate] += 3
} else if tokens[0] == "посмотри" {
scores[interaction.SpeechActRequestToInvestigate] += 1
}
if looksLikeInformationRequest(tokens) {
scores[interaction.SpeechActQuestion] += 4
scores[interaction.SpeechActRequestToInvestigate] += 2
}
if !hasQuestionMark && containsInterrogative(tokens) {
scores[interaction.SpeechActQuestion] += 4
scores[interaction.SpeechActRequestToInvestigate] += 2
}
if containsModelReference(tokens) {
scores[interaction.SpeechActQuestion] += 2
@@ -353,6 +344,12 @@ func needsCognitionForCandidate(primary, secondary interaction.SpeechAct, tokens
if primary == interaction.SpeechActConversational && secondary == interaction.SpeechActDirective {
return true
}
if primary == interaction.SpeechActQuestion && containsMeaningfulInterrogative(tokens) {
return true
}
if isAssistantStatusProbe(primary, secondary, tokens) {
return true
}
if primary == interaction.SpeechActAssertion && containsModelReference(tokens) && containsStemInTokens(tokens, "работает", "падает", "растет", "растёет", "свободно", "виснет") {
return true
}
@@ -362,9 +359,6 @@ func needsCognitionForCandidate(primary, secondary interaction.SpeechAct, tokens
if secondary == interaction.SpeechActQuestion || secondary == interaction.SpeechActRequestToInvestigate {
return true
}
if isAssistantStatusProbe(primary, secondary, tokens) {
return true
}
if primary == interaction.SpeechActDirective && (containsInterrogative(tokens) || containsModelReference(tokens) || containsStemInTokens(tokens, "обрати", "посмотри", "разбер", "исправ", "обнов", "добав")) {
return true
}
@@ -405,9 +399,23 @@ func capabilityHintsFor(primary, secondary interaction.SpeechAct, tokens []strin
add("SEND_EMAIL")
add("READ_EMAIL")
}
if primary == interaction.SpeechActDirective && containsStemInTokens(tokens, "добав", "реализ", "поддержк") {
add("MODIFY_CODE")
}
if containsToken(tokens, "напомни") || containsToken(tokens, "напомин") {
add("SCHEDULE_REMINDER")
}
if primary == interaction.SpeechActRequestToInvestigate || (primary == interaction.SpeechActQuestion && containsInterrogative(tokens) && containsStemInTokens(tokens, "почему", "как", "что")) {
add("COGNITIVE_REASONING")
}
if containsInterrogative(tokens) && containsArithmetic(tokens) {
add("COGNITIVE_REASONING")
}
if containsStemInTokens(tokens, "тормоз", "медлен", "почему", "что", "сервис", "база", "падает") {
add("INSPECT_PROJECT")
add("READ_DATABASE")
add("QUERY_LOGS")
}
if containsToken(tokens, "свободно") || containsToken(tokens, "гб") || containsToken(tokens, "модель") || containsToken(tokens, "postgres") || containsToken(tokens, "база") {
add("VERIFY_FACT")
}
@@ -531,6 +539,66 @@ func containsArithmetic(tokens []string) bool {
return digits >= 2
}
func looksLikeInstructionWithoutPunctuation(first string) bool {
if first == "" {
return false
}
switch {
case strings.HasSuffix(first, "й"), strings.HasSuffix(first, "и"), strings.HasSuffix(first, "ите"):
return utf8.RuneCountInString(first) > 3
default:
return false
}
}
func looksLikeInstructionWithoutPunctuationAfterLead(tokens []string) bool {
if len(tokens) < 2 {
return false
}
if utf8.RuneCountInString(tokens[0]) > 3 {
return false
}
switch tokens[0] {
case "про", "по", "на", "в", "за":
default:
return false
}
for _, token := range tokens[1:] {
if utf8.RuneCountInString(token) <= 3 {
continue
}
switch {
case strings.HasSuffix(token, "ешь"), strings.HasSuffix(token, "и"), strings.HasSuffix(token, "й"), strings.HasSuffix(token, "те"), strings.HasSuffix(token, "йте"):
return true
}
}
return false
}
func looksLikeInformationRequest(tokens []string) bool {
if len(tokens) < 2 {
return false
}
if !containsSuffix(tokens[0], "й", "и", "те", "йте") {
return false
}
for _, token := range tokens[1:] {
if containsStem(token, "что", "как", "почему", "зачем", "когда", "кто", "какой", "какая", "какие", "об", "про", "подроб") {
return true
}
}
return false
}
func containsSuffix(value string, suffixes ...string) bool {
for _, suffix := range suffixes {
if strings.HasSuffix(value, suffix) {
return true
}
}
return false
}
func likelyConsequential(tokens []string) bool {
for _, token := range tokens {
if containsStem(token, "удал", "снес", "замен", "миграц", "парол", "опубли", "перезапуст", "смени", "купи", "установ", "внедр") {
@@ -550,6 +618,24 @@ func followsNibud(tokens []string) bool {
return false
}
func firstPersonPastReport(tokens []string, index int) bool {
if len(tokens) == 0 || index == 0 {
return false
}
if tokens[0] != "я" && tokens[0] != "мы" {
return false
}
if index < 2 {
return false
}
for _, token := range tokens[:index] {
if token == "всё" || token == "все" || token == "сейчас" || token == "уже" {
return true
}
}
return false
}
func containsModelReference(tokens []string) bool {
for _, token := range tokens {
if token == "модель" || token == "модели" || token == "postgres" || token == "postgresql" || strings.Contains(token, "gemma") || strings.Contains(token, "гемма") || strings.Contains(token, "qwen") {
@@ -129,6 +129,8 @@ func TestInterpretUtteranceNaturalRussianHelpflowUsesContextAwareCognitionInput(
{message: "Расскажи как это работает", wantSpeech: "", wantCognition: true, allowNoMatch: true},
{message: "можешь объяснить почему база растёт", wantSpeech: "", wantCognition: true, allowNoMatch: true},
{message: "ПОЧЕМУ postgres использует так много памяти", wantSpeech: "", wantCognition: true, allowNoMatch: true},
{message: "ПОЧЕМУ postgres использует столько памяти", wantSpeech: "", wantCognition: true, allowNoMatch: true},
{message: "разберись почему сервис упал", wantSpeech: "", wantCognition: true, allowNoMatch: true},
{message: "разберись почему сервис упал", wantSpeech: "", wantCognition: true, allowNoMatch: true},
{message: "посмотри что произошло", wantSpeech: "", wantCognition: true, allowNoMatch: true},
{message: "исправь ошибку если найдёшь", wantSpeech: "", wantCognition: true, allowNoMatch: true},
+37 -11
View File
@@ -82,11 +82,16 @@ func DetermineHelpPath(input work.PlanningInput) work.HelpPath {
hasQueryLogsHint := hasPlanningHint(input, capability.CapabilityKindQueryLogs)
hasVerifyHint := hasPlanningHint(input, capability.CapabilityKindVerifyFact)
rawTextFallbackAllowed := len(input.CapabilityHints) == 0 && !input.RequiresClarification && !input.NeedsCognition
if input.SpeechAct != work.SpeechActRef("") && input.SpeechAct != work.SpeechActRef("UNKNOWN") {
rawTextFallbackAllowed = false
}
topSpeech := input.SpeechAct
switch {
case input.RequiresClarification:
return work.HelpPath{Kind: work.HelpPathAnswer, Reason: "clarification is required before capability selection or durable work", Reply: "Нужно короткое уточнение, прежде чем выбирать capability или строить более тяжёлый маршрут."}
case topSpeech == work.SpeechActRef("REQUEST_TO_INVESTIGATE"):
return work.HelpPath{Kind: work.HelpPathDurableWork, NeedsDurableGoal: true, NeedsDurableWork: true, NeedsDurablePlan: true, Reason: "explicit request-to-investigate should materialize durable work"}
case hasReminderHint || (rawTextFallbackAllowed && isReminderRequest(lower)):
return work.HelpPath{Kind: work.HelpPathDirectCapability, SuggestedCapabilityKinds: []capability.CapabilityKind{capability.CapabilityKindScheduleReminder}, Reason: "reminder is a direct scheduling-capability path, not a coding/project workflow", Reply: "Понял: нужен reminder/scheduler capability. В DEV-000063 я фиксирую это как безопасный путь помощи; само напоминание ещё не зарегистрировано, потому что scheduling adapter не подключён."}
case isEmailReviewRequest(lower) && hasReadEmailHint:
@@ -95,10 +100,10 @@ func DetermineHelpPath(input work.PlanningInput) work.HelpPath {
return work.HelpPath{Kind: work.HelpPathCapabilityGap, SuggestedCapabilityKinds: []capability.CapabilityKind{capability.CapabilityKindSendEmail}, CapabilityGap: true, Reason: "email sending needs a communication capability implementation and explicit authority", Reply: "Сейчас у меня ещё не подключена возможность отправлять почту. Я зафиксирую этот capability gap, чтобы позже к нему можно было вернуться."}
case hasModifyHint || hasInspectHint || hasQueryLogsHint || hasReadEmailHint || hasRecallHint || hasVerifyHint || needsDurableWorkFromHints(input):
return work.HelpPath{Kind: work.HelpPathDurableWork, NeedsDurableGoal: true, NeedsDurableWork: true, NeedsDurablePlan: true, Reason: "semantic hints indicate project/code work even if cognition may still be useful upstream"}
case isUnsupportedCapabilityRequest(lower):
return work.HelpPath{Kind: work.HelpPathCapabilityGap, SuggestedCapabilityKinds: []capability.CapabilityKind{}, CapabilityGap: true, Reason: "requested help needs a currently missing capability and may later trigger acquisition/self-extension", Reply: "Похоже, для этой просьбы нужна capability, которой сейчас нет. Это не задача конкретного executor автоматически: в будущем Домовой сможет найти, подключить или создать подходящую capability и вернуться к исходной потребности."}
case rawTextFallbackAllowed && requiresDurableWork(lower):
return work.HelpPath{Kind: work.HelpPathDurableWork, NeedsDurableGoal: true, NeedsDurableWork: true, NeedsDurablePlan: true, Reason: "request needs durable coordination, planning, investigation, or outcome tracking"}
case rawTextFallbackAllowed && isUnsupportedCapabilityRequest(lower):
return work.HelpPath{Kind: work.HelpPathCapabilityGap, SuggestedCapabilityKinds: []capability.CapabilityKind{}, CapabilityGap: true, Reason: "requested help needs a currently missing capability and may later trigger acquisition/self-extension", Reply: "Похоже, для этой просьбы нужна capability, которой сейчас нет. Это не задача конкретного executor автоматически: в будущем Домовой сможет найти, подключить или создать подходящую capability и вернуться к исходной потребности."}
case rawTextFallbackAllowed && containsAny(lower, "объясни", "расскажи", "что делает"):
return work.HelpPath{Kind: work.HelpPathCognition, Reason: "simple help can be answered without durable Goal/Work/Plan"}
case input.NeedsCognition && (topSpeech == work.SpeechActRef("QUESTION") || topSpeech == work.SpeechActRef("REQUEST_TO_INVESTIGATE") || hasRecallHint):
@@ -173,14 +178,32 @@ func ValidatePlanCandidate(candidate work.PlanCandidate) error {
func addScenarioPlan(input work.PlanningInput, millis int64, candidate *work.PlanCandidate) {
lower := strings.ToLower(input.Message)
hasReminderHint := hasPlanningHint(input, capability.CapabilityKindScheduleReminder)
hasSendEmailHint := hasPlanningHint(input, capability.CapabilityKindSendEmail)
hasReadEmailHint := hasPlanningHint(input, capability.CapabilityKindReadEmail)
hasModifyHint := hasPlanningHint(input, capability.CapabilityKindModifyCode)
hasInspectHint := hasPlanningHint(input, capability.CapabilityKindInspectProject)
hasQueryLogsHint := hasPlanningHint(input, capability.CapabilityKindQueryLogs)
hasVerifyHint := hasPlanningHint(input, capability.CapabilityKindVerifyFact)
requiresInvestigation := input.SpeechAct == work.SpeechActRef("REQUEST_TO_INVESTIGATE")
if input.SecondarySpeechAct == work.SpeechActRef("CORRECTION") {
addStep(candidate, work.PlanStepClarify, "Preserve the correction as part of the same human intent history before execution-oriented work.", len(candidate.Steps)+1, millis)
}
switch {
case isReminderRequest(lower):
addRequirement(candidate, capability.CapabilityKindScheduleReminder, "schedule a future reminder after temporal interpretation", epistemics.VerificationNotApplicable, millis)
addStep(candidate, work.PlanStepSchedule, "Interpret the requested reminder time and reminder subject.", 1, millis)
case hasReminderHint:
addRequirement(candidate, capability.CapabilityKindScheduleReminder, "respecting a structured reminder intent", epistemics.VerificationNotApplicable, millis)
addStep(candidate, work.PlanStepSchedule, "Interpret requested reminder horizon and context.", 1, millis)
addStep(candidate, work.PlanStepNoExecution, "Do not register a reminder until scheduling capability/authority is available.", 2, millis)
case hasReadEmailHint:
addRequirement(candidate, capability.CapabilityKindReadEmail, "read incoming communications", epistemics.VerificationUnknown, millis)
addRequirement(candidate, capability.CapabilityKindCognitiveReasoning, "classify messages needing response", epistemics.VerificationUnknown, millis)
addStep(candidate, work.PlanStepObserve, "Read relevant email messages through a mail capability.", 1, millis)
addStep(candidate, work.PlanStepReason, "Classify which messages need a response and why.", 2, millis)
addStep(candidate, work.PlanStepReport, "Report recommended replies without claiming email was sent.", 3, millis)
case hasSendEmailHint:
addRequirement(candidate, capability.CapabilityKindSendEmail, "send email only after authority", epistemics.VerificationNotApplicable, millis)
addStep(candidate, work.PlanStepPrepare, "Resolve recipient and appropriate communication channel.", 1, millis)
addStep(candidate, work.PlanStepCommunicate, "Prepare the message for a communication adapter; do not send without authority.", 2, millis)
case isEmailReviewRequest(lower):
addRequirement(candidate, capability.CapabilityKindReadEmail, "read incoming communications", epistemics.VerificationUnknown, millis)
addRequirement(candidate, capability.CapabilityKindCognitiveReasoning, "classify messages needing response", epistemics.VerificationUnknown, millis)
@@ -191,25 +214,29 @@ func addScenarioPlan(input work.PlanningInput, millis int64, candidate *work.Pla
addRequirement(candidate, capability.CapabilityKindSendEmail, "send email only after authority", epistemics.VerificationNotApplicable, millis)
addStep(candidate, work.PlanStepPrepare, "Resolve recipient and appropriate communication channel.", 1, millis)
addStep(candidate, work.PlanStepCommunicate, "Prepare the message for a communication adapter; do not send without authority.", 2, millis)
case containsAny(lower, "тормозит", "медлен", "производительн", "разберись", "почему"):
case isReminderRequest(lower):
addRequirement(candidate, capability.CapabilityKindScheduleReminder, "schedule a future reminder after temporal interpretation", epistemics.VerificationNotApplicable, millis)
addStep(candidate, work.PlanStepSchedule, "Interpret the requested reminder time and reminder subject.", 1, millis)
addStep(candidate, work.PlanStepNoExecution, "Do not register a reminder until scheduling capability/authority is available.", 2, millis)
case requiresInvestigation || hasInspectHint || hasQueryLogsHint || hasVerifyHint:
addRequirement(candidate, capability.CapabilityKindInspectProject, "inspect project context", epistemics.VerificationNotApplicable, millis)
addRequirement(candidate, capability.CapabilityKindReadDatabase, "collect database performance observations", epistemics.VerificationUnknown, millis)
addRequirement(candidate, capability.CapabilityKindQueryLogs, "collect runtime/log observations", epistemics.VerificationUnknown, millis)
addRequirement(candidate, capability.CapabilityKindCognitiveReasoning, "compare evidence and identify likely causes", epistemics.VerificationUnknown, millis)
if containsAny(lower, "патч", "исправ") {
if containsAny(lower, "патч", "исправ") || containsAny(lower, "обнов", "конфиг") {
addRequirement(candidate, capability.CapabilityKindModifyCode, "modify software after investigation confirms the change", epistemics.VerificationUnknown, millis)
addRequirement(candidate, capability.CapabilityKindRunTests, "validate the patch after code modification", epistemics.VerificationNotApplicable, millis)
}
addStep(candidate, work.PlanStepInvestigate, "Identify the relevant project/service scope and current symptoms.", 1, millis)
addStep(candidate, work.PlanStepObserve, "Collect database, runtime, log and configuration observations.", 2, millis)
addStep(candidate, work.PlanStepReason, "Compare evidence and determine likely performance causes.", 3, millis)
if containsAny(lower, "патч", "исправ") {
if containsAny(lower, "патч", "исправ") || containsAny(lower, "обнов", "конфиг") {
addStep(candidate, work.PlanStepModify, "Prepare a validated patch only after the investigation narrows the likely cause.", 4, millis)
addStep(candidate, work.PlanStepRunChecks, "Run relevant checks before claiming the patch is safe.", 5, millis)
} else {
addStep(candidate, work.PlanStepReport, "Report findings and what remains unverified.", 4, millis)
}
case containsAny(lower, "добавь", "поддержк", "возможность", "реализ"):
case hasModifyHint:
addRequirement(candidate, capability.CapabilityKindModifyCode, "modify software project", epistemics.VerificationNotApplicable, millis)
addRequirement(candidate, capability.CapabilityKindRunTests, "validate software change", epistemics.VerificationNotApplicable, millis)
addStep(candidate, work.PlanStepInvestigate, "Understand desired capability and affected project scope.", 1, millis)
@@ -227,7 +254,6 @@ func addScenarioPlan(input work.PlanningInput, millis int64, candidate *work.Pla
addDelegation(candidate, requirement, millis)
}
}
func requiresDurableWork(lower string) bool {
return containsAny(lower, "тормозит", "медлен", "производительн", "разберись", "почему", "добавь", "поддержк", "возможность", "реализ", "патч", "исправь", "установи", "большую модель")
}
@@ -335,4 +361,4 @@ func isExplicitEmailGapRequest(lower string) bool {
func isUnsupportedCapabilityRequest(lower string) bool {
return containsAny(lower, "не умеешь", "подключи интеграцию", "подключи сервис", "интеграц", "неизвестн", "нет capability", "создай adapter", "создай адаптер", "сервис, которого сейчас нет")
}
}\r\n
+43 -5
View File
@@ -25,7 +25,9 @@ func TestPlanningInvestigationIsNotCodexPrompt(t *testing.T) {
}
func TestPlanningCodeChangeRequiresModifyCodeWithoutExecutor(t *testing.T) {
result := planForTest(t, "В Assistant добавь поддержку X.", "DIRECTIVE", epistemics.VerificationNotApplicable)
input := planningInputForTest("В Assistant добавь поддержку X.", "DIRECTIVE", epistemics.VerificationNotApplicable)
input.CapabilityHints = []capability.CapabilityKind{capability.CapabilityKindModifyCode}
result := planForTestWithInput(t, input)
requireKinds(t, result, capability.CapabilityKindModifyCode, capability.CapabilityKindRunTests)
for _, delegation := range result.Candidate.Delegations {
if delegation.ExecutorHint != "" {
@@ -41,7 +43,9 @@ func TestPlanningEmailReviewIsNotCodexSpecific(t *testing.T) {
}
func TestPlanningReminderUsesSchedulingRequirement(t *testing.T) {
result := planForTest(t, "Напомни завтра позвонить врачу.", "DIRECTIVE", epistemics.VerificationNotApplicable)
input := planningInputForTest("Напомни завтра позвонить врачу.", "DIRECTIVE", epistemics.VerificationNotApplicable)
input.CapabilityHints = []capability.CapabilityKind{capability.CapabilityKindScheduleReminder}
result := planForTestWithInput(t, input)
requireKinds(t, result, capability.CapabilityKindScheduleReminder)
requireNoKind(t, result, capability.CapabilityKindModifyCode)
}
@@ -79,14 +83,18 @@ func TestPlanningFallbackPersistsWithoutCognition(t *testing.T) {
}
func TestHelpPathSimpleAnswerDoesNotRequireDurableGoalWorkPlan(t *testing.T) {
path := DetermineHelpPath(planningInputForTest("Объясни, что делает этот код.", "QUESTION", epistemics.VerificationNotApplicable))
input := planningInputForTest("Объясни, что делает этот код.", "QUESTION", epistemics.VerificationNotApplicable)
input.NeedsCognition = true
path := DetermineHelpPath(input)
if path.Kind != work.HelpPathCognition || path.NeedsDurableGoal || path.NeedsDurableWork || path.NeedsDurablePlan {
t.Fatalf("simple help must not require durable planning: %#v", path)
}
}
func TestHelpPathReminderCanUseDirectCapabilityWithoutWorkflow(t *testing.T) {
path := DetermineHelpPath(planningInputForTest("Напомни завтра позвонить.", "DIRECTIVE", epistemics.VerificationNotApplicable))
input := planningInputForTest("Напомни завтра позвонить.", "DIRECTIVE", epistemics.VerificationNotApplicable)
input.CapabilityHints = []capability.CapabilityKind{capability.CapabilityKindScheduleReminder}
path := DetermineHelpPath(input)
if path.Kind != work.HelpPathDirectCapability || path.NeedsDurableWork || len(path.SuggestedCapabilityKinds) != 1 || path.SuggestedCapabilityKinds[0] != capability.CapabilityKindScheduleReminder {
t.Fatalf("reminder should be direct scheduling capability path: %#v", path)
}
@@ -99,6 +107,31 @@ func TestHelpPathComplexWorkMaterializesDurablePlanning(t *testing.T) {
}
}
func TestHelpPathStructuredIntentNotOverriddenByRawRussianText(t *testing.T) {
path := DetermineHelpPath(work.PlanningInput{
ConversationID: "conversation-1",
UtteranceID: "utterance-1",
IntentID: "intent-1",
ActorID: identity.ActorID("actor-1"),
PrincipalID: identity.PrincipalID("principal-1"),
ProjectID: work.ProjectRef("upo"),
ProjectDisplayName: "УПО",
ProjectProvenance: work.ProjectProvenanceRef("EXPLICIT"),
Message: "Объясни, как добавить поддержку отправки писем в этом проекте.",
SpeechAct: work.SpeechActRef("DIRECTIVE"),
NeedsCognition: false,
CapabilityHints: []capability.CapabilityKind{capability.CapabilityKindModifyCode},
AssertionVerification: epistemics.VerificationNotApplicable,
ReceivedAtUnixMillis: time.Unix(100, 0).UnixMilli(),
})
if path.Kind != work.HelpPathDurableWork || !path.NeedsDurableWork || !path.NeedsDurablePlan {
t.Fatalf("structured durable intent should be preserved over raw phrase heuristics: %#v", path)
}
if path.Kind == work.HelpPathCognition {
t.Fatalf("raw Russian phrase should not force cognition when selected interpretation is already directive: %#v", path)
}
}
func TestHelpPathCapabilityGapDoesNotBecomeCodexTask(t *testing.T) {
path := DetermineHelpPath(planningInputForTest("Подключи интеграцию с неизвестным сервисом, которого сейчас нет.", "DIRECTIVE", epistemics.VerificationNotApplicable))
if path.Kind != work.HelpPathCapabilityGap || !path.CapabilityGap {
@@ -121,7 +154,12 @@ func TestHelpPathStructuredHintsOverrideRawCognitionPhrase(t *testing.T) {
func planForTest(t *testing.T, message string, act string, verification epistemics.VerificationState) work.PlanningResult {
t.Helper()
result, err := BuildPlanningResult(planningInputForTest(message, act, verification), time.Unix(100, 0))
return planForTestWithInput(t, planningInputForTest(message, act, verification))
}
func planForTestWithInput(t *testing.T, input work.PlanningInput) work.PlanningResult {
t.Helper()
result, err := BuildPlanningResult(input, time.Unix(100, 0))
if err != nil {
t.Fatal(err)
}