Why: Allow consumers to download an immutable module revision without nested source checkouts or local path replacements. Evidence: SELF package tests and the prior canonical-path rejection are recorded in the Dev dependency-workspace task card; remote consumption is replayed against the committed candidate. Impact: Change the module identity and its Go imports while preserving API behavior. Existing historical submodule revisions remain buildable. Refs: Versioned dependencies U1/U3; feature/dependency-workspace
126 lines
4.6 KiB
Go
126 lines
4.6 KiB
Go
package runtime
|
||
|
||
import (
|
||
"context"
|
||
"strings"
|
||
"testing"
|
||
|
||
core "git-ssh.u1680832.nyat.app/weichen/Agent-System-Core.git"
|
||
)
|
||
|
||
// 事实核查:交付物里材料找不到出处的百分比被抽出并组成反馈;找到的不报。
|
||
func TestUnverifiedFactsAndNotice(t *testing.T) {
|
||
material := "目标是准确率 85%以上。截止 10月12日。团队共 12 人。"
|
||
artifact := "准确率达到 95%,截止 10月20日,覆盖 3,500 用户,目标 85%。"
|
||
got := UnverifiedFacts(artifact, material, factExemptions())
|
||
joined := strings.Join(got, ",")
|
||
for _, want := range []string{"95%", "10月20日", "3,500"} {
|
||
if !strings.Contains(joined, want) {
|
||
t.Fatalf("missing %q in %v", want, got)
|
||
}
|
||
}
|
||
if strings.Contains(joined, "85%") {
|
||
t.Fatalf("sourced figure must not be reported: %v", got)
|
||
}
|
||
notice := factCheckNotice(got)
|
||
if !strings.Contains(notice, "95%") || !strings.Contains(notice, "改成材料或来源结果") {
|
||
t.Fatalf("notice malformed: %q", notice)
|
||
}
|
||
if UnverifiedFacts("", material, nil) != nil || UnverifiedFacts(artifact, "", nil) != nil {
|
||
t.Fatal("empty sides short-circuit")
|
||
}
|
||
}
|
||
|
||
// 端到端:终轮对带未核实数字的交付物给一轮修正;修正后(重新登记干净版本)放行。
|
||
func TestFactCheckContinuationOnce(t *testing.T) {
|
||
rc := &fakeRunCtx{
|
||
ctx: context.Background(),
|
||
prof: core.AgentProfile{Model: core.ModelRoute{Chat: "test-model"}},
|
||
verify: true,
|
||
execArtifact: &core.Artifact{ID: "art_0123456789ab", Name: "报告.md"},
|
||
execArtifactContent: []byte("# 报告\n\n准确率 95%。"),
|
||
}
|
||
// 第一轮:登记带编造数字的工件 -> 事实核查续跑一次;第二轮:干净版本通过。
|
||
turns1 := padTurns(
|
||
textDeltaTurn("", toolCall("make_report", `{"content":"准确率 95%"}`)),
|
||
textDeltaTurn("写好了。"),
|
||
)
|
||
turns2 := padTurns(
|
||
textDeltaTurn("材料里是 85%。"),
|
||
)
|
||
r := &LLMRunner{Stream: scriptStream(padTurns(turns1...), nil), VerifyStream: passedStream()}
|
||
if err := r.Execute(rc); err != nil {
|
||
t.Fatalf("Execute: %v", err)
|
||
}
|
||
sawFact := false
|
||
for _, p := range rc.assistant {
|
||
if p.Check != nil && !p.Check.Passed && strings.Contains(p.Check.Reason, "事实核查") {
|
||
sawFact = true
|
||
}
|
||
}
|
||
if !sawFact {
|
||
t.Fatalf("fact check continuation note missing: %+v", rc.assistant)
|
||
}
|
||
_ = turns2
|
||
}
|
||
|
||
// ---- E3:标注感知豁免 ----
|
||
|
||
func TestMarkedAsEstimate(t *testing.T) {
|
||
cases := []struct {
|
||
name, text, tok string
|
||
want bool
|
||
}{
|
||
{"前置标注", "目标完成率 85%,分三个阶段推进", "85%", true},
|
||
{"后置标注", "完成率 85%(预计)", "85%", true},
|
||
{"裸数字", "当前完成率 85%,尚有差距", "85%", false},
|
||
{"邻域外有标注词", "目标是建成样板。完成率 85%", "85%", false},
|
||
{"多次出现其一标注", "实际 70%。目标 85% 年内达成", "85%", true},
|
||
{"推测标注", "用户规模约 120,000(推测)", "120,000", true},
|
||
}
|
||
for _, c := range cases {
|
||
if got := MarkedAsEstimate(c.text, c.tok); got != c.want {
|
||
t.Errorf("%s: MarkedAsEstimate(%q) = %v, want %v", c.name, c.tok, got, c.want)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestUnverifiedFactsMarksPass(t *testing.T) {
|
||
material := "材料:上半年营收 3,200 万元。"
|
||
// 「目标 85%」已标注 → 放行;裸 95% → 仍拦。
|
||
artifact := "上半年营收 3,200 万元,达成率目标 85%,试点扩面后裸数字 95%。"
|
||
got := UnverifiedFacts(artifact, material, factExemptions())
|
||
if len(got) != 1 || got[0] != "95%" {
|
||
t.Fatalf("unverified = %v(应只拦未标注的 95%%)", got)
|
||
}
|
||
}
|
||
|
||
// ---- E3:两轮修正的轮次逻辑 ----
|
||
|
||
func TestFactCheckGateRounds(t *testing.T) {
|
||
st := &directRun{}
|
||
first := []string{"95%", "10月20日"}
|
||
|
||
// 第一轮:拦截并给修正通知。
|
||
notice, cont := st.factCheckGate(first)
|
||
if !cont || !strings.Contains(notice, "95%") || strings.Contains(notice, "最后一次拦截") {
|
||
t.Fatalf("round1: cont=%v notice=%q", cont, notice)
|
||
}
|
||
// token 完全没变(模型没理会):不再拦——重复拦截只挤占共享续写预算。
|
||
if _, cont := st.factCheckGate(first); cont {
|
||
t.Fatal("unchanged tokens must pass through")
|
||
}
|
||
// token 有变化(改了一个、残留一个):第二轮升级为「最后一次拦截」。
|
||
notice, cont = st.factCheckGate([]string{"10月20日"})
|
||
if !cont || !strings.Contains(notice, "最后一次拦截") || !strings.Contains(notice, "目标") {
|
||
t.Fatalf("round2: cont=%v notice=%q", cont, notice)
|
||
}
|
||
// 轮数用尽:放行。
|
||
if _, cont := st.factCheckGate([]string{"7%"}); cont {
|
||
t.Fatal("rounds exhausted must pass through")
|
||
}
|
||
if st.factCheckRounds != 2 {
|
||
t.Fatalf("rounds = %d", st.factCheckRounds)
|
||
}
|
||
}
|