Skip to content

Commit 376b6b0

Browse files
authored
fix: Improve usage reporting (#228)
1 parent d4d57b1 commit 376b6b0

File tree

6 files changed

+116
-19
lines changed

6 files changed

+116
-19
lines changed

pkgs/sdk/server-ai/src/Config/LdAiConfig.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ internal ModelConfiguration(string name, IReadOnlyDictionary<string, LdValue> pa
7979

8080
/// <summary>
8181
/// Builder for constructing an LdAiConfig instance, which can be passed as the default
82-
/// value to the AI Client's <see cref="LdAiClient.Config"/> method.
82+
/// value to the AI Client's <see cref="LdAiClient.CompletionConfig"/> method.
8383
/// </summary>
8484
public class Builder
8585
{

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Collections.Generic;
23
using LaunchDarkly.Sdk.Server.Ai.Config;
34

@@ -10,7 +11,7 @@ public interface ILdAiClient
1011
{
1112

1213
/// <summary>
13-
/// Retrieves a LaunchDarkly AI Config identified by the given key. The return value
14+
/// Retrieves a LaunchDarkly AI Completion Config identified by the given key. The return value
1415
/// is an <see cref="ILdAiConfigTracker"/>, which makes the configuration available and
1516
/// provides convenience methods for generating events related to model usage.
1617
///
@@ -19,11 +20,23 @@ public interface ILdAiClient
1920
/// a prompt message.
2021
///
2122
/// </summary>
22-
/// <param name="key">the AI Config key</param>
23+
/// <param name="key">the AI Completion Config key</param>
2324
/// <param name="context">the context</param>
2425
/// <param name="defaultValue">the default config, if unable to retrieve from LaunchDarkly</param>
2526
/// <param name="variables">the list of variables used when interpolating the prompt</param>
26-
/// <returns>an AI Config tracker</returns>
27+
/// <returns>an AI Completion Config tracker</returns>
28+
public ILdAiConfigTracker CompletionConfig(string key, Context context, LdAiConfig defaultValue,
29+
IReadOnlyDictionary<string, object> variables = null);
30+
31+
/// <summary>
32+
/// Retrieves a LaunchDarkly AI Completion Config identified by the given key.
33+
/// </summary>
34+
/// <param name="key">the AI Completion Config key</param>
35+
/// <param name="context">the context</param>
36+
/// <param name="defaultValue">the default config, if unable to retrieve from LaunchDarkly</param>
37+
/// <param name="variables">the list of variables used when interpolating the prompt</param>
38+
/// <returns>an AI Completion Config tracker</returns>
39+
[Obsolete("Use CompletionConfig instead.")]
2740
public ILdAiConfigTracker Config(string key, Context context, LdAiConfig defaultValue,
2841
IReadOnlyDictionary<string, object> variables = null);
2942
}

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

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ public sealed class LdAiClient : ILdAiClient
2020
private readonly ILaunchDarklyClient _client;
2121
private readonly ILogger _logger;
2222

23+
private const string TrackSdkInfo = "$ld:ai:sdk:info";
24+
private const string TrackUsageCompletionConfig = "$ld:ai:usage:completion-config";
25+
2326
/// <summary>
2427
/// Constructs a new LaunchDarkly AI client. Please note, the client library is an alpha release and is
2528
/// not considered ready for production use.
@@ -36,6 +39,17 @@ public LdAiClient(ILaunchDarklyClient client)
3639
{
3740
_client = client ?? throw new ArgumentNullException(nameof(client));
3841
_logger = _client.GetLogger();
42+
43+
_client.Track(
44+
TrackSdkInfo,
45+
Context.Builder(ContextKind.Of("ld_ai"), "ld-internal-tracking").Anonymous(true).Build(),
46+
LdValue.ObjectFrom(new Dictionary<string, LdValue>
47+
{
48+
{ "aiSdkName", LdValue.Of(SdkInfo.Name) },
49+
{ "aiSdkVersion", LdValue.Of(SdkInfo.Version) },
50+
{ "aiSdkLanguage", LdValue.Of(SdkInfo.Language) }
51+
}),
52+
1);
3953
}
4054

4155

@@ -44,11 +58,22 @@ public LdAiClient(ILaunchDarklyClient client)
4458
private const string LdContextVariable = "ldctx";
4559

4660
/// <inheritdoc/>
47-
public ILdAiConfigTracker Config(string key, Context context, LdAiConfig defaultValue,
61+
public ILdAiConfigTracker CompletionConfig(string key, Context context, LdAiConfig defaultValue,
4862
IReadOnlyDictionary<string, object> variables = null)
4963
{
50-
_client.Track("$ld:ai:config:function:single", context, LdValue.Of(key), 1);
64+
_client.Track(TrackUsageCompletionConfig, context, LdValue.Of(key), 1);
5165

66+
return Evaluate(key, context, defaultValue, variables);
67+
}
68+
69+
/// <summary>
70+
/// Internal evaluation method that retrieves and parses an AI Config without tracking usage.
71+
/// This allows higher-level SDK entry methods to track their own usage events without
72+
/// double-counting.
73+
/// </summary>
74+
private ILdAiConfigTracker Evaluate(string key, Context context, LdAiConfig defaultValue,
75+
IReadOnlyDictionary<string, object> variables = null)
76+
{
5277
var result = _client.JsonVariation(key, context, defaultValue.ToLdValue());
5378

5479
var parsed = ParseConfig(result, key);
@@ -72,7 +97,6 @@ public ILdAiConfigTracker Config(string key, Context context, LdAiConfig default
7297
}
7398
}
7499

75-
76100
var prompt = new List<LdAiConfig.Message>();
77101

78102
if (parsed.Messages != null)
@@ -94,7 +118,21 @@ public ILdAiConfigTracker Config(string key, Context context, LdAiConfig default
94118
}
95119

96120
return new LdAiConfigTracker(_client, key, new LdAiConfig(parsed.Meta?.Enabled ?? false, prompt, parsed.Meta, parsed.Model, parsed.Provider), context);
121+
}
97122

123+
/// <summary>
124+
/// Retrieves a LaunchDarkly AI Completion Config identified by the given key.
125+
/// </summary>
126+
/// <param name="key">the AI Completion Config key</param>
127+
/// <param name="context">the context</param>
128+
/// <param name="defaultValue">the default config, if unable to retrieve from LaunchDarkly</param>
129+
/// <param name="variables">the list of variables used when interpolating the prompt</param>
130+
/// <returns>an AI Completion Config tracker</returns>
131+
[Obsolete("Use CompletionConfig instead.")]
132+
public ILdAiConfigTracker Config(string key, Context context, LdAiConfig defaultValue,
133+
IReadOnlyDictionary<string, object> variables = null)
134+
{
135+
return CompletionConfig(key, context, defaultValue, variables);
98136
}
99137

100138

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
namespace LaunchDarkly.Sdk.Server.Ai;
2+
3+
/// <summary>
4+
/// Contains metadata about the AI SDK, such as its name, version, and implementation language.
5+
/// </summary>
6+
public static class SdkInfo
7+
{
8+
/// <summary>
9+
/// The name of the AI SDK package.
10+
/// </summary>
11+
public const string Name = "LaunchDarkly.ServerSdk.Ai";
12+
13+
/// <summary>
14+
/// The version of the AI SDK package.
15+
/// </summary>
16+
public const string Version = "0.9.1"; // x-release-please-version
17+
18+
/// <summary>
19+
/// The implementation language.
20+
/// </summary>
21+
public const string Language = "dotnet";
22+
}

pkgs/sdk/server-ai/test/LdAiClientTest.cs

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public void CanInstantiateWithServerSideClient()
1616
{
1717
var client = new LdClientAdapter(new LdClient(Configuration.Builder("key").Offline(true).Build()));
1818
var aiClient = new LdAiClient(client);
19-
var result= aiClient.Config("foo", Context.New("key"), LdAiConfig.Disabled);
19+
var result= aiClient.CompletionConfig("foo", Context.New("key"), LdAiConfig.Disabled);
2020
Assert.False(result.Config.Enabled);
2121
}
2222

@@ -44,13 +44,13 @@ public void ReturnsDefaultConfigWhenGivenInvalidVariation()
4444

4545
var defaultConfig = LdAiConfig.New().AddMessage("Hello").Build();
4646

47-
var tracker = client.Config("foo", Context.New(ContextKind.Default, "key"), defaultConfig);
47+
var tracker = client.CompletionConfig("foo", Context.New(ContextKind.Default, "key"), defaultConfig);
4848

4949
Assert.Equal(defaultConfig, tracker.Config);
5050
}
5151

5252
[Fact]
53-
public void ConfigMethodCallsTrackWithCorrectParameters()
53+
public void CompletionConfigMethodCallsTrackWithCorrectParameters()
5454
{
5555
var mockClient = new Mock<ILaunchDarklyClient>();
5656
var context = Context.New(ContextKind.Default, "user-key");
@@ -85,17 +85,40 @@ public void ConfigMethodCallsTrackWithCorrectParameters()
8585
var client = new LdAiClient(mockClient.Object);
8686
var defaultConfig = LdAiConfig.New().Build();
8787

88-
var tracker = client.Config(configKey, context, defaultConfig);
88+
var tracker = client.CompletionConfig(configKey, context, defaultConfig);
8989

9090
mockClient.Verify(c => c.Track(
91-
"$ld:ai:config:function:single",
91+
"$ld:ai:usage:completion-config",
9292
context,
9393
LdValue.Of(configKey),
9494
1), Times.Once);
9595

9696
Assert.NotNull(tracker);
9797
}
9898

99+
[Fact]
100+
public void ConstructorTracksSdkInfo()
101+
{
102+
var mockClient = new Mock<ILaunchDarklyClient>();
103+
var mockLogger = new Mock<ILogger>();
104+
mockClient.Setup(x => x.GetLogger()).Returns(mockLogger.Object);
105+
106+
var client = new LdAiClient(mockClient.Object);
107+
Assert.NotNull(client);
108+
109+
mockClient.Verify(c => c.Track(
110+
"$ld:ai:sdk:info",
111+
It.Is<Context>(ctx =>
112+
ctx.Kind == ContextKind.Of("ld_ai") &&
113+
ctx.Key == "ld-internal-tracking" &&
114+
ctx.Anonymous),
115+
It.Is<LdValue>(v =>
116+
v.Get("aiSdkName").AsString == SdkInfo.Name &&
117+
v.Get("aiSdkVersion").AsString == SdkInfo.Version &&
118+
v.Get("aiSdkLanguage").AsString == SdkInfo.Language),
119+
1), Times.Once);
120+
}
121+
99122
private const string MetaDisabledExplicitly = """
100123
{
101124
"_ldMeta": {"variationKey": "1", "enabled": false},
@@ -142,7 +165,7 @@ public void ConfigNotEnabledReturnsDisabledInstance(string json)
142165
// All the JSON inputs here are considered disabled, either due to lack of the 'enabled' property,
143166
// or if present, it is set to false. Therefore, if the default was returned, we'd see the assertion fail
144167
// (since calling LdAiConfig.New() constructs an enabled config by default.)
145-
var tracker = client.Config("foo", Context.New(ContextKind.Default, "key"),
168+
var tracker = client.CompletionConfig("foo", Context.New(ContextKind.Default, "key"),
146169
LdAiConfig.New().AddMessage("foo").Build());
147170

148171
Assert.False(tracker.Config.Enabled);
@@ -162,7 +185,7 @@ public void CanSetAllDefaultValueFields()
162185

163186
var client = new LdAiClient(mockClient.Object);
164187

165-
var tracker = client.Config("foo", Context.New(ContextKind.Default, "key"),
188+
var tracker = client.CompletionConfig("foo", Context.New(ContextKind.Default, "key"),
166189
LdAiConfig.New().
167190
AddMessage("foo").
168191
SetModelParam("foo", LdValue.Of("bar")).
@@ -207,7 +230,7 @@ public void ConfigEnabledReturnsInstance()
207230
var client = new LdAiClient(mockClient.Object);
208231

209232
// We shouldn't get this default.
210-
var tracker = client.Config("foo", context,
233+
var tracker = client.CompletionConfig("foo", context,
211234
LdAiConfig.New().AddMessage("Goodbye!").Build());
212235

213236
Assert.Collection(tracker.Config.Messages,
@@ -259,7 +282,7 @@ public void ModelParametersAreParsed()
259282
var client = new LdAiClient(mockClient.Object);
260283

261284
// We shouldn't get this default.
262-
var tracker = client.Config("foo", context,
285+
var tracker = client.CompletionConfig("foo", context,
263286
LdAiConfig.New().AddMessage("Goodbye!").Build());
264287

265288
Assert.Equal("model-foo", tracker.Config.Model.Name);
@@ -296,7 +319,7 @@ public void ProviderConfigIsParsed()
296319
var client = new LdAiClient(mockClient.Object);
297320

298321
// We shouldn't get this default.
299-
var tracker = client.Config("foo", context,
322+
var tracker = client.CompletionConfig("foo", context,
300323
LdAiConfig.New().AddMessage("Goodbye!").Build());
301324

302325
Assert.Equal("amazing-provider", tracker.Config.Provider.Name);

release-please-config.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,11 @@
3636
"package-name": "LaunchDarkly.ServerSdk.Ai",
3737
"bump-minor-pre-major": true,
3838
"extra-files": [
39-
"src/LaunchDarkly.ServerSdk.Ai.csproj"
39+
"src/LaunchDarkly.ServerSdk.Ai.csproj",
40+
"src/SdkInfo.cs"
4041
]
4142
},
42-
"pkgs/sdk/client": {
43+
"pkgs/sdk/client":{
4344
"release-type": "simple",
4445
"package-name": "LaunchDarkly.ClientSdk",
4546
"extra-files": [

0 commit comments

Comments
 (0)