Files
Agent-System-Backend/internal/ctxasm/finish_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

180 lines
6.8 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 ctxasm
import (
"strings"
"testing"
agentllm "git-ssh.u1680832.nyat.app/weichen/Agent-Native.git/runtime/llm"
core "git-ssh.u1680832.nyat.app/weichen/Agent-System-Core.git"
)
func TestVisibleHistoryDropsExcludedAndUnselectedVariants(t *testing.T) {
hist := []core.Message{
{ID: "u", Role: core.RoleUser, Parts: []core.Part{{Type: core.PartText, Text: "问"}}},
{ID: "a1", Role: core.RoleAssistant, VariantGroup: "g", VariantIndex: 0, Excluded: true, Parts: []core.Part{{Type: core.PartText, Text: "旧"}}},
{ID: "a2", Role: core.RoleAssistant, VariantGroup: "g", VariantIndex: 1, Parts: []core.Part{{Type: core.PartText, Text: "新"}}},
}
msgs, _ := AssembleInput(core.AgentProfile{}, core.EffectiveSettings{}, hist, core.Message{ID: "cur", Role: core.RoleUser}, nil, nil, nil, nil, nil, nil, AssembleOptions{})
blob := ""
for _, m := range msgs {
blob += m.Content
}
if strings.Contains(blob, "旧") || !strings.Contains(blob, "新") {
t.Fatalf("history = %q", blob)
}
}
func TestFinishAssemblyAttachesNoteWithoutTouchingSystem(t *testing.T) {
msgs := []agentllm.Message{
{Role: agentllm.RoleSystem, Content: "前缀"},
{Role: agentllm.RoleUser, Content: "你好"},
}
out, rep := FinishAssembly(msgs, &Report{Items: []string{"system"}}, Tail{
Note: "用短句",
Entries: []Injection{{
Keys: []string{"你好"}, Content: "提到问候时补充天气", ScanDepth: 2, BudgetChars: 100,
}},
History: []core.Message{{Role: core.RoleUser, Parts: []core.Part{{Type: core.PartText, Text: "你好"}}}},
Window: 32000,
})
if out[0].Content != "前缀" {
t.Fatal("system prefix changed")
}
if !strings.Contains(out[len(out)-1].Content, "本会话要求") || !strings.Contains(out[len(out)-1].Content, "天气") {
t.Fatalf("tail = %q", out[len(out)-1].Content)
}
if rep.PrefixHash == "" || rep.EstChars <= 0 || len(rep.Sections) != 3 {
t.Fatalf("report = %#v", rep)
}
}
func TestTrimDropsOldestHistory(t *testing.T) {
msgs := []agentllm.Message{
{Role: agentllm.RoleSystem, Content: "s"},
{Role: agentllm.RoleUser, Content: strings.Repeat("旧", 400)},
{Role: agentllm.RoleUser, Content: "现在"},
}
out, rep := FinishAssembly(msgs, &Report{}, Tail{Window: 30})
if len(out) != 2 {
t.Fatalf("kept %d", len(out))
}
if len(rep.Truncations) == 0 {
t.Fatal("expected truncation")
}
}
// ---- N2 WP1:裁切工具组对齐(验收② 产品侧机器证据)----
// assertNoOrphanToolPairs 双向断言:每个 tool_call 有结果,每个结果有调用。
func assertNoOrphanToolPairs(t *testing.T, msgs []agentllm.Message) {
t.Helper()
calls := map[string]bool{}
results := map[string]bool{}
for _, m := range msgs {
for _, c := range m.ToolCalls {
calls[c.ID] = true
}
if m.Role == agentllm.RoleTool && m.ToolCallID != "" {
results[m.ToolCallID] = true
}
}
for id := range calls {
if !results[id] {
t.Fatalf("孤立 tool_call:%s(无结果)", id)
}
}
for id := range results {
if !calls[id] {
t.Fatalf("悬空 tool 结果:%s(无调用)", id)
}
}
}
func toolMsgs() []agentllm.Message {
call := func(id, name string) agentllm.ToolCall {
return agentllm.ToolCall{ID: id, Name: name, Arguments: "{}"}
}
return []agentllm.Message{
{Role: agentllm.RoleSystem, Content: "s"},
{Role: agentllm.RoleUser, Content: strings.Repeat("旧一", 300)},
{Role: agentllm.RoleAssistant, ToolCalls: []agentllm.ToolCall{call("tc1", "read")}},
{Role: agentllm.RoleTool, ToolCallID: "tc1", Name: "read", Content: strings.Repeat("结果一", 200)},
{Role: agentllm.RoleUser, Content: strings.Repeat("旧二", 300)},
{Role: agentllm.RoleAssistant, ToolCalls: []agentllm.ToolCall{call("tc2", "grep"), call("tc3", "read")}},
{Role: agentllm.RoleTool, ToolCallID: "tc2", Name: "grep", Content: strings.Repeat("结果二", 200)},
{Role: agentllm.RoleTool, ToolCallID: "tc3", Name: "read", Content: strings.Repeat("结果三", 200)},
{Role: agentllm.RoleUser, Content: "现在"},
}
}
// 连续多轮裁切后配对仍成立:assistant 与其结果同退,不逐条拆散。
func TestTrimOldestKeepsToolPairs(t *testing.T) {
out, rep := FinishAssembly(toolMsgs(), &Report{}, Tail{Window: 1600})
assertNoOrphanToolPairs(t, out)
if len(rep.Truncations) == 0 {
t.Fatal("expected truncation")
}
if out[len(out)-1].Content != "现在" {
t.Fatal("当前轮必须保留")
}
// 窗口再压:旧二组也须整组同退,仍不出现孤立配对。
out2, _ := FinishAssembly(toolMsgs(), &Report{}, Tail{Window: 900})
assertNoOrphanToolPairs(t, out2)
}
// 开头即孤立 tool 结果组:整组同退(其 assistant 已不存在)。
func TestTrimOldestDropsLeadingOrphanToolRun(t *testing.T) {
msgs := []agentllm.Message{
{Role: agentllm.RoleSystem, Content: "s"},
{Role: agentllm.RoleTool, ToolCallID: "ghost", Name: "read", Content: strings.Repeat("孤", 500)},
{Role: agentllm.RoleUser, Content: strings.Repeat("旧", 500)},
{Role: agentllm.RoleUser, Content: "现在"},
}
out, _ := FinishAssembly(msgs, &Report{}, Tail{Window: 700})
assertNoOrphanToolPairs(t, out)
for _, m := range out {
if m.ToolCallID == "ghost" {
t.Fatal("孤立结果组应整组丢弃")
}
}
}
// 工具组越过当前轮时不再丢弃:窗口可略超,配对不可破坏。
func TestTrimOldestStopsWhenGroupCrossesCurrentTurn(t *testing.T) {
msgs := []agentllm.Message{
{Role: agentllm.RoleSystem, Content: "s"},
{Role: agentllm.RoleAssistant, ToolCalls: []agentllm.ToolCall{{ID: "tc9", Name: "read", Arguments: "{}"}}},
{Role: agentllm.RoleTool, ToolCallID: "tc9", Name: "read", Content: strings.Repeat("结果", 900)},
}
start, end := oldestDropRange(msgs)
if start != -1 || end != -1 {
t.Fatalf("组越过当前轮应返回 -1,-1,得到 %d,%d", start, end)
}
}
// ---- N2 WP1:PrefixHash 稳定契约(同一输入 → 同一前缀哈希)----
func TestPrefixHashStableForIdenticalInput(t *testing.T) {
build := func() []agentllm.Message {
return []agentllm.Message{
{Role: agentllm.RoleSystem, Content: "系统前缀A"},
{Role: agentllm.RoleSystem, Content: "系统前缀B"},
{Role: agentllm.RoleUser, Content: "历史一"},
{Role: agentllm.RoleAssistant, Content: "回复一"},
{Role: agentllm.RoleUser, Content: "现在"},
}
}
_, rep1 := FinishAssembly(build(), &Report{}, Tail{Instructions: "个人指令"})
_, rep2 := FinishAssembly(build(), &Report{}, Tail{Instructions: "个人指令"})
if rep1.PrefixHash == "" || rep1.PrefixHash != rep2.PrefixHash {
t.Fatalf("同一输入的前缀哈希必须一致:%q vs %q", rep1.PrefixHash, rep2.PrefixHash)
}
// 前缀变化必须改变哈希(钉住哈希语义,防退化为常量)。
other := build()
other[0].Content = "系统前缀X"
_, rep3 := FinishAssembly(other, &Report{}, Tail{Instructions: "个人指令"})
if rep3.PrefixHash == rep1.PrefixHash {
t.Fatal("前缀变化后哈希必须不同")
}
}