Skip to content

Commit 4a4b888

Browse files
committed
refactor(model-signals): drop legacy signal field shims
1 parent 8400e57 commit 4a4b888

8 files changed

Lines changed: 94 additions & 130 deletions

File tree

frontend/src/api/demo.ts

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -940,11 +940,8 @@ function signalRatesFor(group: Session[], groupToolCalls: ToolCall[]): ModelSign
940940

941941
return {
942942
outputExpansionRate: safeRate(outputTokens, inputTokens),
943-
generationTokenOverhead: safeRate(outputTokens, inputTokens),
944943
reasoningTokenShare: safeRate(reasoningOutputTokens, outputTokens),
945944
reasoningOverheadRate: safeRate(reasoningOutputTokens, Math.max(0, outputTokens - reasoningOutputTokens)),
946-
reasoningTokenOverhead: safeRate(reasoningOutputTokens, outputTokens),
947-
reasoningOutputShare: safeRate(reasoningOutputTokens, outputTokens),
948945
cacheMissRate: clampRate(safeRate(inputTokens - cachedInputTokens, inputTokens)),
949946
modelThroughputTokensPerSecond: safeRate(totalTokens, modelDurationSeconds),
950947
modelThroughputOutputTokensPerSecond: safeRate(outputTokens, modelDurationSeconds),
@@ -995,6 +992,8 @@ function metricSetFromTotals(totals: MetricTotals): ModelSignalMetricSet {
995992
const failurePressure = safeRate((totals.failedModelCalls || 0) + totals.failedToolCalls, totals.sessionCount)
996993
const avgModelCallsPerSession = safeRate(totals.modelCalls, totals.sessionCount)
997994
const outputExpansionRate = safeRate(totals.outputTokens, totals.inputTokens)
995+
const visibleOutputTokens = Math.max(0, totals.outputTokens - totals.reasoningOutputTokens)
996+
const billableOutputTokens = totals.outputTokens
998997
const reasoningOverheadRate = safeRate(totals.reasoningOutputTokens, Math.max(0, totals.outputTokens - totals.reasoningOutputTokens))
999998
const cacheMissRate = clampRate(safeRate(totals.inputTokens - totals.cachedInputTokens, totals.inputTokens))
1000999
const modelThroughputOutputTokensPerSecond = safeRate(totals.outputTokens, modelDurationSeconds)
@@ -1012,6 +1011,8 @@ function metricSetFromTotals(totals: MetricTotals): ModelSignalMetricSet {
10121011
cachedInputTokens: totals.cachedInputTokens,
10131012
outputTokens: totals.outputTokens,
10141013
reasoningOutputTokens: totals.reasoningOutputTokens,
1014+
visibleOutputTokens,
1015+
billableOutputTokens,
10151016
modelDurationMs: totals.modelDurationMs,
10161017
wallDurationMs,
10171018
activeDurationMs,
@@ -1041,11 +1042,8 @@ function metricSetFromTotals(totals: MetricTotals): ModelSignalMetricSet {
10411042
}),
10421043
avgModelCallsPerSession,
10431044
outputExpansionRate,
1044-
generationTokenOverhead: outputExpansionRate,
10451045
reasoningTokenShare: safeRate(totals.reasoningOutputTokens, totals.outputTokens),
10461046
reasoningOverheadRate,
1047-
reasoningTokenOverhead: safeRate(totals.reasoningOutputTokens, totals.outputTokens),
1048-
reasoningOutputShare: safeRate(totals.reasoningOutputTokens, totals.outputTokens),
10491047
cacheMissRate,
10501048
modelThroughputTokensPerSecond,
10511049
modelThroughputOutputTokensPerSecond,
@@ -1161,8 +1159,8 @@ function relativeDecrease(current: number, baseline: number): number {
11611159
return (baseline - current) / baseline
11621160
}
11631161

1164-
function reasoningOverhead(metric: Pick<ModelSignalRates, 'reasoningTokenShare' | 'reasoningOverheadRate' | 'reasoningTokenOverhead' | 'reasoningOutputShare'>): number {
1165-
return metric.reasoningOverheadRate ?? metric.reasoningTokenOverhead ?? metric.reasoningOutputShare ?? metric.reasoningTokenShare
1162+
function reasoningOverhead(metric: Pick<ModelSignalRates, 'reasoningOverheadRate'>): number {
1163+
return metric.reasoningOverheadRate
11661164
}
11671165

11681166
function modelSignalDriftFor(current: ModelSignalMetricSet, baseline: ModelSignalMetricSet): ModelSignalDrift {
@@ -1586,13 +1584,11 @@ function anomalySessionsFor(items: Session[], scopedToolCalls: ToolCall[]): Mode
15861584
const reasons: string[] = []
15871585
if (rates.toolFailureRate > 0) reasons.push('Tool failure in session')
15881586
if (reasoningOverhead(rates) >= 0.25) reasons.push('High reasoning overhead')
1589-
if ((rates.generationTokenOverhead ?? rates.outputExpansionRate) >= 0.2) reasons.push('Generation overhead relative to input')
1587+
if (rates.outputExpansionRate >= 0.2) reasons.push('Generation overhead relative to input')
15901588
if (rates.cacheMissRate >= 0.85) reasons.push('Low cache reuse')
15911589
if (rates.modelThroughputTokensPerSecond > 0 && rates.modelThroughputTokensPerSecond < 85) reasons.push('Low model token throughput')
15921590
if (!reasons.length) continue
15931591
anomalies.push({
1594-
session,
1595-
id: session.id,
15961592
sessionId: session.id,
15971593
sessionKey: session.sessionKey,
15981594
codexSessionId: session.codexSessionId,
@@ -1601,22 +1597,25 @@ function anomalySessionsFor(items: Session[], scopedToolCalls: ToolCall[]): Mode
16011597
rawSourcePath: session.rawSourcePath,
16021598
agentKind: session.agentKind,
16031599
agentName: session.agentName,
1604-
sourceId: session.sourceId,
1605-
sourceKey: session.sourceKey,
1606-
sourceLabel: session.sourceLabel,
1607-
sourceRootPath: session.sourceRootPath,
1608-
sourceSessionsPath: session.sourceSessionsPath,
1600+
sourceId: session.sourceId || 0,
1601+
sourceKey: session.sourceKey || '',
1602+
sourceLabel: session.sourceLabel || session.agentName,
1603+
sourceRootPath: session.sourceRootPath || '',
1604+
sourceSessionsPath: session.sourceSessionsPath || '',
16091605
model: session.model,
1606+
modelCalls: modelCallsForSession(session),
16101607
totalTokens: session.tokenUsage.totalTokens,
16111608
inputTokens: session.tokenUsage.inputTokens,
1609+
cachedInputTokens: session.tokenUsage.cachedInputTokens,
16121610
outputTokens: session.tokenUsage.outputTokens,
16131611
reasoningOutputTokens: session.tokenUsage.reasoningOutputTokens,
1612+
visibleOutputTokens: Math.max(0, session.tokenUsage.outputTokens - session.tokenUsage.reasoningOutputTokens),
1613+
billableOutputTokens: session.tokenUsage.outputTokens,
16141614
toolCalls: sessionToolCalls.length,
16151615
failedToolCalls: sessionToolCalls.filter((call) => !isSuccessfulToolStatus(call.status)).length,
16161616
modelDurationMs: session.modelDurationMs,
1617-
severity: reasons.length > 1 ? 'high' : 'medium',
1618-
signal: reasons[0],
16191617
reasons,
1618+
score: clampRate(reasons.length / 5),
16201619
...rates
16211620
})
16221621
}
@@ -1635,6 +1634,8 @@ function modelSignals(filters: UsageScopeFilters = {}): ModelSignals {
16351634
const scopedToolCalls = filteredToolCalls({ agent: filters.agent, project: filters.project, from: filters.from, to: filters.to })
16361635
.filter((call) => sessionIds.has(call.sessionId))
16371636
const rates = signalRatesFor(scoped, scopedToolCalls)
1637+
const outputTokens = sum(scoped, (session) => session.tokenUsage.outputTokens)
1638+
const reasoningOutputTokens = sum(scoped, (session) => session.tokenUsage.reasoningOutputTokens)
16381639
const cohorts = modelSignalCohortsFor(scoped, scopedToolCalls)
16391640
return {
16401641
totalSessions: scoped.length,
@@ -1646,6 +1647,9 @@ function modelSignals(filters: UsageScopeFilters = {}): ModelSignals {
16461647
avgModelCallsPerSession: safeRate(sum(scoped, modelCallsForSession), scoped.length),
16471648
outputExpansionRate: rates.outputExpansionRate,
16481649
reasoningTokenShare: rates.reasoningTokenShare,
1650+
reasoningOverheadRate: rates.reasoningOverheadRate,
1651+
visibleOutputTokens: Math.max(0, outputTokens - reasoningOutputTokens),
1652+
billableOutputTokens: outputTokens,
16491653
cacheMissRate: rates.cacheMissRate,
16501654
modelThroughputTokensPerSecond: rates.modelThroughputTokensPerSecond,
16511655
modelThroughputOutputTokensPerSecond: rates.modelThroughputOutputTokensPerSecond,

frontend/src/api/types.ts

Lines changed: 39 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -279,11 +279,8 @@ export interface TokenAnalytics {
279279

280280
export interface ModelSignalRates {
281281
outputExpansionRate: number
282-
generationTokenOverhead?: number
283282
reasoningTokenShare: number
284-
reasoningOverheadRate?: number
285-
reasoningTokenOverhead?: number
286-
reasoningOutputShare?: number
283+
reasoningOverheadRate: number
287284
cacheMissRate: number
288285
modelThroughputTokensPerSecond: number
289286
modelThroughputOutputTokensPerSecond: number
@@ -303,8 +300,8 @@ export interface ModelSignalMetricSet extends ModelSignalRates {
303300
outputTokens: number
304301
reasoningOutputTokens: number
305302
contextCompressionTokens?: number
306-
visibleOutputTokens?: number
307-
billableOutputTokens?: number
303+
visibleOutputTokens: number
304+
billableOutputTokens: number
308305
modelDurationMs: number
309306
wallDurationMs?: number
310307
activeDurationMs?: number
@@ -464,43 +461,41 @@ export interface ModelSignalsProjectMetric extends ModelSignalMetricSet {
464461
}
465462

466463
export interface ModelSignalAnomalySession {
467-
id?: number
468-
sessionId?: number
469-
session?: Session
470-
sessionKey?: string
464+
sessionId: number
465+
sourceId: number
466+
sourceKey: string
467+
sourceLabel: string
468+
sourceRootPath: string
469+
sourceSessionsPath: string
470+
sessionKey: string
471471
codexSessionId?: string
472-
startedAt?: string
473-
projectPath?: string
474-
rawSourcePath?: string
475-
agentKind?: string
476-
agentName?: string
477-
sourceId?: number
478-
sourceKey?: string
479-
sourceLabel?: string
480-
sourceRootPath?: string
481-
sourceSessionsPath?: string
482-
model?: string
483-
totalTokens?: number
484-
inputTokens?: number
485-
outputTokens?: number
486-
reasoningOutputTokens?: number
472+
projectPath: string
473+
model: string
474+
startedAt: string
475+
rawSourcePath: string
476+
agentKind: string
477+
agentName: string
478+
modelCalls: number
479+
toolCalls: number
480+
failedToolCalls: number
481+
totalTokens: number
482+
inputTokens: number
483+
cachedInputTokens: number
484+
outputTokens: number
485+
reasoningOutputTokens: number
487486
contextCompressionTokens?: number
488-
toolCalls?: number
489-
failedToolCalls?: number
490-
modelDurationMs?: number
491-
outputExpansionRate?: number
492-
reasoningTokenShare?: number
493-
cacheMissRate?: number
494-
modelThroughputTokensPerSecond?: number
495-
toolFailureRate?: number
496-
toolDependencyRate?: number
497-
severity?: string
498-
signal?: string
499-
reasonLabels?: string[] | string
500-
reasons?: string[] | string
501-
signalReasons?: string[] | string
502-
reason?: string
503-
[key: string]: unknown
487+
visibleOutputTokens: number
488+
billableOutputTokens: number
489+
modelDurationMs: number
490+
outputExpansionRate: number
491+
reasoningTokenShare: number
492+
reasoningOverheadRate: number
493+
cacheMissRate: number
494+
modelThroughputTokensPerSecond: number
495+
modelThroughputOutputTokensPerSecond: number
496+
toolFailureRate: number
497+
reasons: string[]
498+
score: number
504499
}
505500

506501
export interface ModelSignals {
@@ -513,9 +508,9 @@ export interface ModelSignals {
513508
avgModelCallsPerSession: number
514509
outputExpansionRate: number
515510
reasoningTokenShare: number
516-
reasoningOverheadRate?: number
517-
visibleOutputTokens?: number
518-
billableOutputTokens?: number
511+
reasoningOverheadRate: number
512+
visibleOutputTokens: number
513+
billableOutputTokens: number
519514
cacheMissRate: number
520515
modelThroughputTokensPerSecond: number
521516
modelThroughputOutputTokensPerSecond: number

frontend/src/components/model-signals/chartMetrics.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -414,11 +414,11 @@ function finiteNumber(value?: number) {
414414
}
415415

416416
function reasoningOverhead(metric?: ModelSignalMetricSet) {
417-
return firstFinite(metric?.reasoningOverheadRate, metric?.reasoningTokenOverhead, metric?.reasoningOutputShare, metric?.reasoningTokenShare)
417+
return finiteNumber(metric?.reasoningOverheadRate)
418418
}
419419

420420
function generationOverhead(metric?: ModelSignalMetricSet) {
421-
return firstFinite(metric?.generationTokenOverhead, metric?.outputExpansionRate)
421+
return finiteNumber(metric?.outputExpansionRate)
422422
}
423423

424424
function safeRate(numerator?: number, denominator?: number) {

frontend/src/presentation/tokenRatios.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ export interface TokenRatioInput {
44
outputTokens?: number | null
55
reasoningOutputTokens?: number | null
66
reasoningOverheadRate?: number | null
7-
reasoningTokenOverhead?: number | null
8-
reasoningOutputShare?: number | null
97
}
108

119
export interface TokenRatioShares {
@@ -21,12 +19,7 @@ export function tokenRatioShares(input: TokenRatioInput): TokenRatioShares {
2119
const outputTokens = positiveNumber(input.outputTokens)
2220
const reasoningOutputTokens = positiveNumber(input.reasoningOutputTokens)
2321
const mainTotal = inputTokens + outputTokens
24-
const reasoningOutput = firstRatio(
25-
input.reasoningOverheadRate,
26-
input.reasoningTokenOverhead,
27-
input.reasoningOutputShare,
28-
outputTokens > 0 ? reasoningOutputTokens / outputTokens : undefined
29-
)
22+
const reasoningOutput = firstRatio(input.reasoningOverheadRate, outputTokens > 0 ? reasoningOutputTokens / outputTokens : undefined)
3023

3124
return {
3225
input: mainTotal > 0 ? inputTokens / mainTotal : 0,

frontend/src/views/ModelSignals.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -813,12 +813,12 @@ onMounted(load)
813813
<template v-else-if="column.key === 'signal'">
814814
<ReasonTags
815815
:reasons="record.reasons"
816-
:color="record.severity === 'high' ? 'warning' : 'processing'"
816+
:color="record.failedToolCalls > 0 || record.score >= 0.45 ? 'warning' : 'processing'"
817817
:empty-text="t('fallback.noReason')"
818818
/>
819819
</template>
820820
<template v-else-if="column.key === 'outputExpansion'"><span class="number-cell">{{ formatPercent(record.outputExpansionRate) }}</span></template>
821-
<template v-else-if="column.key === 'reasoning'"><span class="number-cell">{{ formatPercent(record.reasoningTokenShare) }}</span></template>
821+
<template v-else-if="column.key === 'reasoning'"><span class="number-cell">{{ formatPercent(record.reasoningOverheadRate) }}</span></template>
822822
<template v-else-if="column.key === 'cacheMiss'"><span class="number-cell">{{ formatPercent(record.cacheMissRate) }}</span></template>
823823
<template v-else-if="column.key === 'failedTools'">
824824
<span class="number-cell" :class="{ 'status-error': record.failedToolCalls > 0 }">{{ formatNumber(record.failedToolCalls) }}</span>

frontend/src/views/model-signals/columns.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export function buildAnomalyColumns(t: ModelSignalsTranslate) {
9090
{ title: t('column.model'), dataIndex: 'model', key: 'model', width: 170 },
9191
{ title: t('column.signal'), dataIndex: 'reasons', key: 'signal', width: 260 },
9292
{ title: t('column.outputExpansion'), dataIndex: 'outputExpansionRate', key: 'outputExpansion', width: 112, align: 'right' },
93-
{ title: t('column.reasoning'), dataIndex: 'reasoningTokenShare', key: 'reasoning', width: 100, align: 'right' },
93+
{ title: t('column.reasoning'), dataIndex: 'reasoningOverheadRate', key: 'reasoning', width: 100, align: 'right' },
9494
{ title: t('column.cacheMiss'), dataIndex: 'cacheMissRate', key: 'cacheMiss', width: 120, align: 'right' },
9595
{ title: t('column.failedTools'), dataIndex: 'failedToolCalls', key: 'failedTools', width: 90, align: 'right' },
9696
{ title: t('column.throughput'), dataIndex: 'modelThroughputTokensPerSecond', key: 'throughput', width: 100, align: 'right' },

frontend/src/views/model-signals/display.ts

Lines changed: 25 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ import {
1313
type ModelSignals,
1414
type ModelSignalsHealthSummary,
1515
type ModelSignalsProjectMetric,
16-
type ModelSignalsWindow,
17-
type Session
16+
type ModelSignalsWindow
1817
} from '../../api'
1918
import { sourceDisplay } from '../../presentation/sourceIdentity'
2019
import {
@@ -190,7 +189,7 @@ export function createModelSignalsDisplay(t: ModelSignalsTranslate) {
190189
}
191190

192191
function anomalyRowClass(record: NormalizedAnomalySession) {
193-
return { class: record.failedToolCalls > 0 || record.severity === 'high' ? 'model-signals-warning-row' : '' }
192+
return { class: record.failedToolCalls > 0 || record.score >= 0.45 ? 'model-signals-warning-row' : '' }
194193
}
195194

196195
function reasonText(row: string): string {
@@ -269,59 +268,32 @@ export function createModelSignalsDisplay(t: ModelSignalsTranslate) {
269268
return record.projectPath || `${record.modelCount}:${record.sourceCount}:${record.totalTokens}`
270269
}
271270

272-
function anomalyReasons(row: ModelSignalAnomalySession): string[] {
273-
const candidates = [row.reasons, row.reasonLabels, row.signalReasons, row.reason, row.signal]
274-
const values = candidates.flatMap((value) => {
275-
if (Array.isArray(value)) return value
276-
if (typeof value === 'string') return [value]
277-
return []
278-
})
279-
return [...new Set(values.map((value) => value.trim()).filter(Boolean))]
280-
}
281-
282271
function normalizeAnomaly(row: ModelSignalAnomalySession): NormalizedAnomalySession {
283-
const session = row.session || ({} as Partial<Session>)
284272
return {
285-
id: numberField(row, ['id', 'sessionId']) || session.id || 0,
286-
sessionKey: stringField(row, ['sessionKey']) || session.sessionKey,
287-
codexSessionId: stringField(row, ['codexSessionId']) || session.codexSessionId,
288-
startedAt: stringField(row, ['startedAt']) || session.startedAt,
289-
projectPath: stringField(row, ['projectPath']) || session.projectPath,
290-
rawSourcePath: stringField(row, ['rawSourcePath']) || session.rawSourcePath,
291-
agentKind: stringField(row, ['agentKind']) || session.agentKind,
292-
agentName: stringField(row, ['agentName']) || session.agentName,
293-
sourceId: numberField(row, ['sourceId']) || session.sourceId,
294-
sourceKey: stringField(row, ['sourceKey']) || session.sourceKey,
295-
sourceLabel: stringField(row, ['sourceLabel']) || session.sourceLabel,
296-
sourceRootPath: stringField(row, ['sourceRootPath']) || session.sourceRootPath,
297-
sourceSessionsPath: stringField(row, ['sourceSessionsPath']) || session.sourceSessionsPath,
298-
model: stringField(row, ['model']) || session.model,
299-
totalTokens: numberField(row, ['totalTokens']) || session.tokenUsage?.totalTokens || 0,
300-
outputExpansionRate: numberField(row, ['generationTokenOverhead', 'outputExpansionRate']),
301-
reasoningTokenShare: numberField(row, ['reasoningOverheadRate', 'reasoningTokenOverhead', 'reasoningOutputShare', 'reasoningTokenShare']),
302-
cacheMissRate: numberField(row, ['cacheMissRate']),
303-
modelThroughputTokensPerSecond: numberField(row, ['modelThroughputTokensPerSecond']),
304-
failedToolCalls: numberField(row, ['failedToolCalls']),
305-
modelDurationMs: numberField(row, ['modelDurationMs']) || session.modelDurationMs || 0,
306-
severity: stringField(row, ['severity']),
307-
reasons: anomalyReasons(row)
308-
}
309-
}
310-
311-
function stringField(row: ModelSignalAnomalySession, keys: string[]) {
312-
for (const key of keys) {
313-
const value = row[key]
314-
if (typeof value === 'string' && value.trim()) return value.trim()
273+
id: row.sessionId,
274+
sessionKey: row.sessionKey,
275+
codexSessionId: row.codexSessionId,
276+
startedAt: row.startedAt,
277+
projectPath: row.projectPath,
278+
rawSourcePath: row.rawSourcePath,
279+
agentKind: row.agentKind,
280+
agentName: row.agentName,
281+
sourceId: row.sourceId,
282+
sourceKey: row.sourceKey,
283+
sourceLabel: row.sourceLabel,
284+
sourceRootPath: row.sourceRootPath,
285+
sourceSessionsPath: row.sourceSessionsPath,
286+
model: row.model,
287+
totalTokens: row.totalTokens,
288+
outputExpansionRate: row.outputExpansionRate,
289+
reasoningOverheadRate: row.reasoningOverheadRate,
290+
cacheMissRate: row.cacheMissRate,
291+
modelThroughputTokensPerSecond: row.modelThroughputTokensPerSecond,
292+
failedToolCalls: row.failedToolCalls,
293+
modelDurationMs: row.modelDurationMs,
294+
score: row.score,
295+
reasons: row.reasons
315296
}
316-
return undefined
317-
}
318-
319-
function numberField(row: ModelSignalAnomalySession, keys: string[]) {
320-
for (const key of keys) {
321-
const value = row[key]
322-
if (typeof value === 'number' && Number.isFinite(value)) return value
323-
}
324-
return 0
325297
}
326298

327299
function sessionInfo(record: NormalizedAnomalySession) {

0 commit comments

Comments
 (0)