-
Notifications
You must be signed in to change notification settings - Fork 8
refactor: implement AI Client #36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f25033c
ccdbada
8049816
8c34ad5
43aa7aa
25142ac
0d58582
37f395d
c74c9e3
7b0e446
44956bc
42a6b05
59dc7e7
949c635
e7d8fc2
36152b8
96b8fb0
3abb9e5
bdfe78a
dc4fe3d
796b85f
fa56649
aa1310a
b5a354d
e6e4ee7
d9b343b
6ec3f94
8c18ca5
ce64ccf
c513a2f
dd3492d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| using LaunchDarkly.Logging; | ||
| using LaunchDarkly.Sdk.Server.Ai.Interfaces; | ||
|
|
||
| namespace LaunchDarkly.Sdk.Server.Ai.Adapters; | ||
|
|
||
| /// <summary> | ||
| /// Adapts an <see cref="LdClient"/> to the requirements of <see cref="LdAiClient"/>. | ||
| /// </summary> | ||
| public class LdClientAdapter : ILaunchDarklyClient | ||
| { | ||
| private readonly LdClient _client; | ||
|
|
||
| /// <summary> | ||
| /// Constructs the adapter from an existing client. | ||
| /// </summary> | ||
| /// <param name="client">the adapter</param> | ||
| public LdClientAdapter(LdClient client) | ||
| { | ||
| _client = client; | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| public LdValue JsonVariation(string key, Context context, LdValue defaultValue) | ||
| => _client.JsonVariation(key, context, defaultValue); | ||
|
|
||
| /// <inheritdoc/> | ||
| public void Track(string name, Context context, LdValue data, double metricValue) | ||
| => _client.Track(name, context, data, metricValue); | ||
|
|
||
| /// <inheritdoc/> | ||
| public ILogger GetLogger() => new LoggerAdapter(_client.GetLogger()); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| using LaunchDarkly.Logging; | ||
| using LaunchDarkly.Sdk.Server.Ai.Interfaces; | ||
|
|
||
| namespace LaunchDarkly.Sdk.Server.Ai.Adapters; | ||
|
|
||
| /// <summary> | ||
| /// Adapts a <see cref="Logger"/> to the requirements of the <see cref="LdAiClient"/>'s | ||
| /// logger. | ||
| /// </summary> | ||
| internal class LoggerAdapter : ILogger | ||
| { | ||
| private readonly Logger _logger; | ||
|
|
||
| /// <summary> | ||
| /// Creates a new adapter. | ||
| /// </summary> | ||
| /// <param name="logger">the existing logger</param> | ||
| public LoggerAdapter(Logger logger) | ||
| { | ||
| _logger = logger; | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| public void Error(string format, params object[] allParams) => _logger.Error(format, allParams); | ||
|
|
||
| /// <inheritdoc/> | ||
| public void Warn(string format, params object[] allParams) => _logger.Warn(format, allParams); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,191 @@ | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using LaunchDarkly.Sdk.Server.Ai.DataModel; | ||
|
|
||
| namespace LaunchDarkly.Sdk.Server.Ai.Config; | ||
|
|
||
| /// <summary> | ||
| /// Represents an AI configuration, which contains model parameters and prompt messages. | ||
| /// </summary> | ||
| public record LdAiConfig | ||
| { | ||
|
|
||
| /// <summary> | ||
| /// Represents a single message, which is part of a prompt. | ||
| /// </summary> | ||
| public record Message | ||
| { | ||
| /// <summary> | ||
| /// The content of the message, which may contain Mustache templates. | ||
| /// </summary> | ||
| public readonly string Content; | ||
|
|
||
| /// <summary> | ||
| /// The role of the message. | ||
| /// </summary> | ||
| public readonly Role Role; | ||
|
|
||
| internal Message(string content, Role role) | ||
| { | ||
| Content = content; | ||
| Role = role; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Builder for constructing an LdAiConfig instance, which can be passed as the default | ||
| /// value to the AI Client's <see cref="LdAiClient.ModelConfig"/> method. | ||
| /// </summary> | ||
| public class Builder | ||
| { | ||
| private bool _enabled; | ||
| private readonly List<Message> _prompt; | ||
| private readonly Dictionary<string, object> _modelParams; | ||
|
|
||
| internal Builder() | ||
| { | ||
| _enabled = false; | ||
| _prompt = new List<Message>(); | ||
| _modelParams = new Dictionary<string, object>(); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Adds a prompt message with the given content and role. The default role is <see cref="Role.User"/>. | ||
| /// </summary> | ||
| /// <param name="content">the content, which may contain Mustache templates</param> | ||
| /// <param name="role">the role</param> | ||
| /// <returns>a new builder</returns> | ||
| public Builder AddPromptMessage(string content, Role role = Role.User) | ||
| { | ||
| _prompt.Add(new Message(content, role)); | ||
| return this; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Disables the config. | ||
| /// </summary> | ||
| /// <returns>the builder</returns> | ||
| public Builder Disable() => SetEnabled(false); | ||
|
|
||
| /// <summary> | ||
| /// Enables the config. | ||
| /// </summary> | ||
| /// <returns>the builder</returns> | ||
| public Builder Enable() => SetEnabled(true); | ||
|
|
||
| /// <summary> | ||
| /// Sets the enabled state of the config based on a boolean. | ||
| /// </summary> | ||
| /// <param name="enabled">whether the config is enabled</param> | ||
| /// <returns>the builder</returns> | ||
| public Builder SetEnabled(bool enabled) | ||
| { | ||
| _enabled = enabled; | ||
| return this; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Sets a parameter for the model. The value may be any object. | ||
| /// </summary> | ||
| /// <param name="name">the parameter name</param> | ||
| /// <param name="value">the parameter value</param> | ||
| /// <returns>the builder</returns> | ||
| public Builder SetModelParam(string name, object value) | ||
| { | ||
| _modelParams[name] = value; | ||
| return this; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Builds the LdAiConfig instance. | ||
| /// </summary> | ||
| /// <returns>a new LdAiConfig</returns> | ||
| public LdAiConfig Build() | ||
| { | ||
| return new LdAiConfig(_enabled, _prompt, new Meta(), _modelParams); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// The prompts associated with the config. | ||
| /// </summary> | ||
| public readonly IReadOnlyList<Message> Prompt; | ||
|
|
||
| /// <summary> | ||
| /// The model parameters associated with the config. | ||
| /// </summary> | ||
| public readonly IReadOnlyDictionary<string, object> Model; | ||
|
|
||
|
|
||
|
|
||
| internal LdAiConfig(bool enabled, IEnumerable<Message> prompt, Meta meta, IReadOnlyDictionary<string, object> model) | ||
| { | ||
| Model = model ?? new Dictionary<string, object>(); | ||
| Prompt = prompt?.ToList() ?? new List<Message>(); | ||
| VersionKey = meta?.VersionKey ?? ""; | ||
| Enabled = enabled; | ||
| } | ||
|
|
||
| private static LdValue ObjectToValue(object obj) | ||
| { | ||
| if (obj == null) | ||
| { | ||
| return LdValue.Null; | ||
| } | ||
|
|
||
| return obj switch | ||
| { | ||
| bool b => LdValue.Of(b), | ||
| double d => LdValue.Of(d), | ||
| string s => LdValue.Of(s), | ||
| IEnumerable<object> list => LdValue.ArrayFrom(list.Select(ObjectToValue)), | ||
| IDictionary<string, object> dict => LdValue.ObjectFrom(dict.ToDictionary(kv => kv.Key, | ||
| kv => ObjectToValue(kv.Value))), | ||
| _ => LdValue.Null | ||
| }; | ||
| } | ||
|
|
||
| internal LdValue ToLdValue() | ||
| { | ||
| return LdValue.ObjectFrom(new Dictionary<string, LdValue> | ||
| { | ||
| { "_ldMeta", LdValue.ObjectFrom( | ||
| new Dictionary<string, LdValue> | ||
| { | ||
| { "versionKey", LdValue.Of(VersionKey) }, | ||
| { "enabled", LdValue.Of(Enabled) } | ||
| }) }, | ||
| { "prompt", LdValue.ArrayFrom(Prompt.Select(m => LdValue.ObjectFrom(new Dictionary<string, LdValue> | ||
| { | ||
| { "content", LdValue.Of(m.Content) }, | ||
| { "role", LdValue.Of(m.Role.ToString()) } | ||
| }))) }, | ||
| { "model", ObjectToValue(Model) } | ||
| }); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Creates a new LdAiConfig builder. | ||
| /// </summary> | ||
| /// <returns>a new builder</returns> | ||
| public static Builder New() => new(); | ||
cwaldren-ld marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /// <summary> | ||
| /// Returns true if the config is enabled. | ||
| /// </summary> | ||
| /// <returns>true if enabled</returns> | ||
| public bool Enabled { get; } | ||
|
|
||
|
|
||
| /// <summary> | ||
| /// This field meant for internal LaunchDarkly usage. | ||
| /// </summary> | ||
| public string VersionKey { get; } | ||
|
|
||
| /// <summary> | ||
| /// Convenient helper that returns a disabled LdAiConfig. | ||
| /// </summary> | ||
| public static LdAiConfig Disabled = New().Disable().Build(); | ||
|
|
||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| using System.Collections.Generic; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is some duplication here. This file is meant to contain classes that can be directly JSON deserialized, but this doesn't lend itself to the user-facing types. For instance, these fields have I can't make these internal (deserialization seems to fail) and I can't use I may be missing a more idiomatic way.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should be able to tell it to use a parameterized constructor |
||
| using System.Text.Json.Serialization; | ||
|
|
||
| namespace LaunchDarkly.Sdk.Server.Ai.DataModel; | ||
|
|
||
| /// <summary> | ||
| /// Represents the role of the prompt message. | ||
| /// </summary> | ||
| public enum Role | ||
| { | ||
| /// <summary> | ||
| /// User role. | ||
| /// </summary> | ||
| User, | ||
| /// <summary> | ||
| /// System role. | ||
| /// </summary> | ||
| System, | ||
| /// <summary> | ||
| /// Assistant role. | ||
| /// </summary> | ||
| Assistant | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Represents the JSON serialization of the Meta field. | ||
| /// </summary> | ||
| public class Meta | ||
| { | ||
| /// <summary> | ||
| /// The version key. | ||
| /// </summary> | ||
| [JsonPropertyName("versionKey")] | ||
| public string VersionKey { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// If the config is enabled. | ||
| /// </summary> | ||
| [JsonPropertyName("enabled")] | ||
| public bool Enabled { get; set; } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Represents the JSON serialization of a Message. | ||
| /// </summary> | ||
| public class Message | ||
| { | ||
| /// <summary> | ||
| /// The content. | ||
| /// </summary> | ||
| [JsonPropertyName("content")] | ||
| public string Content { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The role. | ||
| /// </summary> | ||
| [JsonPropertyName("role")] | ||
| [JsonConverter(typeof(JsonStringEnumConverter))] | ||
| public Role Role { get; set; } | ||
| } | ||
|
|
||
|
|
||
| /// <summary> | ||
| /// Represents the JSON serialization of an AiConfig. | ||
| /// </summary> | ||
|
|
||
| public class AiConfig | ||
| { | ||
| /// <summary> | ||
| /// The prompt. | ||
| /// </summary> | ||
| [JsonPropertyName("prompt")] | ||
| public List<Message> Prompt { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// LaunchDarkly metadata. | ||
| /// </summary> | ||
| [JsonPropertyName("_ldMeta")] | ||
| public Meta Meta { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The model params; | ||
| /// </summary> | ||
| [JsonPropertyName("model")] | ||
| public Dictionary<string, object> Model { get; set; } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| namespace LaunchDarkly.Sdk.Server.Ai.Interfaces; | ||
|
|
||
| /// <summary> | ||
| /// Interface representing capabilities needed by the AI Client. These are usually provided | ||
| /// by the LaunchDarkly Server SDK. | ||
| /// </summary> | ||
| public interface ILaunchDarklyClient | ||
| { | ||
| /// <summary> | ||
| /// Returns a JSON variation. | ||
| /// </summary> | ||
| /// <param name="key">the flag key</param> | ||
| /// <param name="context">the context</param> | ||
| /// <param name="defaultValue">the default value</param> | ||
| /// <returns>the evaluation result</returns> | ||
| LdValue JsonVariation(string key, Context context, LdValue defaultValue); | ||
|
|
||
| /// <summary> | ||
| /// Tracks a metric. | ||
| /// </summary> | ||
| /// <param name="name">metric name</param> | ||
| /// <param name="context">context</param> | ||
| /// <param name="data">associated data</param> | ||
| /// <param name="metricValue">metric value</param> | ||
| void Track(string name, Context context, LdValue data, double metricValue); | ||
|
|
||
| /// <summary> | ||
| /// Returns a logger. | ||
| /// </summary> | ||
| /// <returns>a logger</returns> | ||
| ILogger GetLogger(); | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.