feat: Add TrackDurationOf, TrackMetricsOf, TrackJudgeResult, TrackToolCall#287
Merged
Conversation
…rthy/AIC-2634/net-sdk-v-1-0-step-1
…rthy/AIC-2635/net-sdk-v-1-0-step-2
…launchdarkly/dotnet-core into mmccarthy/AIC-2635/net-sdk-v-1-0-step-2
…launchdarkly/dotnet-core into mmccarthy/AIC-2636/net-sdk-v-1-0-step-3
…launchdarkly/dotnet-core into mmccarthy/AIC-2637/net-sdk-v-1-0-step-4
…rthy/AIC-2634/net-sdk-v-1-0-step-1
…launchdarkly/dotnet-core into mmccarthy/AIC-2635/net-sdk-v-1-0-step-2
…launchdarkly/dotnet-core into mmccarthy/AIC-2636/net-sdk-v-1-0-step-3
…launchdarkly/dotnet-core into mmccarthy/AIC-2637/net-sdk-v-1-0-step-4
…launchdarkly/dotnet-core into mmccarthy/AIC-2635/net-sdk-v-1-0-step-2
…launchdarkly/dotnet-core into mmccarthy/AIC-2637/net-sdk-v-1-0-step-4
…launchdarkly/dotnet-core into mmccarthy/AIC-2636/net-sdk-v-1-0-step-3
…launchdarkly/dotnet-core into mmccarthy/AIC-2637/net-sdk-v-1-0-step-4
…/ refactor business logic to a private builder method
jsonbailey
reviewed
Jun 9, 2026
…launchdarkly/dotnet-core into mmccarthy/AIC-2638/net-sdk-v-1-0-step-5
jsonbailey
reviewed
Jun 10, 2026
| TrackDuration((float)sw.Elapsed.TotalMilliseconds); | ||
| } | ||
|
|
||
| var metrics = metricsExtractor(result); |
Contributor
There was a problem hiding this comment.
You should wrap this in its own try catch and log a warning if we fail to extract metrics.
Contributor
Author
There was a problem hiding this comment.
Thinking about this more...
The metricsExtractor is a delegate that comes from the user if I'm not mistaken. I think it's better to let the exception happen if it's due to the metricsExtractor failing. I can still log a warning and re-throw but that seems like noise since the exception carries a stacktrace to the user and the error will hit our logs anyway.
My vote: keep it the way it is
jsonbailey
approved these changes
Jun 11, 2026
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
Adds the compound tracking methods and new event types to
ILdAiConfigTracker. Callers can now measure operation duration via a callable wrapper, extract metrics from an operation result in one call, record judge evaluation outcomes, and track tool invocations. The legacyTrackDurationOfTaskandTrackRequestmethods are preserved but marked[Obsolete].Six new methods on
ILdAiConfigTracker(and implementations onLdAiConfigTracker):TrackDurationOf<T>Accepts a callable (not a pre-started task) so the tracker controls when execution begins — duration measurement starts at invocation, not at some earlier
Task.Runcall site. UsesStopwatchfor wall-clock precision. Duration is recorded even if the operation throws (viafinally). Emits$ld:ai:duration:total.Replaces
TrackDurationOfTask<T>(Task<T>), which is now[Obsolete("Use TrackDurationOf instead.")].TrackMetricsOf<T>All-in-one wrapper that tracks duration, success/error, and optional token usage from a single operation. Flow:
operation.TrackDuration→ callsmetricsExtractor(result)→TrackSuccess/TrackErrorbased onAiMetrics.Success→TrackTokensifAiMetrics.Tokensis non-null.TrackDuration→TrackError→ re-throws.Replaces
TrackRequest(Task<Response>), which is now[Obsolete("Use TrackMetricsOf instead.")].TrackJudgeResultRecords a judge evaluation outcome. The event is silently dropped when
result.Sampled == falseorresult.Success == false— this prevents noisy/invalid scores from polluting metrics.When emitted, the event uses
result.MetricKeyas the track event name andresult.Scoreas the metric value. Ifresult.JudgeConfigKeyis non-null, it's merged into the track data alongside the standardrunId/configKey/variationKey/versionfields.TrackToolCallEmits a
$ld:ai:tool_callevent withtoolKeymerged into the track data. Unlike most tracker methods, this is not at-most-once — it may be called multiple times to record every tool invocation in a run (each emits a separate event with metric value1).TrackToolCallsConvenience batch method — iterates
toolKeysand callsTrackToolCallfor each.New types
AiMetrics— immutable record holdingSuccess(bool) and optionalTokens(Usage?) for use withTrackMetricsOf.JudgeResult— immutable record holdingMetricKey,Score,Sampled,Success, and optionalJudgeConfigKeyfor use withTrackJudgeResult.AgentConfigsevent fixAgentConfigsnow fires only the aggregate$ld:ai:usage:agent-configsevent. It calls the privateBuildAgentConfigpath internally — it does NOT call the publicAgentConfig()method and does NOT fire individual$ld:ai:usage:agent-configevents. Tests updated to assertTimes.Neveron individual events.Test plan
dotnet buildsucceeds acrossnetstandard2.0,net462,net8.0dotnet test --framework net8.0passesLdAiConfigTrackerTestcovers the new tracker surface:TrackDurationOf_MeasuresDuration— verifies wall-clock measurement via a 50ms delayTrackMetricsOf_SuccessPath_TracksAllMetrics— verifies duration + success + tokens all emittedTrackMetricsOf_ErrorPath_TracksErrorAndRethrows— verifies duration + error emitted, exception propagatedTrackJudgeResult_SampledFalse_NoEventEmitted— verifies silent dropTrackJudgeResult_SuccessFalse_NoEventEmitted— verifies silent dropTrackJudgeResult_SuccessPath_EmitsCorrectEvent— verifies metric key, score, judgeConfigKey in dataTrackToolCall_DataIncludesToolKey— verifies$ld:ai:tool_callevent with toolKey in dataTrackToolCall_NoAtMostOnce_EmitsMultipleEvents— verifies repeated calls emit separate eventsDeprecatedShims_StillCallable— verifies[Obsolete]methods remain functionalLdAiClientAgentJudgeTest.AgentConfigs_FiresOnlyAggregateEvent— verifies NO individual$ld:ai:usage:agent-configeventsLdAiClientTest.AgentConfigs_OnlyBatchEventFired— same assertion from the client-level testNote
Medium Risk
Changes public tracker contracts and telemetry event shapes; behavior is well-tested but callers migrating off obsolete APIs need to adopt the new wrappers.
Overview
Extends
ILdAiConfigTrackerwith compound wrappers and new event types for the server AI SDK.TrackDurationOftimes aFunc<Task<T>>(so measurement starts at invocation), records$ld:ai:duration:totaleven on failure, and supersedesTrackDurationOfTask, which stays but is[Obsolete].TrackMetricsOfruns an operation, records duration, then applies success/error and optional tokens via a newAiMetricsextractor;TrackRequestis obsolete in favor of this pattern.TrackJudgeResultemits judge scores under a caller metric key when sampled and successful, optionally mergingjudgeConfigKeyinto track data.TrackToolCall/TrackToolCallsemit$ld:ai:tool_callwithtoolKeyand are not at-most-once. Supporting typesAiMetricsandJudgeResultare added;MergeTrackDataenriches events for judge and tool tracking.Tests document
AgentConfigsaggregate-only usage events (no per-key$ld:ai:usage:agent-config) and cover the new tracker APIs plus deprecated shims.Reviewed by Cursor Bugbot for commit 1e44b8d. Bugbot is set up for automated code reviews on this repo. Configure here.