Files
Agent-System-Backend/internal/runtime/factcheck_test.go
T
weichen f1302ed5b3 Enable canonical versioned Go module consumption
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
2026-10-13 15:47:16 +00:00

126 lines
4.6 KiB
Go
Raw 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 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)
}
}