perf(skills): skip warm prompt search when minimum cannot fit (#157343)

Punchcard-Session: ember-river-harbor-t4
This commit is contained in:
Vincent Koc
2026-09-25 00:14:46 +08:00
committed by GitHub
parent 5f1a5a3508
commit 76c0724047
3 changed files with 59 additions and 11 deletions
+27
View File
@@ -3,6 +3,7 @@ import { formatSkillsForPrompt as upstreamFormatSkillsForPrompt } from "openclaw
import { describe, expect, it } from "vitest";
import { createCanonicalFixtureSkill } from "../test-support/test-helpers.js";
import {
compactSkillsPromptForContext,
formatSkillsForPromptCore,
type Skill,
formatSkillsCompactForPrompt as formatSkillsCompact,
@@ -18,6 +19,32 @@ function makeSkill(name: string, desc = "A skill", filePath = `/skills/${name}/S
});
}
describe("compactSkillsPromptForContext", () => {
it("preserves nested entities and whole surrogate pairs while normalizing whitespace", () => {
const prompt = `<available_skills><description> \t&amp;lt; \n&lt;tag&gt; ${"a".repeat(49)}😀${" tail".repeat(20)}</description></available_skills>`;
expect(compactSkillsPromptForContext(prompt, 1)).toBe(
`<available_skills><description>&amp;lt; &lt;tag&gt; ${"a".repeat(49)}...</description></available_skills>`,
);
});
it.each(["&".repeat(50), "& a"])(
"keeps the original when escaped projection is not strictly shorter: %s",
(description) => {
const prompt = `<available_skills><description>${description}</description></available_skills>`;
expect(compactSkillsPromptForContext(prompt, 1)).toBe(prompt);
},
);
it.each([undefined, 0, -1, Number.NaN, Number.POSITIVE_INFINITY])(
"keeps prompt bytes when the context budget is %s",
(budget) => {
const prompt = `<available_skills><description> ${"long description ".repeat(30)}</description></available_skills>`;
expect(compactSkillsPromptForContext(prompt, budget)).toBe(prompt);
},
);
});
describe("formatSkillsCompact", () => {
it("keeps the full-format XML output aligned with the upstream formatter for visible skills", () => {
const skills = [
+10 -8
View File
@@ -86,14 +86,16 @@ export function compactSkillsPromptForContext(prompt: string, contextTokenBudget
let lo = 64;
let hi = COMPACT_DESCRIPTION_MAX_CHARS;
let result = render(lo);
while (lo <= hi) {
const mid = Math.floor((lo + hi) / 2);
const candidate = render(mid);
if (candidate.length <= targetChars) {
result = candidate;
lo = mid + 1;
} else {
hi = mid - 1;
if (result.length <= targetChars) {
while (lo <= hi) {
const mid = Math.floor((lo + hi) / 2);
const candidate = render(mid);
if (candidate.length <= targetChars) {
result = candidate;
lo = mid + 1;
} else {
hi = mid - 1;
}
}
}
return result.length < prompt.length ? result : prompt;
@@ -46,9 +46,9 @@ function createEntry(name: string): SkillEntry {
}
describe("resolveSkillsPrompt", () => {
it.each([8_192, 32_768])(
"compacts descriptions at %i tokens without changing admitted skill resources",
async (contextTokenBudget) => {
it.each([8_192, 32_768, "minimum", "above minimum"] as const)(
"compacts descriptions at %s without changing admitted skill resources",
async (budget) => {
const entries = Array.from({ length: 24 }, (_, index) => {
const entry = createEntry(`skill-${index}`);
entry.skill.description = `Inspect records & preserve <identifiers>. ${"Detailed matching guidance. ".repeat(10)}`;
@@ -58,12 +58,31 @@ describe("resolveSkillsPrompt", () => {
});
const snapshot = await buildSkillSnapshot("/tmp/openclaw", { entries });
const original = snapshot.prompt.trim();
const minimum = original.replace(
/<description>[\s\S]*?<\/description>/gu,
"<description>Inspect records &amp; preserve &lt;identifiers&gt;. Detailed matching g...</description>",
);
const contextTokenBudget =
typeof budget === "number"
? budget
: (minimum.length + (budget === "above minimum" ? 24 * 10 : 0)) * 5;
const projected = await resolveSkillsPrompt({
workspaceDir: "/tmp/openclaw",
skillsSnapshot: snapshot,
contextTokenBudget,
});
expect(projected.length).toBeLessThan(original.length);
if (budget === "above minimum") {
expect(projected).toBe(
original.replace(
/<description>[\s\S]*?<\/description>/gu,
"<description>Inspect records &amp; preserve &lt;identifiers&gt;. Detailed matching guidance. D...</description>",
),
);
expect(projected.length).toBe(Math.floor(contextTokenBudget / 5));
} else {
expect(projected).toBe(minimum);
}
const omitDescriptions = (prompt: string) =>
prompt.replace(/<description>[\s\S]*?<\/description>/gu, "");
expect(omitDescriptions(projected)).toBe(omitDescriptions(original));