Files
Agent-Intent/intent/corpus_test.go
weichen 1d0fcbd1ba
ci / vet-and-test (pull_request) Successful in 18s
ci / docker-checkout-hostgateway (pull_request) Successful in 0s
Add the multi-turn eval format and self-describing corpora
Why: I1 WP6 - eval needs per-turn command expectations to carry the
SetSlot+Start acceptance case, and corpora must reproduce standalone
in intentctl (the guard case has been red there since before I1
because the guard spec only existed as a test builtin).

Evidence: go vet clean; go test ./... all ok; intentctl eval passes
11/11 on the v1 corpus (guard case fixed via self-declaration) and
1/1 on the new two-turn SetSlot+Start corpus.

Impact: new intent/corpus.go (CorpusCase/CorpusTurn/CommandExpect,
RunCorpusCase with chained expectations); intentctl eval consumes
turns and corpus-declared builtin_specs and domains; report.generate
spec and corpus_multiturn.json fixtures added. v1 corpus format and
conformance unchanged.

Refs: Documents proposal 2026-10-07-agent-stack-n1q1i1-plans.md I1 WP6
2026-10-07 08:12:50 +00:00

80 lines
2.4 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package intent
import (
"context"
"encoding/json"
"os"
"strings"
"testing"
)
// TestEvalMultiTurnCorpus 是 I1 验收载体:v2 多轮语料含「SetSlot 加 Start」
// 用例并通过(链式预期把第二轮转进 expectation_hit)。
func TestEvalMultiTurnCorpus(t *testing.T) {
data, err := os.ReadFile("testdata/corpus_multiturn.json")
if err != nil {
t.Fatal(err)
}
var doc struct {
Cases []CorpusCase `json:"cases"`
}
if err := json.Unmarshal(data, &doc); err != nil {
t.Fatal(err)
}
if len(doc.Cases) == 0 {
t.Fatal("empty corpus")
}
_, snap := buildDemoRegistry(t)
for _, tc := range doc.Cases {
result := RunCorpusCase(context.Background(), snap, tc)
if !result.Passed() {
for _, tr := range result.Turns {
for _, mm := range tr.Mismatches {
t.Errorf("case %s turn %d (%q): %s", tc.Name, tr.Index, tr.Input, mm)
}
}
}
}
}
// TestCorpusV1WrapsAsSingleTurn:v1 单轮形态经同一执行器等价运行(兼容承诺)。
func TestCorpusV1WrapsAsSingleTurn(t *testing.T) {
_, snap := buildDemoRegistry(t)
tc := CorpusCase{Name: "v1", Input: "你好呀", ExpectBranch: "matched", ExpectTop: "demo.greet.hello"}
result := RunCorpusCase(context.Background(), snap, tc)
if !result.Passed() {
t.Fatalf("v1 wrap: %+v", result.Turns)
}
if len(result.Turns) != 1 || result.Turns[0].Branch != "matched" {
t.Fatalf("turns = %+v", result.Turns)
}
}
// TestMatchCommandsFindsMismatches:命令断言的差异定位(kind/action/slot/name)。
func TestMatchCommandsFindsMismatches(t *testing.T) {
got := []Command{
{Kind: CommandStart, Action: ActionRef{Kind: ActionHandler, ID: "h"}, Slots: map[string]string{"topic": "电机"}},
}
mm := matchCommands([]CommandExpect{
{Kind: "start", ActionKind: ActionHandler, ActionID: "other"},
}, got)
if len(mm) != 1 || !strings.Contains(mm[0], "action.id") {
t.Fatalf("mm = %v", mm)
}
mm = matchCommands([]CommandExpect{
{Kind: "start", Slots: map[string]string{"topic": "水泵"}},
}, got)
if len(mm) != 1 || !strings.Contains(mm[0], "slots") {
t.Fatalf("mm = %v", mm)
}
mm = matchCommands([]CommandExpect{
{Kind: "start", ActionKind: ActionHandler, ActionID: "h", Slots: map[string]string{"topic": "电机"}},
}, got)
if len(mm) != 0 {
t.Fatalf("mm = %v", mm)
}
if mm := matchCommands([]CommandExpect{{Kind: "start"}, {Kind: "clarify"}}, got); len(mm) != 1 {
t.Fatalf("count mismatch = %v", mm)
}
}