Skip to content

Commit 2b997fa

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 39ccda7 commit 2b997fa

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
"""
@@ -101,6 +105,8 @@ def __init__(
101105
self._context = context
102106
self._graph_key = graph_key
103107
self._summary = LDAIMetricSummary()
108+
self._run_id = str(uuid.uuid4())
109+
self._tracked: Dict[str, bool] = {}
104110

105111
def __get_track_data(self) -> dict:
106112
"""
@@ -109,6 +115,7 @@ def __get_track_data(self) -> dict:
109115
:return: Dictionary containing variation and config keys.
110116
"""
111117
data = {
118+
"runId": self._run_id,
112119
"variationKey": self._variation_key,
113120
"configKey": self._config_key,
114121
"version": self._version,
@@ -125,6 +132,10 @@ def track_duration(self, duration: int) -> None:
125132
126133
:param duration: Duration in milliseconds.
127134
"""
135+
if 'duration' in self._tracked:
136+
logger.warning("Duration has already been tracked for this execution.")
137+
return
138+
self._tracked['duration'] = True
128139
self._summary._duration = duration
129140
self._ld_client.track(
130141
"$ld:ai:duration:total", self._context, self.__get_track_data(), duration
@@ -136,6 +147,10 @@ def track_time_to_first_token(self, time_to_first_token: int) -> None:
136147
137148
:param time_to_first_token: Time to first token in milliseconds.
138149
"""
150+
if 'time_to_first_token' in self._tracked:
151+
logger.warning("Time to first token has already been tracked for this execution.")
152+
return
153+
self._tracked['time_to_first_token'] = True
139154
self._summary._time_to_first_token = time_to_first_token
140155
self._ld_client.track(
141156
"$ld:ai:tokens:ttf",
@@ -261,6 +276,10 @@ def track_feedback(self, feedback: Dict[str, FeedbackKind]) -> None:
261276
262277
:param feedback: Dictionary containing feedback kind.
263278
"""
279+
if 'feedback' in self._tracked:
280+
logger.warning("Feedback has already been tracked for this execution.")
281+
return
282+
self._tracked['feedback'] = True
264283
self._summary._feedback = feedback
265284
if feedback["kind"] == FeedbackKind.Positive:
266285
self._ld_client.track(
@@ -281,6 +300,10 @@ def track_success(self) -> None:
281300
"""
282301
Track a successful AI generation.
283302
"""
303+
if 'success' in self._tracked:
304+
logger.warning("Success has already been tracked for this execution.")
305+
return
306+
self._tracked['success'] = True
284307
self._summary._success = True
285308
self._ld_client.track(
286309
"$ld:ai:generation:success", self._context, self.__get_track_data(), 1
@@ -290,6 +313,10 @@ def track_error(self) -> None:
290313
"""
291314
Track an unsuccessful AI generation attempt.
292315
"""
316+
if 'success' in self._tracked:
317+
logger.warning("Success has already been tracked for this execution.")
318+
return
319+
self._tracked['success'] = True
293320
self._summary._success = False
294321
self._ld_client.track(
295322
"$ld:ai:generation:error", self._context, self.__get_track_data(), 1
@@ -356,6 +383,10 @@ def track_tokens(self, tokens: TokenUsage) -> None:
356383
357384
:param tokens: Token usage data from either custom, OpenAI, or Bedrock sources.
358385
"""
386+
if 'tokens' in self._tracked:
387+
logger.warning("Tokens have already been tracked for this execution.")
388+
return
389+
self._tracked['tokens'] = True
359390
self._summary._usage = tokens
360391
td = self.__get_track_data()
361392
if tokens.total > 0:

0 commit comments

Comments
 (0)