Skip to content

Commit 541bd33

Browse files
committed
fix(inference): resolve scenario run state at the provider
Filter workflow runs by model, scenario, and precision before global run auto-selection or URL synchronization. Invalid scenario dates and run IDs now resolve synchronously, so July 4 single-turn state cannot leak into Agentic Traces. Remove the downstream fallback-heavy filtering and add focused date and run-coverage regressions. 中文:在全局运行自动选择和 URL 同步之前,按模型、场景和精度过滤 workflow runs。无效的场景日期与运行 ID 现在会同步纠正,避免 7 月 4 日的 single-turn 状态泄漏到 Agentic Traces;同时移除下游复杂回退逻辑并补充定向回归测试。
1 parent 3ad2d0c commit 541bd33

11 files changed

Lines changed: 145 additions & 282 deletions

File tree

packages/app/cypress/support/mock-data.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,6 @@ export function createMockGlobalFilterContext(
447447
availabilityRows: undefined,
448448
workflowInfo: null,
449449
availableRuns: {},
450-
runConfigs: [],
451450
workflowLoading: false,
452451
workflowError: null,
453452
...overrides,

packages/app/src/components/GlobalFilterContext.tsx

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ import {
3636
import { computeAutoSwitchDecision } from '@/lib/unofficial-run-auto-switch';
3737
import { countCurvesByPrecision, resolveEffectivePrecisions } from '@/lib/default-precisions';
3838
import { resolveEffectiveSequence } from '@/lib/default-sequence';
39-
import type { AvailabilityRow, RunConfigRow, WorkflowInfoResponse } from '@/lib/api';
39+
import { scenarioRunIdsForDate } from '@/lib/run-configs';
40+
import { resolveRunDate } from '@/lib/run-date';
41+
import type { AvailabilityRow, WorkflowInfoResponse } from '@/lib/api';
4042

4143
const RUNDATE_RE = /^\d{4}-\d{2}-\d{2}$/u;
4244
const RUNID_RE = /^[A-Za-z0-9_-]{1,64}$/u;
@@ -107,14 +109,6 @@ export interface GlobalFilterContextType {
107109
// Workflow info
108110
workflowInfo: { runInfoBySequence: Record<string, RunInfo> }[] | null;
109111
availableRuns: Record<string, RunInfo>;
110-
/**
111-
* Per-(run, config) coverage for the current date — which workflow runs
112-
* produced benchmark data for which model / scenario / precision. Data-driven
113-
* (from the benchmark rows), so it enumerates every run on the date including
114-
* ones without a changelog entry. The run picker uses this to scope its list
115-
* to the selected model + scenario (see `scenarioRunIdsForDate`).
116-
*/
117-
runConfigs: RunConfigRow[];
118112
workflowLoading: boolean;
119113
workflowError: string | null;
120114
}
@@ -419,14 +413,10 @@ export function GlobalFilterProvider({
419413
setSelectedRunDateRev((v) => v + 1);
420414
}, []);
421415

422-
const effectiveRunDate = useMemo(() => {
423-
if (availableDates.length === 0) return selectedRunDate;
424-
const latest = availableDates.at(-1)!;
425-
if (userPickedDateRef.current && selectedRunDate && availableDates.includes(selectedRunDate)) {
426-
return selectedRunDate;
427-
}
428-
return latest;
429-
}, [availableDates, selectedRunDate]);
416+
const effectiveRunDate = useMemo(
417+
() => resolveRunDate(availableDates, selectedRunDate, userPickedDateRef.current),
418+
[availableDates, selectedRunDate],
419+
);
430420

431421
// Sync selectedRunDate state when effectiveRunDate changes
432422
useEffect(() => {
@@ -445,18 +435,33 @@ export function GlobalFilterProvider({
445435

446436
const workflowError = workflowQueryError ? workflowQueryError.message : null;
447437

448-
const availableRuns = useMemo(
449-
() => (workflowData ? buildRunInfo(workflowData) : {}),
450-
[workflowData],
451-
);
438+
const dateRuns = useMemo(() => (workflowData ? buildRunInfo(workflowData) : {}), [workflowData]);
452439

453440
const runConfigs = useMemo(() => workflowData?.runConfigs ?? [], [workflowData]);
454441

442+
// Keep run selection and g_runid scenario-safe at their source. Filtering
443+
// later in InferenceContext leaves the global single-turn run ID alive.
444+
const availableRuns = useMemo(() => {
445+
const runIds = scenarioRunIdsForDate(
446+
runConfigs,
447+
dbModelKeys,
448+
effectiveSequence,
449+
effectivePrecisions,
450+
);
451+
return Object.fromEntries(Object.entries(dateRuns).filter(([runId]) => runIds.has(runId)));
452+
}, [dateRuns, runConfigs, dbModelKeys, effectiveSequence, effectivePrecisions]);
453+
455454
const workflowInfo = useMemo(
456455
() => (Object.keys(availableRuns).length > 0 ? [{ runInfoBySequence: availableRuns }] : null),
457456
[availableRuns],
458457
);
459458

459+
const effectiveSelectedRunId = useMemo(() => {
460+
const runIds = Object.keys(availableRuns);
461+
if (runIds.includes(selectedRunId)) return selectedRunId;
462+
return runIds.reduce((latest, runId) => (runId > latest ? runId : latest), '');
463+
}, [availableRuns, selectedRunId]);
464+
460465
// Auto-select latest run ID when availableRuns change
461466
const urlInitRef = useRef({ runIdApplied: false });
462467

@@ -494,8 +499,10 @@ export function GlobalFilterProvider({
494499
}
495500
setUrlParams({
496501
g_model: selectedModel,
497-
g_rundate: selectedRunDate,
498-
g_runid: selectedRunId,
502+
// Never write a stale URL date after model/scenario availability has
503+
// already resolved it to a valid date (e.g. Agentic + 2026-07-04).
504+
g_rundate: effectiveRunDate,
505+
g_runid: effectiveSelectedRunId,
499506
// Don't pin the sequence to the URL until it's resolved from real
500507
// availability — writing the pre-load placeholder (8k/1k) would clobber a
501508
// shared `?i_seq=agentic-traces` link before the model's availability
@@ -507,8 +514,8 @@ export function GlobalFilterProvider({
507514
});
508515
}, [
509516
selectedModel,
510-
selectedRunDate,
511-
selectedRunId,
517+
effectiveRunDate,
518+
effectiveSelectedRunId,
512519
effectiveSequence,
513520
sequenceResolved,
514521
effectivePrecisions,
@@ -530,7 +537,7 @@ export function GlobalFilterProvider({
530537
selectedRunDate: effectiveRunDate,
531538
setSelectedRunDate: setSelectedRunDateManual,
532539
selectedRunDateRev,
533-
selectedRunId,
540+
selectedRunId: effectiveSelectedRunId,
534541
setSelectedRunId,
535542
availableModels,
536543
availableSequences,
@@ -540,7 +547,6 @@ export function GlobalFilterProvider({
540547
availabilityRows,
541548
workflowInfo,
542549
availableRuns,
543-
runConfigs,
544550
workflowLoading,
545551
workflowError,
546552
}),
@@ -554,15 +560,14 @@ export function GlobalFilterProvider({
554560
effectiveRunDate,
555561
setSelectedRunDateManual,
556562
selectedRunDateRev,
557-
selectedRunId,
563+
effectiveSelectedRunId,
558564
availableModels,
559565
availableSequences,
560566
availablePrecisions,
561567
availableDates,
562568
availabilityRows,
563569
workflowInfo,
564570
availableRuns,
565-
runConfigs,
566571
workflowLoading,
567572
workflowError,
568573
],

packages/app/src/components/inference/InferenceContext.tsx

Lines changed: 8 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ import {
6363
resolveExclusionToggle,
6464
type ExclusionConflictPolicy,
6565
} from '@/lib/exclusion';
66-
import { filterRunsByModel, getDisplayLabel, restrictRunsToScenario } from '@/lib/utils';
66+
import { filterRunsByModel, getDisplayLabel } from '@/lib/utils';
6767

6868
import {
6969
isAgenticOnlyXAxisMode,
@@ -72,7 +72,6 @@ import {
7272
type XAxisMode,
7373
} from './hooks/useChartData';
7474
import { resolveComparisonEntries } from './utils/comparisonEntry';
75-
import { scenarioRunIdsForDate } from './utils/runEnumeration';
7675
import { resolveLabelState, serializeLabelState } from './utils/label-defaults';
7776
import {
7877
EMPTY_QUICK_FILTERS,
@@ -136,7 +135,6 @@ export function InferenceProvider({
136135
availabilityRows,
137136
workflowInfo,
138137
availableRuns,
139-
runConfigs,
140138
workflowError,
141139
} = useGlobalFilters();
142140

@@ -370,50 +368,13 @@ export function InferenceProvider({
370368
[selectedModel],
371369
);
372370

373-
// DB model keys for the selected display model (e.g. ['dsv4']). Used both by
374-
// the scenario run scoping below and the GPU comparison date picker further down.
375-
const dbModelKeys = useMemo<string[]>(
376-
() => DISPLAY_MODEL_TO_DB[selectedModel] ?? [selectedModel],
377-
[selectedModel],
378-
);
379-
380-
const changelogFilteredRuns = useMemo(
371+
const filteredAvailableRuns = useMemo(
381372
() => filterRunsByModel(availableRuns, modelPrefixes, [...effectivePrecisions]),
382373
[availableRuns, modelPrefixes, effectivePrecisions],
383374
);
384375

385-
// Run ids that actually produced data for the selected model + scenario on the
386-
// date, derived from per-(run, config) coverage (independent of the chart's
387-
// "as of run" query, so no circular dependency). `/api/v1/workflow-info`
388-
// returns every run on a date across all models/scenarios, so without this a
389-
// same-day run for a different scenario (e.g. a single_turn sweep while viewing
390-
// Agentic Traces) would leak into the picker and poison the "as of run" cutoff.
391-
const scenarioRunIds = useMemo(
392-
() =>
393-
scenarioRunIdsForDate(runConfigs, dbModelKeys, effectiveSequence, [...effectivePrecisions]),
394-
[runConfigs, dbModelKeys, effectiveSequence, effectivePrecisions],
395-
);
396-
397-
// The picker list: the changelog/precision-scoped runs, further restricted to
398-
// the ones that shipped data for the selected scenario. When the date has
399-
// coverage rows but none for this scenario (e.g. only single_turn sweeps ran
400-
// that day while viewing Agentic Traces) the picker hides (null) rather than
401-
// listing other-scenario runs; only when coverage data is unavailable
402-
// altogether (old dates / JSON snapshots) does it fall back to the
403-
// changelog-scoped list so the selector still renders.
404-
const filteredAvailableRuns = useMemo(
405-
() =>
406-
restrictRunsToScenario(
407-
changelogFilteredRuns,
408-
availableRuns,
409-
scenarioRunIds,
410-
runConfigs.length > 0,
411-
),
412-
[changelogFilteredRuns, availableRuns, scenarioRunIds, runConfigs],
413-
);
414-
415376
const effectiveSelectedRunId = useMemo(() => {
416-
if (!filteredAvailableRuns) return selectedRunId;
377+
if (!filteredAvailableRuns) return '';
417378
const filteredRunIds = Object.keys(filteredAvailableRuns);
418379
if (filteredRunIds.length === 0 || filteredRunIds.includes(selectedRunId)) return selectedRunId;
419380
return filteredRunIds.reduce((max, id) => (id > max ? id : max), filteredRunIds[0]);
@@ -523,6 +484,11 @@ export function InferenceProvider({
523484
);
524485

525486
// For GPU comparison date picker — use shared availability data from global filters
487+
const dbModelKeys = useMemo<string[]>(
488+
() => DISPLAY_MODEL_TO_DB[selectedModel] ?? [selectedModel],
489+
[selectedModel],
490+
);
491+
526492
const dateRangeAvailableDates = useMemo(() => {
527493
if (selectedGPUs.length === 0) return availableDates;
528494
if (!availabilityRows) return availableDates;

packages/app/src/components/inference/utils/runEnumeration.test.ts

Lines changed: 1 addition & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest';
22

33
import type { RunConfigRow } from '@/lib/api';
44

5-
import { dataRunsForDate, scenarioRunIdsForDate } from './runEnumeration';
5+
import { dataRunsForDate } from './runEnumeration';
66

77
function rc(over: Partial<RunConfigRow>): RunConfigRow {
88
return {
@@ -24,16 +24,6 @@ function rc(over: Partial<RunConfigRow>): RunConfigRow {
2424
};
2525
}
2626

27-
/** A single_turn (fixed-seq) run config for the given isl/osl. */
28-
function single(over: Partial<RunConfigRow>): RunConfigRow {
29-
return rc({ benchmark_type: 'single_turn', isl: 8192, osl: 1024, ...over });
30-
}
31-
32-
/** An agentic_traces run config (null isl/osl, as ingested for agentic rows). */
33-
function agentic(over: Partial<RunConfigRow>): RunConfigRow {
34-
return rc({ benchmark_type: 'agentic_traces', isl: null, osl: null, ...over });
35-
}
36-
3727
const SCOPE = {
3828
modelDbKeys: ['minimaxm3'],
3929
selectedGPUs: ['mi300x_vllm'],
@@ -121,66 +111,3 @@ describe('dataRunsForDate', () => {
121111
expect(dataRunsForDate([rc({ model: 'dsr1' })], SCOPE)).toEqual([]);
122112
});
123113
});
124-
125-
// Mirrors the repro: on one date the same model has a single_turn run and an
126-
// agentic run; each scenario must list ONLY its own run.
127-
const ids = (rows: RunConfigRow[], model: string[], seq: string, prec: string[] = []) =>
128-
[...scenarioRunIdsForDate(rows, model, seq, prec)].toSorted();
129-
130-
describe('scenarioRunIdsForDate', () => {
131-
it('agentic scenario excludes runs that only produced single_turn data', () => {
132-
const rows = [
133-
agentic({ github_run_id: 28955639528, model: 'dsv4' }), // dsv4 agentic
134-
single({ github_run_id: 28900000001, model: 'dsv4' }), // dsv4 single_turn (leaks today)
135-
single({ github_run_id: 28900000002, model: 'glm5' }), // other model
136-
];
137-
expect(ids(rows, ['dsv4'], 'agentic-traces')).toEqual(['28955639528']);
138-
});
139-
140-
it('single_turn scenario excludes agentic-only runs', () => {
141-
const rows = [
142-
agentic({ github_run_id: 1, model: 'dsv4' }),
143-
single({ github_run_id: 2, model: 'dsv4' }),
144-
];
145-
expect(ids(rows, ['dsv4'], '8k/1k')).toEqual(['2']);
146-
});
147-
148-
it('lists a run that produced data for both scenarios under each scenario', () => {
149-
const rows = [
150-
agentic({ github_run_id: 42, model: 'dsv4' }),
151-
single({ github_run_id: 42, model: 'dsv4' }), // same run, both types
152-
];
153-
expect(ids(rows, ['dsv4'], 'agentic-traces')).toEqual(['42']);
154-
expect(ids(rows, ['dsv4'], '8k/1k')).toEqual(['42']);
155-
});
156-
157-
it('scopes to the selected model DB keys', () => {
158-
const rows = [
159-
agentic({ github_run_id: 1, model: 'dsv4' }),
160-
agentic({ github_run_id: 2, model: 'glm5' }),
161-
];
162-
expect(ids(rows, ['dsv4'], 'agentic-traces')).toEqual(['1']);
163-
});
164-
165-
it('respects precision scoping when precisions are provided', () => {
166-
const rows = [
167-
agentic({ github_run_id: 1, model: 'dsv4', precision: 'fp4' }),
168-
agentic({ github_run_id: 2, model: 'dsv4', precision: 'fp8' }),
169-
];
170-
expect(ids(rows, ['dsv4'], 'agentic-traces', ['fp4'])).toEqual(['1']);
171-
// Empty precisions = no precision constraint.
172-
expect(ids(rows, ['dsv4'], 'agentic-traces')).toEqual(['1', '2']);
173-
});
174-
175-
it('dedupes a run appearing across multiple matching configs', () => {
176-
const rows = [
177-
agentic({ github_run_id: 7, model: 'dsv4', hardware: 'b200' }),
178-
agentic({ github_run_id: 7, model: 'dsv4', hardware: 'gb200' }),
179-
];
180-
expect(ids(rows, ['dsv4'], 'agentic-traces')).toEqual(['7']);
181-
});
182-
183-
it('returns an empty set when there is no coverage data', () => {
184-
expect(scenarioRunIdsForDate([], ['dsv4'], 'agentic-traces').size).toBe(0);
185-
});
186-
});

packages/app/src/components/inference/utils/runEnumeration.ts

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
* chart keys them.
1616
*/
1717

18-
import { rowToSequence } from '@semianalysisai/inferencex-constants';
19-
2018
import type { AggDataEntry } from '@/components/inference/types';
2119
import type { RunConfigRow } from '@/lib/api';
2220
import { getHardwareKey } from '@/lib/chart-utils';
@@ -80,38 +78,3 @@ export function dataRunsForDate(runConfigs: RunConfigRow[], scope: RunScope): Da
8078

8179
return [...byRun.values()].toSorted((a, b) => a.runStartedAt.localeCompare(b.runStartedAt));
8280
}
83-
84-
/**
85-
* GitHub run ids (as strings) that produced benchmark data for the given model
86-
* DB keys + scenario (sequence) — and, when `precisions` is non-empty, only for
87-
* those precisions — on a date. Derived from per-(run, config) coverage
88-
* ({@link RunConfigRow}, i.e. the benchmark rows themselves), so a run that
89-
* shipped data without a changelog entry still counts.
90-
*
91-
* The run picker uses this to list ONLY the runs that actually produced data for
92-
* the currently selected model + scenario. `/api/v1/workflow-info` returns every
93-
* run on a date across all models and scenarios, so without this scoping a
94-
* same-day run for a different scenario (e.g. a `single_turn` sweep while the
95-
* user is viewing Agentic Traces) leaks into the picker — and selecting it
96-
* poisons the "as of run" cutoff, blanking the chart.
97-
*
98-
* Scenario matching mirrors the chart's own row filter (`rowToSequence(row) ===
99-
* selectedSequence` in useChartData), so the picker and the plotted data always
100-
* agree on which runs are relevant.
101-
*/
102-
export function scenarioRunIdsForDate(
103-
runConfigs: RunConfigRow[],
104-
modelDbKeys: string[],
105-
sequence: string,
106-
precisions: string[] = [],
107-
): Set<string> {
108-
const precSet = precisions.length > 0 ? new Set(precisions) : null;
109-
const ids = new Set<string>();
110-
for (const rc of runConfigs) {
111-
if (!modelDbKeys.includes(rc.model)) continue;
112-
if (precSet && !precSet.has(rc.precision)) continue;
113-
if (rowToSequence(rc) !== sequence) continue;
114-
ids.add(String(rc.github_run_id));
115-
}
116-
return ids;
117-
}

0 commit comments

Comments
 (0)