Paginate Traces tab with step filter#560
Conversation
Adds server-side step + limit/offset to get_traces, a new get_trace_steps endpoint that returns per-step counts, and reworks the Traces tab to load one page at a time with a step selector + prev/next navigation. Also extends trackio.log() to accept a list of Trace objects so a single step can carry a batch of rollouts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
🪼 branch checks and previews
|
🦄 change detectedThis Pull Request includes changes to the following packages.
|
🪼 branch checks and previews
Install Trackio from this PR (includes built frontend) pip install "https://huggingface.co/buckets/trackio/trackio-wheels/resolve/e743a4b87070762366277953e5c202948bf1cdb6/trackio-0.25.1-py3-none-any.whl" |
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
There was a problem hiding this comment.
Pull request overview
This PR extends Trackio’s trace viewer to handle large trace volumes more efficiently by adding server-side pagination and step-based filtering, plus support for logging batched traces at a single step.
Changes:
- Add
step+limit/offsetsupport to the traces backend API, and introduce aget_trace_stepsendpoint for per-step counts. - Update the Traces UI to include a step dropdown and 50-per-page pagination while preserving search/sort/expandable rows.
- Allow
trackio.log()to acceptlist[Trace]values, and add an example script to generate large batched trace runs.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| trackio/sqlite_storage.py | Adds step filtering for get_traces and implements get_trace_steps aggregation. |
| trackio/server.py | Exposes step in get_traces and registers new get_trace_steps API. |
| trackio/run.py | Converts list[Trace] metric values into serializable dicts and queues media uploads. |
| trackio/frontend/src/pages/Traces.svelte | Adds step filter UI, server pagination wiring, and pagination controls. |
| trackio/frontend/src/lib/staticApi.js | Adds static-mode getTraceSteps and step filtering for traces. |
| trackio/frontend/src/lib/api.js | Adds getTraceSteps client API wrapper (static + server modes). |
| examples/traces/large-trace-batch.py | Adds a performance/stress test example generating large batched traces. |
| .changeset/young-views-do.md | Declares a minor release changeset for the feature. |
Comments suppressed due to low confidence (3)
trackio/frontend/src/pages/Traces.svelte:273
activeTotalmultiplies the already-merged per-step count byselectedRuns.length, which overstates totals when multiple runs are selected (and inflatestotalPages). SinceloadSummary()sums counts across runs intoavailableSteps, the multiplier should be removed so totals/pagination reflect the real trace count.
let activeTotal = $derived.by(() => {
if (stepFilter === "all") return totalCount;
const stepNum = Number(stepFilter);
const entry = availableSteps.find((s) => s.step === stepNum);
return entry ? entry.count * Math.max(1, selectedRuns.length) : 0;
});
trackio/frontend/src/pages/Traces.svelte:167
- The summary refresh/reset logic only keys off
selectedRuns(runsKey(selectedRuns)), so switchingprojectwhile keeping the same run selection can leaveavailableSteps/totalCountstale and pagination state inconsistent. Includeprojectin the key (or tracklastProject) soloadSummary()runs and paging/filter state resets on project changes.
$effect(() => {
project;
const key = runsKey(selectedRuns);
if (key !== lastRunsKey) {
lastRunsKey = key;
page = 0;
stepFilter = "all";
loadSummary();
}
trackio/frontend/src/pages/Traces.svelte:183
- Pagination (
totalPages) is derived fromtotalCount/availableSteps, but those totals ignore the currentsearchfilter. With a non-empty search, the UI can show many pages and allow navigating to pages that will be empty even though there are no more matches. Consider returning a filtered total from the backend (e.g., add a count field/endpoint that takessearch+step), or change the Next/total-pages logic to be driven by whether the current fetch returns a full page of results.
$effect(() => {
const trimmed = search.trim();
if (trimmed !== lastSearch || sortBy !== lastSort || stepFilter !== lastStep) {
if (lastSearch !== "" || trimmed !== "" || sortBy !== lastSort || stepFilter !== lastStep) {
page = 0;
}
lastSearch = trimmed;
lastSort = sortBy;
lastStep = stepFilter;
}
const timeout = setTimeout(() => {
loadTraces(trimmed, sortBy, stepFilter, page);
}, 150);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const stepNum = stepValue === "all" ? null : Number(stepValue); | ||
| const offset = currentPage * PAGE_SIZE; | ||
| const isSingleRun = selectedRuns.length === 1; | ||
| const perRunLimit = isSingleRun ? PAGE_SIZE : offset + PAGE_SIZE; | ||
| const perRunOffset = isSingleRun ? offset : 0; | ||
|
|
||
| const batches = await Promise.all( | ||
| selectedRuns.map(async (run) => { | ||
| const runTraces = await getTraces(project, run, { | ||
| search: searchQuery, | ||
| sort, | ||
| step: stepNum, | ||
| limit: perRunLimit, | ||
| offset: perRunOffset, | ||
| }); |
| "trackio": minor | ||
| --- | ||
|
|
||
| feat:Paginate Traces tab with step filter |
|
Thanks @Saba9! |
Continuing the work that @edbeeching started in #540, this PR implements:
get_tracesgainsstep+limit/offset; newget_trace_stepsreturns per-step counts.Traces.svelteadds a step dropdown and 50/page prev/next; existing search, sort, and expandable rows are preserved.trackio.log()acceptslist[Trace]so one step can carry a batch of rollouts.examples/traces/large-trace-batch.pylogs 200 steps × 40 traces (8000 total) for performance testing.