mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-06 17:27:31 +00:00
fix(core): 裁剪收归基础设施,contextfull 只作为触发信号
## 问题
同一件事(上下文装不下)有三套实现、三份防护差异:
task.go 发 LLM 前 maybeHandleContextOverflow(有恢复预算)
task.go 上游报错 handleUpstreamContextFull(**直调内部,绕过预算**)
resident.go 轮首 checkContextFull(只对驻留子生效)
其中 handleUpstreamContextFull 是"另起炉灶":它自己又调一遍
handleContextOverflow,绕过 f.OverflowRecover 预算——而文档 §四.1 明确
「L4 遇 L4 不能抢占,反复触发只会排队堆积」。
## 改法
裁剪成为**基础设施**,contextfull 只是「判定已被外部短路」的一种入参:
enforceContextBudget(f, query, force)
force=false 本地水位:积累量 > ratio × 窗口
force=true 外部已确认超页(provider 返回 ErrContextFull)
之后的一切(恢复预算、裁剪、raise L4)完全相同。handleUpstreamContextFull
删除,只剩 isUpstreamContextFull 这个纯判定。
## 任务恢复入口补裁剪
中断任务跑完会把它的输入/输出提交进 a.context,恢复时重建出的前缀可能
**比被挂起时更大**。不在这里裁,这份更大的前缀会被原样发出去。
恢复用的是专用入口 enforceResumeContextBudget,两处语义与通用入口不同:
1. **query 取本帧最新有效上下文**(resumeQueryFrom 从 f.Msgs 尾部往回找),
不是 f.Input。被挂起的任务可能隔了很久,f.Input 早已与当前对话脱节;
拿旧 Input 算相关性,会裁掉"对该任务重要、但字面上不匹配旧输入"的事件。
2. **保护范围是本帧自己的现场**(len(f.Msgs)-f.PrefixLen),不是全局末尾
N 条。被挂起任务的现场可能是一整批工具结果,若落在保护范围外被归档,
恢复后的任务引用的内容已不在上下文里,任务自相矛盾。
为此新增 pruneForResume + contextTopKForProtected——不能复用 contextTopK,
它内部会把 topK 抬到 effectiveProtectedCount()+1(全局保护),两套下限
打架会让恢复场景「裁不动」(实测 protected=8、20 条上下文时 topK 被抬到 20)。
## 不变的部分
声明式裁剪(业务策略)语义原样保留:
· 中断/消息注入 —— pruneOnInput,判据 payload.context_policy / ChannelDef
· 工具内容注入 —— stepToolAfter,判据 ToolDef.ContextPolicy
两者走 memoryPass → pruneByQuery,与容量保护共用底层,但职责正交。
判据:任务恢复入口必须裁、恢复查询取本帧最新、保护本帧现场、
恢复预算对所有入口生效。变异验证:恢复不裁/保护退回全局/上游绕过预算,
三种坏法均被抓。
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"runtime/debug"
|
||||
@ -194,9 +195,22 @@ func (a *Agent) executeToolCallOutcome(tc agentAPI.ToolCall, channel string, tur
|
||||
}
|
||||
}()
|
||||
|
||||
// ★ 取消必须**真的传到对端**,而不只是让本函数不再等。
|
||||
//
|
||||
// 旧实现只有 time.After:到点后本函数返回,但那个 goroutine 仍在跑,
|
||||
// 阻塞在跨进程 Call 上且**没有任何通道能通知它**——插件侧的 handler
|
||||
// 毫不知情,继续占着连接/子进程/文件句柄直到自己跑完,结果却被丢弃。
|
||||
// 长工具 + 频繁超时 ⇒ 后台持续堆积。
|
||||
//
|
||||
// 现在把 ctx 沿执行链传下去(经 args 旁路,见 StageHost.ExecuteToolCtx),
|
||||
// 由 proc 桥的 CallContext 在取消时向插件发 call.cancel。
|
||||
// **插件作者一行都不用改**:handler 签名不变。
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel() // 正常返回也要放:否则 ctx 内部的 timer/子 goroutine 会泄漏
|
||||
|
||||
done := make(chan toolOutcome, 1)
|
||||
go func() {
|
||||
done <- a.executeToolCallInner(tc, channel, turnScenes)
|
||||
done <- a.executeToolCallInner(ctx, tc, channel, turnScenes)
|
||||
}()
|
||||
|
||||
// ★ 超时:优先用工具**自己声明**的时长,而不是一刀切 60s。
|
||||
@ -218,14 +232,16 @@ func (a *Agent) executeToolCallOutcome(tc agentAPI.ToolCall, channel string, tur
|
||||
case result := <-done:
|
||||
return result
|
||||
case <-time.After(toolTimeout):
|
||||
log.Printf("[agent] tool %s timed out after %v", tc.Name, toolTimeout)
|
||||
// 先 cancel 再返回:这一行就是"让插件停下来"的触发点。
|
||||
cancel()
|
||||
log.Printf("[agent] tool %s timed out after %v(已向执行方发送取消)", tc.Name, toolTimeout)
|
||||
te := newToolError(ErrReasonTimeout, "", fmt.Sprintf("工具 %s 执行超时(%s)", tc.Name, fmtDuration(toolTimeout)),
|
||||
"该工具本次未在时限内返回。可改用更小的任务,或换用其他工具。")
|
||||
return toolOutcome{Text: te.Error(), Raw: te}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) executeToolCallInner(tc agentAPI.ToolCall, channel string, turnScenes []string) toolOutcome {
|
||||
func (a *Agent) executeToolCallInner(ctx context.Context, tc agentAPI.ToolCall, channel string, turnScenes []string) toolOutcome {
|
||||
// 参数没法用(被 max_tokens 截断,或 JSON 写坏了):**不要**拿着空/残缺参数去调工具。
|
||||
// 否则工具会报 “path is required”“command is required” 这类与真因无关的错,
|
||||
// 模型看不出真因、只能原样重试(实测 cmd_run 失败率高达 34%~48%)。
|
||||
@ -290,7 +306,7 @@ func (a *Agent) executeToolCallInner(tc agentAPI.ToolCall, channel string, turnS
|
||||
}
|
||||
|
||||
if a.stageHost != nil {
|
||||
if result, err := a.stageHost.ExecuteTool(tc.Name, tc.Arguments); err == nil {
|
||||
if result, err := a.stageHost.ExecuteToolCtx(ctx, tc.Name, tc.Arguments); err == nil {
|
||||
// Raw 必须带上:否则结构化失败({"error":…} / ToolError)在这一步被抹平成文本,
|
||||
// Success 又会退回恒真——正是阶段 1b 要修的那个洞。
|
||||
return toolOutcome{Text: renderToolResult(tc.Name, result), Raw: result}
|
||||
|
||||
Reference in New Issue
Block a user