Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkgs/sdk/server-ai/src/Config/LdAiConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ internal ModelConfiguration(string name, IReadOnlyDictionary<string, LdValue> pa

/// <summary>
/// Builder for constructing an LdAiConfig instance, which can be passed as the default
/// value to the AI Client's <see cref="LdAiClient.Config"/> method.
/// value to the AI Client's <see cref="LdAiClient.CompletionConfig"/> method.
/// </summary>
public class Builder
{
Expand Down
13 changes: 13 additions & 0 deletions pkgs/sdk/server-ai/src/Interfaces/ILdAiClient.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using LaunchDarkly.Sdk.Server.Ai.Config;

Expand All @@ -24,6 +25,18 @@ public interface ILdAiClient
/// <param name="defaultValue">the default config, if unable to retrieve from LaunchDarkly</param>
/// <param name="variables">the list of variables used when interpolating the prompt</param>
/// <returns>an AI Config tracker</returns>
public ILdAiConfigTracker CompletionConfig(string key, Context context, LdAiConfig defaultValue,
IReadOnlyDictionary<string, object> variables = null);

/// <summary>
/// Retrieves a LaunchDarkly AI Config identified by the given key.
/// </summary>
/// <param name="key">the AI Config key</param>
/// <param name="context">the context</param>
/// <param name="defaultValue">the default config, if unable to retrieve from LaunchDarkly</param>
/// <param name="variables">the list of variables used when interpolating the prompt</param>
/// <returns>an AI Config tracker</returns>
[Obsolete("Use CompletionConfig instead.")]
public ILdAiConfigTracker Config(string key, Context context, LdAiConfig defaultValue,
IReadOnlyDictionary<string, object> variables = null);
}
34 changes: 32 additions & 2 deletions pkgs/sdk/server-ai/src/LdAiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";

/// <summary>
/// Constructs a new LaunchDarkly AI client. Please note, the client library is an alpha release and is
/// not considered ready for production use.
Expand All @@ -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<string, LdValue>
{
{ "aiSdkName", LdValue.Of(SdkInfo.Name) },
{ "aiSdkVersion", LdValue.Of(SdkInfo.Version) },
{ "aiSdkLanguage", LdValue.Of(SdkInfo.Language) }
}),
1);
}


Expand All @@ -44,10 +58,11 @@ public LdAiClient(ILaunchDarklyClient client)
private const string LdContextVariable = "ldctx";

/// <inheritdoc/>
public ILdAiConfigTracker Config(string key, Context context, LdAiConfig defaultValue,
public ILdAiConfigTracker CompletionConfig(string key, Context context, LdAiConfig defaultValue,
IReadOnlyDictionary<string, object> 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());

Expand Down Expand Up @@ -97,6 +112,21 @@ public ILdAiConfigTracker Config(string key, Context context, LdAiConfig default

}

/// <summary>
/// Retrieves a LaunchDarkly AI Config identified by the given key.
/// </summary>
/// <param name="key">the AI Config key</param>
/// <param name="context">the context</param>
/// <param name="defaultValue">the default config, if unable to retrieve from LaunchDarkly</param>
/// <param name="variables">the list of variables used when interpolating the prompt</param>
/// <returns>an AI Config tracker</returns>
[Obsolete("Use CompletionConfig instead.")]
public ILdAiConfigTracker Config(string key, Context context, LdAiConfig defaultValue,
IReadOnlyDictionary<string, object> variables = null)
{
return CompletionConfig(key, context, defaultValue, variables);
}


private static IDictionary<string, object> AddSingleKindContextAttributes(Context context)
{
Expand Down
22 changes: 22 additions & 0 deletions pkgs/sdk/server-ai/src/SdkInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace LaunchDarkly.Sdk.Server.Ai;

/// <summary>
/// Contains metadata about the AI SDK, such as its name, version, and implementation language.
/// </summary>
public static class SdkInfo
{
/// <summary>
/// The name of the AI SDK package.
/// </summary>
public const string Name = "LaunchDarkly.ServerSdk.Ai";

/// <summary>
/// The version of the AI SDK package.
/// </summary>
public const string Version = "0.9.1"; // x-release-please-version

/// <summary>
/// The implementation language.
/// </summary>
public const string Language = "dotnet";
}
43 changes: 33 additions & 10 deletions pkgs/sdk/server-ai/test/LdAiClientTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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<ILaunchDarklyClient>();
var context = Context.New(ContextKind.Default, "user-key");
Expand Down Expand Up @@ -85,17 +85,40 @@ 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);

Assert.NotNull(tracker);
}

[Fact]
public void ConstructorTracksSdkInfo()
{
var mockClient = new Mock<ILaunchDarklyClient>();
var mockLogger = new Mock<ILogger>();
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<Context>(ctx =>
ctx.Kind == ContextKind.Of("ld_ai") &&
ctx.Key == "ld-internal-tracking" &&
ctx.Anonymous),
It.Is<LdValue>(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},
Expand Down Expand Up @@ -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);
Expand All @@ -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")).
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Loading