From 14daf5b584310ada943af87f731b97c0cb276c79 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Tue, 27 Oct 2026 15:27:35 +0800 Subject: [PATCH] =?UTF-8?q?fix(memory):=20=E8=B6=85=E9=95=BF=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E7=BB=93=E6=9E=9C=E5=9C=A8=E6=97=B6=E9=97=B4=E7=BA=BF?= =?UTF-8?q?=E9=87=8C=E5=8F=AA=E6=B8=B2=E6=9F=93=E5=8F=A5=E6=9F=84=EF=BC=88?= =?UTF-8?q?=E4=B8=8D=E6=B8=B2=E6=9F=93=E6=AD=A3=E6=96=87=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户口径(2026-10-27):渲染工具回执,但**超长的才不渲染** —— 那些结果 已经落盘进工具环(toolresult_store),时间线里只应给「有多少、怎么读」, 正文由 toolresult_read(event_id, offset, limit) 按需取。 为什么不能渲染它的预览:预览是多行文本,塞进历史只会压成"半截结果", 信息量还不如一个句柄;而模型真正需要的是"知道有这份东西、从哪儿读"。 判据:TestTimelineOversizedResultRendersHandleOnly (落盘结果不出现预览正文、必须含 toolresult_read 与 event_id;未落盘回执照旧渲染) --- internal/agent/core/process.go | 15 ++++++++++ .../agent/core/timeline_toolresult_test.go | 30 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/internal/agent/core/process.go b/internal/agent/core/process.go index 21d8da19..8285b44c 100644 --- a/internal/agent/core/process.go +++ b/internal/agent/core/process.go @@ -635,12 +635,27 @@ func (a *Agent) renderedToolResultsOf(e *ContextEvent) []string { if a != nil && a.stageHost != nil { noMem = a.stageHost.NoMemoryToolNames() } + // ★ 超长结果不渲染正文,只给**句柄**(2026-10-27 用户口径: + // 渲染工具回执,但超长的才不渲染 —— 它们已经在工具环里)。 + // + // 为什么不能渲染它的预览:预览本身是多行文本,塞进时间线会把历史压成 + // "半截结果",而模型真正需要的是「知道有这么一份东西、能从哪儿读」。 + // 正文按需用 toolresult_read(event_id, offset, limit) 取,信息不丢。 + stored := make(map[string]ToolResultStore, len(e.ToolResultStores)) + for _, st := range e.ToolResultStores { + stored[st.Tool] = st + } out := make([]string, 0, len(e.ToolResults)) skipped := 0 for _, tr := range e.ToolResults { if noMem[tr.Name] { continue } + if st, ok := stored[tr.Name]; ok { + out = append(out, fmt.Sprintf("%s: [stored %d bytes / %d lines — read with toolresult_read(event_id=%q)]", + tr.Name, st.TotalBytes, st.TotalLines, e.ID)) + continue + } txt := tr.Output if a != nil { txt = a.toolOutputForQuery(tr.Name, txt) // ← Cleaner 生效 diff --git a/internal/agent/core/timeline_toolresult_test.go b/internal/agent/core/timeline_toolresult_test.go index 80cec088..9737da9a 100644 --- a/internal/agent/core/timeline_toolresult_test.go +++ b/internal/agent/core/timeline_toolresult_test.go @@ -67,3 +67,33 @@ func TestTimelineToolResultsBounded(t *testing.T) { 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) + } +}