mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-06 09:17:31 +00:00
fix(core): contextoverflow 改回空中断(按原始设计)+ Python 解释器可配置
## 1. contextoverflow:L4 空中断(推翻三处偏移) 用户口径:**原始设计是注入 L4 空中断,目的是触发语义相关性裁剪, 不含任何消息,就是一个空中断。**此前的实现有三处偏移: ① **中断带正文** —— `Build` 生成「已裁剪 N 条低相关事件到文档记忆…」, 于是 L4 变成一条要模型回应的消息,白烧一整轮推理。 ② **裁剪在 raise 之前做完** —— 先 `pruneByQuery` 再 raise,中断沦为 「裁剪已完成」的回执,晚了一步。 ③ **上游 contextfull 后原地重跑** —— `StepPrepare` 重建后拿**同一份** 超限内容再发一次,必然再超限,快速烧光 recoverBudget。 (这条是修① ② 时暴露的连带问题。) 改动: · `VectorContextOverflow.Build` 返回空串 ⇒ 空中断;级别仍是 L4 · 删除 `overflowNotice` 与 `vectorArgs.Pruned`(中断不需要知道裁了多少) · `handleContextOverflow` 不再裁剪,只发空中断,如实返回 0 · 新增 `pruneForAirInterrupt`:裁剪由空中断任务**真正执行时**触发, query 取上下文里最后一条真实用户输入作相关性基准 · 新增 `isAirInterrupt`:只认「内核来源 + type=interrupt + 无正文」, 插件带正文的中断不受影响(否则"插件发了个空中断"会变成无声裁剪) · `prepareInputTask` 识别空中断 → 裁剪 → terminalSkipped (不进 LLM、不写上下文、不发回复) · 上游 contextfull 后改为 `outcomeSuspended`(让位等裁剪),不再原地重跑 顺带记录偏移成因(`raiseContextOverflow` 注释):当时的**观察**是对的 (L4 看不到被打断者上下文 ⇒ 模型会把"没检索到"推断成"不存在"), 但**结论**错了——它把"补偿 L4 看不到上下文"当成中断该带正文的理由。 模型需要的不是"知道有内容被移走",而是**留下来的内容与当前问题相关**。 判据(均经变异验证): · `TestVectors_OverflowIsAirInterrupt` —— 正文必须为空 + 级别 L4 变异:Build 改回非空 ⇒ 红 · `TestOverflow_RaisesAirInterruptWithoutPruning` —— 发中断那步不改上下文 变异:加回 pruneByQuery ⇒ 红 · `TestAirInterrupt_ResolvedAsValidInput` —— 空中断须被认作有效输入 · `TestAirInterrupt_RejectsOrdinaryEmptyInput` —— 普通空包/插件带正文 中断**不得**误判 · 改写 5 条断言"发中断即裁剪"的旧测试(T1/T2/T5/大事件/自适应), 保留它们原本守的东西(超限判定、恢复预算、终止保护), 只把裁剪步骤显式移到 `pruneForAirInterrupt` ## 2. clawhubadapter:Python 解释器与工作目录可配置 真机事故:`bilibili-all-in-one` 声明 requirements 含 httpx,系统 Python 没装 ⇒ 启动即 `ModuleNotFoundError`。而解释器此前是**硬编码猜**的 (先 /usr/bin/python3,再 /usr/local/bin/python3)。 「猜」没有正确解:不同部署的 Python 布局完全不同(发行版 Python、 pyenv、uv、conda、venv…)。而往系统 Python 里 pip install 是**错的**: PEP 668 明确禁止,`--break-system-packages` 会破坏 apt 包一致性。 新增两个配置项(与既有 skills_dir/simulator_dir 同一风格): · `python_bin` —— 解释器绝对路径,最高优先 · `skill_workdir` —— skill 子进程工作目录 回退顺序:配置 → `<skill>/.venv/bin/python` → 系统 python3。 **配错必须响亮**:配置的路径不存在时返回 error,不静默退回系统解释器 (否则用户以为配好了,实际跑在缺依赖的 Python 上 —— 又一次静默降级)。 判据:`TestPickPython_ConfiguredWins`(配置优先于 venv)/ `TestPickPython_VenvWhenNotConfigured` / `TestPickPython_MissingConfiguredIsLoud` `launchProcessIn` 是 `launchProcess` 的工作目录变体(不改既有 4 个调用点)。
This commit is contained in:
@ -182,6 +182,11 @@ type inputPayload struct {
|
||||
mediaType string
|
||||
// captureTool 是媒体落进 CAS 时记录的来源标签。
|
||||
captureTool string
|
||||
// airInterrupt 表示这是一次**空中断**:内核发的 L4,没有正文。
|
||||
//
|
||||
// 它的唯一用途是触发一次语义相关性裁剪(contextoverflow 的原始设计)。
|
||||
// 空正文的普通输入不是空中断(见 isAirInterrupt)。
|
||||
airInterrupt bool
|
||||
}
|
||||
|
||||
// resolveInput 把 InputEvent 归一成 inputPayload。
|
||||
@ -209,6 +214,19 @@ func (a *Agent) resolveInput(evt *agentIO.InputEvent) (inputPayload, bool) {
|
||||
|
||||
text, _ := evt.Payload["content"].(string)
|
||||
blocks, mediaType := injectedBlocks(evt.Payload)
|
||||
|
||||
// ★ 空中断(kernel/overflow):**没有正文也要算有效输入**。
|
||||
//
|
||||
// 这是 contextoverflow 的原始设计:L4 中断本身不携带任何消息,
|
||||
// 它唯一的用途是"把当前任务抢占下来,让语义相关性裁剪发生"。
|
||||
// 空 content 在这里曾被判成无效输入直接丢弃 —— 于是那个 L4 变成了
|
||||
// 一条什么也不做的空转任务。
|
||||
//
|
||||
// 判定条件刻意窄:必须是内核发出的中断,且确实无正文、无媒体。
|
||||
// 普通输入的空内容仍然算无效(否则客户端空包会白白唤醒一轮)。
|
||||
if text == "" && len(blocks) == 0 && isAirInterrupt(evt) {
|
||||
return inputPayload{text: "", airInterrupt: true}, true
|
||||
}
|
||||
// 文本与媒体都空才算无效输入:只带图不带字是合法的(插件注入常这样)。
|
||||
if text == "" && len(blocks) == 0 {
|
||||
return inputPayload{}, false
|
||||
@ -558,3 +576,23 @@ func (a *Agent) cleanInputFor(evt *agentIO.InputEvent, input string) string {
|
||||
}
|
||||
return input
|
||||
}
|
||||
|
||||
// isAirInterrupt 判定一个输入事件是否为「空中断」。
|
||||
//
|
||||
// 空中断 = 内核发起、type=interrupt、payload 里没有 content。
|
||||
// 用途见 contextoverflow 的中断向量(VectorContextOverflow):
|
||||
// 它只是一次调度信号,用来把当前任务抢占下来,好在任务边界做语义相关性裁剪。
|
||||
//
|
||||
// 为什么必须由内核来源限定:插件也能注入 interrupt,但插件的中断**总是**
|
||||
// 带正文("某设备上线了""某任务失败了")。把它们的空包也当空中断,
|
||||
// 会让"插件发了个没内容的中断"变成一次无声的裁剪,行为不可预测。
|
||||
func isAirInterrupt(evt *agentIO.InputEvent) bool {
|
||||
if evt == nil || evt.Type != "interrupt" {
|
||||
return false
|
||||
}
|
||||
if v, _ := evt.Payload["kernel"].(bool); !v {
|
||||
return false
|
||||
}
|
||||
text, _ := evt.Payload["content"].(string)
|
||||
return text == ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user