Skip to content

Commit 34293ca

Browse files
authored
feat: Add AgentConfig, AgentConfigs, and JudgeConfig methods to ILdAiClient (#282)
## Summary Adds the **public client surface** for agents and judges. Callers can now ask `ILdAiClient` for agent configs (single or batch) and judge configs in addition to the existing completion config. Three new methods on `ILdAiClient` (and implementations on `LdAiClient`): ### `AgentConfig` ```csharp public LdAiAgentConfig AgentConfig( string key, Context context, LdAiAgentConfigDefault defaultValue = null, IReadOnlyDictionary<string, object> variables = null); ``` Single-agent retrieval. Mirrors `CompletionConfig`'s shape exactly — same nullable-default semantics (null → `LdAiAgentConfigDefault.Disabled`), same variable-interpolation contract (`ldctx` is always available; user variables are merged in). Emits a `$ld:ai:usage:agent-config` track event with `LdValue.Of(key)` as the data and `1` as the metric value, matching the completion telemetry shape. ### `AgentConfigs` (batch) ```csharp public IReadOnlyDictionary<string, LdAiAgentConfig> AgentConfigs( IEnumerable<AgentConfigRequest> agentConfigs, Context context); ``` Batch retrieval. Each request is evaluated independently via `BuildAgentConfig` (private helper — shared with the singular `AgentConfig` method). The batch fires a **single** `$ld:ai:usage:agent-configs` aggregate event with the request count as both the data and the metric value. No individual `$ld:ai:usage:agent-config` events are fired — the batch method intentionally bypasses the public `AgentConfig` to avoid N+1 tracking events. `AgentConfigRequest` is a new public class with three settable properties: - `Key` — the agent config flag key. - `DefaultValue` — the per-request fallback. When null, the per-call `AgentConfig` invocation supplies `LdAiAgentConfigDefault.Disabled`. - `Variables` — per-request template variables. A null `agentConfigs` argument is treated as an empty enumerable (still fires the aggregate event with count `0`). ### `JudgeConfig` ```csharp public LdAiJudgeConfig JudgeConfig( string key, Context context, LdAiJudgeConfigDefault defaultValue = null, IReadOnlyDictionary<string, object> variables = null); ``` Single-judge retrieval. Same shape as `AgentConfig`. Emits `$ld:ai:usage:judge-config`. No batched form — judges are evaluated one at a time. All three methods route through the same internal pipeline: track usage event → `JsonVariation` (with the default's `ToLdValue()` as the fallback) → the corresponding `ConfigFactory.Build*Config` method. ## Test plan - [ ] `dotnet build` succeeds across `netstandard2.0`, `net462`, `net8.0` - [ ] `dotnet test --framework net8.0` passes - [ ] `LdAiClientAgentJudgeTest` covers the agent surface: - `AgentConfig_BasicRetrieval_ReturnsCorrectFields` - `AgentConfig_ModeMismatch_ReturnsCallerDefault` - `AgentConfig_InstructionsInterpolated` - `AgentConfig_LdCtxInterpolatedInInstructions` - `AgentConfig_FiresUsageEvent` - `AgentConfigs_BatchRetrieval_ReturnsBothConfigs` - `AgentConfigs_FiresIndividualAndAggregateEvents` (verifies the per-request `$ld:ai:usage:agent-config` events **and** the single `$ld:ai:usage:agent-configs` aggregate) - `AgentConfigs_EmptyBatch_ReturnsEmptyAndFiresEvent` - [ ] Same file covers the judge surface: `JudgeConfig_BasicRetrieval_ReturnsCorrectFields`, `JudgeConfig_ModeMismatch_ReturnsCallerDefault`, `JudgeConfig_FiresUsageEvent`, `JudgeConfig_MessagesInterpolated` - [ ] Reviewer confirms method signatures, default-value semantics, and event names match the cross-SDK contract <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > New public SDK surface and usage-event contracts for AI config retrieval; behavior follows existing CompletionConfig patterns but batch telemetry differs from single-call semantics. > > **Overview** > Extends **`ILdAiClient`** / **`LdAiClient`** with agent and judge retrieval alongside existing completion config: **`AgentConfig`**, batch **`AgentConfigs`**, and **`JudgeConfig`**, plus public **`AgentConfigRequest`** (key, per-request default, variables). > > **`AgentConfig`** and **`JudgeConfig`** mirror **`CompletionConfig`**: nullable defaults fall back to disabled types, **`JsonVariation`** + **`ConfigFactory.Build*Config`**, Mustache variables with **`ldctx`**, and per-call usage tracks (`$ld:ai:usage:agent-config`, `$ld:ai:usage:judge-config`). **`AgentConfigs`** evaluates each request via shared **`BuildAgentConfig`** (no per-key usage events), emits a single **`$ld:ai:usage:agent-configs`** event with batch size, and maps keys to configs (duplicate keys in the batch overwrite in the result dictionary while the aggregate count still reflects every request). > > Tests in **`LdAiClientAgentJudgeTest`** and **`LdAiClientTest`** cover retrieval, mode mismatch fallbacks, interpolation, and telemetry behavior. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit f30a2eb. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 69418c8 commit 34293ca

5 files changed

Lines changed: 854 additions & 0 deletions

File tree

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System.Collections.Generic;
2+
3+
namespace LaunchDarkly.Sdk.Server.Ai.Config;
4+
5+
/// <summary>
6+
/// Represents a single agent config request for use with
7+
/// <see cref="LdAiClient.AgentConfigs"/>.
8+
/// </summary>
9+
public class AgentConfigRequest
10+
{
11+
/// <summary>
12+
/// The AI Agent Config key.
13+
/// </summary>
14+
public string Key { get; set; }
15+
16+
/// <summary>
17+
/// The default config to use if the flag cannot be retrieved or has a mode mismatch.
18+
/// When null, a disabled config is used as the fallback.
19+
/// </summary>
20+
public LdAiAgentConfigDefault DefaultValue { get; set; }
21+
22+
/// <summary>
23+
/// Variables used when interpolating Mustache templates in the agent's instructions.
24+
/// </summary>
25+
public IReadOnlyDictionary<string, object> Variables { get; set; }
26+
}

pkgs/sdk/server-ai/src/Interfaces/ILdAiClient.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,54 @@ public LdAiCompletionConfig CompletionConfig(string key, Context context, LdAiCo
4242
public LdAiCompletionConfig Config(string key, Context context, LdAiCompletionConfigDefault defaultValue = null,
4343
IReadOnlyDictionary<string, object> variables = null);
4444

45+
/// <summary>
46+
/// Retrieves a LaunchDarkly AI Agent Config identified by the given key. The return value
47+
/// is an <see cref="LdAiAgentConfig"/>, which provides <c>Instructions</c>, <c>Tools</c>,
48+
/// and a <c>CreateTracker</c> method for generating a tracker that emits model usage events.
49+
///
50+
/// Any variables provided will be interpolated into the agent's instructions.
51+
/// Additionally, the current LaunchDarkly context will be available as 'ldctx' within
52+
/// the instructions template.
53+
/// </summary>
54+
/// <param name="key">the AI Agent Config key</param>
55+
/// <param name="context">the context</param>
56+
/// <param name="defaultValue">the default config, if unable to retrieve from LaunchDarkly. When not provided,
57+
/// a disabled config is used as the fallback.</param>
58+
/// <param name="variables">the list of variables used when interpolating the instructions</param>
59+
/// <returns>an AI Agent Config</returns>
60+
public LdAiAgentConfig AgentConfig(string key, Context context,
61+
LdAiAgentConfigDefault defaultValue = null,
62+
IReadOnlyDictionary<string, object> variables = null);
63+
64+
/// <summary>
65+
/// Retrieves multiple LaunchDarkly AI Agent Configs in a single call. Each request is
66+
/// evaluated independently. A single aggregate usage event is fired for the entire batch.
67+
/// </summary>
68+
/// <param name="agentConfigs">the collection of agent config requests</param>
69+
/// <param name="context">the context</param>
70+
/// <returns>a dictionary mapping each agent config key to its evaluated <see cref="LdAiAgentConfig"/></returns>
71+
public IReadOnlyDictionary<string, LdAiAgentConfig> AgentConfigs(
72+
IEnumerable<AgentConfigRequest> agentConfigs, Context context);
73+
74+
/// <summary>
75+
/// Retrieves a LaunchDarkly AI Judge Config identified by the given key. The return value
76+
/// is an <see cref="LdAiJudgeConfig"/>, which provides <c>Messages</c>, <c>EvaluationMetricKey</c>,
77+
/// and a <c>CreateTracker</c> method for generating a tracker that emits model usage events.
78+
///
79+
/// Any variables provided will be interpolated into the judge's prompt messages.
80+
/// Additionally, the current LaunchDarkly context will be available as 'ldctx' within
81+
/// the message templates.
82+
/// </summary>
83+
/// <param name="key">the AI Judge Config key</param>
84+
/// <param name="context">the context</param>
85+
/// <param name="defaultValue">the default config, if unable to retrieve from LaunchDarkly. When not provided,
86+
/// a disabled config is used as the fallback.</param>
87+
/// <param name="variables">the list of variables used when interpolating the messages</param>
88+
/// <returns>an AI Judge Config</returns>
89+
public LdAiJudgeConfig JudgeConfig(string key, Context context,
90+
LdAiJudgeConfigDefault defaultValue = null,
91+
IReadOnlyDictionary<string, object> variables = null);
92+
4593
/// <summary>
4694
/// Reconstructs a tracker from a resumption token. This enables cross-process scenarios
4795
/// such as deferred feedback, where a tracker's runId needs to be reused in a different

pkgs/sdk/server-ai/src/LdAiClient.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
34
using LaunchDarkly.Sdk.Server.Ai.Adapters;
45
using LaunchDarkly.Sdk.Server.Ai.Config;
56
using LaunchDarkly.Sdk.Server.Ai.Interfaces;
@@ -17,6 +18,9 @@ public sealed class LdAiClient : ILdAiClient
1718

1819
private const string TrackSdkInfo = "$ld:ai:sdk:info";
1920
private const string TrackUsageCompletionConfig = "$ld:ai:usage:completion-config";
21+
private const string TrackUsageAgentConfig = "$ld:ai:usage:agent-config";
22+
private const string TrackUsageAgentConfigs = "$ld:ai:usage:agent-configs";
23+
private const string TrackUsageJudgeConfig = "$ld:ai:usage:judge-config";
2024

2125
/// <summary>
2226
/// Constructs a new LaunchDarkly AI client. Please note, the client library is an alpha release and is
@@ -73,6 +77,48 @@ public LdAiCompletionConfig Config(string key, Context context, LdAiCompletionCo
7377
return CompletionConfig(key, context, defaultValue, variables);
7478
}
7579

80+
private LdAiAgentConfig BuildAgentConfig(string key, Context context,
81+
LdAiAgentConfigDefault defaultValue,
82+
IReadOnlyDictionary<string, object> variables)
83+
{
84+
defaultValue ??= LdAiAgentConfigDefault.Disabled;
85+
var ldValue = _client.JsonVariation(key, context, defaultValue.ToLdValue());
86+
return _factory.BuildAgentConfig(key, ldValue, context, defaultValue, variables);
87+
}
88+
89+
/// <inheritdoc/>
90+
public LdAiAgentConfig AgentConfig(string key, Context context,
91+
LdAiAgentConfigDefault defaultValue = null,
92+
IReadOnlyDictionary<string, object> variables = null)
93+
{
94+
_client.Track(TrackUsageAgentConfig, context, LdValue.Of(key), 1);
95+
return BuildAgentConfig(key, context, defaultValue, variables);
96+
}
97+
98+
/// <inheritdoc/>
99+
public IReadOnlyDictionary<string, LdAiAgentConfig> AgentConfigs(
100+
IEnumerable<AgentConfigRequest> agentConfigs, Context context)
101+
{
102+
var requests = (agentConfigs ?? Enumerable.Empty<AgentConfigRequest>()).ToList();
103+
_client.Track(TrackUsageAgentConfigs, context, LdValue.Of(requests.Count), requests.Count);
104+
var result = new Dictionary<string, LdAiAgentConfig>();
105+
foreach (var req in requests)
106+
{
107+
result[req.Key] = BuildAgentConfig(req.Key, context, req.DefaultValue, req.Variables);
108+
}
109+
return result;
110+
}
111+
112+
/// <inheritdoc/>
113+
public LdAiJudgeConfig JudgeConfig(string key, Context context,
114+
LdAiJudgeConfigDefault defaultValue = null,
115+
IReadOnlyDictionary<string, object> variables = null)
116+
{
117+
defaultValue ??= LdAiJudgeConfigDefault.Disabled;
118+
_client.Track(TrackUsageJudgeConfig, context, LdValue.Of(key), 1);
119+
var ldValue = _client.JsonVariation(key, context, defaultValue.ToLdValue());
120+
return _factory.BuildJudgeConfig(key, ldValue, context, defaultValue, variables);
121+
}
76122

77123
/// <inheritdoc/>
78124
public ILdAiConfigTracker CreateTracker(string resumptionToken, Context context)

0 commit comments

Comments
 (0)