launchdarkly-server-sdk-ai 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cb83c54faa06f2d86966c06bcd8ad85cd6709f2d638b5a0cf999f19b71995e42
4
- data.tar.gz: a83da3879e8416eaca917534b1de39cb1e3b106820709708f8469a9d1fd74375
3
+ metadata.gz: 4eca8fb4b72e683402ec14537df6b5570c2587004f9fbcd17d8cc9f7038dd46d
4
+ data.tar.gz: 0df3584ec493ea24d3d05e867be8243a1b336b94a72943b56f78e1cec6c524d2
5
5
  SHA512:
6
- metadata.gz: b81d0f405b0179ea765e4619bd9512624a131d60bb523c2314384c458307fde0989c4217f80133eb7b907270da362ae08267f7a4143bc9b27b48cf99b6cf0634
7
- data.tar.gz: 542ae97015889716d185f29eaa31d597af2aec84249b9e9dd77c5e11ef88e0f049c2f656f32c7e77952d9855a91a03208f4ffdeaf2a4f95d5d6dbb0446f1fd67
6
+ metadata.gz: 28792c2f4453c076312b8d9aa6cc56ce8b4fcadb2ad599ff73d0d6853bd037287b5d19ee38afffe0f98790fe7fe197c024ba863a719c1188d28efc041e9c8a6d
7
+ data.tar.gz: d116af21d8ecf7e40182d688d8eb1cf29780c15356d31e7fefcdb7ecbef4f2929b76586cf6906f5ac17e1cc807c7e359f546325aba0f48767791ea24c5b524bb
data/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ # Changelog
2
+
3
+ ## [0.1.0](https://github.com/launchdarkly/ruby-server-sdk-ai/compare/0.0.0...0.1.0) (2025-06-18)
4
+
5
+
6
+ ### Features
7
+
8
+ * Implement the AIClient and AITracker classes ([#1](https://github.com/launchdarkly/ruby-server-sdk-ai/issues/1)) ([7511fe9](https://github.com/launchdarkly/ruby-server-sdk-ai/commit/7511fe96e7eb9cec2140d0292fe251c2fb161840))
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,54 @@
1
+ # Contributing to the LaunchDarkly Server-side AI library for Ruby
2
+
3
+ LaunchDarkly has published an [SDK contributor's guide](https://docs.launchdarkly.com/sdk/concepts/contributors-guide) that provides a detailed explanation of how our SDKs work. See below for additional information on how to contribute to this library.
4
+
5
+ ## Submitting bug reports and feature requests
6
+
7
+ The LaunchDarkly SDK team monitors the [issue tracker](https://github.com/launchdarkly/ruby-server-sdk-ai/issues) in the library repository. Bug reports and feature requests specific to this library should be filed in this issue tracker. The SDK team will respond to all newly filed issues within two business days.
8
+
9
+ ## Submitting pull requests
10
+
11
+ We encourage pull requests and other contributions from the community. Before submitting pull requests, ensure that all temporary or unintended code is removed. Don't worry about adding reviewers to the pull request; the LaunchDarkly SDK team will add themselves. The SDK team will acknowledge all pull requests within two business days.
12
+
13
+ ## Build instructions
14
+
15
+ ### Prerequisites
16
+
17
+ This library is built with [Bundler](https://bundler.io/). To install Bundler, run `gem install bundler`. You might need `sudo` to execute the command successfully.
18
+
19
+ To install the runtime dependencies:
20
+
21
+ ```
22
+ bundle install
23
+ ```
24
+
25
+ ### Testing
26
+
27
+ To run all unit tests:
28
+
29
+ ```
30
+ bundle exec rspec spec
31
+ ```
32
+
33
+ ### Building documentation
34
+
35
+ Documentation is built automatically with YARD for each release. To build the documentation locally:
36
+
37
+ ```
38
+ cd docs
39
+ make
40
+ ```
41
+
42
+ The output will appear in `docs/build/html`.
43
+
44
+ ## Code organization
45
+
46
+ A special case is the namespace `LaunchDarkly::Server::AI::Impl`, and any namespaces within it. Everything under `Impl` is considered a private implementation detail: all files there are excluded from the generated documentation, and are considered subject to change at any time and not supported for direct use by application developers. We do this because Ruby's scope/visibility system is somewhat limited compared to other languages: a method can be `private` or `protected` within a class, but there is no way to make it visible to other classes in the library yet invisible to code outside of the library, and there is similarly no way to hide a class.
47
+
48
+ So, if there is a class whose existence is entirely an implementation detail, it should be in `Impl`. Similarly, classes that are _not_ in `Impl` must not expose any public members that are not meant to be part of the supported public API. This is important because of our guarantee of backward compatibility for all public APIs within a major version: we want to be able to change our implementation details to suit the needs of the code, without worrying about breaking a customer's code. Due to how the language works, we can't actually prevent an application developer from referencing those classes in their code, but this convention makes it clear that such use is discouraged and unsupported.
49
+
50
+ ## Documenting types and methods
51
+
52
+ All classes and public methods outside of `LaunchDarkly::Server::AI::Impl` should have documentation comments. These are used to build the API documentation that is published at https://launchdarkly.github.io/ruby-server-sdk-ai/ and https://www.rubydoc.info/gems/launchdarkly-server-sdk-ai. The documentation generator is YARD; see https://yardoc.org/ for the comment format it uses.
53
+
54
+ Please try to make the style and terminology in documentation comments consistent with other documentation comments in the library. Also, if a class or method is being added that has an equivalent in other libraries, and if we have described it in a consistent away in those other libraries, please reuse the text whenever possible (with adjustments for anything language-specific) rather than writing new text.
data/PROVENANCE.md ADDED
@@ -0,0 +1,43 @@
1
+ ## Verifying SDK build provenance with the SLSA framework
2
+
3
+ LaunchDarkly uses the [SLSA framework](https://slsa.dev/spec/v1.0/about) (Supply-chain Levels for Software Artifacts) to help developers make their supply chain more secure by ensuring the authenticity and build integrity of our published SDK packages.
4
+
5
+ As part of [SLSA requirements for level 3 compliance](https://slsa.dev/spec/v1.0/requirements), LaunchDarkly publishes provenance about our SDK package builds using [GitHub's generic SLSA3 provenance generator](https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/generic/README.md#generation-of-slsa3-provenance-for-arbitrary-projects) for distribution alongside our packages. These attestations are available for download from the GitHub release page for the release version under Assets > `multiple-provenance.intoto.jsonl`.
6
+
7
+ To verify SLSA provenance attestations, we recommend using [slsa-verifier](https://github.com/slsa-framework/slsa-verifier). Example usage for verifying SDK packages is included below:
8
+
9
+ <!-- x-release-please-start-version -->
10
+ ```
11
+ # Set the version of the SDK to verify
12
+ VERSION=0.1.0
13
+ ```
14
+ <!-- x-release-please-end -->
15
+
16
+ ```
17
+ # Download gem
18
+ $ gem fetch launchdarkly-server-sdk-ai -v $VERSION
19
+
20
+ # Download provenance from Github release
21
+ $ curl --location -O \
22
+ https://github.com/launchdarkly/ruby-server-sdk-ai/releases/download/${VERSION}/launchdarkly-server-sdk-ai-${VERSION}.gem.intoto.jsonl
23
+
24
+ # Run slsa-verifier to verify provenance against package artifacts
25
+ $ slsa-verifier verify-artifact \
26
+ --provenance-path launchdarkly-server-sdk-ai-${VERSION}.gem.intoto.jsonl \
27
+ --source-uri github.com/launchdarkly/ruby-server-sdk-ai \
28
+ launchdarkly-server-sdk-ai-${VERSION}.gem
29
+ ```
30
+
31
+ Below is a sample of expected output.
32
+ TODO: Verify these are accurate
33
+ ```
34
+ Verified signature against tlog entry index 83653185 at URL: https://rekor.sigstore.dev/api/v1/log/entries/24296fb24b8ad77a7df0bbf87a7d5fcaafa551a2101d9f993d251a56a918bb113e81d2c575dc7e25
35
+ Verified build using builder "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@refs/tags/v1.10.0" at commit 14c48a68c45871c27409591969e7f4c0ebdcdf62
36
+ Verifying artifact launchdarkly-server-sdk-ai-1.0.0.gem: PASSED
37
+
38
+ PASSED: Verified SLSA provenance
39
+ ```
40
+
41
+ Alternatively, to verify the provenance manually, the SLSA framework specifies [recommendations for verifying build artifacts](https://slsa.dev/spec/v1.0/verifying-artifacts) in their documentation.
42
+
43
+ **Note:** These instructions do not apply when building our libraries from source.
data/README.md ADDED
@@ -0,0 +1,34 @@
1
+ LaunchDarkly Server-side AI library for Ruby
2
+ ==============================================
3
+
4
+ Learn more
5
+ -----------
6
+
7
+ Read our [documentation](http://docs.launchdarkly.com) for in-depth instructions on configuring and using LaunchDarkly. You can also head straight to the [reference guide for the ruby SDK](http://docs.launchdarkly.com/docs/ruby-sdk-reference).
8
+
9
+ Generated API documentation for all versions of the library is on [RubyDoc.info](https://www.rubydoc.info/gems/launchdarkly-server-sdk-ai). The API documentation for the latest version is also on [GitHub Pages](https://launchdarkly.github.io/ruby-server-sdk-ai).
10
+
11
+ Contributing
12
+ ------------
13
+
14
+ We encourage pull requests and other contributions from the community. Check out our [contributing guidelines](CONTRIBUTING.md) for instructions on how to contribute to this library.
15
+
16
+ Verifying library build provenance with the SLSA framework
17
+ ------------
18
+
19
+ LaunchDarkly uses the [SLSA framework](https://slsa.dev/spec/v1.0/about) (Supply-chain Levels for Software Artifacts) to help developers make their supply chain more secure by ensuring the authenticity and build integrity of our published library packages. To learn more, see the [provenance guide](PROVENANCE.md).
20
+
21
+ About LaunchDarkly
22
+ -----------
23
+
24
+ * LaunchDarkly is a continuous delivery platform that provides feature flags as a service and allows developers to iterate quickly and safely. We allow you to easily flag your features and manage them from the LaunchDarkly dashboard. With LaunchDarkly, you can:
25
+ * Roll out a new feature to a subset of your users (like a group of users who opt-in to a beta tester group), gathering feedback and bug reports from real-world use cases.
26
+ * Gradually roll out a feature to an increasing percentage of users, and track the effect that the feature has on key metrics (for instance, how likely is a user to complete a purchase if they have feature A versus feature B?).
27
+ * Turn off a feature that you realize is causing performance problems in production, without needing to re-deploy, or even restart the application with a changed configuration file.
28
+ * Grant access to certain features based on user attributes, like payment plan (eg: users on the ‘gold’ plan get access to more features than users in the ‘silver’ plan). Disable parts of your application to facilitate maintenance, without taking everything offline.
29
+ * LaunchDarkly provides feature flag SDKs for a wide variety of languages and technologies. Read [our documentation](https://docs.launchdarkly.com/sdk) for a complete list.
30
+ * Explore LaunchDarkly
31
+ * [launchdarkly.com](https://www.launchdarkly.com/ "LaunchDarkly Main Website") for more information
32
+ * [docs.launchdarkly.com](https://docs.launchdarkly.com/ "LaunchDarkly Documentation") for our documentation and SDK reference guides
33
+ * [apidocs.launchdarkly.com](https://apidocs.launchdarkly.com/ "LaunchDarkly API Documentation") for our API documentation
34
+ * [blog.launchdarkly.com](https://blog.launchdarkly.com/ "LaunchDarkly Blog Documentation") for the latest product updates
data/SECURITY.md ADDED
@@ -0,0 +1,5 @@
1
+ # Reporting and Fixing Security Issues
2
+
3
+ Please report all security issues to the LaunchDarkly security team by submitting a bug bounty report to our [HackerOne program](https://hackerone.com/launchdarkly?type=team). LaunchDarkly will triage and address all valid security issues following the response targets defined in our program policy. Valid security issues may be eligible for a bounty.
4
+
5
+ Please do not open issues or pull requests for security issues. This makes the problem immediately visible to everyone, including potentially malicious actors.
data/bin/console ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'launchdarkly_server_sdk_ai'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ require 'irb'
11
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -1 +1,30 @@
1
- raise "Reserved for LaunchDarkly"
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+ require 'mustache'
5
+
6
+ require 'server/ai/version'
7
+ require 'server/ai/client'
8
+ require 'server/ai/ai_config_tracker'
9
+
10
+ module LaunchDarkly
11
+ module Server
12
+ #
13
+ # Namespace for the LaunchDarkly AI SDK.
14
+ #
15
+ module AI
16
+ #
17
+ # @return [Logger] the Rails logger if in Rails, or a default Logger at WARN level otherwise
18
+ #
19
+ def self.default_logger
20
+ if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
21
+ Rails.logger
22
+ else
23
+ log = ::Logger.new($stdout)
24
+ log.level = ::Logger::WARN
25
+ log
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,245 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ldclient-rb'
4
+
5
+ module LaunchDarkly
6
+ module Server
7
+ module AI
8
+ #
9
+ # Tracks token usage for AI operations.
10
+ #
11
+ class TokenUsage
12
+ attr_reader :total, :input, :output
13
+
14
+ #
15
+ # @param total [Integer] Total number of tokens used.
16
+ # @param input [Integer] Number of tokens in the prompt.
17
+ # @param output [Integer] Number of tokens in the completion.
18
+ #
19
+ def initialize(total: nil, input: nil, output: nil)
20
+ @total = total
21
+ @input = input
22
+ @output = output
23
+ end
24
+ end
25
+
26
+ #
27
+ # Summary of metrics which have been tracked.
28
+ #
29
+ class MetricSummary
30
+ attr_accessor :duration, :success, :feedback, :usage, :time_to_first_token
31
+
32
+ def initialize
33
+ @duration = nil
34
+ @success = nil
35
+ @feedback = nil
36
+ @usage = nil
37
+ @time_to_first_token = nil
38
+ end
39
+ end
40
+
41
+ #
42
+ # The AIConfigTracker class is used to track AI configuration usage.
43
+ #
44
+ class AIConfigTracker
45
+ attr_reader :ld_client, :config_key, :context, :variation_key, :version, :summary
46
+
47
+ def initialize(ld_client:, variation_key:, config_key:, version:, context:)
48
+ @ld_client = ld_client
49
+ @variation_key = variation_key
50
+ @config_key = config_key
51
+ @version = version
52
+ @context = context
53
+ @summary = MetricSummary.new
54
+ end
55
+
56
+ #
57
+ # Track the duration of an AI operation
58
+ #
59
+ # @param duration [Integer] The duration in milliseconds
60
+ #
61
+ def track_duration(duration)
62
+ @summary.duration = duration
63
+ @ld_client.track(
64
+ '$ld:ai:duration:total',
65
+ @context,
66
+ flag_data,
67
+ duration
68
+ )
69
+ end
70
+
71
+ #
72
+ # Track the duration of a block of code
73
+ #
74
+ # @yield The block to measure
75
+ # @return The result of the block
76
+ #
77
+ def track_duration_of(&block)
78
+ start_time = Time.now
79
+ yield
80
+ ensure
81
+ duration = ((Time.now - start_time) * 1000).to_i
82
+ track_duration(duration)
83
+ end
84
+
85
+ #
86
+ # Track time to first token
87
+ #
88
+ # @param duration [Integer] The duration in milliseconds
89
+ #
90
+ def track_time_to_first_token(time_to_first_token)
91
+ @summary.time_to_first_token = time_to_first_token
92
+ @ld_client.track(
93
+ '$ld:ai:tokens:ttf',
94
+ @context,
95
+ flag_data,
96
+ time_to_first_token
97
+ )
98
+ end
99
+
100
+ #
101
+ # Track user feedback
102
+ #
103
+ # @param kind [Symbol] The kind of feedback (:positive or :negative)
104
+ #
105
+ def track_feedback(kind:)
106
+ @summary.feedback = kind
107
+ event_name = kind == :positive ? '$ld:ai:feedback:user:positive' : '$ld:ai:feedback:user:negative'
108
+ @ld_client.track(
109
+ event_name,
110
+ @context,
111
+ flag_data,
112
+ 1
113
+ )
114
+ end
115
+
116
+ #
117
+ # Track a successful AI generation
118
+ #
119
+ def track_success
120
+ @summary.success = true
121
+ @ld_client.track(
122
+ '$ld:ai:generation',
123
+ @context,
124
+ flag_data,
125
+ 1
126
+ )
127
+ @ld_client.track(
128
+ '$ld:ai:generation:success',
129
+ @context,
130
+ flag_data,
131
+ 1
132
+ )
133
+ end
134
+
135
+ #
136
+ # Track an error in AI generation
137
+ #
138
+ def track_error
139
+ @summary.success = false
140
+ @ld_client.track(
141
+ '$ld:ai:generation',
142
+ @context,
143
+ flag_data,
144
+ 1
145
+ )
146
+ @ld_client.track(
147
+ '$ld:ai:generation:error',
148
+ @context,
149
+ flag_data,
150
+ 1
151
+ )
152
+ end
153
+
154
+ #
155
+ # Track token usage
156
+ #
157
+ # @param token_usage [TokenUsage] An object containing token usage details
158
+ #
159
+ def track_tokens(token_usage)
160
+ @summary.usage = token_usage
161
+ if token_usage.total.positive?
162
+ @ld_client.track(
163
+ '$ld:ai:tokens:total',
164
+ @context,
165
+ flag_data,
166
+ token_usage.total
167
+ )
168
+ end
169
+ if token_usage.input.positive?
170
+ @ld_client.track(
171
+ '$ld:ai:tokens:input',
172
+ @context,
173
+ flag_data,
174
+ token_usage.input
175
+ )
176
+ end
177
+ return unless token_usage.output.positive?
178
+
179
+ @ld_client.track(
180
+ '$ld:ai:tokens:output',
181
+ @context,
182
+ flag_data,
183
+ token_usage.output
184
+ )
185
+ end
186
+
187
+ #
188
+ # Track OpenAI-specific operations.
189
+ # This method tracks the duration, token usage, and success/error status.
190
+ # If the provided block raises, this method will also raise.
191
+ # A failed operation will not have any token usage data.
192
+ #
193
+ # @yield The block to track.
194
+ # @return The result of the tracked block.
195
+ #
196
+ def track_openai_metrics(&block)
197
+ result = track_duration_of(&block)
198
+ track_success
199
+ track_tokens(openai_to_token_usage(result[:usage])) if result[:usage]
200
+ result
201
+ rescue StandardError
202
+ track_error
203
+ raise
204
+ end
205
+
206
+ #
207
+ # Track AWS Bedrock conversation operations.
208
+ # This method tracks the duration, token usage, and success/error status.
209
+ #
210
+ # @yield The block to track.
211
+ # @return [Hash] The original response hash.
212
+ #
213
+ def track_bedrock_converse_metrics(&block)
214
+ result = track_duration_of(&block)
215
+ track_success
216
+ track_tokens(bedrock_to_token_usage(result[:usage])) if result[:usage]
217
+ result
218
+ rescue StandardError
219
+ track_error
220
+ raise
221
+ end
222
+
223
+ private def flag_data
224
+ { variationKey: @variation_key, configKey: @config_key, version: @version }
225
+ end
226
+
227
+ private def openai_to_token_usage(usage)
228
+ TokenUsage.new(
229
+ total: usage[:total_tokens] || usage['total_tokens'],
230
+ input: usage[:prompt_tokens] || usage['prompt_tokens'],
231
+ output: usage[:completion_tokens] || usage['completion_tokens']
232
+ )
233
+ end
234
+
235
+ private def bedrock_to_token_usage(usage)
236
+ TokenUsage.new(
237
+ total: usage[:total_tokens] || usage['total_tokens'],
238
+ input: usage[:input_tokens] || usage['input_tokens'],
239
+ output: usage[:output_tokens] || usage['output_tokens']
240
+ )
241
+ end
242
+ end
243
+ end
244
+ end
245
+ end
@@ -0,0 +1,205 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ldclient-rb'
4
+ require 'mustache'
5
+ require_relative 'ai_config_tracker'
6
+
7
+ module LaunchDarkly
8
+ #
9
+ # Namespace for the LaunchDarkly Server SDK
10
+ #
11
+ module Server
12
+ #
13
+ # Namespace for the LaunchDarkly Server AI SDK.
14
+ #
15
+ module AI
16
+ #
17
+ # Holds AI role and content.
18
+ #
19
+ class Message
20
+ attr_reader :role, :content
21
+
22
+ def initialize(role, content)
23
+ @role = role
24
+ @content = content
25
+ end
26
+
27
+ def to_h
28
+ {
29
+ role: @role,
30
+ content: @content,
31
+ }
32
+ end
33
+ end
34
+
35
+ #
36
+ # The ModelConfig class represents an AI model configuration.
37
+ #
38
+ class ModelConfig
39
+ attr_reader :name
40
+
41
+ def initialize(name:, parameters: {}, custom: {})
42
+ @name = name
43
+ @parameters = parameters
44
+ @custom = custom
45
+ end
46
+
47
+ #
48
+ # Retrieve model-specific parameters.
49
+ #
50
+ # Accessing a named, typed attribute (e.g. name) will result in the call
51
+ # being delegated to the appropriate property.
52
+ #
53
+ # @param key [String] The parameter key to retrieve
54
+ # @return [Object, nil] The parameter value or nil if not found
55
+ #
56
+ def parameter(key)
57
+ return @name if key == 'name'
58
+ return nil unless @parameters.is_a?(Hash)
59
+
60
+ @parameters[key]
61
+ end
62
+
63
+ #
64
+ # Retrieve customer provided data.
65
+ #
66
+ # @param key [String] The custom key to retrieve
67
+ # @return [Object, nil] The custom value or nil if not found
68
+ #
69
+ def custom(key)
70
+ return nil unless @custom.is_a?(Hash)
71
+
72
+ @custom[key]
73
+ end
74
+
75
+ def to_h
76
+ {
77
+ name: @name,
78
+ parameters: @parameters,
79
+ custom: @custom,
80
+ }
81
+ end
82
+ end
83
+
84
+ #
85
+ # Configuration related to the provider.
86
+ #
87
+ class ProviderConfig
88
+ attr_reader :name
89
+
90
+ def initialize(name)
91
+ @name = name
92
+ end
93
+
94
+ def to_h
95
+ {
96
+ name: @name,
97
+ }
98
+ end
99
+ end
100
+
101
+ #
102
+ # The AIConfig class represents an AI configuration.
103
+ #
104
+ class AIConfig
105
+ attr_reader :enabled, :messages, :variables, :tracker, :model, :provider
106
+
107
+ def initialize(enabled: nil, model: nil, messages: nil, tracker: nil, provider: nil)
108
+ @enabled = enabled
109
+ @messages = messages
110
+ @tracker = tracker
111
+ @model = model
112
+ @provider = provider
113
+ end
114
+
115
+ def to_h
116
+ {
117
+ _ldMeta: {
118
+ enabled: @enabled || false,
119
+ },
120
+ messages: @messages.is_a?(Array) ? @messages.map { |msg| msg&.to_h } : nil,
121
+ model: @model&.to_h,
122
+ provider: @provider&.to_h,
123
+ }
124
+ end
125
+ end
126
+
127
+ #
128
+ # The Client class is the main entry point for the LaunchDarkly AI SDK.
129
+ #
130
+ class Client
131
+ attr_reader :logger, :ld_client
132
+
133
+ def initialize(ld_client)
134
+ raise ArgumentError, 'LDClient instance is required' unless ld_client.is_a?(LaunchDarkly::LDClient)
135
+
136
+ @ld_client = ld_client
137
+ @logger = LaunchDarkly::Server::AI.default_logger
138
+ end
139
+
140
+ #
141
+ # Retrieves the AIConfig
142
+ #
143
+ # @param config_key [String] The key of the configuration flag
144
+ # @param context [LDContext] The context used when evaluating the flag
145
+ # @param default_value [AIConfig] The default value to use if the flag is not found
146
+ # @param variables [Hash] Optional variables for rendering messages
147
+ # @return [AIConfig] An AIConfig instance containing the configuration data
148
+ #
149
+ def config(config_key, context, default_value = nil, variables = nil)
150
+ variation = @ld_client.variation(
151
+ config_key,
152
+ context,
153
+ default_value.respond_to?(:to_h) ? default_value.to_h : nil
154
+ )
155
+
156
+ all_variables = variables ? variables.dup : {}
157
+ all_variables[:ldctx] = context.to_h
158
+
159
+ # Process messages and provider configuration
160
+ messages = nil
161
+ if variation[:messages].is_a?(Array) && variation[:messages].all? { |msg| msg.is_a?(Hash) }
162
+ messages = variation[:messages].map do |message|
163
+ next unless message[:content].is_a?(String)
164
+
165
+ Message.new(
166
+ message[:role],
167
+ Mustache.render(message[:content], all_variables)
168
+ )
169
+ end
170
+ end
171
+
172
+ if (provider_config = variation[:provider]) && provider_config.is_a?(Hash)
173
+ provider_config = ProviderConfig.new(provider_config.fetch(:name, ''))
174
+ end
175
+
176
+ if (model = variation[:model]) && model.is_a?(Hash)
177
+ parameters = variation[:model][:parameters]
178
+ custom = variation[:model][:custom]
179
+ model = ModelConfig.new(
180
+ name: variation[:model][:name],
181
+ parameters: parameters,
182
+ custom: custom
183
+ )
184
+ end
185
+
186
+ tracker = LaunchDarkly::Server::AI::AIConfigTracker.new(
187
+ ld_client: @ld_client,
188
+ variation_key: variation.dig(:_ldMeta, :variationKey) || '',
189
+ config_key: config_key,
190
+ version: variation.dig(:_ldMeta, :version) || 1,
191
+ context: context
192
+ )
193
+
194
+ AIConfig.new(
195
+ enabled: variation.dig(:_ldMeta, :enabled) || false,
196
+ messages: messages,
197
+ tracker: tracker,
198
+ model: model,
199
+ provider: provider_config
200
+ )
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LaunchDarkly
4
+ module Server
5
+ module AI
6
+ VERSION = '0.1.0' # x-release-please-version
7
+ end
8
+ end
9
+ end
metadata CHANGED
@@ -1,25 +1,180 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: launchdarkly-server-sdk-ai
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - LaunchDarkly
8
8
  bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
- dependencies: []
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: launchdarkly-server-sdk
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '8.5'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '8.5'
26
+ - !ruby/object:Gem::Dependency
27
+ name: logger
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: mustache
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.1'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.1'
54
+ - !ruby/object:Gem::Dependency
55
+ name: bundler
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '2.0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '2.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rake
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '13.0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '13.0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: rspec
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '3.0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '3.0'
96
+ - !ruby/object:Gem::Dependency
97
+ name: rubocop
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '1.21'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '1.21'
110
+ - !ruby/object:Gem::Dependency
111
+ name: rubocop-performance
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '1.15'
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '1.15'
124
+ - !ruby/object:Gem::Dependency
125
+ name: rubocop-rake
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: '0.6'
131
+ type: :development
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: '0.6'
138
+ - !ruby/object:Gem::Dependency
139
+ name: rubocop-rspec
140
+ requirement: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: '3.6'
145
+ type: :development
146
+ prerelease: false
147
+ version_requirements: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - "~>"
150
+ - !ruby/object:Gem::Version
151
+ version: '3.6'
12
152
  description: LaunchDarkly SDK AI Configs integration for the Ruby server side SDK
13
153
  email:
14
154
  - team@launchdarkly.com
15
- executables: []
155
+ executables:
156
+ - console
157
+ - setup
16
158
  extensions: []
17
159
  extra_rdoc_files: []
18
160
  files:
161
+ - CHANGELOG.md
162
+ - CONTRIBUTING.md
163
+ - PROVENANCE.md
164
+ - README.md
165
+ - SECURITY.md
166
+ - bin/console
167
+ - bin/setup
19
168
  - lib/launchdarkly-server-sdk-ai.rb
169
+ - lib/server/ai/ai_config_tracker.rb
170
+ - lib/server/ai/client.rb
171
+ - lib/server/ai/version.rb
172
+ homepage: https://github.com/launchdarkly/ruby-server-sdk-ai
20
173
  licenses:
21
174
  - Apache-2.0
22
- metadata: {}
175
+ metadata:
176
+ source_code_uri: https://github.com/launchdarkly/ruby-server-sdk-ai
177
+ changelog_uri: https://github.com/launchdarkly/ruby-server-sdk-ai/blob/main/CHANGELOG.md
23
178
  rdoc_options: []
24
179
  require_paths:
25
180
  - lib