Skip to content

feat(evaluations): event-based offline evaluations runner - #39

Open
donei003 wants to merge 29 commits into
mainfrom
devin/1786604824-evaluations-scaffold
Open

feat(evaluations): event-based offline evaluations runner#39
donei003 wants to merge 29 commits into
mainfrom
devin/1786604824-evaluations-scaffold

Conversation

@donei003

@donei003 donei003 commented Aug 13, 2026

Copy link
Copy Markdown

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

  • Added an evaluations module (packages/client/src/launchdarkly_ai_server/evaluations/) with api.py, module.py, runner.py, and types.py that runs handlers per dataset row, emits one $ld:ai:offline-evals:generation custom 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 UTC emittedAt), prints emittedAt and eventId to stdout, flushes events before fetching the summary, and returns the summary-derived pass/fail with pending rows.
  • Wired the new module into launchdarkly_ai_server/__init__.py and documented it in packages/ai/README.md, packages/client/README.md, packages/client/agents.md, AGENTS.md, and a new CLAUDE.md, covering source="api", the configurable UI base URI resolution order (ui_base_uriLD_UI_BASE_URIhttps://app.launchdarkly.com), and the removal of the batch-ingest feature gate.
  • Added focused behavioral tests in packages/client/tests/test_evaluations.py and test_evaluations_run.py covering 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 emittedAt and eventId. The behavioral assertions cover: single $ld:ai:offline-evals:generation track per row, no client.variation flag check, flush prior to summary fetch, no polling of the run endpoint (only /summary), source="api" on run creation, exclusion of input/expected_output/metadata/variables from the event payload, summary-derived passed=False with pending rows, and configurable ui_base_uri precedence. 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 passed

============================= test session starts ==============================
platform darwin -- Python 3.12.9, pytest-9.1.1, pluggy-1.6.0 -- /Users/dylanoneill/.no-mistakes/worktrees/00b1855f4236/01M12526T042CE8MPR96Z6F2DY/.venv/bin/python
cachedir: .pytest_cache
rootdir: /Users/dylanoneill/.no-mistakes/worktrees/00b1855f4236/01M12526T042CE8MPR96Z6F2DY
configfile: pyproject.toml
plugins: mock-3.15.1, anyio-4.14.1, asyncio-1.4.0, langsmith-0.9.3
asyncio: mode=Mode.AUTO, debug=False, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function
collecting ... collected 7 items

tests/test_evaluations_run.py::test_complete_run_with_zero_failed_and_error_rows_passes PASSED
tests/test_evaluations_run.py::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
tests/test_evaluations_run.py::test_run_rejects_instructions_and_messages_before_network_io PASSED
tests/test_evaluations_run.py::test_missing_tool_aborts_before_any_mutating_request PASSED
tests/test_evaluations_run.py::test_empty_dataset_fails_before_evaluation_or_run_creation PASSED
tests/test_evaluations_run.py::test_complete_run_with_failed_or_error_rows_does_not_pass[failed-row] $ld:ai:offline-evals:generation emittedAt=2026-08-27T17:53:34.202081Z eventId=9b23c10d7540ed811c06d3b525ae641b07e6e72c82987879e7d2ef456ee37782
$ld:ai:offline-evals:generation emittedAt=2026-08-27T17:53:34.202270Z eventId=29e0723eba9f584fffc40e0a9918c3228543a84c7376027fc6e892250530196e
PASSED
tests/test_evaluations_run.py::test_complete_run_with_failed_or_error_rows_does_not_pass[error-row] $ld:ai:offline-evals:generation emittedAt=2026-08-27T17:53:34.203568Z eventId=9b23c10d7540ed811c06d3b525ae641b07e6e72c82987879e7d2ef456ee37782
$ld:ai:offline-evals:generation emittedAt=2026-08-27T17:53:34.203668Z eventId=29e0723eba9f584fffc40e0a9918c3228543a84c7376027fc6e892250530196e
PASSED

============================== 7 passed in 0.18s ===============================

Pipeline

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/main
  • ⚠️ packages/client/src/launchdarkly_ai_server/__init__.py - merge conflict rebasing onto origin/main

🔧 Fix applied.
✅ Re-checked - no issues remain.

⚠️ **Review** - 1 info
  • ℹ️ 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 passed
  • uv 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 contract
  • git cat-file -e HEAD:packages/client/src/launchdarkly_ai_server/evaluations/flags.py — confirmed the batch-ingest flag module is removed
  • git 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() plus await evals.run(...) loads an LD dataset, creates an evaluation and source="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_KEY is set, each row queues exactly one $ld:ai:offline-evals:generation custom event (identifiers, status, output or error, usage, timing, stable IDs, RFC3339 emittedAt on stdout and in payload; dataset-owned input/expected output/metadata/variables omitted), then flushes before polling /summary until rows are fully accounted (3-minute timeout). Management API traffic uses LD_API_TOKEN and optional LD_API_BASE_URI (not LD_BASE_URI); run links use ui_base_uriLD_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.md imports AGENTS.md. Tests cover API retries, the full run orchestration, event shape, and summary polling semantics (passed fails 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.

@donei003 donei003 self-assigned this Aug 13, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

devin-ai-integration[bot]

This comment was marked as resolved.

@donei003
donei003 force-pushed the devin/1786604824-evaluations-scaffold branch from 8151552 to 9ffa481 Compare August 20, 2026 19:16
@donei003 donei003 changed the title feat: add evaluations module scaffold, credentials, LD API client, result types feat: run client-side evaluations from the Python SDK Aug 20, 2026
Comment thread packages/client/src/launchdarkly_ai_server/evaluations/runner.py Outdated
@donei003 donei003 changed the title feat: run client-side evaluations from the Python SDK feat(evaluations): run generation-only evaluations from code Aug 21, 2026
@donei003 donei003 changed the title feat(evaluations): run generation-only evaluations from code feat(evaluations): run LaunchDarkly evaluations from the SDK Aug 24, 2026
@donei003 donei003 changed the title feat(evaluations): run LaunchDarkly evaluations from the SDK feat(evaluations): add SDK-driven evaluation runner Aug 24, 2026
@donei003 donei003 changed the title feat(evaluations): add SDK-driven evaluation runner feat(evaluations): add client-side evaluations runner Aug 24, 2026
@donei003 donei003 changed the title feat(evaluations): add client-side evaluations runner feat(evaluations): run evaluations from the SDK Aug 25, 2026
@donei003 donei003 changed the title feat(evaluations): run evaluations from the SDK feat(evaluations): scaffold offline evaluations with SDK event emission Aug 25, 2026
@donei003
donei003 requested review from a team, XieX, aknight-ld and jsonbailey August 27, 2026 17:40
@donei003
donei003 marked this pull request as ready for review August 27, 2026 17:40
devin-ai-integration[bot]

This comment was marked as resolved.

cursor[bot]

This comment was marked as resolved.

@donei003
donei003 force-pushed the devin/1786604824-evaluations-scaffold branch from 0974c98 to e3d6b6c Compare August 27, 2026 17:57
@donei003 donei003 changed the title feat(evaluations): scaffold offline evaluations with SDK event emission feat(evaluations): event-based offline evaluations runner Aug 27, 2026

@XieX XieX 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.

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 ^_^

Comment thread packages/client/src/launchdarkly_ai_server/evaluations/module.py Outdated
Comment thread packages/client/src/launchdarkly_ai_server/evaluations/api.py Outdated
Comment thread packages/client/src/launchdarkly_ai_server/evaluations/api.py
Comment thread packages/client/src/launchdarkly_ai_server/evaluations/module.py Outdated
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@donei003
donei003 requested a review from XieX August 31, 2026 23:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants