mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-08 17:58:13 +01:00
fix(context-bill): a bill with any estimated item under --exact is labelled mixed and keeps its error band, in --json and text
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
1f93c2dfc1
commit
58715ba660
2 files changed
+51
-3
No files matched your search
+18
-3
@@ -568,10 +568,12 @@ export interface Bill {
|
||||
|
||||
export function buildBill(
|
||||
root: string,
|
||||
{ tokensOf = estimateTokensOf, tokenSource, calibration }: {
|
||||
{ tokensOf = estimateTokensOf, tokenSource, calibration, missedKeys }: {
|
||||
tokensOf?: TokensOf;
|
||||
tokenSource?: string;
|
||||
calibration?: Calibration;
|
||||
/** Keys a measured tokensOf had to estimate; filled while the bill is priced. */
|
||||
missedKeys?: ReadonlySet<string>;
|
||||
} = {},
|
||||
): Bill {
|
||||
const resolved = path.resolve(root);
|
||||
@@ -581,12 +583,17 @@ export function buildBill(
|
||||
);
|
||||
const total = skills.reduce((n, s) => n + s.totalMdBytes, 0);
|
||||
const totalTokens = skills.reduce((n, s) => n + s.totalMdTokens, 0);
|
||||
const estimated = tokenSource ? (missedKeys?.size ?? 0) : 0;
|
||||
return {
|
||||
root: resolved,
|
||||
// Named so a reader never has to guess whether a figure was measured.
|
||||
tokenSource: tokenSource ?? "estimate: calibrated bytes/token per content class",
|
||||
tokenSource: !tokenSource
|
||||
? "estimate: calibrated bytes/token per content class"
|
||||
: estimated
|
||||
? `mixed: ${tokenSource}, with ${estimated} item(s) estimated offline`
|
||||
: tokenSource,
|
||||
tokenEstimate: TOKEN_DIVISORS,
|
||||
tokenEstimateErrorPct: tokenSource ? 0 : TOKEN_ESTIMATE_ERROR_PCT,
|
||||
tokenEstimateErrorPct: tokenSource && !estimated ? 0 : TOKEN_ESTIMATE_ERROR_PCT,
|
||||
// Present only under --exact: how far the offline estimate was off, per file.
|
||||
...(calibration ? { calibration } : {}),
|
||||
skills,
|
||||
@@ -769,6 +776,12 @@ export function tokenDisclaimer(bill: Pick<Bill, "tokenEstimateErrorPct" | "toke
|
||||
if (bill.tokenEstimateErrorPct === 0) {
|
||||
return `Token counts measured with ${bill.tokenSource}. Bytes are exact.`;
|
||||
}
|
||||
if (bill.tokenSource.startsWith("mixed: ")) {
|
||||
return (
|
||||
`Token counts are MIXED (${bill.tokenSource.slice("mixed: ".length)}). Any figure that includes an estimated ` +
|
||||
`item can be off by up to ${bill.tokenEstimateErrorPct}% for that item. Bytes are always exact.`
|
||||
);
|
||||
}
|
||||
const per = Object.entries(TOKEN_DIVISORS).map(([k, v]) => `${k} /${v}`).join(", ");
|
||||
return (
|
||||
`Token counts are ESTIMATES: bytes divided per content class (${per}), calibrated against ` +
|
||||
@@ -1211,6 +1224,7 @@ export async function contextBillMain(argv: string[], options: MainOptions = {})
|
||||
const exactFor = async (tree: string, set: ExactSendSet | undefined): Promise<{
|
||||
tokensOf?: TokensOf;
|
||||
tokenSource?: string;
|
||||
missedKeys?: ReadonlySet<string>;
|
||||
calibration?: Calibration;
|
||||
onDone?: () => void;
|
||||
}> => {
|
||||
@@ -1260,6 +1274,7 @@ export async function contextBillMain(argv: string[], options: MainOptions = {})
|
||||
return {
|
||||
tokensOf: measured.tokensOf,
|
||||
tokenSource: measured.tokenSource,
|
||||
missedKeys: measured.missedKeys,
|
||||
calibration: calibrationTable(measured.counts, tree, measured.realOf),
|
||||
onDone: () => {
|
||||
if (measured.missedKeys.size) {
|
||||
|
||||
@@ -1292,6 +1292,39 @@ describe("--exact (opt-in measurement; offline here via an injected fetch)", ()
|
||||
expect(bill.skills[0].totalMdBytes).toBe(skillMd.length + "CHECKOUT-FLOW".length + "PRIVATE-DIARY".length);
|
||||
});
|
||||
|
||||
it("labels a bill with any estimated item as mixed, in --json and in text", async () => {
|
||||
if (process.platform === "win32") return;
|
||||
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "context-bill-mixed-"));
|
||||
const outside = path.join(tmp, "outside", "ext");
|
||||
fs.mkdirSync(outside, { recursive: true });
|
||||
fs.writeFileSync(path.join(outside, "SKILL.md"), "---\nname: ext\ndescription: d\n---\n" + "e".repeat(6_000));
|
||||
const skills = path.join(tmp, "skills");
|
||||
fs.mkdirSync(path.join(skills, "qa"), { recursive: true });
|
||||
fs.writeFileSync(path.join(skills, "qa", "SKILL.md"), "---\nname: qa\ndescription: d\n---\n# qa\n");
|
||||
fs.symlinkSync(outside, path.join(skills, "ext"));
|
||||
const run = async (args: string[]) => {
|
||||
const stdout = capture();
|
||||
const code = await contextBillMain([skills, "--exact", ...args], {
|
||||
stdout: stdout.stream,
|
||||
stderr: capture().stream,
|
||||
apiKey: "sk-test",
|
||||
fetchImpl: fakeFetch(),
|
||||
egressHome,
|
||||
});
|
||||
return { code, out: stdout.text() };
|
||||
};
|
||||
const json = await run(["--json"]);
|
||||
const text = await run([]);
|
||||
fs.rmSync(tmp, { recursive: true, force: true });
|
||||
expect(json.code).toBe(0);
|
||||
const bill = JSON.parse(json.out);
|
||||
expect(bill.tokenSource).toStartWith("mixed: count_tokens");
|
||||
expect(bill.tokenSource).toContain("estimated offline");
|
||||
expect(bill.tokenEstimateErrorPct).toBeGreaterThan(0);
|
||||
expect(text.out).toContain("Token counts are MIXED");
|
||||
expect(text.out).not.toContain("Token counts measured with");
|
||||
});
|
||||
|
||||
async function exactRun(skills: string) {
|
||||
const calls: { body: string }[] = [];
|
||||
const stderr = capture();
|
||||
|
||||
Reference in new issue
Block a user