feat(kimi-code): show cache read details in debug timing (#1074)

* feat(kimi-code): show cache read details in debug timing

* chore: remove changeset
This commit is contained in:
liruifengv 2026-06-24 19:39:42 +08:00 committed by GitHub
parent ff177155ca
commit b62b3a147f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 68 additions and 3 deletions

View file

@ -1,7 +1,16 @@
import { formatTokenCount } from './usage-format';
interface DebugTokenUsage {
readonly inputOther?: number;
readonly inputCacheRead?: number;
readonly inputCacheCreation?: number;
readonly output?: number;
}
export interface StepTimingInput {
readonly llmFirstTokenLatencyMs?: number | undefined;
readonly llmStreamDurationMs?: number | undefined;
readonly usage?: { readonly output: number } | undefined;
readonly llmFirstTokenLatencyMs?: number;
readonly llmStreamDurationMs?: number;
readonly usage?: DebugTokenUsage;
}
// Decode TPS is only meaningful when the output actually streamed over a
@ -29,9 +38,33 @@ export function formatStepDebugTiming(input: StepTimingInput): string | undefine
);
}
}
const inputTokens = usageInputTotal(input.usage);
const hasInputUsage =
input.usage !== undefined &&
(input.usage.inputOther !== undefined ||
input.usage.inputCacheRead !== undefined ||
input.usage.inputCacheCreation !== undefined);
if (hasInputUsage && (inputTokens > 0 || (outputTokens ?? 0) > 0)) {
const cacheReadTokens = input.usage.inputCacheRead ?? 0;
const cacheCreationTokens = input.usage.inputCacheCreation ?? 0;
const cacheHitRate = inputTokens > 0 ? Math.round((cacheReadTokens / inputTokens) * 100) : 0;
const cacheParts = [`cache read ${formatTokenCount(cacheReadTokens)} (${cacheHitRate}%)`];
if (cacheCreationTokens > 0) {
cacheParts.push(`write ${formatTokenCount(cacheCreationTokens)}`);
}
parts.push(`tokens in ${formatTokenCount(inputTokens)}`);
parts.push(cacheParts.join(' / '));
}
return `[Debug] ${parts.join(' | ')}`;
}
function usageInputTotal(usage: DebugTokenUsage | undefined): number {
if (usage === undefined) return 0;
return (usage.inputOther ?? 0) + (usage.inputCacheRead ?? 0) + (usage.inputCacheCreation ?? 0);
}
function formatDuration(ms: number): string {
if (ms < 1000) return `${Math.round(ms)}ms`;
return `${(ms / 1000).toFixed(1)}s`;

View file

@ -27,6 +27,38 @@ describe('formatStepDebugTiming', () => {
expect(result).toBe('[Debug] TTFT: 800ms | TPS: 40.0 tok/s (200 tokens in 5.0s)');
});
it('formats input tokens and cache read/write counts', () => {
const result = formatStepDebugTiming({
llmFirstTokenLatencyMs: 800,
llmStreamDurationMs: 5000,
usage: {
inputOther: 700,
inputCacheRead: 1200,
inputCacheCreation: 100,
output: 200,
},
});
expect(result).toBe(
'[Debug] TTFT: 800ms | TPS: 40.0 tok/s (200 tokens in 5.0s) | tokens in 2.0k | cache read 1.2k (60%) / write 100',
);
});
it('omits cache write count when it is zero', () => {
const result = formatStepDebugTiming({
llmFirstTokenLatencyMs: 800,
llmStreamDurationMs: 5000,
usage: {
inputOther: 1000,
inputCacheRead: 0,
inputCacheCreation: 0,
output: 200,
},
});
expect(result).toContain('tokens in 1.0k');
expect(result).toContain('cache read 0 (0%)');
expect(result).not.toContain('/ write 0');
});
it('omits TPS when the streamed window is too short to measure', () => {
const result = formatStepDebugTiming({
llmFirstTokenLatencyMs: 1200,