Skip to content

feat(client): stamp modelKey and modelVersion from _ldMeta on tracking events - #97

Merged
atornsii merged 4 commits into
mainfrom
feat/model-key-version-ldmeta
Sep 18, 2026
Merged

atornsii merged 4 commits into
mainfrom
feat/model-key-version-ldmeta

Conversation

@atornsii

@atornsii atornsii commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Human

Adding modelKey and modelVersion fields to metadata

Summary

Gonfalon moved the pinned model config's identity into _ldMeta.modelKey / _ldMeta.modelVersion (launchdarkly/gonfalon#67230). The legacy python-server-sdk-ai already stamps both fields on every $ld:ai:* event; this SDK had no reference to either, so events from it could not be cost-attributed by pinned model version (AIC-2881).

  • Root cause: extract_variation rebuilt meta from a fixed key list and silently dropped the two fields. It now merges them in.
  • New model_stamps_from_meta helper is the single source for all four TrackData builders: execute_and_track, execute_and_stream, _build_graph, and the shared make_track_data used by the native graph adapters. Keys are omitted when absent; an empty modelKey is treated as absent; modelVersion accepts int (not bool), integral float and integer strings, and anything else is omitted so telemetry metadata can never raise and abort an invocation.
  • run_judge strips the two stamps from parent_track_data before overlaying the judge's own, so a judge variation without a linked model config no longer reports the parent's model identity in the returned track_data. Judges with their own stamps are unchanged, and the judge's emitted events were never affected.

Spec: TESTING.md §1.5, §3.10 "Model stamps on trackData", §3.13, §3.16, Appendix A.12 (launchdarkly/ai-sdks-monorepo PR opened alongside this one). Companion TypeScript PR in launchdarkly/js-ai-sdk.

Test plan

  • New tests for config().invoke() / .stream() payloads, graph-level payloads, inspect_config / extract_variation meta preservation, model_stamps_from_meta (including malformed inputs), omit_model_stamps, make_track_data, and run_judge result track_data. All were confirmed to fail before the implementation.
  • uv run pytest (1276 passing), mypy, ruff check, ruff format --check clean.
  • Manual: ran the agent scenario (uv run python main.py agent sdk-test ...) against a production AI Config pinned to OpenAI.gpt-4.1-mini. Exit 0, output JSON written, and the returned track_data carries modelKey: "OpenAI.gpt-4.1-mini", modelVersion: 1 alongside modelName: "gpt-4.1-mini". Failure case (doesnt-exist) exits 1 with a clean error and writes nothing.

🤖 Generated with Claude Code


Devin Review


Note

Overview
Adds pinned model-config identity (modelKey, modelVersion) from flag _ldMeta to VariationMeta, TrackData, and every $ld:ai:* payload so events align with Gonfalon cost attribution (matching the legacy Python AI SDK).

A shared model_stamps_from_meta helper copies the two fields when present, omits them (never None), treats empty modelKey as absent, and coerces modelVersion safely so bad flag payloads cannot abort an invocation. It is wired into extract_variation, execute_and_track / execute_and_stream, graph-level track data, and make_track_data for native graph adapters. omit_model_stamps is used in run_judge so judges without their own pinned model config do not inherit the parent’s stamps in returned track_data (other parent keys like graphKey still flow through).

Docs and public exports are updated; package versions bump to 0.2.2 / 0.1.6 with broad unit tests for invoke, stream, graph events, lifecycle meta, helpers, and judge merging.

Reviewed by Cursor Bugbot for commit 4e92178. Bugbot is set up for automated code reviews on this repo. Configure here.

atornsii and others added 2 commits September 17, 2026 09:14
…g events

Gonfalon (launchdarkly/gonfalon#67230) delivers the pinned model config's
identity in _ldMeta.modelKey / _ldMeta.modelVersion. extract_variation
rebuilt meta from a fixed key list and dropped them; it now preserves both.
Copy them onto every TrackData (execute_and_track, execute_and_stream,
resolve_graph, make_track_data) so they reach every $ld:ai:* event payload
and the __ld variable, matching the legacy server-ai SDK. Keys are omitted
when absent; empty modelKey is treated as absent; modelVersion is int.

Adds the shared model_stamps_from_meta helper (exported from the package root).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ng parent model identity

Code-review follow-up to the modelKey/modelVersion passthrough:

- model_stamps_from_meta no longer raises on a malformed _ldMeta.modelVersion.
  It accepts int (not bool), integral float and integer strings; anything else
  is omitted so telemetry metadata can never abort an invocation.
- run_judge strips modelKey/modelVersion from the parent track_data before
  overlaying the judge's own, so a judge without a pinned model config no
  longer reports the parent's model identity. Judges with their own stamps
  are unchanged. Adds omit_model_stamps to utils.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 2 potential issues.

2 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)

Devin Review

Comment thread packages/client/src/launchdarkly_ai_server/tracking.py
"version": meta.get("version", 1) if isinstance(meta, dict) else 1,
"modelName": "",
"providerName": "",
**model_stamps_from_meta(meta),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Graph model stamps never refresh

After a graph variation changes, graph_track_data keeps cached model stamps for the same context. Later graph events remain attributed to the old pinned model version.

Learn more

GraphInstance.invoke caches both the resolved GraphDefinition and graph_track_data by context in the context cache. There is no expiry or LaunchDarkly-driven invalidation. The cached definition also contains every node's metadata, so both graph-level and node-level model stamps remain fixed for the lifetime of that cache entry. Model reassignment or version changes therefore never reach later tracking events from the same GraphInstance and context.

Example: A graph first resolves with modelVersion=5. LaunchDarkly updates the pin to version 6, but the next invocation with the same context reuses version 5 in all affected events.

Recommended fix: Do not indefinitely cache variation-derived metadata. Reevaluate graph and node variations for each invocation, or add a bounded freshness/invalidation mechanism that refreshes the cached definition and tracking data together.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

atornsii and others added 2 commits September 17, 2026 09:47
Parity with the Devin review on launchdarkly/js-ai-sdk#66: a truthy
non-string _ldMeta.modelKey (number, object, list, bool) was passed through
to tracking payloads. Only a non-empty str is accepted now.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@andrewklatzke

Copy link
Copy Markdown
Contributor

Chatted offline about addressing bugbot feedback, but looking good 👍

@atornsii
atornsii merged commit 4e6998a into main Sep 18, 2026
8 checks passed
@atornsii
atornsii deleted the feat/model-key-version-ldmeta branch September 18, 2026 23:21
@github-actions github-actions Bot mentioned this pull request Sep 18, 2026
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.

2 participants