langfuse-rb 0.3.0 → 0.4.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: 60270020fc35460c5e29381351bbca35f1cdfe8b7dfe05eca43a0fb20dc06b7b
4
- data.tar.gz: 4951c9b1546de4c9d00bb3b3be4c5325c8edf4d5bf6f5ecab7d9520ab052451b
3
+ metadata.gz: e356e95702ea99ed80bfc258a3a1dbfa6c71985bb8a31110f26c09e6ac8c8b8d
4
+ data.tar.gz: 742033eb63885f9a1b33377258c423a25c0fe6af8554965de8fd76b1807d3a93
5
5
  SHA512:
6
- metadata.gz: 84fa1fc6ea91bda9ddcaa32dd43cb457439e0e4cdee06e3f3df88aae9c54c5b10abd16cb120f234da28733ba6497c466d0ece7888410d7b2711815e13f3956ef
7
- data.tar.gz: 785bd5801a8c6b0ecd7c94f43083bdd6f12463bb918fa5f4a6faee32924a87a5058cf9700b20984fa9e8964cb3070975235771ddfddabca115cb33538020b065
6
+ metadata.gz: d26c930cf723cbdbd5fdd738fcbae8b2181aca3f340eb0929c3ab2d76720aa3b1f217fe8f71804607f0cac32a3b115b7a625e02cf982d30ceceb1c0c45c0d603
7
+ data.tar.gz: a4667dc7d5abe11b7facd78c26e406a394696f9d0ca6663a200ad0232adcd7bd45d7d4bf495b2f98c7a7ed23b5f7b62ab7c2ba7b76a606b546859072b98cedec
data/CHANGELOG.md CHANGED
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.0] - 2026-02-08
11
+
12
+ ### Added
13
+ - Dataset and dataset item management support (#40)
14
+ - Experiment runner for evaluating datasets (#41)
15
+ - Project-scoped URL generation for traces, observations, datasets, and experiments (#43)
16
+
17
+ ### Changed
18
+ - Extracted duplicated Faraday rescue pattern in API client (#44)
19
+
20
+ ### Documentation
21
+ - YARD documentation for all public methods (#42)
22
+ - Dataset and experiment usage guides (#45)
23
+
10
24
  ## [0.3.0] - 2026-01-23
11
25
 
12
26
  ### Added
@@ -40,7 +54,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
40
54
  - Migrated from legacy ingestion API to OTLP endpoint
41
55
  - Removed `tracing_enabled` configuration flag (#2)
42
56
 
43
- [Unreleased]: https://github.com/simplepractice/langfuse-rb/compare/v0.3.0...HEAD
57
+ [Unreleased]: https://github.com/simplepractice/langfuse-rb/compare/v0.4.0...HEAD
58
+ [0.4.0]: https://github.com/simplepractice/langfuse-rb/compare/v0.3.0...v0.4.0
44
59
  [0.3.0]: https://github.com/simplepractice/langfuse-rb/compare/v0.2.0...v0.3.0
45
60
  [0.2.0]: https://github.com/simplepractice/langfuse-rb/compare/v0.1.0...v0.2.0
46
61
  [0.1.0]: https://github.com/simplepractice/langfuse-rb/releases/tag/v0.1.0
@@ -22,7 +22,23 @@ module Langfuse
22
22
  # )
23
23
  #
24
24
  class ApiClient # rubocop:disable Metrics/ClassLength
25
- attr_reader :public_key, :secret_key, :base_url, :timeout, :logger, :cache
25
+ # @return [String] Langfuse public API key
26
+ attr_reader :public_key
27
+
28
+ # @return [String] Langfuse secret API key
29
+ attr_reader :secret_key
30
+
31
+ # @return [String] Base URL for Langfuse API
32
+ attr_reader :base_url
33
+
34
+ # @return [Integer] HTTP request timeout in seconds
35
+ attr_reader :timeout
36
+
37
+ # @return [Logger] Logger instance for debugging
38
+ attr_reader :logger
39
+
40
+ # @return [PromptCache, RailsCacheAdapter, nil] Optional cache for prompt responses
41
+ attr_reader :cache
26
42
 
27
43
  # Initialize a new API client
28
44
  #
@@ -31,7 +47,8 @@ module Langfuse
31
47
  # @param base_url [String] Base URL for Langfuse API
32
48
  # @param timeout [Integer] HTTP request timeout in seconds
33
49
  # @param logger [Logger] Logger instance for debugging
34
- # @param cache [PromptCache, nil] Optional cache for prompt responses
50
+ # @param cache [PromptCache, RailsCacheAdapter, nil] Optional cache for prompt responses
51
+ # @return [ApiClient]
35
52
  def initialize(public_key:, secret_key:, base_url:, timeout: 5, logger: nil, cache: nil)
36
53
  @public_key = public_key
37
54
  @secret_key = secret_key
@@ -72,22 +89,15 @@ module Langfuse
72
89
  # puts "#{prompt['name']} (v#{prompt['version']})"
73
90
  # end
74
91
  def list_prompts(page: nil, limit: nil)
75
- params = {}
76
- params[:page] = page if page
77
- params[:limit] = limit if limit
92
+ with_faraday_error_handling do
93
+ params = { page: page, limit: limit }.compact
78
94
 
79
- path = "/api/public/v2/prompts"
80
- response = connection.get(path, params)
81
- result = handle_response(response)
95
+ response = connection.get("/api/public/v2/prompts", params)
96
+ result = handle_response(response)
82
97
 
83
- # API returns { data: [...], meta: {...} }
84
- result["data"] || []
85
- rescue Faraday::RetriableResponse => e
86
- logger.error("Faraday error: Retries exhausted - #{e.response.status}")
87
- handle_response(e.response)
88
- rescue Faraday::Error => e
89
- logger.error("Faraday error: #{e.message}")
90
- raise ApiError, "HTTP request failed: #{e.message}"
98
+ # API returns { data: [...], meta: {...} }
99
+ result["data"] || []
100
+ end
91
101
  end
92
102
 
93
103
  # Fetch a prompt from the Langfuse API
@@ -135,25 +145,21 @@ module Langfuse
135
145
  #
136
146
  # rubocop:disable Metrics/ParameterLists
137
147
  def create_prompt(name:, prompt:, type:, config: {}, labels: [], tags: [], commit_message: nil)
138
- path = "/api/public/v2/prompts"
139
- payload = {
140
- name: name,
141
- prompt: prompt,
142
- type: type,
143
- config: config,
144
- labels: labels,
145
- tags: tags
146
- }
147
- payload[:commitMessage] = commit_message if commit_message
148
-
149
- response = connection.post(path, payload)
150
- handle_response(response)
151
- rescue Faraday::RetriableResponse => e
152
- logger.error("Faraday error: Retries exhausted - #{e.response.status}")
153
- handle_response(e.response)
154
- rescue Faraday::Error => e
155
- logger.error("Faraday error: #{e.message}")
156
- raise ApiError, "HTTP request failed: #{e.message}"
148
+ with_faraday_error_handling do
149
+ path = "/api/public/v2/prompts"
150
+ payload = {
151
+ name: name,
152
+ prompt: prompt,
153
+ type: type,
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
157
163
  end
158
164
  # rubocop:enable Metrics/ParameterLists
159
165
 
@@ -177,17 +183,13 @@ module Langfuse
177
183
  def update_prompt(name:, version:, labels:)
178
184
  raise ArgumentError, "labels must be an array" unless labels.is_a?(Array)
179
185
 
180
- path = "/api/public/v2/prompts/#{URI.encode_uri_component(name)}/versions/#{version}"
181
- payload = { newLabels: labels }
186
+ with_faraday_error_handling do
187
+ path = "/api/public/v2/prompts/#{URI.encode_uri_component(name)}/versions/#{version}"
188
+ payload = { newLabels: labels }
182
189
 
183
- response = connection.patch(path, payload)
184
- handle_response(response)
185
- rescue Faraday::RetriableResponse => e
186
- logger.error("Faraday error: Retries exhausted - #{e.response.status}")
187
- handle_response(e.response)
188
- rescue Faraday::Error => e
189
- logger.error("Faraday error: #{e.message}")
190
- raise ApiError, "HTTP request failed: #{e.message}"
190
+ response = connection.patch(path, payload)
191
+ handle_response(response)
192
+ end
191
193
  end
192
194
 
193
195
  # Send a batch of events to the Langfuse ingestion API
@@ -198,6 +200,7 @@ module Langfuse
198
200
  #
199
201
  # @param events [Array<Hash>] Array of event hashes to send
200
202
  # @return [void]
203
+ # @raise [ArgumentError] if events is not an Array or is empty
201
204
  # @raise [UnauthorizedError] if authentication fails
202
205
  # @raise [ApiError] for other API errors after retries exhausted
203
206
  #
@@ -229,10 +232,233 @@ module Langfuse
229
232
  raise ApiError, "Batch send failed: #{e.message}"
230
233
  end
231
234
 
235
+ # Create a dataset run item (link a trace to a dataset item within a run)
236
+ #
237
+ # @param dataset_item_id [String] Dataset item ID (required)
238
+ # @param run_name [String] Run name (required)
239
+ # @param trace_id [String, nil] Trace ID to link
240
+ # @param observation_id [String, nil] Observation ID to link
241
+ # @param metadata [Hash, nil] Optional metadata
242
+ # @param run_description [String, nil] Optional run description
243
+ # @return [Hash] The created dataset run item data
244
+ # @raise [UnauthorizedError] if authentication fails
245
+ # @raise [ApiError] for other API errors
246
+ #
247
+ # @example
248
+ # api_client.create_dataset_run_item(dataset_item_id: "item-123", run_name: "eval-v1", trace_id: "trace-abc")
249
+ def create_dataset_run_item(dataset_item_id:, run_name:, trace_id: nil,
250
+ observation_id: nil, metadata: nil, run_description: nil)
251
+ with_faraday_error_handling do
252
+ payload = { datasetItemId: dataset_item_id, runName: run_name }
253
+ payload[:traceId] = trace_id if trace_id
254
+ payload[:observationId] = observation_id if observation_id
255
+ payload[:metadata] = metadata if metadata
256
+ payload[:runDescription] = run_description if run_description
257
+
258
+ response = connection.post("/api/public/dataset-run-items", payload)
259
+ handle_response(response)
260
+ end
261
+ end
262
+
263
+ # Fetch projects accessible with the current API keys
264
+ #
265
+ # @return [Hash] The parsed response body containing project data
266
+ # @raise [UnauthorizedError] if authentication fails
267
+ # @raise [ApiError] for other API errors
268
+ #
269
+ # @example
270
+ # data = api_client.get_projects
271
+ # project_id = data["data"][0]["id"]
272
+ def get_projects # rubocop:disable Naming/AccessorMethodName
273
+ with_faraday_error_handling do
274
+ response = connection.get("/api/public/projects")
275
+ handle_response(response)
276
+ end
277
+ end
278
+
279
+ # Shut down the API client and release resources
280
+ #
281
+ # Shuts down the cache if it supports shutdown (e.g., SWR thread pool).
282
+ #
283
+ # @return [void]
232
284
  def shutdown
233
285
  cache.shutdown if cache.respond_to?(:shutdown)
234
286
  end
235
287
 
288
+ # List all datasets in the project
289
+ #
290
+ # @param page [Integer, nil] Optional page number for pagination
291
+ # @param limit [Integer, nil] Optional limit per page
292
+ # @return [Array<Hash>] Array of dataset metadata hashes
293
+ # @raise [UnauthorizedError] if authentication fails
294
+ # @raise [ApiError] for other API errors
295
+ #
296
+ # @example
297
+ # datasets = api_client.list_datasets(page: 1, limit: 10)
298
+ def list_datasets(page: nil, limit: nil)
299
+ with_faraday_error_handling do
300
+ params = { page: page, limit: limit }.compact
301
+
302
+ response = connection.get("/api/public/v2/datasets", params)
303
+ result = handle_response(response)
304
+ result["data"] || []
305
+ end
306
+ end
307
+
308
+ # Fetch a dataset by name
309
+ #
310
+ # @param name [String] Dataset name (supports folder paths like "evaluation/qa-dataset")
311
+ # @return [Hash] The dataset data
312
+ # @raise [NotFoundError] if the dataset is not found
313
+ # @raise [UnauthorizedError] if authentication fails
314
+ # @raise [ApiError] for other API errors
315
+ #
316
+ # @example
317
+ # data = api_client.get_dataset("my-dataset")
318
+ def get_dataset(name)
319
+ with_faraday_error_handling do
320
+ encoded_name = URI.encode_uri_component(name)
321
+ response = connection.get("/api/public/v2/datasets/#{encoded_name}")
322
+ handle_response(response)
323
+ end
324
+ end
325
+
326
+ # Create a new dataset
327
+ #
328
+ # @param name [String] Dataset name (required)
329
+ # @param description [String, nil] Optional description
330
+ # @param metadata [Hash, nil] Optional metadata hash
331
+ # @return [Hash] The created dataset data
332
+ # @raise [UnauthorizedError] if authentication fails
333
+ # @raise [ApiError] for other API errors
334
+ #
335
+ # @example
336
+ # data = api_client.create_dataset(name: "my-dataset", description: "QA evaluation set")
337
+ def create_dataset(name:, description: nil, metadata: nil)
338
+ with_faraday_error_handling do
339
+ payload = { name: name, description: description, metadata: metadata }.compact
340
+
341
+ response = connection.post("/api/public/v2/datasets", payload)
342
+ handle_response(response)
343
+ end
344
+ end
345
+
346
+ # Create a new dataset item (or upsert if id is provided)
347
+ #
348
+ # @param dataset_name [String] Name of the dataset (required)
349
+ # @param input [Object, nil] Input data for the item
350
+ # @param expected_output [Object, nil] Expected output for evaluation
351
+ # @param metadata [Hash, nil] Optional metadata
352
+ # @param id [String, nil] Optional ID for upsert behavior
353
+ # @param source_trace_id [String, nil] Link to source trace
354
+ # @param source_observation_id [String, nil] Link to source observation
355
+ # @param status [Symbol, nil] Item status (:active or :archived)
356
+ # @return [Hash] The created dataset item data
357
+ # @raise [UnauthorizedError] if authentication fails
358
+ # @raise [ApiError] for other API errors
359
+ #
360
+ # @example
361
+ # data = api_client.create_dataset_item(
362
+ # dataset_name: "my-dataset",
363
+ # input: { query: "What is Ruby?" },
364
+ # expected_output: { answer: "A programming language" }
365
+ # )
366
+ # rubocop:disable Metrics/ParameterLists
367
+ def create_dataset_item(dataset_name:, input: nil, expected_output: nil,
368
+ metadata: nil, id: nil, source_trace_id: nil,
369
+ source_observation_id: nil, status: nil)
370
+ with_faraday_error_handling do
371
+ payload = build_dataset_item_payload(
372
+ dataset_name: dataset_name, input: input, expected_output: expected_output,
373
+ metadata: metadata, id: id, source_trace_id: source_trace_id,
374
+ source_observation_id: source_observation_id, status: status
375
+ )
376
+
377
+ response = connection.post("/api/public/dataset-items", payload)
378
+ handle_response(response)
379
+ end
380
+ end
381
+ # rubocop:enable Metrics/ParameterLists
382
+
383
+ # Fetch a dataset item by ID
384
+ #
385
+ # @param id [String] Dataset item ID
386
+ # @return [Hash] The dataset item data
387
+ # @raise [NotFoundError] if the item is not found
388
+ # @raise [UnauthorizedError] if authentication fails
389
+ # @raise [ApiError] for other API errors
390
+ #
391
+ # @example
392
+ # data = api_client.get_dataset_item("item-uuid-123")
393
+ def get_dataset_item(id)
394
+ with_faraday_error_handling do
395
+ encoded_id = URI.encode_uri_component(id)
396
+ response = connection.get("/api/public/dataset-items/#{encoded_id}")
397
+ handle_response(response)
398
+ end
399
+ end
400
+
401
+ # List items in a dataset with optional filters
402
+ #
403
+ # @param dataset_name [String] Name of the dataset (required)
404
+ # @param page [Integer, nil] Optional page number for pagination
405
+ # @param limit [Integer, nil] Optional limit per page
406
+ # @param source_trace_id [String, nil] Filter by source trace ID
407
+ # @param source_observation_id [String, nil] Filter by source observation ID
408
+ # @return [Array<Hash>] Array of dataset item hashes
409
+ # @raise [UnauthorizedError] if authentication fails
410
+ # @raise [ApiError] for other API errors
411
+ #
412
+ # @example
413
+ # items = api_client.list_dataset_items(dataset_name: "my-dataset", limit: 50)
414
+ def list_dataset_items(dataset_name:, page: nil, limit: nil,
415
+ source_trace_id: nil, source_observation_id: nil)
416
+ result = list_dataset_items_paginated(
417
+ dataset_name: dataset_name, page: page, limit: limit,
418
+ source_trace_id: source_trace_id, source_observation_id: source_observation_id
419
+ )
420
+ result["data"] || []
421
+ end
422
+
423
+ # Full paginated response including "meta" for internal pagination use
424
+ #
425
+ # @api private
426
+ # @return [Hash] Full response hash with "data" array and "meta" pagination info
427
+ def list_dataset_items_paginated(dataset_name:, page: nil, limit: nil,
428
+ source_trace_id: nil, source_observation_id: nil)
429
+ with_faraday_error_handling do
430
+ params = build_dataset_items_params(
431
+ dataset_name: dataset_name, page: page, limit: limit,
432
+ source_trace_id: source_trace_id, source_observation_id: source_observation_id
433
+ )
434
+
435
+ response = connection.get("/api/public/dataset-items", params)
436
+ handle_response(response)
437
+ end
438
+ end
439
+
440
+ # Delete a dataset item by ID
441
+ #
442
+ # @param id [String] Dataset item ID
443
+ # @return [Hash] The response body
444
+ # @raise [UnauthorizedError] if authentication fails
445
+ # @raise [ApiError] for other API errors
446
+ # @note 404 responses are treated as success to keep DELETE idempotent across retries
447
+ #
448
+ # @example
449
+ # api_client.delete_dataset_item("item-uuid-123")
450
+ def delete_dataset_item(id)
451
+ encoded_id = URI.encode_uri_component(id)
452
+ response = connection.delete("/api/public/dataset-items/#{encoded_id}")
453
+ handle_delete_dataset_item_response(response, id)
454
+ rescue Faraday::RetriableResponse => e
455
+ logger.error("Faraday error: Retries exhausted - #{e.response.status}")
456
+ handle_delete_dataset_item_response(e.response, id)
457
+ rescue Faraday::Error => e
458
+ logger.error("Faraday error: #{e.message}")
459
+ raise ApiError, "HTTP request failed: #{e.message}"
460
+ end
461
+
236
462
  private
237
463
 
238
464
  # Fetch prompt using the most appropriate caching strategy available
@@ -262,6 +488,43 @@ module Langfuse
262
488
  cache.respond_to?(:fetch_with_lock)
263
489
  end
264
490
 
491
+ # Build payload for create_dataset_item
492
+ # rubocop:disable Metrics/ParameterLists
493
+ def build_dataset_item_payload(dataset_name:, input:, expected_output:,
494
+ metadata:, id:, source_trace_id:,
495
+ source_observation_id:, status:)
496
+ { datasetName: dataset_name }.tap do |payload|
497
+ add_optional_dataset_item_fields(payload, input, expected_output, metadata, id)
498
+ add_optional_source_fields(payload, source_trace_id, source_observation_id, status)
499
+ end
500
+ end
501
+ # rubocop:enable Metrics/ParameterLists
502
+
503
+ def add_optional_dataset_item_fields(payload, input, expected_output, metadata, id)
504
+ payload[:id] = id if id
505
+ payload[:input] = input if input
506
+ payload[:expectedOutput] = expected_output if expected_output
507
+ payload[:metadata] = metadata if metadata
508
+ end
509
+
510
+ def add_optional_source_fields(payload, source_trace_id, source_observation_id, status)
511
+ payload[:sourceTraceId] = source_trace_id if source_trace_id
512
+ payload[:sourceObservationId] = source_observation_id if source_observation_id
513
+ payload[:status] = status.to_s.upcase if status
514
+ end
515
+
516
+ # Build params for list_dataset_items
517
+ def build_dataset_items_params(dataset_name:, page:, limit:,
518
+ source_trace_id:, source_observation_id:)
519
+ {
520
+ datasetName: dataset_name,
521
+ page: page,
522
+ limit: limit,
523
+ sourceTraceId: source_trace_id,
524
+ sourceObservationId: source_observation_id
525
+ }.compact
526
+ end
527
+
265
528
  # Fetch with SWR cache
266
529
  def fetch_with_swr_cache(cache_key, name, version, label)
267
530
  cache.fetch_with_stale_while_revalidate(cache_key) do
@@ -296,18 +559,13 @@ module Langfuse
296
559
  # @raise [UnauthorizedError] if authentication fails
297
560
  # @raise [ApiError] for other API errors
298
561
  def fetch_prompt_from_api(name, version: nil, label: nil)
299
- params = build_prompt_params(version: version, label: label)
300
- path = "/api/public/v2/prompts/#{URI.encode_uri_component(name)}"
562
+ with_faraday_error_handling do
563
+ params = build_prompt_params(version: version, label: label)
564
+ path = "/api/public/v2/prompts/#{URI.encode_uri_component(name)}"
301
565
 
302
- response = connection.get(path, params)
303
- handle_response(response)
304
- rescue Faraday::RetriableResponse => e
305
- # Retry middleware exhausted all retries - handle the final response
306
- logger.error("Faraday error: Retries exhausted - #{e.response.status}")
307
- handle_response(e.response)
308
- rescue Faraday::Error => e
309
- logger.error("Faraday error: #{e.message}")
310
- raise ApiError, "HTTP request failed: #{e.message}"
566
+ response = connection.get(path, params)
567
+ handle_response(response)
568
+ end
311
569
  end
312
570
 
313
571
  # Build a new Faraday connection
@@ -332,7 +590,7 @@ module Langfuse
332
590
  # Retries transient errors with exponential backoff:
333
591
  # - Max 2 retries (3 total attempts)
334
592
  # - Exponential backoff (0.05s * 2^retry_count)
335
- # - Retries GET and PATCH requests (idempotent operations)
593
+ # - Retries GET, PATCH, and DELETE requests (idempotent operations)
336
594
  # - Retries POST requests to batch endpoint (idempotent due to event UUIDs)
337
595
  # - Note: POST to create_prompt is NOT idempotent; retries may create duplicate versions
338
596
  # - Retries on: 429 (rate limit), 503 (service unavailable), 504 (gateway timeout)
@@ -344,7 +602,7 @@ module Langfuse
344
602
  max: 2,
345
603
  interval: 0.05,
346
604
  backoff_factor: 2,
347
- methods: %i[get post patch],
605
+ methods: %i[get post patch delete],
348
606
  retry_statuses: [429, 503, 504],
349
607
  exceptions: [Faraday::TimeoutError, Faraday::ConnectionFailed]
350
608
  }
@@ -382,10 +640,25 @@ module Langfuse
382
640
  # @param label [String, nil] Optional label
383
641
  # @return [Hash] Query parameters
384
642
  def build_prompt_params(version: nil, label: nil)
385
- params = {}
386
- params[:version] = version if version
387
- params[:label] = label if label
388
- params
643
+ { version: version, label: label }.compact
644
+ end
645
+
646
+ # Wrap a block with standard Faraday error handling.
647
+ #
648
+ # Catches RetriableResponse (retries exhausted) and generic Faraday errors,
649
+ # translating them into ApiError with consistent logging.
650
+ #
651
+ # @yield The block containing the Faraday request and response handling
652
+ # @return [Object] The return value of the block
653
+ # @raise [ApiError] when a Faraday error occurs
654
+ def with_faraday_error_handling
655
+ yield
656
+ rescue Faraday::RetriableResponse => e
657
+ logger.error("Faraday error: Retries exhausted - #{e.response.status}")
658
+ handle_response(e.response)
659
+ rescue Faraday::Error => e
660
+ logger.error("Faraday error: #{e.message}")
661
+ raise ApiError, "HTTP request failed: #{e.message}"
389
662
  end
390
663
 
391
664
  # Handle HTTP response and raise appropriate errors
@@ -402,13 +675,20 @@ module Langfuse
402
675
  when 401
403
676
  raise UnauthorizedError, "Authentication failed. Check your API keys."
404
677
  when 404
405
- raise NotFoundError, "Prompt not found"
678
+ raise NotFoundError, extract_error_message(response)
406
679
  else
407
680
  error_message = extract_error_message(response)
408
681
  raise ApiError, "API request failed (#{response.status}): #{error_message}"
409
682
  end
410
683
  end
411
684
 
685
+ def handle_delete_dataset_item_response(response, id)
686
+ return { "id" => id } if response&.status == 404
687
+ return response.body if [200, 201].include?(response&.status)
688
+
689
+ handle_response(response)
690
+ end
691
+
412
692
  # Handle HTTP response for batch requests
413
693
  #
414
694
  # @param response [Faraday::Response] The HTTP response
@@ -20,6 +20,7 @@ module Langfuse
20
20
  # end
21
21
  #
22
22
  class CacheWarmer
23
+ # @return [Client] Langfuse client used for fetching prompts
23
24
  attr_reader :client
24
25
 
25
26
  # Initialize a new cache warmer
@@ -35,8 +36,8 @@ module Langfuse
35
36
  # safe to call multiple times.
36
37
  #
37
38
  # @param prompt_names [Array<String>] List of prompt names to cache
38
- # @param versions [Hash<String, Integer>, nil] Optional version numbers per prompt
39
- # @param labels [Hash<String, String>, nil] Optional labels per prompt
39
+ # @param versions [Hash<String, Integer>] Optional version numbers per prompt
40
+ # @param labels [Hash<String, String>] Optional labels per prompt
40
41
  # @return [Hash] Results with :success and :failed arrays
41
42
  #
42
43
  # @example Basic warming
@@ -73,8 +74,8 @@ module Langfuse
73
74
  # are cached without manually specifying them.
74
75
  #
75
76
  # @param default_label [String, nil] Label to use for all prompts (default: "production")
76
- # @param versions [Hash<String, Integer>, nil] Optional version numbers per prompt
77
- # @param labels [Hash<String, String>, nil] Optional labels per specific prompts (overrides default_label)
77
+ # @param versions [Hash<String, Integer>] Optional version numbers per prompt
78
+ # @param labels [Hash<String, String>] Optional labels per specific prompts (overrides default_label)
78
79
  # @return [Hash] Results with :success and :failed arrays
79
80
  #
80
81
  # @example Auto-discover and warm all prompts with "production" label
@@ -119,8 +120,8 @@ module Langfuse
119
120
  # Useful when you want to abort deployment if cache warming fails.
120
121
  #
121
122
  # @param prompt_names [Array<String>] List of prompt names to cache
122
- # @param versions [Hash<String, Integer>, nil] Optional version numbers per prompt
123
- # @param labels [Hash<String, String>, nil] Optional labels per prompt
123
+ # @param versions [Hash<String, Integer>] Optional version numbers per prompt
124
+ # @param labels [Hash<String, String>] Optional labels per prompt
124
125
  # @return [Hash] Results with :success array
125
126
  # @raise [CacheWarmingError] if any prompts fail to cache
126
127
  #
@@ -20,7 +20,23 @@ module Langfuse
20
20
  # chat_prompt.labels # => ["production"]
21
21
  #
22
22
  class ChatPromptClient
23
- attr_reader :name, :version, :labels, :tags, :config, :prompt
23
+ # @return [String] Prompt name
24
+ attr_reader :name
25
+
26
+ # @return [Integer] Prompt version number
27
+ attr_reader :version
28
+
29
+ # @return [Array<String>] Labels assigned to this prompt
30
+ attr_reader :labels
31
+
32
+ # @return [Array<String>] Tags assigned to this prompt
33
+ attr_reader :tags
34
+
35
+ # @return [Hash] Prompt configuration
36
+ attr_reader :config
37
+
38
+ # @return [Array<Hash>] Array of message hashes with role and content
39
+ attr_reader :prompt
24
40
 
25
41
  # Initialize a new chat prompt client
26
42
  #