From a236ddc0fa1cb651ac3122c7d07495541f6a9ce9 Mon Sep 17 00:00:00 2001 From: weichen Date: Mon, 21 Sep 2026 06:12:15 +0000 Subject: [PATCH] =?UTF-8?q?fix(intent):=20accept=20=E7=BC=96=E5=86=99/?= =?UTF-8?q?=E7=BC=96=E5=88=B6/=E8=B5=B7=E8=8D=89=20and=20keep=20intake=20m?= =?UTF-8?q?arkers=20on=20intake?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The chat fallback only treated 生成/做/跑/出 as task verbs, and the shorter RDev alias 用户需求 stole 初始用户需求表. Co-authored-by: Cursor --- internal/intent/recognizer.go | 42 +++++++++++++++++++++++------- internal/intent/recognizer_test.go | 8 ++++++ 2 files changed, 41 insertions(+), 9 deletions(-) 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) {