mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-10-26 10:45:22 +00:00
refactor(pi-compaction): 改为压力触发的观察遮蔽与空闲阈值整段摘要
原实现对所有工具结果(含 read)在下一次请求即替换为约 4K 的首尾存根, 不看上下文压力,导致模型看不到大文件中段;后台逐条语义摘要又反复改写 历史、打碎 prompt 缓存。参照 Anthropic 上下文编辑、Manus 与 JetBrains 观察遮蔽研究重构为: - L2:上下文达到阈值时在 turn_end 批量提交 Pi 原生 context_edit,把最近 keepRecentTurns 轮之外的旧 toolResult 换成确定性占位符;trigger/target 回差与 minClearTokens 保证少触发、一次清够;原始条目保留,可经 context_tree_query 取回 - L3:空闲 agent_settled 且超过绝对阈值时调用一次 ctx.compact - 阈值按绝对 token 计并受窗口比例约束,默认针对 1M 窗口(DeepSeek V4.1 Flash):遮蔽 200K→100K、整段摘要 400K - 删除逐条存根、语义 worker、soft/hard/target 比例与自行取消 Pi 压缩的门控; Pi 原生阈值压缩保留为最终安全网 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent
a254d4fd68
commit
a6d8416c76
@@ -0,0 +1,188 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { MASK_PREFIX, planMasking, rebuildMaskedLedger } from "../src/masking.ts";
|
||||
import { effectiveThreshold } from "../src/token-accounting.ts";
|
||||
import type { MaskingConfig } from "../src/types.ts";
|
||||
|
||||
const MASKING: MaskingConfig = {
|
||||
enabled: true,
|
||||
triggerTokens: 1_000,
|
||||
triggerRatio: 0.9,
|
||||
targetTokens: 400,
|
||||
targetRatio: 0.35,
|
||||
minClearTokens: 200,
|
||||
keepRecentTurns: 2,
|
||||
minEntryTokens: 10,
|
||||
excludeTools: ["ask_user_question"],
|
||||
};
|
||||
|
||||
const estimate = (message: any): number => Math.ceil(JSON.stringify(message).length / 4);
|
||||
|
||||
function assistantMessage(id: string, calls: Array<{ id: string; name: string; args?: unknown }>) {
|
||||
return {
|
||||
role: "assistant",
|
||||
content: calls.map((call) => ({ type: "toolCall", id: call.id, name: call.name, arguments: call.args ?? {} })),
|
||||
timestamp: id,
|
||||
};
|
||||
}
|
||||
|
||||
function toolResultMessage(id: string, toolCallId: string, text: string, toolName = "bash") {
|
||||
return {
|
||||
role: "toolResult",
|
||||
toolCallId,
|
||||
toolName,
|
||||
content: [{ type: "text", text }],
|
||||
timestamp: id,
|
||||
};
|
||||
}
|
||||
|
||||
function entry(id: string, message: any, messages = [message]) {
|
||||
return { sourceEntry: { id, type: "message", message }, messages };
|
||||
}
|
||||
|
||||
function protect(turns = MASKING.keepRecentTurns) {
|
||||
return Array.from({ length: turns }, (_unused, index) =>
|
||||
entry(`recent-${index}`, { role: "assistant", content: [{ type: "text", text: "ok" }], timestamp: `recent-${index}` }));
|
||||
}
|
||||
|
||||
test("below the effective trigger plans no drafts", () => {
|
||||
const entries = [
|
||||
entry("e1", assistantMessage("a1", [{ id: "c1", name: "bash", args: { command: "ls" } }])),
|
||||
entry("e2", toolResultMessage("r1", "c1", "x".repeat(8_000))),
|
||||
...protect(),
|
||||
];
|
||||
const plan = planMasking({ contextEntries: entries, currentTokens: 999, contextWindow: 0, masking: MASKING, estimate });
|
||||
assert.equal(plan.drafts.length, 0);
|
||||
assert.equal(plan.masked.length, 0);
|
||||
});
|
||||
|
||||
test("above the trigger masks oldest first until context fits under target", () => {
|
||||
const entries = [
|
||||
entry("e1", assistantMessage("a1", [
|
||||
{ id: "c1", name: "bash", args: { command: "one" } },
|
||||
{ id: "c2", name: "bash", args: { command: "two" } },
|
||||
{ id: "c3", name: "bash", args: { command: "three" } },
|
||||
])),
|
||||
entry("e2", toolResultMessage("r1", "c1", "a".repeat(4_000))),
|
||||
entry("e3", toolResultMessage("r2", "c2", "b".repeat(4_000))),
|
||||
entry("e4", toolResultMessage("r3", "c3", "c".repeat(4_000))),
|
||||
entry("e5", toolResultMessage("r4", "c4", "recent result".repeat(200))),
|
||||
...protect(),
|
||||
];
|
||||
// needed = 2_000 - 400 = 1_600; each result clears ~980 tokens.
|
||||
const plan = planMasking({ contextEntries: entries, currentTokens: 2_000, contextWindow: 0, masking: MASKING, estimate });
|
||||
assert.deepEqual(plan.drafts.map((draft) => draft.targetId), ["e2", "e3"]);
|
||||
});
|
||||
|
||||
test("results inside the recent keepRecentTurns window are never masked", () => {
|
||||
const entries = [
|
||||
entry("e1", toolResultMessage("r1", "c1", "a".repeat(4_000))),
|
||||
entry("recent-a", assistantMessage("ra", [{ id: "rc", name: "bash", args: {} }])),
|
||||
entry("recent-r", toolResultMessage("rr", "rc", "b".repeat(4_000))),
|
||||
entry("recent-b", assistantMessage("rb", [])),
|
||||
];
|
||||
const plan = planMasking({ contextEntries: entries, currentTokens: 3_500, contextWindow: 0, masking: MASKING, estimate });
|
||||
assert.deepEqual(plan.drafts.map((draft) => draft.targetId), ["e1"]);
|
||||
});
|
||||
|
||||
test("excluded tools, SKILL.md reads, already-masked and small results are skipped", () => {
|
||||
const entries = [
|
||||
entry("e1", assistantMessage("a1", [
|
||||
{ id: "c1", name: "ask_user_question", args: { question: "x" } },
|
||||
{ id: "c2", name: "read", args: { path: "/repo/skills/foo/SKILL.md" } },
|
||||
{ id: "c3", name: "bash", args: { command: "old" } },
|
||||
{ id: "c4", name: "bash", args: { command: "tiny" } },
|
||||
])),
|
||||
entry("e2", toolResultMessage("r1", "c1", "a".repeat(4_000), "ask_user_question")),
|
||||
entry("e3", toolResultMessage("r2", "c2", "b".repeat(4_000), "read")),
|
||||
entry("e4", toolResultMessage("r3", "c3", `${MASK_PREFIX}bash result cleared to save context (~900 tokens).`), ),
|
||||
entry("e5", toolResultMessage("r4", "c4", "tiny")),
|
||||
entry("e6", toolResultMessage("r5", "c5", "d".repeat(4_000))),
|
||||
...protect(),
|
||||
];
|
||||
const masking = { ...MASKING, minClearTokens: 100, minEntryTokens: 100 };
|
||||
const plan = planMasking({ contextEntries: entries, currentTokens: 3_500, contextWindow: 0, masking, estimate });
|
||||
assert.deepEqual(plan.drafts.map((draft) => draft.targetId), ["e6"]);
|
||||
assert.equal(plan.masked[0]!.toolCallId, "c5");
|
||||
});
|
||||
|
||||
test("when clearable volume stays under minClear nothing is masked", () => {
|
||||
const entries = [
|
||||
entry("e1", toolResultMessage("r1", "c1", "a".repeat(600))),
|
||||
...protect(),
|
||||
];
|
||||
const plan = planMasking({ contextEntries: entries, currentTokens: 3_500, contextWindow: 0, masking: MASKING, estimate });
|
||||
// ~130 tokens clearable, below minClearTokens (200) → not worth invalidating cache.
|
||||
assert.equal(plan.drafts.length, 0);
|
||||
});
|
||||
|
||||
test("placeholder is deterministic, names the tool and call id, and truncates args at 300 chars", () => {
|
||||
const longArgs = { command: `run ${"v".repeat(600)}` };
|
||||
const entries = [
|
||||
entry("e1", assistantMessage("a1", [{ id: "c1", name: "bash", args: longArgs }])),
|
||||
entry("e2", toolResultMessage("r1", "c1", "a".repeat(4_000))),
|
||||
...protect(),
|
||||
];
|
||||
const masking = { ...MASKING, minClearTokens: 1 };
|
||||
const first = planMasking({ contextEntries: entries, currentTokens: 3_500, contextWindow: 0, masking, estimate });
|
||||
const second = planMasking({ contextEntries: entries, currentTokens: 3_500, contextWindow: 0, masking, estimate });
|
||||
assert.equal(first.drafts.length, 1);
|
||||
const placeholder = first.masked[0]!.placeholder;
|
||||
assert.equal(placeholder, second.masked[0]!.placeholder);
|
||||
assert.ok(placeholder.startsWith(MASK_PREFIX));
|
||||
assert.match(placeholder, /bash result cleared/);
|
||||
assert.match(placeholder, /"toolCallIds":\["c1"\]/);
|
||||
const argsText = placeholder.match(/Call args: (.*)\. Re-run/)?.[1] ?? "";
|
||||
assert.ok(argsText.length <= 301, `args excerpt ${argsText.length} exceeded 300+ellipsis`);
|
||||
assert.ok(placeholder.includes("…"));
|
||||
});
|
||||
|
||||
test("the small-window ratio caps both trigger and target", () => {
|
||||
const masking: MaskingConfig = {
|
||||
...MASKING,
|
||||
triggerTokens: 200_000,
|
||||
triggerRatio: 0.6,
|
||||
targetTokens: 100_000,
|
||||
targetRatio: 0.35,
|
||||
minClearTokens: 50_000,
|
||||
};
|
||||
assert.equal(effectiveThreshold(masking.triggerTokens, masking.triggerRatio, 272_000), 163_200);
|
||||
assert.equal(effectiveThreshold(masking.targetTokens, masking.targetRatio, 272_000), 95_200);
|
||||
const entries = [
|
||||
entry("e1", toolResultMessage("r1", "c1", "a".repeat(300_000))),
|
||||
...protect(),
|
||||
];
|
||||
const below = planMasking({ contextEntries: entries, currentTokens: 163_199, contextWindow: 272_000, masking, estimate });
|
||||
assert.equal(below.drafts.length, 0);
|
||||
const above = planMasking({ contextEntries: entries, currentTokens: 163_200, contextWindow: 272_000, masking, estimate });
|
||||
assert.equal(above.drafts.length, 1);
|
||||
});
|
||||
|
||||
test("ledger rebuild counts only masked targets still inside the active context", () => {
|
||||
const placeholder = (id: string) =>
|
||||
`${MASK_PREFIX}bash result cleared to save context (~100 tokens). Call args: {}. Re-run the call if still needed, or recover the original with context_tree_query {"toolCallIds":["${id}"]}.`;
|
||||
const branch = [
|
||||
{ type: "message", id: "u1", parentId: null, timestamp: "t", message: { role: "user", content: "start", timestamp: 1 } },
|
||||
{ type: "message", id: "r-old", parentId: "u1", timestamp: "t", message: toolResultMessage("m-old", "c-old", "o".repeat(2_000)) },
|
||||
{ type: "context_edit", id: "e-old", parentId: "r-old", timestamp: "t", targetId: "r-old", replacement: { content: placeholder("c-old") } },
|
||||
{ type: "message", id: "m-keep", parentId: "e-old", timestamp: "t", message: { role: "user", content: "kept question", timestamp: 2 } },
|
||||
{ type: "compaction", id: "c1", parentId: "m-keep", timestamp: "t", summary: "summary", firstKeptEntryId: "m-keep", tokensBefore: 9_999 },
|
||||
{ type: "message", id: "a-keep", parentId: "c1", timestamp: "t", message: assistantMessage("a-keep", []) },
|
||||
{ type: "message", id: "r-new", parentId: "a-keep", timestamp: "t", message: toolResultMessage("m-new", "c-new", "n".repeat(2_000)) },
|
||||
{ type: "context_edit", id: "e-new", parentId: "r-new", timestamp: "t", targetId: "r-new", replacement: { content: placeholder("c-new") } },
|
||||
];
|
||||
const ledger = rebuildMaskedLedger(branch, "e-new", estimate);
|
||||
assert.equal(ledger.size, 1);
|
||||
assert.ok(ledger.has("c-new"));
|
||||
assert.ok(!ledger.has("c-old"));
|
||||
});
|
||||
|
||||
test("user and assistant messages are never masked", () => {
|
||||
const entries = [
|
||||
entry("e1", { role: "user", content: "u".repeat(8_000), timestamp: "u1" }),
|
||||
entry("e2", { role: "assistant", content: [{ type: "text", text: "a".repeat(8_000) }], timestamp: "a1" }),
|
||||
...protect(),
|
||||
];
|
||||
const plan = planMasking({ contextEntries: entries, currentTokens: 9_000, contextWindow: 0, masking: { ...MASKING, keepRecentTurns: 1, minClearTokens: 1 }, estimate });
|
||||
assert.equal(plan.drafts.length, 0);
|
||||
});
|
||||
Reference in New Issue
Block a user