Skip to content

Commit a55fa40

Browse files
jsonbaileyclaude
andcommitted
feat!: Add per-execution runId and at-most-once event tracking
- Each tracker now carries a runId (UUIDv4) included in all emitted events, scoping every metric to a single execution - At-most-once semantics: duplicate calls to track_duration, track_tokens, track_success/track_error, track_feedback, and track_time_to_first_token on the same tracker are dropped with a warning Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent ccae38a commit a55fa40

2 files changed

Lines changed: 154 additions & 42 deletions

File tree

packages/sdk/server-ai/src/ldai/tracker.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
import logging
12
import time
3+
import uuid
24
from dataclasses import dataclass
35
from enum import Enum
46
from typing import Any, Callable, Dict, Iterable, List, Optional
57

68
from ldclient import Context, LDClient
79

10+
logger = logging.getLogger(__name__)
11+
812

913
class FeedbackKind(Enum):
1014
"""
@@ -97,6 +101,8 @@ def __init__(
97101
self._provider_name = provider_name
98102
self._context = context
99103
self._summary = LDAIMetricSummary()
104+
self._run_id = str(uuid.uuid4())
105+
self._tracked: Dict[str, bool] = {}
100106

101107
def __get_track_data(self, graph_key: Optional[str] = None) -> dict:
102108
"""
@@ -106,6 +112,7 @@ def __get_track_data(self, graph_key: Optional[str] = None) -> dict:
106112
:return: Dictionary containing variation and config keys.
107113
"""
108114
data = {
115+
"runId": self._run_id,
109116
"variationKey": self._variation_key,
110117
"configKey": self._config_key,
111118
"version": self._version,
@@ -124,6 +131,10 @@ def track_duration(self, duration: int, *, graph_key: Optional[str] = None) -> N
124131
:param graph_key: When set, include ``graphKey`` in the event payload
125132
(e.g. config-level metrics inside a graph).
126133
"""
134+
if 'duration' in self._tracked:
135+
logger.warning("Duration has already been tracked for this execution.")
136+
return
137+
self._tracked['duration'] = True
127138
self._summary._duration = duration
128139
self._ld_client.track(
129140
"$ld:ai:duration:total", self._context, self.__get_track_data(graph_key), duration
@@ -138,6 +149,10 @@ def track_time_to_first_token(
138149
:param time_to_first_token: Time to first token in milliseconds.
139150
:param graph_key: When set, include ``graphKey`` in the event payload.
140151
"""
152+
if 'time_to_first_token' in self._tracked:
153+
logger.warning("Time to first token has already been tracked for this execution.")
154+
return
155+
self._tracked['time_to_first_token'] = True
141156
self._summary._time_to_first_token = time_to_first_token
142157
self._ld_client.track(
143158
"$ld:ai:tokens:ttf",
@@ -297,6 +312,10 @@ def track_feedback(self, feedback: Dict[str, FeedbackKind], *, graph_key: Option
297312
:param feedback: Dictionary containing feedback kind.
298313
:param graph_key: When set, include ``graphKey`` in the event payload.
299314
"""
315+
if 'feedback' in self._tracked:
316+
logger.warning("Feedback has already been tracked for this execution.")
317+
return
318+
self._tracked['feedback'] = True
300319
self._summary._feedback = feedback
301320
if feedback["kind"] == FeedbackKind.Positive:
302321
self._ld_client.track(
@@ -319,6 +338,10 @@ def track_success(self, *, graph_key: Optional[str] = None) -> None:
319338
320339
:param graph_key: When set, include ``graphKey`` in the event payload.
321340
"""
341+
if 'success' in self._tracked:
342+
logger.warning("Success has already been tracked for this execution.")
343+
return
344+
self._tracked['success'] = True
322345
self._summary._success = True
323346
self._ld_client.track(
324347
"$ld:ai:generation:success", self._context, self.__get_track_data(graph_key=graph_key), 1
@@ -330,6 +353,10 @@ def track_error(self, *, graph_key: Optional[str] = None) -> None:
330353
331354
:param graph_key: When set, include ``graphKey`` in the event payload.
332355
"""
356+
if 'success' in self._tracked:
357+
logger.warning("Success has already been tracked for this execution.")
358+
return
359+
self._tracked['success'] = True
333360
self._summary._success = False
334361
self._ld_client.track(
335362
"$ld:ai:generation:error", self._context, self.__get_track_data(graph_key=graph_key), 1
@@ -397,6 +424,10 @@ def track_tokens(self, tokens: TokenUsage, *, graph_key: Optional[str] = None) -
397424
:param tokens: Token usage data from either custom, OpenAI, or Bedrock sources.
398425
:param graph_key: When set, include ``graphKey`` in the event payload.
399426
"""
427+
if 'tokens' in self._tracked:
428+
logger.warning("Tokens have already been tracked for this execution.")
429+
return
430+
self._tracked['tokens'] = True
400431
self._summary._usage = tokens
401432
td = self.__get_track_data(graph_key=graph_key)
402433
if tokens.total > 0:

0 commit comments

Comments
 (0)