Files
weichenandCursor 43555e5d08 feat(intent-plan): wire U9 PI segment proposer and per-flag U10–U12 evidence
Rules that already split skip the LLM. Independent flags stay off by
default and do not raise MULTI_INTENT_PLAN_TIER.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-17 03:14:57 +00:00

126 lines
3.9 KiB
Go

package app
import (
"context"
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
"time"
"chat-agent-system/internal/agent"
"chat-agent-system/internal/app/intentflow"
"chat-agent-system/internal/config"
)
type recordingSegmentBackend struct {
last agent.Request
payload []byte
body string
fail string
}
func (r *recordingSegmentBackend) Name() string { return "recording-segment" }
func (r *recordingSegmentBackend) Execute(_ context.Context, req agent.Request) (*agent.Result, error) {
r.last = req
if b, err := os.ReadFile(filepath.Join(req.WorkDir, "input.json")); err == nil {
r.payload = b
}
if r.fail != "" {
return &agent.Result{Success: false, ErrorReason: r.fail}, nil
}
body := r.body
if body == "" {
body = `{"schema_version":"intentflow.segment_proposal.v1","digest":"d1","spans":[{"start":0,"end":3,"text":"abc"}]}`
}
return &agent.Result{Success: true, Patches: map[string][]byte{"spans.md": []byte(body)}}, nil
}
func TestPiSegmentProposer_NoToolsAndDigest(t *testing.T) {
rec := &recordingSegmentBackend{}
p := &PiSegmentProposer{Backend: rec, Model: "test"}
got, err := p.Propose(context.Background(), intentflow.SegmentProposalRequest{
Text: "abc", RequestDigest: "d1",
})
if err != nil {
t.Fatal(err)
}
if got == nil || len(got.Spans) != 1 {
t.Fatalf("got=%+v", got)
}
if !strings.Contains(strings.ToLower(rec.last.Instructions), "zero tool") {
t.Fatalf("instructions=%s", rec.last.Instructions)
}
var in map[string]any
if err := json.Unmarshal(rec.payload, &in); err != nil {
t.Fatal(err)
}
if in["digest"] != "d1" {
t.Fatalf("input=%v", in)
}
}
func TestPiSegmentProposer_MalformedAndTimeout(t *testing.T) {
p := &PiSegmentProposer{Backend: &recordingSegmentBackend{body: "not-json"}}
if _, err := p.Propose(context.Background(), intentflow.SegmentProposalRequest{Text: "abc", RequestDigest: "d1"}); err != intentflow.ErrSegmentProposalMalformed {
t.Fatalf("malformed: %v", err)
}
p = &PiSegmentProposer{Backend: &recordingSegmentBackend{fail: "TIMEOUT"}}
if _, err := p.Propose(context.Background(), intentflow.SegmentProposalRequest{Text: "abc", RequestDigest: "d1"}); err != intentflow.ErrSegmentProposalTimeout {
t.Fatalf("timeout: %v", err)
}
p = &PiSegmentProposer{Backend: &recordingSegmentBackend{body: `{"schema_version":"intentflow.segment_proposal.v1","digest":"d1","spans":[{"start":0,"end":99,"text":"nope"}]}`}}
if _, err := p.Propose(context.Background(), intentflow.SegmentProposalRequest{Text: "abc", RequestDigest: "d1"}); err != intentflow.ErrSegmentProposalOutOfBounds {
t.Fatalf("oob: %v", err)
}
}
func TestPiSegmentProposer_LiveMiniMax(t *testing.T) {
if os.Getenv("RUN_PI_E2E") != "1" {
t.Skip("set RUN_PI_E2E=1 for live PI segment proposal")
}
if testing.Short() {
t.Skip("live PI segment is not a -short test")
}
if err := os.Chdir(filepath.Clean(filepath.Join("..", ".."))); err != nil {
t.Fatal(err)
}
cfg, err := config.Load("")
if err != nil || cfg == nil {
t.Fatalf("config: %v", err)
}
if strings.TrimSpace(cfg.PiAgentRunnerAuthToken) == "" {
if tok := runnerTokenFromDotenv(); tok != "" {
cfg.PiAgentRunnerAuthToken = tok
}
}
if strings.TrimSpace(cfg.PiAgentRunnerAuthToken) == "" {
t.Fatal("runner auth token missing")
}
cfg.PiAgentRunnerURL = "http://127.0.0.1:18865"
dataDir, err := filepath.Abs("data/dev")
if err != nil {
t.Fatal(err)
}
p := &PiSegmentProposer{
Backend: agent.NewBackend("pi-agent", cfg),
Model: cfg.PiAgentModel,
Timeout: 4 * time.Minute,
DataDir: dataDir,
}
text := "帮我看看电气安全条款生成立项建议"
start := time.Now()
got, err := p.Propose(context.Background(), intentflow.SegmentProposalRequest{
Text: text, RequestDigest: "live-u9-digest",
})
if err != nil {
t.Fatalf("live propose: %v", err)
}
t.Logf("elapsed=%s proposal=%+v", time.Since(start).Round(time.Millisecond), got)
if got.Digest != "live-u9-digest" {
t.Fatalf("digest=%s", got.Digest)
}
}