Skip to content

feat: write online scores idempotently and keep them past retention - #4

Merged
RobertCrupa merged 5 commits into
mainfrom
feat/online-evals-score-writing
Oct 9, 2026
Merged

RobertCrupa merged 5 commits into
mainfrom
feat/online-evals-score-writing

Conversation

@RobertCrupa

Copy link
Copy Markdown
Contributor

Closes apify/ai-team#270. Part of apify/ai-team#249, stacked on the #269 PR.

  • `online-scores.ts`: each verdict is written twice, to the trace (id `--p-i<JUDGE_IMPL_VERSION>-`) and to an invented dataset run `apify-ai-online-YYYY-MM-DD` (id suffixed `-run`, no dataset, no items; reads back with the `experimentId` filter). BOOLEAN, source defaults to API.
  • Idempotency: already-judged traces (same prompt version, impl version and model in the holistic score's metadata) are filtered out BEFORE judging; `force` bypasses. The holistic trace copy is written last so a half-written trace is retried.
  • Daily rollup as dataset item `rollup-YYYY-MM-DD` in `apify-ai-online-rollups` (pass rate and n per criterion, sample rate, coverage counters), keyed on the window start day.
  • The checkpoint is now written only after scores and rollup succeed. All writes failed, or all judgements failed, holds the window and fails the run.

Every Langfuse API assumption was verified live on 2026-09-09 with throwaway objects (score create and same-day replace, the run subject without a run, exactly-one-subject, EVAL rejected, datasets and items in events_only mode, v3 read with a traceId list). One inference remains and is marked as such: metrics-API aggregation of run-attached scores. No public API deletes a dataset, so the rollup dataset name is permanent once created.

@RobertCrupa
RobertCrupa added this pull request to stack #6 September 9, 2026 15:14
@RobertCrupa
RobertCrupa force-pushed the feat/online-evals-score-writing branch from 4a4cefb to 77f215f Compare October 7, 2026 09:00
@RobertCrupa
RobertCrupa removed this pull request from stack #6 October 7, 2026 09:08
@RobertCrupa
RobertCrupa added this pull request to stack #26 October 7, 2026 09:08

@vojtechj-apify vojtechj-apify left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two-copy design and the idempotent ids work well, and verifying every API assumption live shows. I'd fix the first three inline comments before the schedule goes live: environment isolation, the mostly-failed batch, and writing per trace.

Question: the rollup dataset name is permanent once it's created, since there's no public delete. Can we explicitly agree on apify-ai-online-rollups before the first prod run?

Comment thread actors/judge/src/online-scores.ts
Comment thread actors/judge/src/online-scores.ts
Comment thread actors/judge/src/main.ts
Comment thread actors/judge/src/online-scores.ts
Comment thread actors/judge/src/main.ts
Comment thread actors/judge/src/online-scores.ts
@RobertCrupa
RobertCrupa force-pushed the feat/online-evals-score-writing branch 2 times, most recently from 32eff63 to 21088f7 Compare October 8, 2026 13:27
Base automatically changed from feat/online-evals-scoring to main October 8, 2026 13:31
…ai-team#270)

Every verdict from the online judge is written to Langfuse twice: once on
the trace, once under an invented datasetRunId apify-ai-online-YYYY-MM-DD so
the score survives the 30-day trace sweep. Score ids are readable,
<traceId>-<scoreName>-p<promptVersion>-i<JUDGE_IMPL_VERSION>-<judgeModel>,
with a -run suffix on the archival copy; a bumped version writes beside the
old score. Sampled traces already judged under the same version are dropped
before judging, so a re-run spends nothing on the LLM.

A daily rollup (pass rate and n per score, coverage counters) is upserted as
dataset item rollup-YYYY-MM-DD in apify-ai-online-rollups, merging runs of
the same day. The checkpoint write moves out of selectTraces (writeCheckpoint
option) and happens only after the scores and the rollup are written; a
rollup failure fails the run and leaves the window to be retried.

OUTPUT gains scoresSkipped, scoresWritten, failedToWrite and rollupItemId.
…the window day (ai-team#270 review)

A batch whose writes all failed was reported as success: the rollup came from
every judged verdict, the checkpoint advanced and the run exited 0, so a
Langfuse outage silently lost the window and its LLM spend. writeOnlineScores
now returns the verdicts that were written, only those feed the rollup, the
rollup coverage gains scoresWritten and failedToWrite, and an all-failed batch
skips the rollup, keeps the checkpoint and exits non-zero.

Run ids and rollup items are keyed on the UTC date of the window start, not
the write, so a backfill lands on its own day; the daily run therefore labels
D-1 when it writes at ~06:0x on D (README says so for the #271 monitor). The
rollup is skipped when nothing was sampled or judged. README and comments now
describe the ReplacingMergeTree semantics (same-day rewrite replaces the row,
cross-day retry of a half-written trace leaves two rows) and the retry double
count of the window counters. A round-trip test pins writer and reader to
each other.
… a judge failure (ai-team#270 round 3)

A live verification against langfuse.apify.dev (2026-09-09) confirmed every
API assumption. datasets.create is idempotent by name, so ensureRollupDataset
calls it unconditionally; the events_only hedges become "verified live"; the
README records the experimentId read filter and the experiment subject kind,
that ANNOTATION needs a configId (API is the only usable source), that BOOLEAN
scores read back as JSON booleans in environment default, and that no public
API deletes a dataset (ROLLUP_DATASET_NAME is permanent; pinned by a test).

isNotFound stays on statusCode, now pinned by a test that constructs the
client's real NotFoundError (@langfuse/core added as a devDependency for it).
finishOnlineRun gains the judge-side twin guard: when every trace given to the
judge failed to judge, no rollup, no checkpoint, AllJudgementsFailedError and
a non-zero exit. Window override docs say the ISO 8601 value needs an offset.
…cs claim (ai-team#270 follow-ups)

The all-judgements-failed guard is judged === 0 && failedToJudge > 0, which
is what sampled - scoresSkipped > 0 && judged === 0 meant, so the extra
scoresSkipped option on finishOnlineRun goes. The README no longer claims the
metrics aggregation of the run copy was verified live: read-back via
experimentId and the subject kind were, aggregation under an invented run is
the one open inference, and the working endpoint in events_only mode is
GET /api/public/v2/metrics. The two wordings for the held checkpoint are one.
main bumped JUDGE_IMPL_VERSION to 0.4.0 in #13. The score-id test and the
README example still used 0.1.0. The old stack made the same change (to
0.3.0) in its merge of the PoC branch.
@RobertCrupa
RobertCrupa force-pushed the feat/online-evals-score-writing branch from 21088f7 to 325292b Compare October 8, 2026 13:31
@apify-service-account apify-service-account added the tested Temporary label used only programatically for some analytics. label Oct 8, 2026
@apify-service-account apify-service-account added the t-ai Issues owned by the AI team. label Oct 8, 2026
@vojtechj-apify
vojtechj-apify self-requested a review October 8, 2026 13:32

@vojtechj-apify vojtechj-apify left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Thanks for the fix.

@RobertCrupa RobertCrupa added the adhoc Ad-hoc unplanned task added during the sprint. label Oct 9, 2026
@RobertCrupa
RobertCrupa merged commit 723cd70 into main Oct 9, 2026
3 of 7 checks passed
@RobertCrupa
RobertCrupa deleted the feat/online-evals-score-writing branch October 9, 2026 10:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

adhoc Ad-hoc unplanned task added during the sprint. t-ai Issues owned by the AI team. tested Temporary label used only programatically for some analytics.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants