Files
Agent-System/internal/intent/proposal_test.go
T
weichenandCursor 932ba87daf feat(intent): add rule segmenter and shadow PlanProposal
Generate 2–4 step L1 proposals with stable digests while leaving TopIntent and the legacy IntentPlan router unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-15 23:14:29 +00:00

242 lines
8.0 KiB
Go

package intent_test
import (
"context"
"encoding/json"
"os"
"path/filepath"
"runtime"
"testing"
"chat-agent-system/internal/intent"
)
func TestSplitPlanSegments_ConnectorAndNumbered(t *testing.T) {
got := intent.SplitPlanSegments("查一下项目风险,然后查相关法规")
if len(got) != 2 {
t.Fatalf("connector split got %d %#v", len(got), got)
}
numbered := intent.SplitPlanSegments("1. 查一下相关法规\n2. 查相似案例\n3. 查一下我的权限")
if len(numbered) != 3 {
t.Fatalf("numbered split got %d %#v", len(numbered), numbered)
}
}
func TestSplitPlanSegments_PeriodAndSemicolon(t *testing.T) {
period := intent.SplitPlanSegments("查一下相关法规。查相似案例。")
if len(period) != 2 {
t.Fatalf("period split got %d %#v", len(period), period)
}
semi := intent.SplitPlanSegments("查一下相关法规;查相似案例")
if len(semi) != 2 {
t.Fatalf("semicolon split got %d %#v", len(semi), semi)
}
}
func TestSplitPlanSegments_InternalChainNotSplit(t *testing.T) {
got := intent.SplitPlanSegments("请查询相关法规并总结要点")
if len(got) != 1 {
t.Fatalf("internal chain must stay 1, got %d %#v", len(got), got)
}
}
func TestSplitPlanSegments_FirstThenFinally(t *testing.T) {
got := intent.SplitPlanSegments("先查一下相关法规再查相似案例最后查一下我的权限")
if len(got) != 3 {
t.Fatalf("first/then/finally got %d %#v", len(got), got)
}
}
func TestSplitPlanSegments_FiveNumberedKeptIntact(t *testing.T) {
got := intent.SplitPlanSegments("1. 查法规\n2. 查案例\n3. 查进度\n4. 查权限\n5. 查文档状态")
if len(got) != 5 {
t.Fatalf("must not truncate 5 segments, got %d", len(got))
}
}
func TestPlanProposal_WriteNotDroppedAndLegacyUnchanged(t *testing.T) {
rec := intent.NewRecognizer()
res, err := rec.Recognize(context.Background(), "查一下相关法规,然后帮我生成报价单", nil)
if err != nil {
t.Fatal(err)
}
if res.IntentPlan != nil {
t.Fatal("production IntentPlan must stay nil")
}
if res.TopIntent == nil || res.TopIntent.Intent != intent.IntentQueryRegulations {
t.Fatalf("TopIntent changed: %+v", res.TopIntent)
}
if res.PlanProposal == nil || len(res.PlanProposal.Segments) < 2 {
t.Fatalf("proposal must keep both segments: %+v", res.PlanProposal)
}
if res.PlanProposal.ComboHint != intent.ComboNeedsConfirm {
t.Fatalf("combo hint = %s", res.PlanProposal.ComboHint)
}
if res.PlanShadow == nil || !res.PlanShadow.LegacyDroppedWrite {
t.Fatalf("shadow must record legacy write drop: %+v", res.PlanShadow)
}
if res.PlanShadow.CounterfactualAutoRead != "would_confirm" {
t.Fatalf("auto_read counterfactual = %s", res.PlanShadow.CounterfactualAutoRead)
}
}
func TestPlanProposal_QueryPairAutoReadCandidate(t *testing.T) {
rec := intent.NewRecognizer()
res, err := rec.Recognize(context.Background(), "查一下项目风险,然后查相关法规", nil)
if err != nil {
t.Fatal(err)
}
if res.IntentPlan == nil {
t.Fatal("legacy plan still required")
}
if res.PlanProposal == nil || res.PlanProposal.ComboHint != intent.ComboReadOnly {
t.Fatalf("proposal %+v", res.PlanProposal)
}
if res.PlanShadow.CounterfactualAutoRead != "would_execute" {
t.Fatalf("counterfactual = %s", res.PlanShadow.CounterfactualAutoRead)
}
}
func TestPlanProposal_AnaphoraClarify(t *testing.T) {
rec := intent.NewRecognizer()
res, err := rec.Recognize(context.Background(), "查一下相关法规,然后再看一下它的适用范围", nil)
if err != nil {
t.Fatal(err)
}
if res.PlanProposal == nil {
t.Fatal("missing proposal")
}
if res.PlanProposal.ComboHint != intent.ComboNeedsClarify {
t.Fatalf("anaphora must clarify, got %s segs=%+v", res.PlanProposal.ComboHint, res.PlanProposal.Segments)
}
if res.PlanShadow.CounterfactualAutoRead != "would_clarify" {
t.Fatalf("counterfactual = %s", res.PlanShadow.CounterfactualAutoRead)
}
}
func TestPlanProposal_SecurityRejectsWhole(t *testing.T) {
rec := intent.NewRecognizer()
res, err := rec.Recognize(context.Background(), "查一下相关法规,然后修改我的工号", nil)
if err != nil {
t.Fatal(err)
}
if res.TopIntent == nil || res.TopIntent.Intent != intent.IntentSecurityForbidden {
t.Fatalf("security TopIntent got %+v", res.TopIntent)
}
if res.PlanProposal == nil || res.PlanProposal.ComboHint != intent.ComboRejected {
t.Fatalf("proposal %+v", res.PlanProposal)
}
if res.PlanShadow.CounterfactualAutoRead != "would_reject" {
t.Fatalf("counterfactual = %s", res.PlanShadow.CounterfactualAutoRead)
}
}
func TestPlanProposal_BatchSizeRejected(t *testing.T) {
rec := intent.NewRecognizer()
res, err := rec.Recognize(context.Background(), "1. 查法规\n2. 查案例\n3. 查进度\n4. 查权限\n5. 查文档状态", nil)
if err != nil {
t.Fatal(err)
}
if res.PlanProposal == nil || res.PlanProposal.ComboHint != intent.ComboRejectedBatchSize {
t.Fatalf("proposal %+v", res.PlanProposal)
}
if len(res.PlanProposal.Segments) != 5 {
t.Fatalf("must keep 5 spans, got %d", len(res.PlanProposal.Segments))
}
}
func TestPlanProposal_DigestStable(t *testing.T) {
rec := intent.NewRecognizer()
input := "查一下项目风险,然后查相关法规"
a, err := rec.Recognize(context.Background(), input, nil)
if err != nil {
t.Fatal(err)
}
b, err := rec.Recognize(context.Background(), input, nil)
if err != nil {
t.Fatal(err)
}
if a.PlanProposal == nil || b.PlanProposal == nil || a.PlanProposal.Digest == "" {
t.Fatal("digest missing")
}
if a.PlanProposal.Digest != b.PlanProposal.Digest {
t.Fatalf("digest drifted %s vs %s", a.PlanProposal.Digest, b.PlanProposal.Digest)
}
}
func TestPlanProposalEvalGolden(t *testing.T) {
_, file, _, ok := runtime.Caller(0)
if !ok {
t.Fatal("caller")
}
raw, err := os.ReadFile(filepath.Join(filepath.Dir(file), "testdata", "plan-proposal-eval-v1.json"))
if err != nil {
t.Fatal(err)
}
var gf struct {
Cases []struct {
ID string `json:"id"`
Input string `json:"input"`
ExpectCombo string `json:"expect_combo"`
ExpectCounterfactual string `json:"expect_counterfactual"`
LegacyPlanSteps int `json:"legacy_plan_steps"`
HardNegative bool `json:"hard_negative"`
} `json:"cases"`
}
if err := json.Unmarshal(raw, &gf); err != nil {
t.Fatal(err)
}
rec := intent.NewRecognizer()
falseAuto := 0
denom := len(gf.Cases)
for _, c := range gf.Cases {
res, err := rec.Recognize(context.Background(), c.Input, nil)
if err != nil {
t.Fatalf("%s: %v", c.ID, err)
}
if res.PlanProposal == nil || res.PlanShadow == nil {
t.Fatalf("%s missing proposal/shadow", c.ID)
}
if string(res.PlanProposal.ComboHint) != c.ExpectCombo {
t.Errorf("%s combo=%s want %s", c.ID, res.PlanProposal.ComboHint, c.ExpectCombo)
}
if res.PlanShadow.CounterfactualAutoRead != c.ExpectCounterfactual {
t.Errorf("%s counterfactual=%s want %s", c.ID, res.PlanShadow.CounterfactualAutoRead, c.ExpectCounterfactual)
}
steps := 0
if res.IntentPlan != nil {
steps = len(res.IntentPlan.Steps)
}
if c.LegacyPlanSteps > 0 && steps != c.LegacyPlanSteps {
t.Errorf("%s legacy steps=%d want %d", c.ID, steps, c.LegacyPlanSteps)
}
if c.HardNegative && res.PlanShadow.CounterfactualAutoRead == "would_execute" {
falseAuto++
t.Errorf("%s hard-negative would auto-execute", c.ID)
}
}
if falseAuto != 0 {
t.Fatalf("false auto-execute %d / denom %d", falseAuto, denom)
}
t.Logf("plan-proposal eval denom=%d false_auto_execute=0", denom)
}
func TestPlanProposal_HardNegativeNoFalseAutoExecute(t *testing.T) {
rec := intent.NewRecognizer()
cases := []string{
"查一下相关法规,然后帮我生成报价单",
"查一下相关法规,然后修改我的工号",
"1. 查法规\n2. 查案例\n3. 查进度\n4. 查权限\n5. 查文档状态",
"查一下相关法规,然后再看一下它的适用范围",
}
for _, in := range cases {
res, err := rec.Recognize(context.Background(), in, nil)
if err != nil {
t.Fatal(err)
}
if res.PlanShadow != nil && res.PlanShadow.CounterfactualAutoRead == "would_execute" {
t.Fatalf("hard-negative %q would auto-execute", in)
}
}
}