langfuse-rb 0.9.0 → 0.10.1
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 +19 -1
- data/README.md +8 -65
- data/lib/langfuse/api_client.rb +182 -261
- data/lib/langfuse/cache_constants.rb +32 -0
- data/lib/langfuse/cache_warmer.rb +21 -13
- data/lib/langfuse/chat_prompt_client.rb +3 -3
- data/lib/langfuse/client.rb +163 -150
- data/lib/langfuse/config.rb +15 -4
- data/lib/langfuse/prompt_cache.rb +145 -7
- data/lib/langfuse/prompt_cache_coordinator.rb +288 -0
- data/lib/langfuse/prompt_cache_events.rb +131 -0
- data/lib/langfuse/prompt_fetch_result.rb +122 -0
- data/lib/langfuse/rails_cache_adapter.rb +170 -9
- data/lib/langfuse/score_client.rb +12 -13
- data/lib/langfuse/stale_while_revalidate.rb +62 -19
- data/lib/langfuse/text_prompt_client.rb +2 -5
- data/lib/langfuse/version.rb +1 -1
- data/lib/langfuse.rb +4 -0
- metadata +6 -2
data/lib/langfuse/api_client.rb
CHANGED
|
@@ -5,6 +5,8 @@ require "faraday/retry"
|
|
|
5
5
|
require "base64"
|
|
6
6
|
require "json"
|
|
7
7
|
require "uri"
|
|
8
|
+
require_relative "prompt_fetch_result"
|
|
9
|
+
require_relative "prompt_cache_coordinator"
|
|
8
10
|
|
|
9
11
|
module Langfuse
|
|
10
12
|
# HTTP client for Langfuse API
|
|
@@ -22,6 +24,8 @@ module Langfuse
|
|
|
22
24
|
# )
|
|
23
25
|
#
|
|
24
26
|
class ApiClient # rubocop:disable Metrics/ClassLength
|
|
27
|
+
include PromptCacheEvents
|
|
28
|
+
|
|
25
29
|
# @return [String] Langfuse public API key
|
|
26
30
|
attr_reader :public_key
|
|
27
31
|
|
|
@@ -48,15 +52,24 @@ module Langfuse
|
|
|
48
52
|
# @param timeout [Integer] HTTP request timeout in seconds
|
|
49
53
|
# @param logger [Logger] Logger instance for debugging
|
|
50
54
|
# @param cache [PromptCache, RailsCacheAdapter, nil] Optional cache for prompt responses
|
|
55
|
+
# @param cache_observer [#call, nil] Optional observer for prompt cache events
|
|
51
56
|
# @return [ApiClient]
|
|
52
|
-
|
|
57
|
+
# rubocop:disable Metrics/ParameterLists
|
|
58
|
+
def initialize(public_key:, secret_key:, base_url:, timeout: 5, logger: nil, cache: nil, cache_observer: nil)
|
|
53
59
|
@public_key = public_key
|
|
54
60
|
@secret_key = secret_key
|
|
55
61
|
@base_url = base_url
|
|
56
62
|
@timeout = timeout
|
|
57
63
|
@logger = logger || Logger.new($stdout, level: Logger::WARN)
|
|
58
64
|
@cache = cache
|
|
65
|
+
setup_prompt_cache_events(cache_observer: cache_observer)
|
|
66
|
+
@prompt_cache_coordinator = PromptCacheCoordinator.new(
|
|
67
|
+
cache: cache,
|
|
68
|
+
event_emitter: self,
|
|
69
|
+
fetch_prompt: ->(name, version:, label:) { fetch_prompt_from_api(name, version: version, label: label) }
|
|
70
|
+
)
|
|
59
71
|
end
|
|
72
|
+
# rubocop:enable Metrics/ParameterLists
|
|
60
73
|
|
|
61
74
|
# Get a Faraday connection
|
|
62
75
|
#
|
|
@@ -89,15 +102,7 @@ module Langfuse
|
|
|
89
102
|
# puts "#{prompt['name']} (v#{prompt['version']})"
|
|
90
103
|
# end
|
|
91
104
|
def list_prompts(page: nil, limit: nil)
|
|
92
|
-
|
|
93
|
-
params = { page: page, limit: limit }.compact
|
|
94
|
-
|
|
95
|
-
response = connection.get("/api/public/v2/prompts", params)
|
|
96
|
-
result = handle_response(response)
|
|
97
|
-
|
|
98
|
-
# API returns { data: [...], meta: {...} }
|
|
99
|
-
result["data"] || []
|
|
100
|
-
end
|
|
105
|
+
request(:get, "/api/public/v2/prompts", params: { page: page, limit: limit }.compact)["data"] || []
|
|
101
106
|
end
|
|
102
107
|
|
|
103
108
|
# Fetch a prompt from the Langfuse API
|
|
@@ -109,18 +114,102 @@ module Langfuse
|
|
|
109
114
|
# @param name [String] The name of the prompt
|
|
110
115
|
# @param version [Integer, nil] Optional specific version number
|
|
111
116
|
# @param label [String, nil] Optional label (e.g., "production", "latest")
|
|
117
|
+
# @param cache_ttl [Integer, nil] Optional TTL override for this fetch
|
|
112
118
|
# @return [Hash] The prompt data
|
|
113
119
|
# @raise [ArgumentError] if both version and label are provided
|
|
114
120
|
# @raise [NotFoundError] if the prompt is not found
|
|
115
121
|
# @raise [UnauthorizedError] if authentication fails
|
|
116
122
|
# @raise [ApiError] for other API errors
|
|
117
|
-
def get_prompt(name, version: nil, label: nil)
|
|
118
|
-
|
|
119
|
-
|
|
123
|
+
def get_prompt(name, version: nil, label: nil, cache_ttl: nil)
|
|
124
|
+
get_prompt_result(name, version: version, label: label, cache_ttl: cache_ttl).prompt
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Fetch a prompt and include cache metadata.
|
|
128
|
+
#
|
|
129
|
+
# @param name [String] The name of the prompt
|
|
130
|
+
# @param version [Integer, nil] Optional specific version number
|
|
131
|
+
# @param label [String, nil] Optional label (e.g., "production", "latest")
|
|
132
|
+
# @param cache_ttl [Integer, nil] Optional TTL override for this fetch
|
|
133
|
+
# @return [PromptFetchResult] Prompt data plus cache metadata
|
|
134
|
+
# @raise [ArgumentError] if both version and label are provided
|
|
135
|
+
# @raise [ArgumentError] if cache_ttl is negative
|
|
136
|
+
# @raise [NotFoundError] if the prompt is not found
|
|
137
|
+
# @raise [UnauthorizedError] if authentication fails
|
|
138
|
+
# @raise [ApiError] for other API errors
|
|
139
|
+
def get_prompt_result(name, version: nil, label: nil, cache_ttl: nil)
|
|
140
|
+
@prompt_cache_coordinator.get_prompt_result(name, version: version, label: label, cache_ttl: cache_ttl)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Refresh a prompt from the API, optionally writing through to cache.
|
|
144
|
+
#
|
|
145
|
+
# @param name [String] The name of the prompt
|
|
146
|
+
# @param version [Integer, nil] Optional specific version number
|
|
147
|
+
# @param label [String, nil] Optional label
|
|
148
|
+
# @param cache_ttl [Integer, nil] Optional TTL override for this refresh
|
|
149
|
+
# @return [PromptFetchResult] Prompt data plus cache metadata
|
|
150
|
+
# @raise [ArgumentError] if both version and label are provided
|
|
151
|
+
# @raise [ArgumentError] if cache_ttl is negative
|
|
152
|
+
# @raise [NotFoundError] if the prompt is not found
|
|
153
|
+
# @raise [UnauthorizedError] if authentication fails
|
|
154
|
+
# @raise [ApiError] for other API errors
|
|
155
|
+
def refresh_prompt(name, version: nil, label: nil, cache_ttl: nil)
|
|
156
|
+
@prompt_cache_coordinator.refresh_prompt(name, version: version, label: label, cache_ttl: cache_ttl)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Inspect the logical and generated cache keys for a prompt.
|
|
160
|
+
#
|
|
161
|
+
# @param name [String] The prompt name
|
|
162
|
+
# @param version [Integer, nil] Optional specific version number
|
|
163
|
+
# @param label [String, nil] Optional label
|
|
164
|
+
# @return [PromptCacheKey] Logical and generated cache keys
|
|
165
|
+
# @raise [ArgumentError] if both version and label are provided
|
|
166
|
+
def prompt_cache_key(name, version: nil, label: nil)
|
|
167
|
+
@prompt_cache_coordinator.prompt_cache_key(name, version: version, label: label)
|
|
168
|
+
end
|
|
120
169
|
|
|
121
|
-
|
|
122
|
-
|
|
170
|
+
# Invalidate one exact logical prompt cache key.
|
|
171
|
+
#
|
|
172
|
+
# @param name [String] The prompt name
|
|
173
|
+
# @param version [Integer, nil] Optional specific version number
|
|
174
|
+
# @param label [String, nil] Optional label
|
|
175
|
+
# @return [PromptCacheKey] The invalidated key
|
|
176
|
+
# @raise [ArgumentError] if both version and label are provided
|
|
177
|
+
def invalidate_prompt_cache(name, version: nil, label: nil)
|
|
178
|
+
@prompt_cache_coordinator.invalidate_prompt_cache(name, version: version, label: label)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Invalidate all cached variants for one prompt name.
|
|
182
|
+
#
|
|
183
|
+
# @param name [String] The prompt name
|
|
184
|
+
# @return [Integer, nil] New generation, or nil when cache is disabled
|
|
185
|
+
def invalidate_prompt_cache_by_name(name)
|
|
186
|
+
@prompt_cache_coordinator.invalidate_prompt_cache_by_name(name)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Logically clear the whole Langfuse prompt cache namespace.
|
|
190
|
+
#
|
|
191
|
+
# @return [Integer, nil] New global generation, or nil when cache is disabled
|
|
192
|
+
def clear_prompt_cache
|
|
193
|
+
@prompt_cache_coordinator.clear_prompt_cache
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Return prompt cache statistics.
|
|
197
|
+
#
|
|
198
|
+
# @return [Hash] Cache statistics
|
|
199
|
+
def prompt_cache_stats
|
|
200
|
+
@prompt_cache_coordinator.prompt_cache_stats
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Validate the configured prompt cache backend.
|
|
204
|
+
#
|
|
205
|
+
# @return [Boolean] true when the configured backend is usable
|
|
206
|
+
# @raise [ConfigurationError] if the backend is invalid
|
|
207
|
+
# rubocop:disable Naming/PredicateMethod
|
|
208
|
+
def validate_prompt_cache_backend!
|
|
209
|
+
@cache&.validate!
|
|
210
|
+
true
|
|
123
211
|
end
|
|
212
|
+
# rubocop:enable Naming/PredicateMethod
|
|
124
213
|
|
|
125
214
|
# Create a new prompt (or new version if prompt with same name exists)
|
|
126
215
|
#
|
|
@@ -145,21 +234,12 @@ module Langfuse
|
|
|
145
234
|
#
|
|
146
235
|
# rubocop:disable Metrics/ParameterLists
|
|
147
236
|
def create_prompt(name:, prompt:, type:, config: {}, labels: [], tags: [], commit_message: nil)
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
config: config,
|
|
155
|
-
labels: labels,
|
|
156
|
-
tags: tags
|
|
157
|
-
}
|
|
158
|
-
payload[:commitMessage] = commit_message if commit_message
|
|
159
|
-
|
|
160
|
-
response = connection.post(path, payload)
|
|
161
|
-
handle_response(response)
|
|
162
|
-
end
|
|
237
|
+
payload = {
|
|
238
|
+
name: name, prompt: prompt, type: type, config: config,
|
|
239
|
+
labels: labels, tags: tags, commitMessage: commit_message
|
|
240
|
+
}.compact
|
|
241
|
+
request(:post, "/api/public/v2/prompts", body: payload)
|
|
242
|
+
.tap { @prompt_cache_coordinator.invalidate_after_mutation(name) }
|
|
163
243
|
end
|
|
164
244
|
# rubocop:enable Metrics/ParameterLists
|
|
165
245
|
|
|
@@ -183,13 +263,9 @@ module Langfuse
|
|
|
183
263
|
def update_prompt(name:, version:, labels:)
|
|
184
264
|
raise ArgumentError, "labels must be an array" unless labels.is_a?(Array)
|
|
185
265
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
response = connection.patch(path, payload)
|
|
191
|
-
handle_response(response)
|
|
192
|
-
end
|
|
266
|
+
path = "/api/public/v2/prompts/#{URI.encode_uri_component(name)}/versions/#{version}"
|
|
267
|
+
request(:patch, path, body: { newLabels: labels })
|
|
268
|
+
.tap { @prompt_cache_coordinator.invalidate_after_mutation(name) }
|
|
193
269
|
end
|
|
194
270
|
|
|
195
271
|
# Send a batch of events to the Langfuse ingestion API
|
|
@@ -218,13 +294,9 @@ module Langfuse
|
|
|
218
294
|
raise ArgumentError, "events must be an array" unless events.is_a?(Array)
|
|
219
295
|
raise ArgumentError, "events array cannot be empty" if events.empty?
|
|
220
296
|
|
|
221
|
-
|
|
222
|
-
payload = { batch: events }
|
|
223
|
-
|
|
224
|
-
response = connection.post(path, payload)
|
|
297
|
+
response = connection.post("/api/public/ingestion", { batch: events })
|
|
225
298
|
handle_batch_response(response)
|
|
226
299
|
rescue Faraday::RetriableResponse => e
|
|
227
|
-
# Retry middleware exhausted all retries - handle the final response
|
|
228
300
|
logger.error("Langfuse batch send failed: Retries exhausted - #{e.response.status}")
|
|
229
301
|
handle_batch_response(e.response)
|
|
230
302
|
rescue Faraday::Error => e
|
|
@@ -248,16 +320,12 @@ module Langfuse
|
|
|
248
320
|
# api_client.create_dataset_run_item(dataset_item_id: "item-123", run_name: "eval-v1", trace_id: "trace-abc")
|
|
249
321
|
def create_dataset_run_item(dataset_item_id:, run_name:, trace_id: nil,
|
|
250
322
|
observation_id: nil, metadata: nil, run_description: nil)
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
response = connection.post("/api/public/dataset-run-items", payload)
|
|
259
|
-
handle_response(response)
|
|
260
|
-
end
|
|
323
|
+
payload = {
|
|
324
|
+
datasetItemId: dataset_item_id, runName: run_name,
|
|
325
|
+
traceId: trace_id, observationId: observation_id,
|
|
326
|
+
metadata: metadata, runDescription: run_description
|
|
327
|
+
}.compact
|
|
328
|
+
request(:post, "/api/public/dataset-run-items", body: payload)
|
|
261
329
|
end
|
|
262
330
|
|
|
263
331
|
# Fetch a dataset run by dataset and run name
|
|
@@ -269,10 +337,7 @@ module Langfuse
|
|
|
269
337
|
# @raise [UnauthorizedError] if authentication fails
|
|
270
338
|
# @raise [ApiError] for other API errors
|
|
271
339
|
def get_dataset_run(dataset_name:, run_name:)
|
|
272
|
-
|
|
273
|
-
response = connection.get(dataset_run_path(dataset_name: dataset_name, run_name: run_name))
|
|
274
|
-
handle_response(response)
|
|
275
|
-
end
|
|
340
|
+
request(:get, dataset_run_path(dataset_name: dataset_name, run_name: run_name))
|
|
276
341
|
end
|
|
277
342
|
|
|
278
343
|
# List dataset runs in a dataset
|
|
@@ -284,8 +349,7 @@ module Langfuse
|
|
|
284
349
|
# @raise [UnauthorizedError] if authentication fails
|
|
285
350
|
# @raise [ApiError] for other API errors
|
|
286
351
|
def list_dataset_runs(dataset_name:, page: nil, limit: nil)
|
|
287
|
-
|
|
288
|
-
result["data"] || []
|
|
352
|
+
list_dataset_runs_paginated(dataset_name: dataset_name, page: page, limit: limit)["data"] || []
|
|
289
353
|
end
|
|
290
354
|
|
|
291
355
|
# Full paginated response including "meta" for internal pagination use
|
|
@@ -293,10 +357,7 @@ module Langfuse
|
|
|
293
357
|
# @api private
|
|
294
358
|
# @return [Hash] Full response hash with "data" array and "meta" pagination info
|
|
295
359
|
def list_dataset_runs_paginated(dataset_name:, page: nil, limit: nil)
|
|
296
|
-
|
|
297
|
-
response = connection.get(dataset_runs_path(dataset_name), build_dataset_runs_params(page: page, limit: limit))
|
|
298
|
-
handle_response(response)
|
|
299
|
-
end
|
|
360
|
+
request(:get, dataset_runs_path(dataset_name), params: { page: page, limit: limit }.compact)
|
|
300
361
|
end
|
|
301
362
|
|
|
302
363
|
# Delete a dataset run by name
|
|
@@ -325,19 +386,16 @@ module Langfuse
|
|
|
325
386
|
# data = api_client.get_projects
|
|
326
387
|
# project_id = data["data"][0]["id"]
|
|
327
388
|
def get_projects # rubocop:disable Naming/AccessorMethodName
|
|
328
|
-
|
|
329
|
-
response = connection.get("/api/public/projects")
|
|
330
|
-
handle_response(response)
|
|
331
|
-
end
|
|
389
|
+
request(:get, "/api/public/projects")
|
|
332
390
|
end
|
|
333
391
|
|
|
334
392
|
# Shut down the API client and release resources
|
|
335
393
|
#
|
|
336
|
-
# Shuts down the cache
|
|
394
|
+
# Shuts down the cache backend's SWR thread pool when present.
|
|
337
395
|
#
|
|
338
396
|
# @return [void]
|
|
339
397
|
def shutdown
|
|
340
|
-
cache
|
|
398
|
+
@cache&.shutdown
|
|
341
399
|
end
|
|
342
400
|
|
|
343
401
|
# List traces in the project
|
|
@@ -367,14 +425,13 @@ module Langfuse
|
|
|
367
425
|
from_timestamp: nil, to_timestamp: nil, order_by: nil,
|
|
368
426
|
tags: nil, version: nil, release: nil, environment: nil,
|
|
369
427
|
fields: nil, filter: nil)
|
|
370
|
-
|
|
428
|
+
list_traces_paginated(
|
|
371
429
|
page: page, limit: limit, user_id: user_id, name: name,
|
|
372
430
|
session_id: session_id, from_timestamp: from_timestamp,
|
|
373
431
|
to_timestamp: to_timestamp, order_by: order_by, tags: tags,
|
|
374
432
|
version: version, release: release, environment: environment,
|
|
375
433
|
fields: fields, filter: filter
|
|
376
|
-
)
|
|
377
|
-
result["data"] || []
|
|
434
|
+
)["data"] || []
|
|
378
435
|
end
|
|
379
436
|
# rubocop:enable Metrics/ParameterLists
|
|
380
437
|
|
|
@@ -387,17 +444,14 @@ module Langfuse
|
|
|
387
444
|
from_timestamp: nil, to_timestamp: nil, order_by: nil,
|
|
388
445
|
tags: nil, version: nil, release: nil, environment: nil,
|
|
389
446
|
fields: nil, filter: nil)
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
response = connection.get("/api/public/traces", params)
|
|
399
|
-
handle_response(response)
|
|
400
|
-
end
|
|
447
|
+
params = build_traces_params(
|
|
448
|
+
page: page, limit: limit, user_id: user_id, name: name,
|
|
449
|
+
session_id: session_id, from_timestamp: from_timestamp,
|
|
450
|
+
to_timestamp: to_timestamp, order_by: order_by, tags: tags,
|
|
451
|
+
version: version, release: release, environment: environment,
|
|
452
|
+
fields: fields, filter: filter
|
|
453
|
+
)
|
|
454
|
+
request(:get, "/api/public/traces", params: params)
|
|
401
455
|
end
|
|
402
456
|
# rubocop:enable Metrics/ParameterLists
|
|
403
457
|
|
|
@@ -412,11 +466,7 @@ module Langfuse
|
|
|
412
466
|
# @example
|
|
413
467
|
# trace = api_client.get_trace("trace-uuid-123")
|
|
414
468
|
def get_trace(id)
|
|
415
|
-
|
|
416
|
-
encoded_id = URI.encode_uri_component(id)
|
|
417
|
-
response = connection.get("/api/public/traces/#{encoded_id}")
|
|
418
|
-
handle_response(response)
|
|
419
|
-
end
|
|
469
|
+
request(:get, "/api/public/traces/#{URI.encode_uri_component(id)}")
|
|
420
470
|
end
|
|
421
471
|
|
|
422
472
|
# List all datasets in the project
|
|
@@ -430,13 +480,7 @@ module Langfuse
|
|
|
430
480
|
# @example
|
|
431
481
|
# datasets = api_client.list_datasets(page: 1, limit: 10)
|
|
432
482
|
def list_datasets(page: nil, limit: nil)
|
|
433
|
-
|
|
434
|
-
params = { page: page, limit: limit }.compact
|
|
435
|
-
|
|
436
|
-
response = connection.get("/api/public/v2/datasets", params)
|
|
437
|
-
result = handle_response(response)
|
|
438
|
-
result["data"] || []
|
|
439
|
-
end
|
|
483
|
+
request(:get, "/api/public/v2/datasets", params: { page: page, limit: limit }.compact)["data"] || []
|
|
440
484
|
end
|
|
441
485
|
|
|
442
486
|
# Fetch a dataset by name
|
|
@@ -450,11 +494,7 @@ module Langfuse
|
|
|
450
494
|
# @example
|
|
451
495
|
# data = api_client.get_dataset("my-dataset")
|
|
452
496
|
def get_dataset(name)
|
|
453
|
-
|
|
454
|
-
encoded_name = URI.encode_uri_component(name)
|
|
455
|
-
response = connection.get("/api/public/v2/datasets/#{encoded_name}")
|
|
456
|
-
handle_response(response)
|
|
457
|
-
end
|
|
497
|
+
request(:get, "/api/public/v2/datasets/#{URI.encode_uri_component(name)}")
|
|
458
498
|
end
|
|
459
499
|
|
|
460
500
|
# Create a new dataset
|
|
@@ -469,12 +509,8 @@ module Langfuse
|
|
|
469
509
|
# @example
|
|
470
510
|
# data = api_client.create_dataset(name: "my-dataset", description: "QA evaluation set")
|
|
471
511
|
def create_dataset(name:, description: nil, metadata: nil)
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
response = connection.post("/api/public/v2/datasets", payload)
|
|
476
|
-
handle_response(response)
|
|
477
|
-
end
|
|
512
|
+
request(:post, "/api/public/v2/datasets",
|
|
513
|
+
body: { name: name, description: description, metadata: metadata }.compact)
|
|
478
514
|
end
|
|
479
515
|
|
|
480
516
|
# Create a new dataset item (or upsert if id is provided)
|
|
@@ -501,16 +537,13 @@ module Langfuse
|
|
|
501
537
|
def create_dataset_item(dataset_name:, input: nil, expected_output: nil,
|
|
502
538
|
metadata: nil, id: nil, source_trace_id: nil,
|
|
503
539
|
source_observation_id: nil, status: nil)
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
response = connection.post("/api/public/dataset-items", payload)
|
|
512
|
-
handle_response(response)
|
|
513
|
-
end
|
|
540
|
+
payload = {
|
|
541
|
+
datasetName: dataset_name, id: id, input: input,
|
|
542
|
+
expectedOutput: expected_output, metadata: metadata,
|
|
543
|
+
sourceTraceId: source_trace_id, sourceObservationId: source_observation_id,
|
|
544
|
+
status: status&.to_s&.upcase
|
|
545
|
+
}.compact
|
|
546
|
+
request(:post, "/api/public/dataset-items", body: payload)
|
|
514
547
|
end
|
|
515
548
|
# rubocop:enable Metrics/ParameterLists
|
|
516
549
|
|
|
@@ -525,11 +558,7 @@ module Langfuse
|
|
|
525
558
|
# @example
|
|
526
559
|
# data = api_client.get_dataset_item("item-uuid-123")
|
|
527
560
|
def get_dataset_item(id)
|
|
528
|
-
|
|
529
|
-
encoded_id = URI.encode_uri_component(id)
|
|
530
|
-
response = connection.get("/api/public/dataset-items/#{encoded_id}")
|
|
531
|
-
handle_response(response)
|
|
532
|
-
end
|
|
561
|
+
request(:get, "/api/public/dataset-items/#{URI.encode_uri_component(id)}")
|
|
533
562
|
end
|
|
534
563
|
|
|
535
564
|
# List items in a dataset with optional filters
|
|
@@ -545,13 +574,8 @@ module Langfuse
|
|
|
545
574
|
#
|
|
546
575
|
# @example
|
|
547
576
|
# items = api_client.list_dataset_items(dataset_name: "my-dataset", limit: 50)
|
|
548
|
-
def list_dataset_items(
|
|
549
|
-
|
|
550
|
-
result = list_dataset_items_paginated(
|
|
551
|
-
dataset_name: dataset_name, page: page, limit: limit,
|
|
552
|
-
source_trace_id: source_trace_id, source_observation_id: source_observation_id
|
|
553
|
-
)
|
|
554
|
-
result["data"] || []
|
|
577
|
+
def list_dataset_items(**)
|
|
578
|
+
list_dataset_items_paginated(**)["data"] || []
|
|
555
579
|
end
|
|
556
580
|
|
|
557
581
|
# Full paginated response including "meta" for internal pagination use
|
|
@@ -560,15 +584,11 @@ module Langfuse
|
|
|
560
584
|
# @return [Hash] Full response hash with "data" array and "meta" pagination info
|
|
561
585
|
def list_dataset_items_paginated(dataset_name:, page: nil, limit: nil,
|
|
562
586
|
source_trace_id: nil, source_observation_id: nil)
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
response = connection.get("/api/public/dataset-items", params)
|
|
570
|
-
handle_response(response)
|
|
571
|
-
end
|
|
587
|
+
params = {
|
|
588
|
+
datasetName: dataset_name, page: page, limit: limit,
|
|
589
|
+
sourceTraceId: source_trace_id, sourceObservationId: source_observation_id
|
|
590
|
+
}.compact
|
|
591
|
+
request(:get, "/api/public/dataset-items", params: params)
|
|
572
592
|
end
|
|
573
593
|
|
|
574
594
|
# Delete a dataset item by ID
|
|
@@ -582,8 +602,7 @@ module Langfuse
|
|
|
582
602
|
# @example
|
|
583
603
|
# api_client.delete_dataset_item("item-uuid-123")
|
|
584
604
|
def delete_dataset_item(id)
|
|
585
|
-
|
|
586
|
-
response = connection.delete("/api/public/dataset-items/#{encoded_id}")
|
|
605
|
+
response = connection.delete("/api/public/dataset-items/#{URI.encode_uri_component(id)}")
|
|
587
606
|
handle_delete_dataset_item_response(response, id)
|
|
588
607
|
rescue Faraday::RetriableResponse => e
|
|
589
608
|
logger.error("Faraday error: Retries exhausted - #{e.response.status}")
|
|
@@ -595,126 +614,42 @@ module Langfuse
|
|
|
595
614
|
|
|
596
615
|
private
|
|
597
616
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
# @param cache_key [String] The cache key for this prompt
|
|
601
|
-
# @param name [String] The name of the prompt
|
|
602
|
-
# @param version [Integer, nil] Optional specific version number
|
|
603
|
-
# @param label [String, nil] Optional label
|
|
604
|
-
# @return [Hash] The prompt data
|
|
605
|
-
def fetch_with_appropriate_caching_strategy(cache_key, name, version, label)
|
|
606
|
-
if swr_cache_available?
|
|
607
|
-
fetch_with_swr_cache(cache_key, name, version, label)
|
|
608
|
-
elsif distributed_cache_available?
|
|
609
|
-
fetch_with_distributed_cache(cache_key, name, version, label)
|
|
610
|
-
else
|
|
611
|
-
fetch_with_simple_cache(cache_key, name, version, label)
|
|
612
|
-
end
|
|
613
|
-
end
|
|
614
|
-
|
|
615
|
-
# Check if SWR cache is available
|
|
616
|
-
def swr_cache_available?
|
|
617
|
-
cache.respond_to?(:swr_enabled?) && cache.swr_enabled?
|
|
617
|
+
def cache_backend_name
|
|
618
|
+
@prompt_cache_coordinator.backend_name
|
|
618
619
|
end
|
|
619
620
|
|
|
620
|
-
#
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
#
|
|
626
|
-
#
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
add_optional_dataset_item_fields(payload, input, expected_output, metadata, id)
|
|
632
|
-
add_optional_source_fields(payload, source_trace_id, source_observation_id, status)
|
|
621
|
+
# Issue an HTTP request, raise on Faraday errors, parse the response.
|
|
622
|
+
#
|
|
623
|
+
# @api private
|
|
624
|
+
# @param verb [Symbol] HTTP verb (:get, :post, :patch, :delete)
|
|
625
|
+
# @param path [String] Request path
|
|
626
|
+
# @param params [Hash, nil] Query string params (GET/DELETE)
|
|
627
|
+
# @param body [Hash, nil] JSON body (POST/PATCH)
|
|
628
|
+
# @return [Hash] Parsed response body
|
|
629
|
+
def request(verb, path, params: nil, body: nil)
|
|
630
|
+
with_faraday_error_handling do
|
|
631
|
+
handle_response(connection.public_send(verb, path, body || params))
|
|
633
632
|
end
|
|
634
633
|
end
|
|
635
|
-
# rubocop:enable Metrics/ParameterLists
|
|
636
|
-
|
|
637
|
-
def add_optional_dataset_item_fields(payload, input, expected_output, metadata, id)
|
|
638
|
-
payload[:id] = id if id
|
|
639
|
-
payload[:input] = input if input
|
|
640
|
-
payload[:expectedOutput] = expected_output if expected_output
|
|
641
|
-
payload[:metadata] = metadata if metadata
|
|
642
|
-
end
|
|
643
634
|
|
|
644
|
-
def
|
|
645
|
-
payload[:sourceTraceId] = source_trace_id if source_trace_id
|
|
646
|
-
payload[:sourceObservationId] = source_observation_id if source_observation_id
|
|
647
|
-
payload[:status] = status.to_s.upcase if status
|
|
648
|
-
end
|
|
649
|
-
|
|
650
|
-
# Build params for list_dataset_items
|
|
651
|
-
def build_dataset_items_params(dataset_name:, page:, limit:,
|
|
652
|
-
source_trace_id:, source_observation_id:)
|
|
635
|
+
def build_traces_params(**options)
|
|
653
636
|
{
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
637
|
+
page: options[:page], limit: options[:limit], userId: options[:user_id], name: options[:name],
|
|
638
|
+
sessionId: options[:session_id],
|
|
639
|
+
fromTimestamp: options[:from_timestamp]&.iso8601,
|
|
640
|
+
toTimestamp: options[:to_timestamp]&.iso8601,
|
|
641
|
+
orderBy: options[:order_by], tags: options[:tags], version: options[:version],
|
|
642
|
+
release: options[:release], environment: options[:environment], fields: options[:fields],
|
|
643
|
+
filter: options[:filter]
|
|
659
644
|
}.compact
|
|
660
645
|
end
|
|
661
646
|
|
|
662
|
-
# Build params for list_dataset_runs
|
|
663
|
-
def build_dataset_runs_params(page:, limit:)
|
|
664
|
-
{ page: page, limit: limit }.compact
|
|
665
|
-
end
|
|
666
|
-
|
|
667
|
-
# Build endpoint path for dataset runs
|
|
668
647
|
def dataset_runs_path(dataset_name)
|
|
669
|
-
|
|
670
|
-
"/api/public/datasets/#{encoded_name}/runs"
|
|
648
|
+
"/api/public/datasets/#{URI.encode_uri_component(dataset_name)}/runs"
|
|
671
649
|
end
|
|
672
650
|
|
|
673
|
-
# Build endpoint path for a specific dataset run
|
|
674
651
|
def dataset_run_path(dataset_name:, run_name:)
|
|
675
|
-
|
|
676
|
-
"#{dataset_runs_path(dataset_name)}/#{encoded_run_name}"
|
|
677
|
-
end
|
|
678
|
-
|
|
679
|
-
# Build query params for list_traces, mapping snake_case to camelCase
|
|
680
|
-
# rubocop:disable Metrics/ParameterLists
|
|
681
|
-
def build_traces_params(page:, limit:, user_id:, name:, session_id:,
|
|
682
|
-
from_timestamp:, to_timestamp:, order_by:,
|
|
683
|
-
tags:, version:, release:, environment:, fields:, filter:)
|
|
684
|
-
{
|
|
685
|
-
page: page, limit: limit, userId: user_id, name: name,
|
|
686
|
-
sessionId: session_id,
|
|
687
|
-
fromTimestamp: from_timestamp&.iso8601,
|
|
688
|
-
toTimestamp: to_timestamp&.iso8601,
|
|
689
|
-
orderBy: order_by, tags: tags, version: version,
|
|
690
|
-
release: release, environment: environment, fields: fields,
|
|
691
|
-
filter: filter
|
|
692
|
-
}.compact
|
|
693
|
-
end
|
|
694
|
-
# rubocop:enable Metrics/ParameterLists
|
|
695
|
-
|
|
696
|
-
# Fetch with SWR cache
|
|
697
|
-
def fetch_with_swr_cache(cache_key, name, version, label)
|
|
698
|
-
cache.fetch_with_stale_while_revalidate(cache_key) do
|
|
699
|
-
fetch_prompt_from_api(name, version: version, label: label)
|
|
700
|
-
end
|
|
701
|
-
end
|
|
702
|
-
|
|
703
|
-
# Fetch with distributed cache (Rails.cache with stampede protection)
|
|
704
|
-
def fetch_with_distributed_cache(cache_key, name, version, label)
|
|
705
|
-
cache.fetch_with_lock(cache_key) do
|
|
706
|
-
fetch_prompt_from_api(name, version: version, label: label)
|
|
707
|
-
end
|
|
708
|
-
end
|
|
709
|
-
|
|
710
|
-
# Fetch with simple cache (in-memory cache)
|
|
711
|
-
def fetch_with_simple_cache(cache_key, name, version, label)
|
|
712
|
-
cached_data = cache.get(cache_key)
|
|
713
|
-
return cached_data if cached_data
|
|
714
|
-
|
|
715
|
-
prompt_data = fetch_prompt_from_api(name, version: version, label: label)
|
|
716
|
-
cache.set(cache_key, prompt_data)
|
|
717
|
-
prompt_data
|
|
652
|
+
"#{dataset_runs_path(dataset_name)}/#{URI.encode_uri_component(run_name)}"
|
|
718
653
|
end
|
|
719
654
|
|
|
720
655
|
# Fetch a prompt from the API (without caching)
|
|
@@ -727,13 +662,8 @@ module Langfuse
|
|
|
727
662
|
# @raise [UnauthorizedError] if authentication fails
|
|
728
663
|
# @raise [ApiError] for other API errors
|
|
729
664
|
def fetch_prompt_from_api(name, version: nil, label: nil)
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
path = "/api/public/v2/prompts/#{URI.encode_uri_component(name)}"
|
|
733
|
-
|
|
734
|
-
response = connection.get(path, params)
|
|
735
|
-
handle_response(response)
|
|
736
|
-
end
|
|
665
|
+
path = "/api/public/v2/prompts/#{URI.encode_uri_component(name)}"
|
|
666
|
+
request(:get, path, params: { version: version, label: label }.compact)
|
|
737
667
|
end
|
|
738
668
|
|
|
739
669
|
# Build a new Faraday connection
|
|
@@ -802,15 +732,6 @@ module Langfuse
|
|
|
802
732
|
"langfuse-rb/#{Langfuse::VERSION}"
|
|
803
733
|
end
|
|
804
734
|
|
|
805
|
-
# Build query parameters for prompt request
|
|
806
|
-
#
|
|
807
|
-
# @param version [Integer, nil] Optional version number
|
|
808
|
-
# @param label [String, nil] Optional label
|
|
809
|
-
# @return [Hash] Query parameters
|
|
810
|
-
def build_prompt_params(version: nil, label: nil)
|
|
811
|
-
{ version: version, label: label }.compact
|
|
812
|
-
end
|
|
813
|
-
|
|
814
735
|
# Wrap a block with standard Faraday error handling.
|
|
815
736
|
#
|
|
816
737
|
# Catches RetriableResponse (retries exhausted) and generic Faraday errors,
|