File tree Expand file tree Collapse file tree
app/src/components/RunOutput Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -28,14 +28,23 @@ export function isExplicitCompression(event: LangfuseEvent): boolean {
2828 return typeof meta === 'string' && / " t a s k " \s * : \s * " c o m p r e s s i o n " / i. test ( meta ) ;
2929}
3030
31- /** Input-token count reported by a generation event, or null. */
31+ /**
32+ * True prompt-token count for a generation event, or null. Prompt-caching providers (DeepSeek,
33+ * Anthropic, …) report `input` as the cache-MISS tokens only and put the cached prefix in a
34+ * separate field — so the real context size is `input + cache_read + cache_creation`. Summing them
35+ * is essential: without it a warm cache hit (e.g. input 67k → 3k while 69k is cached) looks like a
36+ * context drop and is misread as a compression. These cache fields are reported *separately from*
37+ * `input` (not a subset of it), so the sum never double-counts.
38+ */
3239export function inputTokens ( event : LangfuseEvent ) : number | null {
3340 if ( ( event . body [ 'langfuse.observation.type' ] as string | undefined ) !== 'generation' ) return null ;
3441 const raw = event . body [ 'langfuse.observation.usage_details' ] as string | undefined ;
3542 if ( ! raw ) return null ;
3643 try {
37- const usage = JSON . parse ( raw ) as { input ?: number } ;
38- return typeof usage . input === 'number' ? usage . input : null ;
44+ const usage = JSON . parse ( raw ) as Record < string , unknown > ;
45+ if ( typeof usage . input !== 'number' ) return null ;
46+ const num = ( k : string ) => ( typeof usage [ k ] === 'number' ? ( usage [ k ] as number ) : 0 ) ;
47+ return usage . input + num ( 'cache_read_input_tokens' ) + num ( 'cache_creation_input_tokens' ) ;
3948 } catch {
4049 return null ;
4150 }
You can’t perform that action at this time.
0 commit comments