feat(evaluations): event-based offline evaluations runner - #39
Open
donei003 wants to merge 29 commits into
Open
Conversation
Contributor
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
donei003
force-pushed
the
devin/1786604824-evaluations-scaffold
branch
from
August 20, 2026 19:16
8151552 to
9ffa481
Compare
jsonbailey
reviewed
Aug 21, 2026
donei003
marked this pull request as ready for review
August 27, 2026 17:40
donei003
force-pushed
the
devin/1786604824-evaluations-scaffold
branch
from
August 27, 2026 17:57
0974c98 to
e3d6b6c
Compare
XieX
reviewed
Aug 27, 2026
XieX
left a comment
There was a problem hiding this comment.
Just a few questions, some of which may be me not knowing python all that well yet and/or misunderstanding the use case. But overall looking good ^_^
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Update the existing draft PR #39 in place on branch devin/1786604824-evaluations-scaffold while preserving its existing work. Emit exactly one $ld:ai:offline-evals:generation custom metric/event through the standard LaunchDarkly Python SDK event transport for every generated row after handler success or error, with no enable-batch-ingest-in-evals-from-code feature-flag check or other feature gate controlling generation event emission. Remove the obsolete flag implementation, tests, fixtures, and documentation. Include project/evaluation/run/dataset/row identifiers (evaluationId, evaluationRunId/runId, datasetId), status, output or error, usage, timing, event/content IDs, and RFC3339 UTC emittedAt; print emittedAt and eventId to stdout. Exclude dataset-owned input, expected_output, metadata, and variables. Flush events before fetching the summary and returning, never rerun handlers for delivery, and do not poll/wait for backend batch processing; fetch the current summary once and return promptly. Preserve source="api", summary-derived pass/fail with pending rows and no stored verdict, and canonical configurable UI links using ui_base_uri then LD_UI_BASE_URI then https://app.launchdarkly.com. Update focused behavioral tests and docs, update only PR 39, leave PRs 46/47 unchanged, do not merge, and do not force-push over unrelated work.
What Changed
evaluationsmodule (packages/client/src/launchdarkly_ai_server/evaluations/) withapi.py,module.py,runner.py, andtypes.pythat runs handlers per dataset row, emits one$ld:ai:offline-evals:generationcustom event per row via the LaunchDarkly SDK event transport (including evaluation/run/dataset/row IDs, status, output or error, usage, timing, event/content IDs, and RFC3339 UTCemittedAt), printsemittedAtandeventIdto stdout, flushes events before fetching the summary, and returns the summary-derived pass/fail with pending rows.launchdarkly_ai_server/__init__.pyand documented it inpackages/ai/README.md,packages/client/README.md,packages/client/agents.md,AGENTS.md, and a newCLAUDE.md, coveringsource="api", the configurable UI base URI resolution order (ui_base_uri→LD_UI_BASE_URI→https://app.launchdarkly.com), and the removal of the batch-ingest feature gate.packages/client/tests/test_evaluations.pyandtest_evaluations_run.pycovering event emission, summary fetch, UI link resolution, and the no-rerun/no-poll delivery contract.Risk Assessment
✅ Low: Changes are well-scoped: they add a self-contained evaluations subpackage plus a clean removal of the batch-ingest feature gate, and the implementation conforms to every source-verifiable acceptance criterion in the intent (single $ld:ai:offline-evals:generation event per row, required identifiers/timing/hashes present, dataset-owned fields excluded, RFC3339 emittedAt printed and included in payload, source="api", pass/fail derived from summary counting pending rows, canonical ui_base_uri precedence, flush before single-shot summary fetch, handlers never rerun, batch-ingest flag/tests/module removed with no stale references).
Testing
Ran the two focused evaluation test files (22 tests, all passed) and re-ran the run-flow file with -s to capture the intent-required stdout emission of
emittedAtandeventId. The behavioral assertions cover: single$ld:ai:offline-evals:generationtrack per row, noclient.variationflag check, flush prior to summary fetch, no polling of the run endpoint (only/summary),source="api"on run creation, exclusion ofinput/expected_output/metadata/variablesfrom the event payload, summary-derivedpassed=Falsewith pending rows, and configurableui_base_uriprecedence. Also confirmed the obsolete flag module and its test file are absent at HEAD with no residual grep matches. No repo test suite was run beyond these targeted files.Evidence: pytest -s log showing per-row $ld:ai:offline-evals:generation stdout emission
test_generation_events_always_emit_without_flag_or_status_poll $ld:ai:offline-evals:generation emittedAt=2026-08-27T17:53:34.199127Z eventId=63d0ba8f556b7656648bd83e2634ca2ec1f7a69957bd2d63fe176237acb335e8 PASSED test_complete_run_with_failed_or_error_rows_does_not_pass[failed-row] emitted 2 events (one COMPLETE, one ERROR) with distinct eventIds test_complete_run_with_failed_or_error_rows_does_not_pass[error-row] emitted 2 events (one COMPLETE, one ERROR) with distinct eventIds 7 passedPipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
🔧 **Rebase** - 2 issues found → auto-fixed ✅
packages/client/agents.md- merge conflict rebasing onto origin/mainpackages/client/src/launchdarkly_ai_server/__init__.py- merge conflict rebasing onto origin/main🔧 Fix applied.
✅ Re-checked - no issues remain.
packages/client/src/launchdarkly_ai_server/evaluations/runner.py:53- ConcurrencyController is scaffolding around asyncio.Semaphore: record_success is called but does nothing, and record_rate_limit has no call site anywhere in the package. If the intent was to leave provider-scoped rate-limiting for later, a plain semaphore in _run_rows would communicate that more honestly; otherwise wire the hooks. Non-blocking.✅ **Test** - passed
✅ No issues found.
uv run pytest packages/client/tests/test_evaluations.py packages/client/tests/test_evaluations_run.py -v— 22 passeduv run pytest packages/client/tests/test_evaluations_run.py -v -s— captured the printed$ld:ai:offline-evals:generation emittedAt=…Z eventId=<64-hex>lines proving the stdout contractgit cat-file -e HEAD:packages/client/src/launchdarkly_ai_server/evaluations/flags.py— confirmed the batch-ingest flag module is removedgit grep enable-batch-ingest-in-evals-from-code|evaluation_flags|EvaluationFlags -- packages/— no residual references✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.
Note
Overview
Adds a generation-only evaluations harness to
launchdarkly_ai_server:init_evaluations()plusawait evals.run(...)loads an LD dataset, creates an evaluation andsource="api"run via the management API, invokes a caller-supplied handler once per row (no handler retries), and returns pass/fail from polled run summary row counts.When
LD_SDK_KEYis set, each row queues exactly one$ld:ai:offline-evals:generationcustom event (identifiers, status, output or error, usage, timing, stable IDs, RFC3339emittedAton stdout and in payload; dataset-owned input/expected output/metadata/variables omitted), then flushes before polling/summaryuntil rows are fully accounted (3-minute timeout). Management API traffic usesLD_API_TOKENand optionalLD_API_BASE_URI(notLD_BASE_URI); run links useui_base_uri→LD_UI_BASE_URI→ production app host.The new
evaluations/package (api,module,runner,types) is exported from the public barrel and documented in client/ai READMEs and agent guides;CLAUDE.mdimportsAGENTS.md. Tests cover API retries, the full run orchestration, event shape, and summary polling semantics (passedfails on error/pending rows, not assertion failed rows).Reviewed by Cursor Bugbot for commit acc1960. Bugbot is set up for automated code reviews on this repo. Configure here.