launchdarkly-server-sdk-ai 0.3.0 → 0.5.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 +4 -4
- data/CHANGELOG.md +25 -0
- data/PROVENANCE.md +28 -22
- data/SECURITY.md +5 -2
- data/lib/server/ai/ai_config_tracker.rb +121 -11
- data/lib/server/ai/client.rb +100 -24
- data/lib/server/ai/version.rb +1 -1
- metadata +16 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b23fa19941fcf0773e891e61be94c5761a0889cf2c7a5b362f9b4e0e5986b6ba
|
|
4
|
+
data.tar.gz: 4ca5652c32a43e51b39f0a00bca215bd2bc4d1db1340da04441b33d48ccdc758
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a51ee90183e5421ea039d3340ca107b36100e24bfcaf8a496ded3a461424fb0e5a8adffa46ba802519db976d290f50443386aa6c8c317bb4ebac1d61675f608e
|
|
7
|
+
data.tar.gz: 84cc8dd823cd58a6880fa819ed5f1485cdfc1324422cd4124905175400287dbe351319e4581b30c821410ab74e1a6b58e4d553cb5b0e5068a39d56dd0d31348d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.5.0](https://github.com/launchdarkly/ruby-server-sdk-ai/compare/0.4.0...0.5.0) (2026-09-02)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **server-ai:** stamp modelKey and modelVersion on AI usage events (AIC-2857) ([#36](https://github.com/launchdarkly/ruby-server-sdk-ai/issues/36)) ([2f7fda4](https://github.com/launchdarkly/ruby-server-sdk-ai/commit/2f7fda4d57b09483cf15fda6e3102e24b1eecb27))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## [0.4.0](https://github.com/launchdarkly/ruby-server-sdk-ai/compare/0.3.0...0.4.0) (2026-05-15)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### ⚠ BREAKING CHANGES
|
|
15
|
+
|
|
16
|
+
* Replace tracker tuple from completion_config with AIConfig#create_tracker factory
|
|
17
|
+
* Track each AIConfigTracker metric at most once per tracker
|
|
18
|
+
* Add per-execution runId, at-most-once tracking, and cross-process tracker resumption
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* Add Client#create_tracker(token:, context:) to resume a tracker across processes ([20f06f1](https://github.com/launchdarkly/ruby-server-sdk-ai/commit/20f06f1f24a42692953ae44cebea70aaa416b29d))
|
|
23
|
+
* Add per-execution runId to correlate AIConfigTracker events ([20f06f1](https://github.com/launchdarkly/ruby-server-sdk-ai/commit/20f06f1f24a42692953ae44cebea70aaa416b29d))
|
|
24
|
+
* Add per-execution runId, at-most-once tracking, and cross-process tracker resumption ([20f06f1](https://github.com/launchdarkly/ruby-server-sdk-ai/commit/20f06f1f24a42692953ae44cebea70aaa416b29d))
|
|
25
|
+
* Replace tracker tuple from completion_config with AIConfig#create_tracker factory ([20f06f1](https://github.com/launchdarkly/ruby-server-sdk-ai/commit/20f06f1f24a42692953ae44cebea70aaa416b29d))
|
|
26
|
+
* Track each AIConfigTracker metric at most once per tracker ([20f06f1](https://github.com/launchdarkly/ruby-server-sdk-ai/commit/20f06f1f24a42692953ae44cebea70aaa416b29d))
|
|
27
|
+
|
|
3
28
|
## [0.3.0](https://github.com/launchdarkly/ruby-server-sdk-ai/compare/0.2.2...0.3.0) (2026-03-05)
|
|
4
29
|
|
|
5
30
|
|
data/PROVENANCE.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
## Verifying SDK build provenance with
|
|
1
|
+
## Verifying SDK build provenance with GitHub artifact attestations
|
|
2
2
|
|
|
3
|
-
LaunchDarkly uses
|
|
3
|
+
LaunchDarkly uses [GitHub artifact attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds) to help developers make their supply chain more secure by ensuring the authenticity and build integrity of our published SDK packages.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
LaunchDarkly publishes provenance about our SDK package builds using [GitHub's `actions/attest` action](https://github.com/actions/attest). These attestations are stored in GitHub's attestation API and can be verified using the [GitHub CLI](https://cli.github.com/).
|
|
6
6
|
|
|
7
|
-
To verify
|
|
7
|
+
To verify build provenance attestations, we recommend using the [GitHub CLI `attestation verify` command](https://cli.github.com/manual/gh_attestation_verify). Example usage for verifying SDK packages is included below:
|
|
8
8
|
|
|
9
9
|
<!-- x-release-please-start-version -->
|
|
10
10
|
```
|
|
11
|
-
# Set the version of the
|
|
12
|
-
VERSION=0.
|
|
11
|
+
# Set the version of the library to verify
|
|
12
|
+
VERSION=0.5.0
|
|
13
13
|
```
|
|
14
14
|
<!-- x-release-please-end -->
|
|
15
15
|
|
|
@@ -17,27 +17,33 @@ VERSION=0.3.0
|
|
|
17
17
|
# Download gem
|
|
18
18
|
$ gem fetch launchdarkly-server-sdk-ai -v $VERSION
|
|
19
19
|
|
|
20
|
-
#
|
|
21
|
-
$
|
|
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
|
|
20
|
+
# Verify provenance using the GitHub CLI
|
|
21
|
+
$ gh attestation verify launchdarkly-server-sdk-ai-${VERSION}.gem --owner launchdarkly
|
|
29
22
|
```
|
|
30
23
|
|
|
31
24
|
Below is a sample of expected output.
|
|
32
|
-
|
|
25
|
+
|
|
33
26
|
```
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
27
|
+
Loaded digest sha256:... for file://launchdarkly-server-sdk-ai-0.3.0.gem
|
|
28
|
+
Loaded 1 attestation from GitHub API
|
|
29
|
+
|
|
30
|
+
The following policy criteria will be enforced:
|
|
31
|
+
- Predicate type must match:................ https://slsa.dev/provenance/v1
|
|
32
|
+
- Source Repository Owner URI must match:... https://github.com/launchdarkly
|
|
33
|
+
- Subject Alternative Name must match regex: (?i)^https://github.com/launchdarkly/
|
|
34
|
+
- OIDC Issuer must match:................... https://token.actions.githubusercontent.com
|
|
35
|
+
|
|
36
|
+
✓ Verification succeeded!
|
|
37
|
+
|
|
38
|
+
The following 1 attestation matched the policy criteria
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
- Attestation #1
|
|
41
|
+
- Build repo:..... launchdarkly/ruby-server-sdk-ai
|
|
42
|
+
- Build workflow:. .github/workflows/release-please.yml
|
|
43
|
+
- Signer repo:.... launchdarkly/ruby-server-sdk-ai
|
|
44
|
+
- Signer workflow: .github/workflows/release-please.yml
|
|
39
45
|
```
|
|
40
46
|
|
|
41
|
-
|
|
47
|
+
For more information, see [GitHub's documentation on verifying artifact attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds#verifying-artifact-attestations-with-the-github-cli).
|
|
42
48
|
|
|
43
|
-
**Note:** These instructions do not apply when building our libraries from source.
|
|
49
|
+
**Note:** These instructions do not apply when building our libraries from source.
|
data/SECURITY.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# Reporting and Fixing Security Issues
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Do not open Issues or Pull Requests for security issues.**
|
|
4
|
+
This will make potential issues publicly visible before LaunchDarkly's Security Team can address them, which could lead to a compromise of the platform and negatively impact our customers.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
Security issues must be reported through our [Bug Bounty program](https://bugcrowd.com/engagements/launchdarkly-mbb-og), following the program policy, for triage and remediation by the LaunchDarkly Security Team. Valid security issues may be eligible for a bounty.
|
|
7
|
+
|
|
8
|
+
Please do not attempt to directly contact members of LaunchDarkly staff.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'base64'
|
|
4
|
+
require 'json'
|
|
3
5
|
require 'ldclient-rb'
|
|
4
6
|
|
|
5
7
|
module LaunchDarkly
|
|
@@ -39,10 +41,18 @@ module LaunchDarkly
|
|
|
39
41
|
end
|
|
40
42
|
|
|
41
43
|
#
|
|
42
|
-
# The AIConfigTracker
|
|
44
|
+
# The AIConfigTracker records metrics for a single AI run. Unless
|
|
45
|
+
# otherwise noted, the tracker's methods are not safe for concurrent use.
|
|
46
|
+
#
|
|
47
|
+
# All events a tracker emits share a runId (a UUIDv4) so LaunchDarkly can
|
|
48
|
+
# correlate them in metrics views. See individual track methods for their
|
|
49
|
+
# specific semantics. Call create_tracker on the AI Config to start a new
|
|
50
|
+
# run. A resumption token preserves the runId, so events emitted by a
|
|
51
|
+
# tracker reconstructed in another process share the original runId.
|
|
43
52
|
#
|
|
44
53
|
class AIConfigTracker
|
|
45
|
-
attr_reader :ld_client, :config_key, :context, :variation_key, :version, :summary, :model_name, :provider_name
|
|
54
|
+
attr_reader :ld_client, :config_key, :context, :variation_key, :version, :summary, :model_name, :provider_name,
|
|
55
|
+
:model_key, :model_version
|
|
46
56
|
|
|
47
57
|
#
|
|
48
58
|
# Initialize a new AIConfigTracker instance.
|
|
@@ -53,25 +63,78 @@ module LaunchDarkly
|
|
|
53
63
|
# @param version [Integer] The version number
|
|
54
64
|
# @param model_name [String] The name of the AI model being used
|
|
55
65
|
# @param provider_name [String] The name of the AI provider
|
|
66
|
+
# @param model_key [String, nil] The stable, unique key of the model used
|
|
67
|
+
# @param model_version [Integer] The pinned version of the model used
|
|
56
68
|
# @param context [LDContext] The context used for the flag evaluation
|
|
57
69
|
#
|
|
58
|
-
def initialize(ld_client:,
|
|
70
|
+
def initialize(ld_client:, run_id:, config_key:, variation_key:, version:, context:, model_name:, provider_name:,
|
|
71
|
+
model_key: nil, model_version: 1)
|
|
59
72
|
@ld_client = ld_client
|
|
60
73
|
@variation_key = variation_key
|
|
61
74
|
@config_key = config_key
|
|
62
75
|
@version = version
|
|
63
76
|
@model_name = model_name
|
|
64
77
|
@provider_name = provider_name
|
|
78
|
+
@model_key = model_key
|
|
79
|
+
@model_version = model_version
|
|
65
80
|
@context = context
|
|
66
81
|
@summary = MetricSummary.new
|
|
82
|
+
@run_id = run_id
|
|
83
|
+
@logger = LaunchDarkly::Server::AI.default_logger
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
#
|
|
87
|
+
# Returns a URL-safe Base64-encoded JSON token that can be used to reconstruct
|
|
88
|
+
# a tracker in a different process (e.g. for deferred feedback).
|
|
89
|
+
#
|
|
90
|
+
# The token contains: runId, configKey, variationKey, version.
|
|
91
|
+
# modelName, providerName, modelKey, and modelVersion are NOT included.
|
|
92
|
+
#
|
|
93
|
+
# @return [String] the resumption token
|
|
94
|
+
#
|
|
95
|
+
def resumption_token
|
|
96
|
+
payload = { runId: @run_id, configKey: @config_key }
|
|
97
|
+
payload[:variationKey] = @variation_key if @variation_key && !@variation_key.empty?
|
|
98
|
+
payload[:version] = @version
|
|
99
|
+
Base64.urlsafe_encode64(JSON.generate(payload), padding: false)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
#
|
|
103
|
+
# Reconstructs a tracker from a resumption token.
|
|
104
|
+
#
|
|
105
|
+
# @param token [String] A URL-safe Base64-encoded JSON resumption token
|
|
106
|
+
# @param ld_client [LDClient] The LaunchDarkly client instance
|
|
107
|
+
# @param context [LDContext] The context for track events
|
|
108
|
+
# @return [AIConfigTracker] A new tracker instance
|
|
109
|
+
#
|
|
110
|
+
def self.from_resumption_token(token:, ld_client:, context:)
|
|
111
|
+
json = Base64.urlsafe_decode64(token)
|
|
112
|
+
payload = JSON.parse(json)
|
|
113
|
+
|
|
114
|
+
new(
|
|
115
|
+
ld_client: ld_client,
|
|
116
|
+
run_id: payload['runId'],
|
|
117
|
+
config_key: payload['configKey'],
|
|
118
|
+
variation_key: payload.fetch('variationKey', ''),
|
|
119
|
+
version: payload['version'],
|
|
120
|
+
context: context,
|
|
121
|
+
model_name: '',
|
|
122
|
+
provider_name: ''
|
|
123
|
+
)
|
|
67
124
|
end
|
|
68
125
|
|
|
69
126
|
#
|
|
70
|
-
# Track the duration of an AI
|
|
127
|
+
# Track the duration of an AI run.
|
|
128
|
+
#
|
|
129
|
+
# Records at most once per Tracker; further calls are ignored.
|
|
71
130
|
#
|
|
72
131
|
# @param duration [Integer] The duration in milliseconds
|
|
73
132
|
#
|
|
74
133
|
def track_duration(duration)
|
|
134
|
+
unless @summary.duration.nil?
|
|
135
|
+
@logger&.warn("Skipping track_duration: duration already recorded on this tracker. Call create_tracker on the AI Config for a new run. #{flag_data}")
|
|
136
|
+
return
|
|
137
|
+
end
|
|
75
138
|
@summary.duration = duration
|
|
76
139
|
@ld_client.track(
|
|
77
140
|
'$ld:ai:duration:total',
|
|
@@ -96,11 +159,18 @@ module LaunchDarkly
|
|
|
96
159
|
end
|
|
97
160
|
|
|
98
161
|
#
|
|
99
|
-
# Track time to first token
|
|
162
|
+
# Track time to first token.
|
|
163
|
+
#
|
|
164
|
+
# Records at most once per Tracker; further calls are ignored.
|
|
100
165
|
#
|
|
101
166
|
# @param duration [Integer] The duration in milliseconds
|
|
102
167
|
#
|
|
103
168
|
def track_time_to_first_token(time_to_first_token)
|
|
169
|
+
unless @summary.time_to_first_token.nil?
|
|
170
|
+
@logger&.warn("Skipping track_time_to_first_token: time-to-first-token already recorded on this tracker. " \
|
|
171
|
+
"Call create_tracker on the AI Config for a new run. #{flag_data}")
|
|
172
|
+
return
|
|
173
|
+
end
|
|
104
174
|
@summary.time_to_first_token = time_to_first_token
|
|
105
175
|
@ld_client.track(
|
|
106
176
|
'$ld:ai:tokens:ttf',
|
|
@@ -111,11 +181,17 @@ module LaunchDarkly
|
|
|
111
181
|
end
|
|
112
182
|
|
|
113
183
|
#
|
|
114
|
-
# Track user feedback
|
|
184
|
+
# Track user feedback.
|
|
185
|
+
#
|
|
186
|
+
# Records at most once per Tracker; further calls are ignored.
|
|
115
187
|
#
|
|
116
188
|
# @param kind [Symbol] The kind of feedback (:positive or :negative)
|
|
117
189
|
#
|
|
118
190
|
def track_feedback(kind:)
|
|
191
|
+
unless @summary.feedback.nil?
|
|
192
|
+
@logger&.warn("Skipping track_feedback: feedback already recorded on this tracker. Call create_tracker on the AI Config for a new run. #{flag_data}")
|
|
193
|
+
return
|
|
194
|
+
end
|
|
119
195
|
@summary.feedback = kind
|
|
120
196
|
event_name = kind == :positive ? '$ld:ai:feedback:user:positive' : '$ld:ai:feedback:user:negative'
|
|
121
197
|
@ld_client.track(
|
|
@@ -127,9 +203,17 @@ module LaunchDarkly
|
|
|
127
203
|
end
|
|
128
204
|
|
|
129
205
|
#
|
|
130
|
-
# Track a successful AI generation
|
|
206
|
+
# Track a successful AI generation.
|
|
207
|
+
#
|
|
208
|
+
# Records at most once per Tracker. track_success and track_error share
|
|
209
|
+
# state; only one of the two can record per Tracker, and subsequent
|
|
210
|
+
# calls are ignored.
|
|
131
211
|
#
|
|
132
212
|
def track_success
|
|
213
|
+
unless @summary.success.nil?
|
|
214
|
+
@logger&.warn("Skipping track_success: success/error already recorded on this tracker. Call create_tracker on the AI Config for a new run. #{flag_data}")
|
|
215
|
+
return
|
|
216
|
+
end
|
|
133
217
|
@summary.success = true
|
|
134
218
|
@ld_client.track(
|
|
135
219
|
'$ld:ai:generation:success',
|
|
@@ -140,9 +224,17 @@ module LaunchDarkly
|
|
|
140
224
|
end
|
|
141
225
|
|
|
142
226
|
#
|
|
143
|
-
# Track an error in AI generation
|
|
227
|
+
# Track an error in AI generation.
|
|
228
|
+
#
|
|
229
|
+
# Records at most once per Tracker. track_success and track_error share
|
|
230
|
+
# state; only one of the two can record per Tracker, and subsequent
|
|
231
|
+
# calls are ignored.
|
|
144
232
|
#
|
|
145
233
|
def track_error
|
|
234
|
+
unless @summary.success.nil?
|
|
235
|
+
@logger&.warn("Skipping track_error: success/error already recorded on this tracker. Call create_tracker on the AI Config for a new run. #{flag_data}")
|
|
236
|
+
return
|
|
237
|
+
end
|
|
146
238
|
@summary.success = false
|
|
147
239
|
@ld_client.track(
|
|
148
240
|
'$ld:ai:generation:error',
|
|
@@ -153,11 +245,17 @@ module LaunchDarkly
|
|
|
153
245
|
end
|
|
154
246
|
|
|
155
247
|
#
|
|
156
|
-
# Track token usage
|
|
248
|
+
# Track token usage.
|
|
249
|
+
#
|
|
250
|
+
# Records at most once per Tracker; further calls are ignored.
|
|
157
251
|
#
|
|
158
252
|
# @param token_usage [TokenUsage] An object containing token usage details
|
|
159
253
|
#
|
|
160
254
|
def track_tokens(token_usage)
|
|
255
|
+
unless @summary.usage.nil?
|
|
256
|
+
@logger&.warn("Skipping track_tokens: token usage already recorded on this tracker. Call create_tracker on the AI Config for a new run. #{flag_data}")
|
|
257
|
+
return
|
|
258
|
+
end
|
|
161
259
|
@summary.usage = token_usage
|
|
162
260
|
if token_usage.total.positive?
|
|
163
261
|
@ld_client.track(
|
|
@@ -191,6 +289,10 @@ module LaunchDarkly
|
|
|
191
289
|
# If the provided block raises, this method will also raise.
|
|
192
290
|
# A failed operation will not have any token usage data.
|
|
193
291
|
#
|
|
292
|
+
# Subsequent calls re-run the inner block but emit only metrics not
|
|
293
|
+
# already recorded on this Tracker. Call create_tracker on the AI
|
|
294
|
+
# Config to start a new run.
|
|
295
|
+
#
|
|
194
296
|
# @yield The block to track.
|
|
195
297
|
# @return The result of the tracked block.
|
|
196
298
|
#
|
|
@@ -208,6 +310,10 @@ module LaunchDarkly
|
|
|
208
310
|
# Track AWS Bedrock conversation operations.
|
|
209
311
|
# This method tracks the duration, token usage, and success/error status.
|
|
210
312
|
#
|
|
313
|
+
# Subsequent calls re-run the inner block but emit only metrics not
|
|
314
|
+
# already recorded on this Tracker. Call create_tracker on the AI
|
|
315
|
+
# Config to start a new run.
|
|
316
|
+
#
|
|
211
317
|
# @yield The block to track.
|
|
212
318
|
# @return [Hash] The original response hash.
|
|
213
319
|
#
|
|
@@ -222,13 +328,17 @@ module LaunchDarkly
|
|
|
222
328
|
end
|
|
223
329
|
|
|
224
330
|
private def flag_data
|
|
225
|
-
{
|
|
226
|
-
|
|
331
|
+
data = {
|
|
332
|
+
runId: @run_id,
|
|
227
333
|
configKey: @config_key,
|
|
228
334
|
version: @version,
|
|
229
335
|
modelName: @model_name,
|
|
230
336
|
providerName: @provider_name,
|
|
337
|
+
modelVersion: @model_version,
|
|
231
338
|
}
|
|
339
|
+
data[:variationKey] = @variation_key if @variation_key && !@variation_key.empty?
|
|
340
|
+
data[:modelKey] = @model_key if @model_key && !@model_key.empty?
|
|
341
|
+
data
|
|
232
342
|
end
|
|
233
343
|
|
|
234
344
|
private def openai_to_token_usage(usage)
|
data/lib/server/ai/client.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'ldclient-rb'
|
|
4
4
|
require 'mustache'
|
|
5
|
+
require 'securerandom'
|
|
5
6
|
require_relative 'ai_config_tracker'
|
|
6
7
|
require_relative 'sdk_info'
|
|
7
8
|
|
|
@@ -100,26 +101,65 @@ module LaunchDarkly
|
|
|
100
101
|
end
|
|
101
102
|
|
|
102
103
|
#
|
|
103
|
-
# The
|
|
104
|
+
# The AIConfigDefault class represents a user-provided fallback AI
|
|
105
|
+
# configuration.
|
|
104
106
|
#
|
|
105
|
-
class
|
|
106
|
-
|
|
107
|
+
# Pass an instance of this class as the +default:+ parameter to
|
|
108
|
+
# {Client#completion_config} to control the fallback values when a flag
|
|
109
|
+
# is not found or cannot be evaluated.
|
|
110
|
+
#
|
|
111
|
+
# This is an input-only type: it is what an application supplies to the
|
|
112
|
+
# SDK, never what the SDK returns. The SDK always returns an
|
|
113
|
+
# {AIConfig}, which carries a tracker factory; AIConfigDefault does not.
|
|
114
|
+
#
|
|
115
|
+
class AIConfigDefault
|
|
116
|
+
attr_reader :enabled, :messages, :model, :provider
|
|
107
117
|
|
|
108
|
-
|
|
118
|
+
#
|
|
119
|
+
# Returns a new AIConfigDefault with enabled: false and no model,
|
|
120
|
+
# messages, or provider.
|
|
121
|
+
#
|
|
122
|
+
# @return [AIConfigDefault] a new disabled fallback config
|
|
123
|
+
#
|
|
124
|
+
def self.disabled
|
|
125
|
+
new(enabled: false)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def initialize(enabled: false, model: nil, messages: nil, provider: nil)
|
|
109
129
|
@enabled = enabled
|
|
110
130
|
@messages = messages
|
|
111
|
-
@tracker = tracker
|
|
112
131
|
@model = model
|
|
113
132
|
@provider = provider
|
|
114
133
|
end
|
|
115
134
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
135
|
+
def to_h
|
|
136
|
+
{
|
|
137
|
+
_ldMeta: {
|
|
138
|
+
enabled: @enabled || false,
|
|
139
|
+
},
|
|
140
|
+
messages: @messages.is_a?(Array) ? @messages.map { |msg| msg&.to_h } : nil,
|
|
141
|
+
model: @model&.to_h,
|
|
142
|
+
provider: @provider&.to_h,
|
|
143
|
+
}
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
#
|
|
148
|
+
# The AIConfig class represents an AI configuration returned by the SDK.
|
|
149
|
+
#
|
|
150
|
+
# Instances are created by {Client#completion_config} and always carry a
|
|
151
|
+
# tracker factory; see {#create_tracker}. Do not instantiate directly.
|
|
152
|
+
# For application-supplied fallback values, use {AIConfigDefault}.
|
|
153
|
+
#
|
|
154
|
+
class AIConfig
|
|
155
|
+
attr_reader :enabled, :messages, :model, :provider
|
|
156
|
+
|
|
157
|
+
def initialize(tracker_factory:, enabled: nil, model: nil, messages: nil, provider: nil)
|
|
158
|
+
@enabled = enabled
|
|
159
|
+
@messages = messages
|
|
160
|
+
@model = model
|
|
161
|
+
@provider = provider
|
|
162
|
+
@tracker_factory = tracker_factory
|
|
123
163
|
end
|
|
124
164
|
|
|
125
165
|
def to_h
|
|
@@ -132,6 +172,18 @@ module LaunchDarkly
|
|
|
132
172
|
provider: @provider&.to_h,
|
|
133
173
|
}
|
|
134
174
|
end
|
|
175
|
+
|
|
176
|
+
#
|
|
177
|
+
# Creates a new tracker for a fresh AI run. Each call mints a new
|
|
178
|
+
# runId (a UUIDv4) that LaunchDarkly uses to correlate the tracker's
|
|
179
|
+
# events in metrics views. Call this once per AI run; metrics from
|
|
180
|
+
# different runIds cannot be combined.
|
|
181
|
+
#
|
|
182
|
+
# @return [AIConfigTracker] a new tracker instance
|
|
183
|
+
#
|
|
184
|
+
def create_tracker
|
|
185
|
+
@tracker_factory.call
|
|
186
|
+
end
|
|
135
187
|
end
|
|
136
188
|
|
|
137
189
|
#
|
|
@@ -172,14 +224,26 @@ module LaunchDarkly
|
|
|
172
224
|
#
|
|
173
225
|
# @param key [String] The key of the configuration flag
|
|
174
226
|
# @param context [LDContext] The context used when evaluating the flag
|
|
175
|
-
# @param default [
|
|
227
|
+
# @param default [AIConfigDefault] The default value to use if the flag is not found
|
|
176
228
|
# @param variables [Hash] Optional variables for rendering messages
|
|
177
229
|
# @return [AIConfig] An AIConfig instance containing the configuration data
|
|
178
230
|
#
|
|
179
231
|
def completion_config(key:, context:, default: nil, variables: nil)
|
|
180
232
|
@ld_client.track(TRACK_USAGE_COMPLETION_CONFIG, context, key, 1)
|
|
181
233
|
|
|
182
|
-
_completion_config(key:, context:, default: default ||
|
|
234
|
+
_completion_config(key:, context:, default: default || AIConfigDefault.disabled, variables:)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
#
|
|
238
|
+
# Reconstructs a tracker from a resumption token, allowing deferred tracking
|
|
239
|
+
# (e.g. feedback from a different process).
|
|
240
|
+
#
|
|
241
|
+
# @param token [String] A resumption token obtained from AIConfigTracker#resumption_token
|
|
242
|
+
# @param context [LDContext] The context for track events
|
|
243
|
+
# @return [AIConfigTracker] A new tracker instance
|
|
244
|
+
#
|
|
245
|
+
def create_tracker(token:, context:)
|
|
246
|
+
AIConfigTracker.from_resumption_token(token: token, ld_client: @ld_client, context: context)
|
|
183
247
|
end
|
|
184
248
|
|
|
185
249
|
# @deprecated Use {#completion_config} instead.
|
|
@@ -226,20 +290,32 @@ module LaunchDarkly
|
|
|
226
290
|
)
|
|
227
291
|
end
|
|
228
292
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
293
|
+
variation_key = variation.dig(:_ldMeta, :variationKey) || ''
|
|
294
|
+
version = variation.dig(:_ldMeta, :version) || 1
|
|
295
|
+
model_name = model&.name || ''
|
|
296
|
+
provider_name = provider_config&.name || ''
|
|
297
|
+
model_key = variation.dig(:_ldMeta, :modelKey)
|
|
298
|
+
model_version = (variation.dig(:_ldMeta, :modelVersion) || 1).to_i
|
|
299
|
+
|
|
300
|
+
tracker_factory = lambda {
|
|
301
|
+
LaunchDarkly::Server::AI::AIConfigTracker.new(
|
|
302
|
+
ld_client: @ld_client,
|
|
303
|
+
run_id: SecureRandom.uuid,
|
|
304
|
+
variation_key: variation_key,
|
|
305
|
+
config_key: key,
|
|
306
|
+
version: version,
|
|
307
|
+
model_name: model_name,
|
|
308
|
+
provider_name: provider_name,
|
|
309
|
+
model_key: model_key,
|
|
310
|
+
model_version: model_version,
|
|
311
|
+
context: context
|
|
312
|
+
)
|
|
313
|
+
}
|
|
238
314
|
|
|
239
315
|
AIConfig.new(
|
|
240
316
|
enabled: variation.dig(:_ldMeta, :enabled) || false,
|
|
241
317
|
messages:,
|
|
242
|
-
|
|
318
|
+
tracker_factory:,
|
|
243
319
|
model:,
|
|
244
320
|
provider: provider_config
|
|
245
321
|
)
|
data/lib/server/ai/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: launchdarkly-server-sdk-ai
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- LaunchDarkly
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-09-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: base64
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
13
27
|
- !ruby/object:Gem::Dependency
|
|
14
28
|
name: launchdarkly-server-sdk
|
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|