mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-01 14:57:38 +00:00
用户口径(2026-10-27):渲染工具回执,但**超长的才不渲染** —— 那些结果 已经落盘进工具环(toolresult_store),时间线里只应给「有多少、怎么读」, 正文由 toolresult_read(event_id, offset, limit) 按需取。 为什么不能渲染它的预览:预览是多行文本,塞进历史只会压成"半截结果", 信息量还不如一个句柄;而模型真正需要的是"知道有这份东西、从哪儿读"。 判据:TestTimelineOversizedResultRendersHandleOnly (落盘结果不出现预览正文、必须含 toolresult_read 与 event_id;未落盘回执照旧渲染)
100 lines
3.7 KiB
Go
100 lines
3.7 KiB
Go
package core
|
||
|
||
// 时间线必须按**声明**渲染工具回执(2026-10-27 用户裁定)。
|
||
//
|
||
// 现场:`formatMergedTimeline` 只写「→ 调用工具: qq_get_message」,
|
||
// 工具取回的正文在下一轮彻底消失 —— 模型"刚说的话转头就忘":
|
||
// 它记得自己查过,却不记得查到了什么。而 ToolDef 上的 Cleaner /
|
||
// NoMemory 声明早就存在,缺的一直是**消费端**。
|
||
//
|
||
// 判据(三条,对应三条规则):
|
||
// ① NoMemory=true 的工具不渲染;
|
||
// ② 有 Cleaner 的走 Cleaner;
|
||
// ③ 都没有渲染原文首段,并按条数/长度截断。
|
||
|
||
import (
|
||
"strings"
|
||
"testing"
|
||
|
||
sdk "github.com/JianFeeeee/HologramAgent/internal/sdk"
|
||
)
|
||
|
||
func newTimelineTestAgent(defs []sdk.ToolDef) *Agent {
|
||
h := &StageHost{toolDefs: defs}
|
||
return &Agent{stageHost: h}
|
||
}
|
||
|
||
func TestTimelineRendersToolResultsWithCleaner(t *testing.T) {
|
||
a := newTimelineTestAgent([]sdk.ToolDef{
|
||
{
|
||
Name: "qq_get_message",
|
||
Cleaner: func(string) string { return "因为魔芋爽" },
|
||
},
|
||
{Name: "secret_tool", NoMemory: true},
|
||
{Name: "raw_tool"},
|
||
})
|
||
e := &ContextEvent{
|
||
Input: "想报河南医药大学可以来dd",
|
||
ToolResults: []ToolResultItem{
|
||
{Name: "qq_get_message", Output: `{"content":"因为魔芋爽","raw":"…"}`},
|
||
{Name: "secret_tool", Output: "SHOULD-NOT-APPEAR"},
|
||
{Name: "raw_tool", Output: "第一行\n第二行"},
|
||
},
|
||
}
|
||
got := strings.Join(a.renderedToolResultsOf(e), " | ")
|
||
if !strings.Contains(got, "因为魔芋爽") {
|
||
t.Fatalf("声明了 Cleaner 的工具必须按其清洗结果渲染,实际: %q", got)
|
||
}
|
||
if strings.Contains(got, "SHOULD-NOT-APPEAR") {
|
||
t.Fatalf("NoMemory=true 的工具不得进时间线,实际: %q", got)
|
||
}
|
||
if !strings.Contains(got, "第一行 第二行") {
|
||
t.Fatalf("无 Cleaner 的工具应渲染原文(压成单行),实际: %q", got)
|
||
}
|
||
}
|
||
|
||
func TestTimelineToolResultsBounded(t *testing.T) {
|
||
a := newTimelineTestAgent(nil)
|
||
e := &ContextEvent{}
|
||
for i := 0; i < toolResultTimelineMax+2; i++ {
|
||
e.ToolResults = append(e.ToolResults, ToolResultItem{Name: "t", Output: "x"})
|
||
}
|
||
got := a.renderedToolResultsOf(e)
|
||
if len(got) != toolResultTimelineMax+1 {
|
||
t.Fatalf("应渲染 %d 条 + 1 条省略提示,实际 %d 条: %v", toolResultTimelineMax, len(got), got)
|
||
}
|
||
if !strings.Contains(got[len(got)-1], "+2 more") {
|
||
t.Fatalf("省略提示应说明还有几条,实际: %q", got[len(got)-1])
|
||
}
|
||
}
|
||
|
||
// 超长结果(已落盘进工具环)在时间线里**不渲染正文**,只给句柄。
|
||
//
|
||
// 用户口径(2026-10-27):渲染工具回执,但超长的才不渲染 —— 那种结果
|
||
// 已经在 toolresult_store 里,模型用 toolresult_read(event_id, offset, limit)
|
||
// 按需取全文。渲染它的预览会把历史压成半截结果,信息还不如一个句柄。
|
||
func TestTimelineOversizedResultRendersHandleOnly(t *testing.T) {
|
||
a := newTimelineTestAgent(nil)
|
||
e := &ContextEvent{
|
||
ID: "evt_oversize_1",
|
||
Input: "跑个大数据",
|
||
ToolResults: []ToolResultItem{
|
||
{Name: "cmd_run", Output: "预览:第 1 行\n第 2 行\n第 3 行"},
|
||
{Name: "qq_get_message", Output: `{"content":"短回执"}`},
|
||
},
|
||
ToolResultStores: []ToolResultStore{
|
||
{Tool: "cmd_run", File: "tool_0.txt", TotalBytes: 36783, TotalLines: 900},
|
||
},
|
||
}
|
||
got := strings.Join(a.renderedToolResultsOf(e), " | ")
|
||
if strings.Contains(got, "预览") || strings.Contains(got, "第 3 行") {
|
||
t.Fatalf("超长结果不得渲染正文,实际: %q", got)
|
||
}
|
||
if !strings.Contains(got, "toolresult_read") || !strings.Contains(got, "evt_oversize_1") {
|
||
t.Fatalf("超长结果必须给出句柄与读法,实际: %q", got)
|
||
}
|
||
if !strings.Contains(got, "短回执") {
|
||
t.Fatalf("未落盘的工具回执照旧渲染,实际: %q", got)
|
||
}
|
||
}
|