Files
weichen b3f885f215
ci / vet-and-test (pull_request) Successful in 43s
Strip inline think blocks from subagent summaries and card bodies
Why: the round-6 real-model dual-speed run showed MiniMax writing its
reasoning as inline <think> blocks; finishChild's partsText fed them into
the background notice turn and the inbox result card verbatim.
Evidence: new runmgr test covers closed-block stripping with body preserved
and conservative handling of unclosed blocks; go test ./... 32 pkgs green.
Impact: partsText removes closed <think>...</think> spans before summary,
notice, and card assembly; unclosed blocks are kept as-is.
Refs: Dev task feature/dual-speed-real-path.
2026-10-10 10:01:15 +00:00

181 lines
6.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 runtime
import (
"context"
"encoding/json"
"fmt"
"strings"
"testing"
"time"
agentllm "agent-native/runtime/llm"
core "agent-system-core"
)
// 双速提醒经内核 Reminders 钩子进入下一请求的消息(不是响应部件):
// 这里捕获每次模型请求的原文,检索提醒标记。
type dualSpeedRecorder struct {
clock *stepClock
turns [][]agentllm.Event
request []string
base agentllm.StreamFunc
}
// stepClock 每 tick 前进 5 秒;runDirect 起 0 时刻,模型每被调用一次 tick 一格,
// 20 次调用即 100 秒量级,足以压过默认 75 秒阈值。
type stepClock struct{ step int64 }
func (c *stepClock) now() time.Time { return time.Unix(0, c.step*int64(5*time.Second)) }
func (c *stepClock) tick() { c.step++ }
func (rec *dualSpeedRecorder) stream(base agentllm.StreamFunc) agentllm.StreamFunc {
return func(ctx context.Context, model string, msgs []agentllm.Message, tools []agentllm.ToolDecl, opts agentllm.StreamOptions) <-chan agentllm.Event {
rec.clock.tick()
for _, m := range msgs {
if b, err := json.Marshal(m); err == nil {
rec.request = append(rec.request, string(b))
}
}
return base(ctx, model, msgs, tools, opts)
}
}
func (rec *dualSpeedRecorder) nudged() bool {
for _, r := range rec.request {
if strings.Contains(r, "任务快慢双速") {
return true
}
}
return false
}
func runDualSpeed(t *testing.T, turns [][]agentllm.Event, setup func(rc *fakeRunCtx)) *dualSpeedRecorder {
t.Helper()
rec := &dualSpeedRecorder{clock: &stepClock{}, turns: turns}
r := &LLMRunner{Stream: rec.stream(scriptStream(turns, nil)), Now: rec.clock.now}
rc := &fakeRunCtx{ctx: context.Background(), prof: verifyOn()}
if setup != nil {
setup(rc)
}
if err := r.Execute(rc); err != nil {
t.Fatalf("Execute: %v", err)
}
return rec
}
func toolOnlyTurns(n int) [][]agentllm.Event {
var turns [][]agentllm.Event
for i := 0; i < n; i++ {
// 每次调用用独立 ID(toolCall 帮手的 ID 固定,重复会撞内核的工具槽位)。
call := agentllm.ToolCall{ID: fmt.Sprintf("c-ds-%d", i), Name: "make_report", Arguments: "{}"}
turns = append(turns, textDeltaTurn("", call))
}
return turns
}
// 长时间纯工具轮、用户零答复 → 阈值后恰好注入一次双速提醒。
func TestDualSpeedNudgeFiresOnceAfterThreshold(t *testing.T) {
turns := append(toolOnlyTurns(20), textDeltaTurn("完成。"))
rec := runDualSpeed(t, turns, nil)
if !rec.nudged() {
t.Fatal("dual-speed nudge missing after threshold with no user reply")
}
count := 0
for _, r := range rec.request {
count += strings.Count(r, "任务快慢双速")
}
if count > 1 {
t.Fatalf("nudge injected %d times, want at most once per request window", count)
}
}
// 短任务(阈值内完成)不提醒。
func TestDualSpeedNudgeQuietBeforeThreshold(t *testing.T) {
turns := append(toolOnlyTurns(1), textDeltaTurn("完成。"))
rec := runDualSpeed(t, turns, nil)
if rec.nudged() {
t.Fatal("nudge fired before the threshold")
}
}
// 设置 runtime.dualSpeedNudgeSeconds=0 → 关闭,超时也不提醒。
func TestDualSpeedNudgeDisabledBySetting(t *testing.T) {
turns := append(toolOnlyTurns(20), textDeltaTurn("完成。"))
rec := runDualSpeed(t, turns, func(rc *fakeRunCtx) {
rc.runtimeExtra = map[string]any{"dualSpeedNudgeSeconds": float64(0)}
})
if rec.nudged() {
t.Fatal("nudge fired although disabled via runtime.dualSpeedNudgeSeconds=0")
}
}
// 模型已给出面向用户的答复(快通道成立)→ 即使后续事实核查续写拖过阈值也不提醒。
func TestDualSpeedNudgeSkippedWhenReplied(t *testing.T) {
turns := [][]agentllm.Event{
textDeltaTurn("", toolCall("make_report", `{"content":"准确率 95%"}`)),
textDeltaTurn("完整总结:产出物与要点。"),
textDeltaTurn("已修正并重新登记。"),
}
rec := runDualSpeed(t, turns, func(rc *fakeRunCtx) {
rc.verify = true
rc.execArtifact = &core.Artifact{ID: "art_0123456789ab", Name: "报告.md"}
rc.execArtifactContent = []byte("# 报告\n\n准确率 95%。")
})
if rec.nudged() {
t.Fatal("nudge fired although the model already gave its user reply")
}
}
// 已把深度工作放后台(慢通道成立)→ 不再提醒。
func TestDualSpeedNudgeSkippedWhenDelegated(t *testing.T) {
turns := [][]agentllm.Event{
textDeltaTurn("", toolCall("delegate_task", `{"name":"深度解析","task":"逐页解析","background":true}`)),
}
turns = append(turns, toolOnlyTurns(19)...)
turns = append(turns, textDeltaTurn("后台进行中,先给结论。"))
rec := runDualSpeed(t, turns, func(rc *fakeRunCtx) {
rc.bgID = "run_bg_1"
})
if rec.nudged() {
t.Fatal("nudge fired although background delegation already happened")
}
}
// 首次被无视后按 45s 间隔升级重复(最多 3 次),第二次起为硬性措辞。
func TestDualSpeedNudgeEscalates(t *testing.T) {
turns := append(toolOnlyTurns(35), textDeltaTurn("完成。")) // 35×5s=175s → 75s 起、120s、165s 三档
rec := runDualSpeed(t, turns, nil)
marks, hard := 0, 0
for _, r := range rec.request {
marks += strings.Count(r, "用户已经等待")
hard += strings.Count(r, "硬性要求")
}
if marks < 1 {
t.Fatalf("escalation missing: requests=%d", len(rec.request))
}
if marks > 2 {
t.Fatalf("escalated %d times, want at most 2 repeats after the first", marks)
}
}
// 入口前置:带附件的任务第 0 回合注入快慢双速选项;无附件不注入;阈值关闭不注入。
func TestDualSpeedEntryHintWithAttachments(t *testing.T) {
withAtt := runDualSpeed(t, [][]agentllm.Event{textDeltaTurn("完成。")}, func(rc *fakeRunCtx) {
rc.attachments = []core.ParsedDocument{{Meta: core.DocMeta{Filename: "meeting.docx"}}}
})
if !strings.Contains(strings.Join(withAtt.request, "\n"), "快慢双速") {
t.Fatal("entry hint missing for an attachment-bearing task")
}
noAtt := runDualSpeed(t, [][]agentllm.Event{textDeltaTurn("完成。")}, nil)
if strings.Contains(strings.Join(noAtt.request, "\n"), "本任务附带材料") {
t.Fatal("entry hint fired without attachments")
}
disabled := runDualSpeed(t, [][]agentllm.Event{textDeltaTurn("完成。")}, func(rc *fakeRunCtx) {
rc.attachments = []core.ParsedDocument{{Meta: core.DocMeta{Filename: "meeting.docx"}}}
rc.runtimeExtra = map[string]any{"dualSpeedNudgeSeconds": float64(0)}
})
if strings.Contains(strings.Join(disabled.request, "\n"), "本任务附带材料") {
t.Fatal("entry hint fired although the nudge setting is disabled")
}
}