Repository navigation
Conversation
A judge runs after the graded handler has ended its span, so each judge starts a trace of its own with its own launchdarkly.run.id, and nothing on that trace points to the run it graded. Only the judge's metric event carried the graded run id. run_judges (inline) and run_judge (deferred) now wrap each judge run in stamp_span_attributes, the same write-if-absent mechanism offline evaluations use for launchdarkly.evaluation_run.id. Every SDK span started during the judge run carries: - launchdarkly.judge.target.run.id - launchdarkly.judge.target.config.key - launchdarkly.judge.target.variation.key Inline judges read base_track_data, deferred judges read the task's parent_track_data. Each key is written only when its value is a non-empty string. Matches the JS turnkey SDK.
Uses a real tracer provider with ConversationIdSpanProcessor and an in-memory exporter. A stand-in judge handler opens invoke_agent and a chat span under it. Covers the inline path, the deferred path reading the task, and a span outside any judge run, which stays unstamped.
Adds the three keys to the judge section of the telemetry contract, where they come from on each path, and that capture settings do not gate them because they are identifiers, not content.
Adds the cases the cross-SDK rule requires beyond the inline, deferred and outside-a-run checks: - a span from a non-SDK tracer inside the judge run stays unstamped - the attributes are written with the judge handler's capture off - an empty variationKey leaves that one key off and writes the other two - a span that already carries a target key keeps its value - in graph().invoke(), a per-node judge points at the node's run, and the graph-level judge points at the graph run, graph key and graph variation key, each equal to what that judge's metric event names
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.
Summary
A judge runs after the graded handler has ended its span, so each judge starts a trace of its own with its own
launchdarkly.run.id. Nothing on that trace points to the run it graded. Today the only link is the judge's metric event, which carries the graded run id in its track data.This puts the same link on the trace. Every SDK span started during a judge run now carries the graded run's identity, so a judge trace can be joined to the run it scored exactly instead of by timestamp.
This matches a JS turnkey SDK change in launchdarkly/js-ai-sdk (launchdarkly/js-ai-sdk#200). The cross-SDK spec rule is in launchdarkly/ai-sdks-monorepo#122.
What changed
run_judges(inline) andrun_judge(deferred) inlaunchdarkly_ai_server/judges.pywrap each judge run instamp_span_attributes. That is the existing write-if-absent mechanism offline evaluations use to putlaunchdarkly.evaluation_run.idon every span of a dataset row, applied byConversationIdSpanProcessorto spans from this SDK's tracers only.judges.py:launchdarkly.judge.target.run.id(LD_JUDGE_TARGET_RUN_ID)launchdarkly.judge.target.config.key(LD_JUDGE_TARGET_CONFIG_KEY)launchdarkly.judge.target.variation.key(LD_JUDGE_TARGET_VARIATION_KEY)base_track_data. Deferred judges readJudgeTask.parent_track_data. Each key is written only when its value is a non-empty string, and never overwrites a value already on the span.TELEMETRY-CONTRACT.mdsection 4a documents the keys.The constants are not added to the package's
__all__, same as JS, so the public API report is unchanged. The cross-handler vocabulary lock intests/test_cross_handler_parity.pyscans for keys passed toset_attributeandgen_ai.*literals; it does not listlaunchdarkly.evaluation_run.ideither, so these keys are outside its scope and it is unchanged.Testing
packages/client/tests/test_judge_target_run.pyuses a realTracerProviderwithConversationIdSpanProcessorand an in-memory exporter. A stand-in judge handler opensinvoke_agentand achatspan under it on an SDK tracer. It covers each test the spec rule requires:parent_track_data.capture_content=False, both spans still carry the three values.variationKey: "", that key is absent and the other two are present.launchdarkly.judge.target.run.idset to"own"keeps"own".graph().invoke()with a per-node judge and agraph_judge, the per-node judge's spans carry the node's run id (equal to that judge's metric event), the node's config key and its variation key. The graph judge's spans carry the graph run id (equal to thelaunchdarkly.graphspan'slaunchdarkly.run.idand to its metric event), the graph key and the graph variation key. The graded node's own spans carry none of the three.Checked that the tests catch regressions:
Results:
uv run pytest: 3775 passed, 18 skipped.ruff check .,ruff format --check .,mypy packages/*/srcand the API report check are clean.Spec: launchdarkly/ai-sdks-monorepo#122
Original branch:
ag/judge-target-runNote
Overview
Judge runs start their own trace after the graded handler finishes, so traces were only linkable via metric events. This PR stamps every SDK span in a judge run with the graded run’s
runId,configKey, andvariationKeyusing the existing write-if-absentstamp_span_attributesmechanism (same pattern as offline evaluation rows).Inline
run_judgeswraps each judge in_judge_target(base_track_data); deferredrun_judgeuses_judge_target(task.parent_track_data). Three new attributes (launchdarkly.judge.target.run.id,.config.key,.variation.key) are set only for non-empty strings and are not gated by content capture.TELEMETRY-CONTRACT.mdsection 4a documents the keys.test_judge_target_run.pyasserts inline and deferred paths stampinvoke_agentandchatspans, and that spans outside a judge run stay untouched.Reviewed by Cursor Bugbot for commit bb98172. Bugbot is set up for automated code reviews on this repo. Configure here.