@@ -84,14 +84,20 @@ def resumption_token(self) -> Optional[str]:
8484 """
8585 URL-safe Base64-encoded resumption token captured at tracker
8686 instantiation. Useful for deferred feedback flows where a downstream
87- process needs to associate events with the original execution .
87+ process needs to associate events with the original AI run .
8888 """
8989 return self ._resumption_token
9090
9191
9292class LDAIConfigTracker :
9393 """
94- Tracks configuration and usage metrics for LaunchDarkly AI operations.
94+ Records metrics for a single AI run.
95+
96+ All events a tracker emits share a runId (a UUIDv4) so LaunchDarkly can correlate
97+ them in metrics views. See individual track methods for their specific semantics.
98+ Call ``create_tracker`` on the AI Config to start a new run. A resumption token
99+ preserves the runId, so events emitted by a tracker reconstructed in another
100+ process correlate with the original run.
95101 """
96102
97103 def __init__ (
@@ -110,7 +116,7 @@ def __init__(
110116 Initialize an AI Config tracker.
111117
112118 :param ld_client: LaunchDarkly client instance.
113- :param run_id: Unique identifier for this execution .
119+ :param run_id: Unique identifier for this AI run .
114120 :param config_key: Configuration key for tracking.
115121 :param variation_key: Variation key for tracking.
116122 :param version: Version of the variation.
@@ -162,7 +168,7 @@ def from_resumption_token(cls, token: str, ld_client: LDClient, context: Context
162168
163169 This is used for cross-process scenarios such as deferred feedback,
164170 where a different service needs to associate tracking events with the
165- original execution 's ``runId``.
171+ original tracker 's ``runId``.
166172
167173 :param token: A URL-safe Base64-encoded resumption token obtained from
168174 :attr:`resumption_token`.
@@ -219,12 +225,18 @@ def __get_track_data(self) -> dict:
219225
220226 def track_duration (self , duration : int ) -> None :
221227 """
222- Manually track the duration of an AI operation.
228+ Manually track the duration of an AI run.
229+
230+ Records at most once per Tracker; further calls are ignored.
223231
224232 :param duration: Duration in milliseconds.
225233 """
226234 if self ._summary .duration_ms is not None :
227- log .warning ("Duration has already been tracked for this execution. %s" , self .__get_track_data ())
235+ log .warning (
236+ "Skipping track_duration: duration already recorded on this tracker. "
237+ "Call create_tracker on the AI Config for a new run. %s" ,
238+ self .__get_track_data (),
239+ )
228240 return
229241 self ._summary ._duration_ms = duration
230242 self ._ld_client .track (
@@ -233,13 +245,16 @@ def track_duration(self, duration: int) -> None:
233245
234246 def track_time_to_first_token (self , time_to_first_token : int ) -> None :
235247 """
236- Manually track the time to first token of an AI operation.
248+ Manually track the time to first token of an AI run.
249+
250+ Records at most once per Tracker; further calls are ignored.
237251
238252 :param time_to_first_token: Time to first token in milliseconds.
239253 """
240254 if self ._summary .time_to_first_token is not None :
241255 log .warning (
242- "Time to first token has already been tracked for this execution. %s" ,
256+ "Skipping track_time_to_first_token: time-to-first-token already recorded on this tracker. "
257+ "Call create_tracker on the AI Config for a new run. %s" ,
243258 self .__get_track_data (),
244259 )
245260 return
@@ -253,10 +268,10 @@ def track_time_to_first_token(self, time_to_first_token: int) -> None:
253268
254269 def track_duration_of (self , func ):
255270 """
256- Automatically track the duration of an AI operation .
271+ Automatically track the duration of an AI run .
257272
258- An exception occurring during the execution of the function will still
259- track the duration. The exception will be re-thrown.
273+ An exception raised while the function runs will still record the
274+ duration. The exception will be re-thrown.
260275
261276 :param func: Function to track (synchronous only).
262277 :return: Result of the tracked function.
@@ -317,6 +332,10 @@ def track_metrics_of(
317332 non-``None`` ``duration_ms`` field, that value is used as the measured duration
318333 instead of the wall-clock elapsed time.
319334
335+ Because each inner metric is at-most-once per Tracker, calling this twice
336+ on the same Tracker will run the inner block again but produce no
337+ additional metric events.
338+
320339 :param metrics_extractor: Function that extracts LDAIMetrics from the operation result
321340 :param func: Synchronous callable that runs the operation
322341 :return: The result of the operation
@@ -348,6 +367,10 @@ async def track_metrics_of_async(
348367 non-``None`` ``duration_ms`` field, that value is used as the measured duration
349368 instead of the wall-clock elapsed time.
350369
370+ Because each inner metric is at-most-once per Tracker, calling this twice
371+ on the same Tracker will run the inner block again but produce no
372+ additional metric events.
373+
351374 :param metrics_extractor: Function that extracts LDAIMetrics from the operation result
352375 :param func: Async callable or zero-arg callable that returns an awaitable when called
353376 :return: The result of the operation
@@ -370,6 +393,9 @@ def track_judge_result(self, judge_result: Any) -> None:
370393 """
371394 Track a judge result, including the evaluation score with judge config key.
372395
396+ May be called multiple times per Tracker; each call records the
397+ provided judge result.
398+
373399 :param judge_result: JudgeResult object containing score, metric key, and success status
374400 """
375401 if not judge_result .sampled :
@@ -388,12 +414,18 @@ def track_judge_result(self, judge_result: Any) -> None:
388414
389415 def track_feedback (self , feedback : Dict [str , FeedbackKind ]) -> None :
390416 """
391- Track user feedback for an AI operation.
417+ Track user feedback for an AI run.
418+
419+ Records at most once per Tracker; further calls are ignored.
392420
393421 :param feedback: Dictionary containing feedback kind.
394422 """
395423 if self ._summary .feedback is not None :
396- log .warning ("Feedback has already been tracked for this execution. %s" , self .__get_track_data ())
424+ log .warning (
425+ "Skipping track_feedback: feedback already recorded on this tracker. "
426+ "Call create_tracker on the AI Config for a new run. %s" ,
427+ self .__get_track_data (),
428+ )
397429 return
398430 self ._summary ._feedback = feedback
399431 if feedback ["kind" ] == FeedbackKind .Positive :
@@ -413,11 +445,14 @@ def track_feedback(self, feedback: Dict[str, FeedbackKind]) -> None:
413445
414446 def track_tool_calls (self , tool_calls : Iterable [str ]) -> None :
415447 """
416- Track the tool calls made during an AI operation .
448+ Track the tool calls made during an AI run .
417449
418450 Appends to the summary's tool call list and fires a
419451 ``$ld:ai:tool_call`` event for each tool.
420452
453+ May be called multiple times per Tracker; each call records an event
454+ for every tool identifier provided.
455+
421456 :param tool_calls: Tool identifiers (e.g. from a model response).
422457 """
423458 tool_calls_list = list (tool_calls )
@@ -428,9 +463,17 @@ def track_tool_calls(self, tool_calls: Iterable[str]) -> None:
428463 def track_success (self ) -> None :
429464 """
430465 Track a successful AI generation.
466+
467+ Records at most once per Tracker. track_success and track_error share
468+ state; only one of the two can record per Tracker, and subsequent calls
469+ are ignored.
431470 """
432471 if self ._summary .success is not None :
433- log .warning ("Success has already been tracked for this execution. %s" , self .__get_track_data ())
472+ log .warning (
473+ "Skipping track_success: success/error already recorded on this tracker. "
474+ "Call create_tracker on the AI Config for a new run. %s" ,
475+ self .__get_track_data (),
476+ )
434477 return
435478 self ._summary ._success = True
436479 self ._ld_client .track (
@@ -440,9 +483,17 @@ def track_success(self) -> None:
440483 def track_error (self ) -> None :
441484 """
442485 Track an unsuccessful AI generation attempt.
486+
487+ Records at most once per Tracker. track_success and track_error share
488+ state; only one of the two can record per Tracker, and subsequent calls
489+ are ignored.
443490 """
444491 if self ._summary .success is not None :
445- log .warning ("Success has already been tracked for this execution. %s" , self .__get_track_data ())
492+ log .warning (
493+ "Skipping track_error: success/error already recorded on this tracker. "
494+ "Call create_tracker on the AI Config for a new run. %s" ,
495+ self .__get_track_data (),
496+ )
446497 return
447498 self ._summary ._success = False
448499 self ._ld_client .track (
@@ -475,10 +526,16 @@ def track_tokens(self, tokens: TokenUsage) -> None:
475526 """
476527 Track token usage metrics.
477528
529+ Records at most once per Tracker; further calls are ignored.
530+
478531 :param tokens: Token usage data from either custom, OpenAI, or Bedrock sources.
479532 """
480533 if self ._summary .tokens is not None :
481- log .warning ("Tokens have already been tracked for this execution. %s" , self .__get_track_data ())
534+ log .warning (
535+ "Skipping track_tokens: token usage already recorded on this tracker. "
536+ "Call create_tracker on the AI Config for a new run. %s" ,
537+ self .__get_track_data (),
538+ )
482539 return
483540 self ._summary ._tokens = tokens
484541 td = self .__get_track_data ()
@@ -506,7 +563,10 @@ def track_tokens(self, tokens: TokenUsage) -> None:
506563
507564 def track_tool_call (self , tool_key : str ) -> None :
508565 """
509- Track a tool invocation for this configuration (standalone or within a graph).
566+ Track a tool call for this configuration (standalone or within a graph).
567+
568+ May be called multiple times per Tracker; each call records a tool
569+ call event for the provided tool key.
510570
511571 :param tool_key: Identifier of the tool that was invoked.
512572 """
@@ -604,12 +664,18 @@ def __get_track_data(self):
604664
605665 def track_invocation_success (self ) -> None :
606666 """
607- Track a successful graph invocation.
667+ Track a successful graph run.
668+
669+ Records at most once per graph tracker. track_invocation_success and
670+ track_invocation_failure share state; only one of the two can record
671+ per graph tracker, and subsequent calls are ignored.
608672 """
609673 if self ._summary .success is not None :
610674 log .warning (
611- "Invocation status has already been tracked for this graph execution. %s" ,
612- self .__get_track_data ())
675+ "Skipping track_invocation_success: invocation result already recorded on this graph tracker. "
676+ "Call create_tracker on the agent graph for a new run. %s" ,
677+ self .__get_track_data (),
678+ )
613679 return
614680 self ._summary .success = True
615681 self ._ld_client .track (
@@ -621,12 +687,18 @@ def track_invocation_success(self) -> None:
621687
622688 def track_invocation_failure (self ) -> None :
623689 """
624- Track an unsuccessful graph invocation.
690+ Track an unsuccessful graph run.
691+
692+ Records at most once per graph tracker. track_invocation_success and
693+ track_invocation_failure share state; only one of the two can record
694+ per graph tracker, and subsequent calls are ignored.
625695 """
626696 if self ._summary .success is not None :
627697 log .warning (
628- "Invocation status has already been tracked for this graph execution. %s" ,
629- self .__get_track_data ())
698+ "Skipping track_invocation_failure: invocation result already recorded on this graph tracker. "
699+ "Call create_tracker on the agent graph for a new run. %s" ,
700+ self .__get_track_data (),
701+ )
630702 return
631703 self ._summary .success = False
632704 self ._ld_client .track (
@@ -638,12 +710,18 @@ def track_invocation_failure(self) -> None:
638710
639711 def track_duration (self , duration : int ) -> None :
640712 """
641- Track the total duration of graph execution.
713+ Track the total duration of a graph run.
714+
715+ Records at most once per graph tracker; further calls are ignored.
642716
643717 :param duration: Duration in milliseconds.
644718 """
645719 if self ._summary .duration_ms is not None :
646- log .warning ("Duration has already been tracked for this graph execution. %s" , self .__get_track_data ())
720+ log .warning (
721+ "Skipping track_duration: duration already recorded on this graph tracker. "
722+ "Call create_tracker on the agent graph for a new run. %s" ,
723+ self .__get_track_data (),
724+ )
647725 return
648726 self ._summary .duration_ms = duration
649727 self ._ld_client .track (
@@ -655,14 +733,20 @@ def track_duration(self, duration: int) -> None:
655733
656734 def track_total_tokens (self , tokens : Optional [TokenUsage ] = None ) -> None :
657735 """
658- Track aggregated token usage across the entire graph invocation.
736+ Track aggregated token usage across the entire graph run.
737+
738+ Records at most once per graph tracker; further calls are ignored.
659739
660740 :param tokens: Token usage data, or ``None`` when usage is unknown.
661741 """
662742 if tokens is None or tokens .total <= 0 :
663743 return
664744 if self ._summary .tokens is not None :
665- log .warning ("Token usage has already been tracked for this graph execution. %s" , self .__get_track_data ())
745+ log .warning (
746+ "Skipping track_total_tokens: tokens already recorded on this graph tracker. "
747+ "Call create_tracker on the agent graph for a new run. %s" ,
748+ self .__get_track_data (),
749+ )
666750 return
667751 self ._summary .tokens = tokens
668752 self ._ld_client .track (
@@ -674,10 +758,14 @@ def track_total_tokens(self, tokens: Optional[TokenUsage] = None) -> None:
674758
675759 def track_path (self , path : List [str ]) -> None :
676760 """
677- Track the execution path through the graph.
761+ Track the path traversed through the graph during a graph run .
678762
679763 Appends to the summary's path list and fires a ``$ld:ai:graph:path``
680- event. Can be called multiple times to build the path incrementally.
764+ event.
765+
766+ May be called multiple times per Tracker; each call records the
767+ provided path segment and appends it to the summary so the full
768+ path can be built incrementally.
681769
682770 :param path: An array of configuration keys representing the sequence of nodes executed during graph traversal.
683771 """
0 commit comments