diff --git a/pkgs/sdk/server-ai/src/Config/LdAiConfig.cs b/pkgs/sdk/server-ai/src/Config/LdAiConfig.cs index ea53d6eb..7fe18702 100644 --- a/pkgs/sdk/server-ai/src/Config/LdAiConfig.cs +++ b/pkgs/sdk/server-ai/src/Config/LdAiConfig.cs @@ -79,7 +79,7 @@ internal ModelConfiguration(string name, IReadOnlyDictionary pa /// /// Builder for constructing an LdAiConfig instance, which can be passed as the default - /// value to the AI Client's method. + /// value to the AI Client's method. /// public class Builder { diff --git a/pkgs/sdk/server-ai/src/Interfaces/ILdAiClient.cs b/pkgs/sdk/server-ai/src/Interfaces/ILdAiClient.cs index 2d8009cb..0d3f30d2 100644 --- a/pkgs/sdk/server-ai/src/Interfaces/ILdAiClient.cs +++ b/pkgs/sdk/server-ai/src/Interfaces/ILdAiClient.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using LaunchDarkly.Sdk.Server.Ai.Config; @@ -24,6 +25,18 @@ public interface ILdAiClient /// the default config, if unable to retrieve from LaunchDarkly /// the list of variables used when interpolating the prompt /// an AI Config tracker + public ILdAiConfigTracker CompletionConfig(string key, Context context, LdAiConfig defaultValue, + IReadOnlyDictionary variables = null); + + /// + /// Retrieves a LaunchDarkly AI Config identified by the given key. + /// + /// the AI Config key + /// the context + /// the default config, if unable to retrieve from LaunchDarkly + /// the list of variables used when interpolating the prompt + /// an AI Config tracker + [Obsolete("Use CompletionConfig instead.")] public ILdAiConfigTracker Config(string key, Context context, LdAiConfig defaultValue, IReadOnlyDictionary variables = null); } diff --git a/pkgs/sdk/server-ai/src/LdAiClient.cs b/pkgs/sdk/server-ai/src/LdAiClient.cs index f7528ab7..8cbb82ac 100644 --- a/pkgs/sdk/server-ai/src/LdAiClient.cs +++ b/pkgs/sdk/server-ai/src/LdAiClient.cs @@ -20,6 +20,9 @@ public sealed class LdAiClient : ILdAiClient private readonly ILaunchDarklyClient _client; private readonly ILogger _logger; + private const string TrackSdkInfo = "$ld:ai:sdk:info"; + private const string TrackUsageCompletionConfig = "$ld:ai:usage:completion-config"; + /// /// Constructs a new LaunchDarkly AI client. Please note, the client library is an alpha release and is /// not considered ready for production use. @@ -36,6 +39,17 @@ public LdAiClient(ILaunchDarklyClient client) { _client = client ?? throw new ArgumentNullException(nameof(client)); _logger = _client.GetLogger(); + + _client.Track( + TrackSdkInfo, + Context.Builder(ContextKind.Of("ld_ai"), "ld-internal-tracking").Anonymous(true).Build(), + LdValue.ObjectFrom(new Dictionary + { + { "aiSdkName", LdValue.Of(SdkInfo.Name) }, + { "aiSdkVersion", LdValue.Of(SdkInfo.Version) }, + { "aiSdkLanguage", LdValue.Of(SdkInfo.Language) } + }), + 1); } @@ -44,10 +58,11 @@ public LdAiClient(ILaunchDarklyClient client) private const string LdContextVariable = "ldctx"; /// - public ILdAiConfigTracker Config(string key, Context context, LdAiConfig defaultValue, + public ILdAiConfigTracker CompletionConfig(string key, Context context, LdAiConfig defaultValue, IReadOnlyDictionary variables = null) { - _client.Track("$ld:ai:config:function:single", context, LdValue.Of(key), 1); + _client.Track(TrackUsageCompletionConfig, context, LdValue.Of(key), 1); + var result = _client.JsonVariation(key, context, defaultValue.ToLdValue()); @@ -97,6 +112,21 @@ public ILdAiConfigTracker Config(string key, Context context, LdAiConfig default } + /// + /// Retrieves a LaunchDarkly AI Config identified by the given key. + /// + /// the AI Config key + /// the context + /// the default config, if unable to retrieve from LaunchDarkly + /// the list of variables used when interpolating the prompt + /// an AI Config tracker + [Obsolete("Use CompletionConfig instead.")] + public ILdAiConfigTracker Config(string key, Context context, LdAiConfig defaultValue, + IReadOnlyDictionary variables = null) + { + return CompletionConfig(key, context, defaultValue, variables); + } + private static IDictionary AddSingleKindContextAttributes(Context context) { diff --git a/pkgs/sdk/server-ai/src/SdkInfo.cs b/pkgs/sdk/server-ai/src/SdkInfo.cs new file mode 100644 index 00000000..9e968175 --- /dev/null +++ b/pkgs/sdk/server-ai/src/SdkInfo.cs @@ -0,0 +1,22 @@ +namespace LaunchDarkly.Sdk.Server.Ai; + +/// +/// Contains metadata about the AI SDK, such as its name, version, and implementation language. +/// +public static class SdkInfo +{ + /// + /// The name of the AI SDK package. + /// + public const string Name = "LaunchDarkly.ServerSdk.Ai"; + + /// + /// The version of the AI SDK package. + /// + public const string Version = "0.9.1"; // x-release-please-version + + /// + /// The implementation language. + /// + public const string Language = "dotnet"; +} diff --git a/pkgs/sdk/server-ai/test/LdAiClientTest.cs b/pkgs/sdk/server-ai/test/LdAiClientTest.cs index f650c2b1..4d8b2639 100644 --- a/pkgs/sdk/server-ai/test/LdAiClientTest.cs +++ b/pkgs/sdk/server-ai/test/LdAiClientTest.cs @@ -16,7 +16,7 @@ public void CanInstantiateWithServerSideClient() { var client = new LdClientAdapter(new LdClient(Configuration.Builder("key").Offline(true).Build())); var aiClient = new LdAiClient(client); - var result= aiClient.Config("foo", Context.New("key"), LdAiConfig.Disabled); + var result= aiClient.CompletionConfig("foo", Context.New("key"), LdAiConfig.Disabled); Assert.False(result.Config.Enabled); } @@ -44,13 +44,13 @@ public void ReturnsDefaultConfigWhenGivenInvalidVariation() var defaultConfig = LdAiConfig.New().AddMessage("Hello").Build(); - var tracker = client.Config("foo", Context.New(ContextKind.Default, "key"), defaultConfig); + var tracker = client.CompletionConfig("foo", Context.New(ContextKind.Default, "key"), defaultConfig); Assert.Equal(defaultConfig, tracker.Config); } [Fact] - public void ConfigMethodCallsTrackWithCorrectParameters() + public void CompletionConfigMethodCallsTrackWithCorrectParameters() { var mockClient = new Mock(); var context = Context.New(ContextKind.Default, "user-key"); @@ -85,10 +85,10 @@ public void ConfigMethodCallsTrackWithCorrectParameters() var client = new LdAiClient(mockClient.Object); var defaultConfig = LdAiConfig.New().Build(); - var tracker = client.Config(configKey, context, defaultConfig); + var tracker = client.CompletionConfig(configKey, context, defaultConfig); mockClient.Verify(c => c.Track( - "$ld:ai:config:function:single", + "$ld:ai:usage:completion-config", context, LdValue.Of(configKey), 1), Times.Once); @@ -96,6 +96,29 @@ public void ConfigMethodCallsTrackWithCorrectParameters() Assert.NotNull(tracker); } + [Fact] + public void ConstructorTracksSdkInfo() + { + var mockClient = new Mock(); + var mockLogger = new Mock(); + mockClient.Setup(x => x.GetLogger()).Returns(mockLogger.Object); + + var client = new LdAiClient(mockClient.Object); + Assert.NotNull(client); + + mockClient.Verify(c => c.Track( + "$ld:ai:sdk:info", + It.Is(ctx => + ctx.Kind == ContextKind.Of("ld_ai") && + ctx.Key == "ld-internal-tracking" && + ctx.Anonymous), + It.Is(v => + v.Get("aiSdkName").AsString == SdkInfo.Name && + v.Get("aiSdkVersion").AsString == SdkInfo.Version && + v.Get("aiSdkLanguage").AsString == SdkInfo.Language), + 1), Times.Once); + } + private const string MetaDisabledExplicitly = """ { "_ldMeta": {"variationKey": "1", "enabled": false}, @@ -142,7 +165,7 @@ public void ConfigNotEnabledReturnsDisabledInstance(string json) // All the JSON inputs here are considered disabled, either due to lack of the 'enabled' property, // or if present, it is set to false. Therefore, if the default was returned, we'd see the assertion fail // (since calling LdAiConfig.New() constructs an enabled config by default.) - var tracker = client.Config("foo", Context.New(ContextKind.Default, "key"), + var tracker = client.CompletionConfig("foo", Context.New(ContextKind.Default, "key"), LdAiConfig.New().AddMessage("foo").Build()); Assert.False(tracker.Config.Enabled); @@ -162,7 +185,7 @@ public void CanSetAllDefaultValueFields() var client = new LdAiClient(mockClient.Object); - var tracker = client.Config("foo", Context.New(ContextKind.Default, "key"), + var tracker = client.CompletionConfig("foo", Context.New(ContextKind.Default, "key"), LdAiConfig.New(). AddMessage("foo"). SetModelParam("foo", LdValue.Of("bar")). @@ -207,7 +230,7 @@ public void ConfigEnabledReturnsInstance() var client = new LdAiClient(mockClient.Object); // We shouldn't get this default. - var tracker = client.Config("foo", context, + var tracker = client.CompletionConfig("foo", context, LdAiConfig.New().AddMessage("Goodbye!").Build()); Assert.Collection(tracker.Config.Messages, @@ -259,7 +282,7 @@ public void ModelParametersAreParsed() var client = new LdAiClient(mockClient.Object); // We shouldn't get this default. - var tracker = client.Config("foo", context, + var tracker = client.CompletionConfig("foo", context, LdAiConfig.New().AddMessage("Goodbye!").Build()); Assert.Equal("model-foo", tracker.Config.Model.Name); @@ -296,7 +319,7 @@ public void ProviderConfigIsParsed() var client = new LdAiClient(mockClient.Object); // We shouldn't get this default. - var tracker = client.Config("foo", context, + var tracker = client.CompletionConfig("foo", context, LdAiConfig.New().AddMessage("Goodbye!").Build()); Assert.Equal("amazing-provider", tracker.Config.Provider.Name);