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 io
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
@ -738,7 +739,17 @@ func (m *IOManager) DeviceOfTool(name string) (string, bool) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
// ExecuteTool 执行设备工具(无取消语义)。
|
||||
func (m *IOManager) ExecuteTool(name string, args map[string]interface{}) (ret interface{}, err error) {
|
||||
return m.ExecuteToolCtx(context.Background(), name, args)
|
||||
}
|
||||
|
||||
// ExecuteToolCtx 执行设备工具,并把 ctx 传给设备。
|
||||
//
|
||||
// ctx 同样不进 Device.Execute 的签名(那是插件作者的契约),而是**旁路**
|
||||
// 放进 args(键与 core.executeCtxArgKey 一致)。内置插件若要消费它,
|
||||
// 在自己的 handler 里取一次即可;不取就与今天完全一致。
|
||||
func (m *IOManager) ExecuteToolCtx(ctx context.Context, name string, args map[string]interface{}) (ret interface{}, err error) {
|
||||
m.mu.RLock()
|
||||
type nameDevice struct {
|
||||
name string
|
||||
@ -765,7 +776,7 @@ func (m *IOManager) ExecuteTool(name string, args map[string]interface{}) (ret i
|
||||
parent := m.parent
|
||||
m.mu.RUnlock()
|
||||
if parent != nil {
|
||||
ret, err := parent.ExecuteTool(name, args)
|
||||
ret, err := parent.ExecuteToolCtx(ctx, name, args)
|
||||
if err == nil {
|
||||
return ret, nil
|
||||
}
|
||||
@ -781,7 +792,7 @@ func (m *IOManager) ExecuteTool(name string, args map[string]interface{}) (ret i
|
||||
err = fmt.Errorf("tool %s execute panic: %v", name, r)
|
||||
}
|
||||
}()
|
||||
return candidates[0].dev.Execute(name, args)
|
||||
return candidates[0].dev.Execute(name, bindExecCtx(ctx, args))
|
||||
}
|
||||
|
||||
func (m *IOManager) ListDevices() []Device {
|
||||
@ -1125,3 +1136,27 @@ func (m *IOManager) ClearToolBlocks(callID string) {
|
||||
delete(m.toolPendingBlocks, callID)
|
||||
m.toolBlocksMu.Unlock()
|
||||
}
|
||||
|
||||
// execCtxArgKey 是内核内部传递执行上下文的旁路键。
|
||||
//
|
||||
// ⚠️ 必须与 core.executeCtxArgKey 逐字一致(理由见 proc/execctx.go:
|
||||
// 复制常量是为了避免 core ↔ io 的导入环,不一致的表现只是"取消不生效")。
|
||||
const execCtxArgKey = "\x00kernel.execute_ctx\x00"
|
||||
|
||||
// bindExecCtx 把 ctx 放到 args 里随调用下传。
|
||||
//
|
||||
// 为什么不改 Device.Execute 签名:那会让全部内置插件跟着改(同一个理由
|
||||
// 见 StageHost.ExecuteToolCtx 的注释)。旁路键对不消费它的插件完全不可见——
|
||||
// 但**必须**在真正下发给插件前剥掉,否则严格校验参数的插件会报未知参数。
|
||||
// 剥除由各插件自己的入口决定:core 侧统一下发前已剥(见 StageHost),
|
||||
// io 侧保持原样,因为设备插件直接读 args、不会做严格白名单校验。
|
||||
func bindExecCtx(ctx context.Context, args map[string]interface{}) map[string]interface{} {
|
||||
if ctx == nil {
|
||||
return args
|
||||
}
|
||||
if args == nil {
|
||||
args = make(map[string]interface{}, 1)
|
||||
}
|
||||
args[execCtxArgKey] = ctx
|
||||
return args
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user