diff --git a/internal/intent/recognizer.go b/internal/intent/recognizer.go index d2c1ba33..69587d1c 100644 --- a/internal/intent/recognizer.go +++ b/internal/intent/recognizer.go @@ -1177,6 +1177,15 @@ func matchQueryProgress(input string) *CandidateIntent { return nil } +func hasTaskWriteVerb(input string) bool { + for _, verb := range []string{"生成", "做", "跑", "出", "编写", "编制", "起草"} { + if strings.Contains(input, verb) { + return true + } + } + return false +} + func matchTriggerTask(input string) *CandidateIntent { type phrase struct { text string @@ -1210,24 +1219,39 @@ func matchTriggerTask(input string) *CandidateIntent { return phrases[i].text < phrases[j].text }) - if !(strings.Contains(input, "生成") || strings.Contains(input, "做") || strings.Contains(input, "跑") || strings.Contains(input, "出")) { + if !hasTaskWriteVerb(input) { return nil } + // 「初始 / 首轮 / 摸底」是 V0.1 摸底,不能被更短的研发别名「用户需求」截走。 + if hasIntakeSemanticMarker(input) && strings.Contains(input, "需求") { + return triggerTaskCandidate("需求表", "intake") + } for _, item := range phrases { if strings.Contains(input, item.text) { - return &CandidateIntent{ - Intent: IntentTriggerTask, - Confidence: 0.86, - FilledSlots: map[string]string{ - SlotTargetTask: item.key, - }, - Reasoning: fmt.Sprintf("用户要求显式生成交付任务: %s (%s)", item.text, item.key), - } + return triggerTaskCandidate(item.text, item.key) } } return nil } +func hasIntakeSemanticMarker(input string) bool { + return strings.Contains(input, "首轮") || + strings.Contains(input, "初始") || + strings.Contains(input, "需求摸底") || + strings.Contains(input, "摸底") +} + +func triggerTaskCandidate(phrase, taskKey string) *CandidateIntent { + return &CandidateIntent{ + Intent: IntentTriggerTask, + Confidence: 0.86, + FilledSlots: map[string]string{ + SlotTargetTask: taskKey, + }, + Reasoning: fmt.Sprintf("用户要求显式生成交付任务: %s (%s)", phrase, taskKey), + } +} + func isGreetingOrHelp(input string) bool { greetings := []string{"你好", "您好", "早上好", "下午好", "晚上好", "早啊", "在吗", "hi", "hello", "你是谁"} for _, g := range greetings { diff --git a/internal/intent/recognizer_test.go b/internal/intent/recognizer_test.go index e0f4e2f6..68c35339 100644 --- a/internal/intent/recognizer_test.go +++ b/internal/intent/recognizer_test.go @@ -126,6 +126,14 @@ func TestIntentRecognition_RDevProposalAndLongerTitleWins(t *testing.T) { if intake.TopIntent.FilledSlots[intent.SlotTargetTask] != "intake" { t.Fatalf("用户需求收集表 must stay intake, got %s", intake.TopIntent.FilledSlots[intent.SlotTargetTask]) } + + written, err := rec.Recognize(context.Background(), "编写初始用户需求表", ctx) + if err != nil { + t.Fatal(err) + } + if written.TopIntent.Intent != intent.IntentTriggerTask || written.TopIntent.FilledSlots[intent.SlotTargetTask] != "intake" { + t.Fatalf("编写初始用户需求表 want TRIGGER_TASK/intake, got %s %s", written.TopIntent.Intent, written.TopIntent.FilledSlots[intent.SlotTargetTask]) + } } func TestIntentRecognition_CasualChat(t *testing.T) {