mirror of
https://github.com/openclaw/openclaw.git
synced 2026-09-28 14:12:28 +08:00
perf(skills): skip warm prompt search when minimum cannot fit (#157343)
Punchcard-Session: ember-river-harbor-t4
This commit is contained in:
@@ -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&lt; \n<tag> ${"a".repeat(49)}😀${" tail".repeat(20)}</description></available_skills>`;
|
||||
|
||||
expect(compactSkillsPromptForContext(prompt, 1)).toBe(
|
||||
`<available_skills><description>&lt; <tag> ${"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 = [
|
||||
|
||||
@@ -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 & preserve <identifiers>. 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 & preserve <identifiers>. 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));
|
||||
|
||||
Reference in New Issue
Block a user