langfuse-rb 0.10.0 → 0.11.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 +44 -1
- data/lib/langfuse/api_client.rb +175 -509
- data/lib/langfuse/app_root_tracking.rb +164 -0
- data/lib/langfuse/cache_warmer.rb +21 -13
- data/lib/langfuse/chat_prompt_client.rb +21 -4
- data/lib/langfuse/client.rb +174 -229
- data/lib/langfuse/config.rb +308 -65
- data/lib/langfuse/evaluation.rb +8 -4
- data/lib/langfuse/exit_hook.rb +77 -0
- data/lib/langfuse/fork_safety.rb +71 -0
- data/lib/langfuse/masking_exporter.rb +98 -0
- data/lib/langfuse/observations.rb +2 -1
- data/lib/langfuse/otel_attributes.rb +1 -0
- data/lib/langfuse/otel_setup.rb +32 -44
- data/lib/langfuse/otel_span_batch.rb +113 -0
- data/lib/langfuse/otel_span_masking.rb +89 -0
- data/lib/langfuse/otel_span_patch_applier.rb +97 -0
- data/lib/langfuse/pending_score_queue.rb +62 -0
- data/lib/langfuse/prompt_cache.rb +11 -0
- data/lib/langfuse/prompt_cache_coordinator.rb +288 -0
- data/lib/langfuse/prompt_cache_events.rb +31 -10
- data/lib/langfuse/prompt_variables.rb +54 -0
- data/lib/langfuse/propagation.rb +101 -34
- data/lib/langfuse/rails_cache_adapter.rb +27 -2
- data/lib/langfuse/read_api.rb +242 -0
- data/lib/langfuse/resilient_metrics_reporter.rb +60 -0
- data/lib/langfuse/score_client.rb +209 -89
- data/lib/langfuse/score_value.rb +58 -0
- data/lib/langfuse/span_processor.rb +53 -4
- data/lib/langfuse/stale_while_revalidate.rb +3 -4
- data/lib/langfuse/text_prompt_client.rb +15 -6
- data/lib/langfuse/trace_export_guard.rb +47 -0
- data/lib/langfuse/traced_execution.rb +18 -12
- data/lib/langfuse/types.rb +15 -1
- data/lib/langfuse/version.rb +1 -1
- data/lib/langfuse.rb +157 -44
- metadata +16 -2
data/lib/langfuse/client.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "forwardable"
|
|
4
|
+
|
|
3
5
|
module Langfuse
|
|
4
6
|
# Main client for Langfuse SDK
|
|
5
7
|
#
|
|
@@ -19,14 +21,56 @@ module Langfuse
|
|
|
19
21
|
#
|
|
20
22
|
# rubocop:disable Metrics/ClassLength
|
|
21
23
|
class Client
|
|
24
|
+
extend Forwardable
|
|
25
|
+
|
|
22
26
|
# @return [Integer] Default page size when fetching all dataset items
|
|
23
27
|
DATASET_ITEMS_PAGE_SIZE = 50
|
|
24
28
|
|
|
25
29
|
# @return [Config] The client configuration
|
|
26
30
|
attr_reader :config
|
|
27
31
|
|
|
32
|
+
# Return the underlying API client, building it on first non-telemetry use.
|
|
33
|
+
#
|
|
28
34
|
# @return [ApiClient] The underlying API client
|
|
29
|
-
|
|
35
|
+
# @raise [ConfigurationError] if the full client configuration is invalid
|
|
36
|
+
def api_client
|
|
37
|
+
@api_client || @api_client_mutex.synchronize { @api_client ||= build_validated_api_client }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Pure pass-throughs to {ApiClient}. See {ApiClient} for parameter and
|
|
41
|
+
# return-value documentation; the public surface here is identical.
|
|
42
|
+
#
|
|
43
|
+
# @!method list_prompts(page: nil, limit: nil)
|
|
44
|
+
# @!method invalidate_prompt_cache(name, version: nil, label: nil)
|
|
45
|
+
# @!method invalidate_prompt_cache_by_name(name)
|
|
46
|
+
# @!method clear_prompt_cache
|
|
47
|
+
# @!method prompt_cache_stats
|
|
48
|
+
# @!method prompt_cache_key(name, version: nil, label: nil)
|
|
49
|
+
# @!method validate_prompt_cache_backend!
|
|
50
|
+
# @!method list_traces(**options)
|
|
51
|
+
# @!method get_trace(id)
|
|
52
|
+
# @!method list_observations(**options)
|
|
53
|
+
# @!method query_metrics(query:)
|
|
54
|
+
# @!method list_scores(**options)
|
|
55
|
+
# @!method list_datasets(page: nil, limit: nil)
|
|
56
|
+
# @!method get_dataset_run(dataset_name:, run_name:)
|
|
57
|
+
# @!method create_dataset_run_item(**)
|
|
58
|
+
def_delegators :api_client,
|
|
59
|
+
:list_prompts,
|
|
60
|
+
:invalidate_prompt_cache,
|
|
61
|
+
:invalidate_prompt_cache_by_name,
|
|
62
|
+
:clear_prompt_cache,
|
|
63
|
+
:prompt_cache_stats,
|
|
64
|
+
:prompt_cache_key,
|
|
65
|
+
:validate_prompt_cache_backend!,
|
|
66
|
+
:list_traces,
|
|
67
|
+
:get_trace,
|
|
68
|
+
:list_observations,
|
|
69
|
+
:query_metrics,
|
|
70
|
+
:list_scores,
|
|
71
|
+
:list_datasets,
|
|
72
|
+
:get_dataset_run,
|
|
73
|
+
:create_dataset_run_item
|
|
30
74
|
|
|
31
75
|
# Initialize a new Langfuse client
|
|
32
76
|
#
|
|
@@ -34,29 +78,19 @@ module Langfuse
|
|
|
34
78
|
# @return [Client]
|
|
35
79
|
def initialize(config)
|
|
36
80
|
@config = config
|
|
37
|
-
@
|
|
38
|
-
|
|
39
|
-
# Create cache if enabled
|
|
40
|
-
cache = create_cache if cache_enabled?
|
|
41
|
-
|
|
42
|
-
# Create API client with cache
|
|
43
|
-
@api_client = ApiClient.new(
|
|
44
|
-
public_key: config.public_key,
|
|
45
|
-
secret_key: config.secret_key,
|
|
46
|
-
base_url: config.base_url,
|
|
47
|
-
timeout: config.timeout,
|
|
48
|
-
logger: config.logger,
|
|
49
|
-
cache: cache,
|
|
50
|
-
cache_observer: config.prompt_cache_observer
|
|
51
|
-
)
|
|
52
|
-
|
|
81
|
+
@api_client_mutex = Mutex.new
|
|
82
|
+
@score_client_mutex = Mutex.new
|
|
53
83
|
@project_id = nil
|
|
54
84
|
# One-shot lookup: avoids repeated blocking API calls in URL helpers
|
|
55
85
|
# (trace_url, dataset_url, dataset_run_url) when the project endpoint is down.
|
|
56
86
|
@project_id_fetched = false
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
87
|
+
if telemetry_enabled?
|
|
88
|
+
config.validate!
|
|
89
|
+
@api_client = build_api_client
|
|
90
|
+
@score_client = build_score_client
|
|
91
|
+
else
|
|
92
|
+
config.validate_telemetry_disabled!
|
|
93
|
+
end
|
|
60
94
|
end
|
|
61
95
|
|
|
62
96
|
# Fetch a prompt and return the appropriate client
|
|
@@ -134,79 +168,6 @@ module Langfuse
|
|
|
134
168
|
build_client_fetch_result(api_result, build_prompt_client(api_result.prompt))
|
|
135
169
|
end
|
|
136
170
|
|
|
137
|
-
# Invalidate one exact logical prompt cache key.
|
|
138
|
-
#
|
|
139
|
-
# @param name [String] The prompt name
|
|
140
|
-
# @param version [Integer, nil] Optional specific version number
|
|
141
|
-
# @param label [String, nil] Optional label
|
|
142
|
-
# @return [PromptCacheKey] The invalidated key
|
|
143
|
-
def invalidate_prompt_cache(name, version: nil, label: nil)
|
|
144
|
-
api_client.invalidate_prompt_cache(name, version: version, label: label)
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
# Invalidate all cached variants for one prompt name.
|
|
148
|
-
#
|
|
149
|
-
# @param name [String] The prompt name
|
|
150
|
-
# @return [Integer, nil] New generation, or nil when cache is disabled
|
|
151
|
-
def invalidate_prompt_cache_by_name(name)
|
|
152
|
-
api_client.invalidate_prompt_cache_by_name(name)
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
# Logically clear the whole Langfuse prompt cache namespace.
|
|
156
|
-
#
|
|
157
|
-
# @return [Integer, nil] New global generation, or nil when cache is disabled
|
|
158
|
-
def clear_prompt_cache
|
|
159
|
-
api_client.clear_prompt_cache
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
# Return prompt cache statistics.
|
|
163
|
-
#
|
|
164
|
-
# @return [Hash] Cache statistics
|
|
165
|
-
def prompt_cache_stats
|
|
166
|
-
api_client.prompt_cache_stats
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
# Inspect the logical and generated cache keys for a prompt.
|
|
170
|
-
#
|
|
171
|
-
# @param name [String] The prompt name
|
|
172
|
-
# @param version [Integer, nil] Optional specific version number
|
|
173
|
-
# @param label [String, nil] Optional label
|
|
174
|
-
# @return [PromptCacheKey] Logical and generated cache keys
|
|
175
|
-
def prompt_cache_key(name, version: nil, label: nil)
|
|
176
|
-
api_client.prompt_cache_key(name, version: version, label: label)
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
# Validate the configured prompt cache backend before first prompt fetch.
|
|
180
|
-
#
|
|
181
|
-
# @return [Boolean] true when the configured backend is usable
|
|
182
|
-
# @raise [ConfigurationError] if the backend is invalid
|
|
183
|
-
# rubocop:disable Naming/PredicateMethod
|
|
184
|
-
def validate_prompt_cache_backend!
|
|
185
|
-
api_client.cache&.validate! if api_client.cache.respond_to?(:validate!)
|
|
186
|
-
true
|
|
187
|
-
end
|
|
188
|
-
# rubocop:enable Naming/PredicateMethod
|
|
189
|
-
|
|
190
|
-
# List all prompts in the Langfuse project
|
|
191
|
-
#
|
|
192
|
-
# Fetches a list of all prompt names available in your project.
|
|
193
|
-
# Returns metadata only, not full prompt content.
|
|
194
|
-
#
|
|
195
|
-
# @param page [Integer, nil] Optional page number for pagination
|
|
196
|
-
# @param limit [Integer, nil] Optional limit per page
|
|
197
|
-
# @return [Array<Hash>] Array of prompt metadata hashes
|
|
198
|
-
# @raise [UnauthorizedError] if authentication fails
|
|
199
|
-
# @raise [ApiError] for other API errors
|
|
200
|
-
#
|
|
201
|
-
# @example
|
|
202
|
-
# prompts = client.list_prompts
|
|
203
|
-
# prompts.each do |prompt|
|
|
204
|
-
# puts "#{prompt['name']} (v#{prompt['version']})"
|
|
205
|
-
# end
|
|
206
|
-
def list_prompts(page: nil, limit: nil)
|
|
207
|
-
api_client.list_prompts(page: page, limit: limit)
|
|
208
|
-
end
|
|
209
|
-
|
|
210
171
|
# Convenience method: fetch and compile a prompt in one call
|
|
211
172
|
#
|
|
212
173
|
# This is a shorthand for calling get_prompt followed by compile.
|
|
@@ -387,14 +348,14 @@ module Langfuse
|
|
|
387
348
|
#
|
|
388
349
|
# @param name [String] Score name (required)
|
|
389
350
|
# @param value [Numeric, Integer, String] Score value (type depends on data_type)
|
|
390
|
-
# @param id [String, nil] Score ID
|
|
351
|
+
# @param id [String, nil] Score ID; use a stable value as an idempotency key
|
|
391
352
|
# @param trace_id [String, nil] Trace ID to associate with the score
|
|
392
353
|
# @param session_id [String, nil] Session ID to associate with the score
|
|
393
354
|
# @param observation_id [String, nil] Observation ID to associate with the score
|
|
394
355
|
# @param comment [String, nil] Optional comment
|
|
395
356
|
# @param metadata [Hash, nil] Optional metadata hash
|
|
396
|
-
# @param environment [String, nil] Optional environment
|
|
397
|
-
# @param data_type [Symbol] Data type (:numeric, :boolean, :categorical)
|
|
357
|
+
# @param environment [String, nil] Optional per-score environment override
|
|
358
|
+
# @param data_type [Symbol] Data type (:numeric, :boolean, :categorical, :text, :correction)
|
|
398
359
|
# @param dataset_run_id [String, nil] Optional dataset run ID to associate with the score
|
|
399
360
|
# @param config_id [String, nil] Optional score config ID
|
|
400
361
|
# @return [void]
|
|
@@ -408,10 +369,59 @@ module Langfuse
|
|
|
408
369
|
#
|
|
409
370
|
# @example Categorical score
|
|
410
371
|
# client.create_score(name: "category", value: "high", trace_id: "abc123", data_type: :categorical)
|
|
372
|
+
#
|
|
373
|
+
# @example Text score (1 to 500 characters)
|
|
374
|
+
# client.create_score(name: "reviewer_notes", value: "Helpful but verbose",
|
|
375
|
+
# trace_id: "abc123", data_type: :text)
|
|
376
|
+
#
|
|
377
|
+
# @example Corrected output (conventionally named "output")
|
|
378
|
+
# client.create_score(name: "output", value: "The corrected output", trace_id: "abc123",
|
|
379
|
+
# observation_id: "def456", data_type: :correction)
|
|
411
380
|
# rubocop:disable Metrics/ParameterLists
|
|
412
381
|
def create_score(name:, value:, id: nil, trace_id: nil, session_id: nil, observation_id: nil, comment: nil,
|
|
413
382
|
metadata: nil, environment: nil, data_type: :numeric, dataset_run_id: nil, config_id: nil)
|
|
414
|
-
|
|
383
|
+
active_score_client&.create(
|
|
384
|
+
name: name,
|
|
385
|
+
value: value,
|
|
386
|
+
id: id,
|
|
387
|
+
trace_id: trace_id,
|
|
388
|
+
session_id: session_id,
|
|
389
|
+
observation_id: observation_id,
|
|
390
|
+
comment: comment,
|
|
391
|
+
metadata: metadata,
|
|
392
|
+
environment: environment,
|
|
393
|
+
data_type: data_type,
|
|
394
|
+
dataset_run_id: dataset_run_id,
|
|
395
|
+
config_id: config_id
|
|
396
|
+
)
|
|
397
|
+
end
|
|
398
|
+
# rubocop:enable Metrics/ParameterLists
|
|
399
|
+
|
|
400
|
+
# Create a score immediately through the Scores API. See {ScoreClient#create!}.
|
|
401
|
+
#
|
|
402
|
+
# @param name [String] Score name (required)
|
|
403
|
+
# @param value [Numeric, Integer, String] Score value (type depends on data_type)
|
|
404
|
+
# @param id [String, nil] Score ID; use a stable value as an idempotency key
|
|
405
|
+
# @param trace_id [String, nil] Trace ID to associate with the score
|
|
406
|
+
# @param session_id [String, nil] Session ID to associate with the score
|
|
407
|
+
# @param observation_id [String, nil] Observation ID to associate with the score
|
|
408
|
+
# @param comment [String, nil] Optional comment
|
|
409
|
+
# @param metadata [Hash, nil] Optional metadata hash
|
|
410
|
+
# @param environment [String, nil] Optional per-score environment override
|
|
411
|
+
# @param data_type [Symbol] Data type (:numeric, :boolean, :categorical, :text, :correction)
|
|
412
|
+
# @param dataset_run_id [String, nil] Optional dataset run ID to associate with the score
|
|
413
|
+
# @param config_id [String, nil] Optional score config ID
|
|
414
|
+
# @return [String, nil] ID of the created score, or nil when telemetry is disabled
|
|
415
|
+
# @raise [ArgumentError] if validation fails
|
|
416
|
+
# @raise [UnauthorizedError] if authentication fails
|
|
417
|
+
# @raise [ApiError] if the API request fails
|
|
418
|
+
#
|
|
419
|
+
# @example Create a score with an idempotency key
|
|
420
|
+
# client.create_score!(id: "feedback-abc123", name: "quality", value: 0.85, trace_id: "abc123")
|
|
421
|
+
# rubocop:disable Metrics/ParameterLists
|
|
422
|
+
def create_score!(name:, value:, id: nil, trace_id: nil, session_id: nil, observation_id: nil, comment: nil,
|
|
423
|
+
metadata: nil, environment: nil, data_type: :numeric, dataset_run_id: nil, config_id: nil)
|
|
424
|
+
active_score_client&.create!(
|
|
415
425
|
name: name,
|
|
416
426
|
value: value,
|
|
417
427
|
id: id,
|
|
@@ -436,7 +446,7 @@ module Langfuse
|
|
|
436
446
|
# @param value [Numeric, Integer, String] Score value
|
|
437
447
|
# @param comment [String, nil] Optional comment
|
|
438
448
|
# @param metadata [Hash, nil] Optional metadata hash
|
|
439
|
-
# @param data_type [Symbol] Data type (:numeric, :boolean, :categorical)
|
|
449
|
+
# @param data_type [Symbol] Data type (:numeric, :boolean, :categorical, :text, :correction)
|
|
440
450
|
# @return [void]
|
|
441
451
|
# @raise [ArgumentError] if no active span or validation fails
|
|
442
452
|
#
|
|
@@ -445,7 +455,7 @@ module Langfuse
|
|
|
445
455
|
# client.score_active_observation(name: "accuracy", value: 0.92)
|
|
446
456
|
# end
|
|
447
457
|
def score_active_observation(name:, value:, comment: nil, metadata: nil, data_type: :numeric)
|
|
448
|
-
|
|
458
|
+
active_score_client&.score_active_observation(
|
|
449
459
|
name: name,
|
|
450
460
|
value: value,
|
|
451
461
|
comment: comment,
|
|
@@ -462,7 +472,7 @@ module Langfuse
|
|
|
462
472
|
# @param value [Numeric, Integer, String] Score value
|
|
463
473
|
# @param comment [String, nil] Optional comment
|
|
464
474
|
# @param metadata [Hash, nil] Optional metadata hash
|
|
465
|
-
# @param data_type [Symbol] Data type (:numeric, :boolean, :categorical)
|
|
475
|
+
# @param data_type [Symbol] Data type (:numeric, :boolean, :categorical, :text, :correction)
|
|
466
476
|
# @return [void]
|
|
467
477
|
# @raise [ArgumentError] if no active span or validation fails
|
|
468
478
|
#
|
|
@@ -471,7 +481,7 @@ module Langfuse
|
|
|
471
481
|
# client.score_active_trace(name: "overall_quality", value: 5)
|
|
472
482
|
# end
|
|
473
483
|
def score_active_trace(name:, value:, comment: nil, metadata: nil, data_type: :numeric)
|
|
474
|
-
|
|
484
|
+
active_score_client&.score_active_trace(
|
|
475
485
|
name: name,
|
|
476
486
|
value: value,
|
|
477
487
|
comment: comment,
|
|
@@ -489,7 +499,7 @@ module Langfuse
|
|
|
489
499
|
# @example
|
|
490
500
|
# client.flush_scores
|
|
491
501
|
def flush_scores
|
|
492
|
-
|
|
502
|
+
active_score_client&.flush
|
|
493
503
|
end
|
|
494
504
|
|
|
495
505
|
# Shutdown the client and flush any pending scores
|
|
@@ -498,8 +508,8 @@ module Langfuse
|
|
|
498
508
|
#
|
|
499
509
|
# @return [void]
|
|
500
510
|
def shutdown
|
|
501
|
-
@score_client
|
|
502
|
-
@api_client
|
|
511
|
+
@score_client&.shutdown
|
|
512
|
+
@api_client_mutex.synchronize { @api_client }&.shutdown
|
|
503
513
|
end
|
|
504
514
|
|
|
505
515
|
# Create a new dataset
|
|
@@ -533,49 +543,6 @@ module Langfuse
|
|
|
533
543
|
DatasetClient.new(data, client: self)
|
|
534
544
|
end
|
|
535
545
|
|
|
536
|
-
# List all datasets in the project
|
|
537
|
-
#
|
|
538
|
-
# @param page [Integer, nil] Optional page number for pagination
|
|
539
|
-
# @param limit [Integer, nil] Optional limit per page
|
|
540
|
-
# @return [Array<Hash>] Array of dataset metadata hashes
|
|
541
|
-
# @raise [UnauthorizedError] if authentication fails
|
|
542
|
-
# @raise [ApiError] for other API errors
|
|
543
|
-
#
|
|
544
|
-
# @example
|
|
545
|
-
# datasets = client.list_datasets(page: 1, limit: 10)
|
|
546
|
-
def list_datasets(page: nil, limit: nil)
|
|
547
|
-
api_client.list_datasets(page: page, limit: limit)
|
|
548
|
-
end
|
|
549
|
-
|
|
550
|
-
# List traces in the project
|
|
551
|
-
#
|
|
552
|
-
# @param page [Integer, nil] Optional page number for pagination
|
|
553
|
-
# @param limit [Integer, nil] Optional limit per page
|
|
554
|
-
# @param filters [Hash] Additional filters (user_id, name, session_id, etc.)
|
|
555
|
-
# @return [Array<Hash>] Array of trace hashes
|
|
556
|
-
# @raise [UnauthorizedError] if authentication fails
|
|
557
|
-
# @raise [ApiError] for other API errors
|
|
558
|
-
#
|
|
559
|
-
# @example
|
|
560
|
-
# traces = client.list_traces(page: 1, limit: 10, name: "my-trace")
|
|
561
|
-
def list_traces(page: nil, limit: nil, **filters)
|
|
562
|
-
api_client.list_traces(page: page, limit: limit, **filters)
|
|
563
|
-
end
|
|
564
|
-
|
|
565
|
-
# Fetch a trace by ID
|
|
566
|
-
#
|
|
567
|
-
# @param id [String] Trace ID
|
|
568
|
-
# @return [Hash] The trace data
|
|
569
|
-
# @raise [NotFoundError] if the trace is not found
|
|
570
|
-
# @raise [UnauthorizedError] if authentication fails
|
|
571
|
-
# @raise [ApiError] for other API errors
|
|
572
|
-
#
|
|
573
|
-
# @example
|
|
574
|
-
# trace = client.get_trace("trace-uuid-123")
|
|
575
|
-
def get_trace(id)
|
|
576
|
-
api_client.get_trace(id)
|
|
577
|
-
end
|
|
578
|
-
|
|
579
546
|
# Create a new dataset item
|
|
580
547
|
#
|
|
581
548
|
# @param dataset_name [String] Name of the dataset to add item to (required)
|
|
@@ -669,39 +636,6 @@ module Langfuse
|
|
|
669
636
|
nil
|
|
670
637
|
end
|
|
671
638
|
|
|
672
|
-
# Create a dataset run item (link a trace to a dataset item)
|
|
673
|
-
#
|
|
674
|
-
# @param dataset_item_id [String] Dataset item ID (required)
|
|
675
|
-
# @param run_name [String] Run name (required)
|
|
676
|
-
# @param trace_id [String, nil] Trace ID
|
|
677
|
-
# @param observation_id [String, nil] Observation ID
|
|
678
|
-
# @param metadata [Hash, nil] Optional metadata
|
|
679
|
-
# @param run_description [String, nil] Optional run description
|
|
680
|
-
# @return [Hash] The created dataset run item data
|
|
681
|
-
def create_dataset_run_item(dataset_item_id:, run_name:, trace_id: nil,
|
|
682
|
-
observation_id: nil, metadata: nil, run_description: nil)
|
|
683
|
-
api_client.create_dataset_run_item(
|
|
684
|
-
dataset_item_id: dataset_item_id,
|
|
685
|
-
run_name: run_name,
|
|
686
|
-
trace_id: trace_id,
|
|
687
|
-
observation_id: observation_id,
|
|
688
|
-
metadata: metadata,
|
|
689
|
-
run_description: run_description
|
|
690
|
-
)
|
|
691
|
-
end
|
|
692
|
-
|
|
693
|
-
# Fetch a dataset run by dataset and run name
|
|
694
|
-
#
|
|
695
|
-
# @param dataset_name [String] Dataset name (required)
|
|
696
|
-
# @param run_name [String] Run name (required)
|
|
697
|
-
# @return [Hash] The dataset run data, including linked run items
|
|
698
|
-
# @raise [NotFoundError] if the dataset run is not found
|
|
699
|
-
# @raise [UnauthorizedError] if authentication fails
|
|
700
|
-
# @raise [ApiError] for other API errors
|
|
701
|
-
def get_dataset_run(dataset_name:, run_name:)
|
|
702
|
-
api_client.get_dataset_run(dataset_name: dataset_name, run_name: run_name)
|
|
703
|
-
end
|
|
704
|
-
|
|
705
639
|
# List dataset runs for a dataset
|
|
706
640
|
#
|
|
707
641
|
# When page is nil (default), auto-paginates to fetch all runs.
|
|
@@ -776,6 +710,38 @@ module Langfuse
|
|
|
776
710
|
|
|
777
711
|
private
|
|
778
712
|
|
|
713
|
+
def build_validated_api_client
|
|
714
|
+
config.validate!
|
|
715
|
+
build_api_client
|
|
716
|
+
end
|
|
717
|
+
|
|
718
|
+
def build_api_client
|
|
719
|
+
cache = create_cache if cache_enabled?
|
|
720
|
+
ApiClient.new(
|
|
721
|
+
public_key: config.public_key,
|
|
722
|
+
secret_key: config.secret_key,
|
|
723
|
+
base_url: config.base_url,
|
|
724
|
+
timeout: config.timeout,
|
|
725
|
+
logger: config.logger,
|
|
726
|
+
cache: cache,
|
|
727
|
+
cache_observer: config.prompt_cache_observer
|
|
728
|
+
)
|
|
729
|
+
end
|
|
730
|
+
|
|
731
|
+
def telemetry_enabled?
|
|
732
|
+
config.telemetry_enabled?
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
def active_score_client
|
|
736
|
+
return unless telemetry_enabled?
|
|
737
|
+
|
|
738
|
+
@score_client_mutex.synchronize { @score_client ||= build_score_client }
|
|
739
|
+
end
|
|
740
|
+
|
|
741
|
+
def build_score_client
|
|
742
|
+
ScoreClient.new(api_client: api_client, config: config)
|
|
743
|
+
end
|
|
744
|
+
|
|
779
745
|
attr_reader :score_client
|
|
780
746
|
|
|
781
747
|
# Build a project-scoped URL, returning nil if project ID is unavailable
|
|
@@ -877,7 +843,7 @@ module Langfuse
|
|
|
877
843
|
|
|
878
844
|
def fallback_cache_status(cache_ttl)
|
|
879
845
|
return CacheStatus::BYPASS if cache_ttl&.zero?
|
|
880
|
-
return CacheStatus::DISABLED unless api_client.
|
|
846
|
+
return CacheStatus::DISABLED unless api_client.prompt_cache_stats[:enabled]
|
|
881
847
|
|
|
882
848
|
CacheStatus::MISS
|
|
883
849
|
end
|
|
@@ -906,7 +872,7 @@ module Langfuse
|
|
|
906
872
|
end
|
|
907
873
|
|
|
908
874
|
def rails_cache_available?
|
|
909
|
-
|
|
875
|
+
RailsCacheAdapter.available?
|
|
910
876
|
end
|
|
911
877
|
|
|
912
878
|
# Create in-memory cache with SWR support if enabled
|
|
@@ -932,21 +898,22 @@ module Langfuse
|
|
|
932
898
|
)
|
|
933
899
|
end
|
|
934
900
|
|
|
901
|
+
VALID_PROMPT_TYPES = %i[text chat].freeze
|
|
902
|
+
private_constant :VALID_PROMPT_TYPES
|
|
903
|
+
|
|
935
904
|
# Build the appropriate prompt client based on prompt type
|
|
936
905
|
#
|
|
937
906
|
# @param prompt_data [Hash] The prompt data from API
|
|
938
907
|
# @return [TextPromptClient, ChatPromptClient]
|
|
939
908
|
# @raise [ApiError] if prompt type is unknown
|
|
940
|
-
def build_prompt_client(prompt_data)
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
case type
|
|
909
|
+
def build_prompt_client(prompt_data, is_fallback: false)
|
|
910
|
+
case prompt_data["type"]
|
|
944
911
|
when "text"
|
|
945
|
-
TextPromptClient.new(prompt_data)
|
|
912
|
+
TextPromptClient.new(prompt_data, is_fallback: is_fallback)
|
|
946
913
|
when "chat"
|
|
947
|
-
ChatPromptClient.new(prompt_data)
|
|
914
|
+
ChatPromptClient.new(prompt_data, is_fallback: is_fallback)
|
|
948
915
|
else
|
|
949
|
-
raise ApiError, "Unknown prompt type: #{type}"
|
|
916
|
+
raise ApiError, "Unknown prompt type: #{prompt_data['type']}"
|
|
950
917
|
end
|
|
951
918
|
end
|
|
952
919
|
|
|
@@ -959,24 +926,18 @@ module Langfuse
|
|
|
959
926
|
# @raise [ArgumentError] if type is invalid
|
|
960
927
|
def build_fallback_prompt_client(name, fallback, type)
|
|
961
928
|
validate_prompt_type!(type)
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
case type
|
|
975
|
-
when :text
|
|
976
|
-
TextPromptClient.new(prompt_data, is_fallback: true)
|
|
977
|
-
when :chat
|
|
978
|
-
ChatPromptClient.new(prompt_data, is_fallback: true)
|
|
979
|
-
end
|
|
929
|
+
build_prompt_client(
|
|
930
|
+
{
|
|
931
|
+
"name" => name,
|
|
932
|
+
"version" => 0,
|
|
933
|
+
"type" => type.to_s,
|
|
934
|
+
"prompt" => fallback,
|
|
935
|
+
"labels" => [],
|
|
936
|
+
"tags" => ["fallback"],
|
|
937
|
+
"config" => {}
|
|
938
|
+
},
|
|
939
|
+
is_fallback: true
|
|
940
|
+
)
|
|
980
941
|
end
|
|
981
942
|
|
|
982
943
|
# Validate prompt type parameter
|
|
@@ -984,8 +945,7 @@ module Langfuse
|
|
|
984
945
|
# @param type [Symbol] The type to validate
|
|
985
946
|
# @raise [ArgumentError] if type is invalid
|
|
986
947
|
def validate_prompt_type!(type)
|
|
987
|
-
|
|
988
|
-
return if valid_types.include?(type)
|
|
948
|
+
return if VALID_PROMPT_TYPES.include?(type)
|
|
989
949
|
|
|
990
950
|
raise ArgumentError, "Invalid type: #{type}. Must be :text or :chat"
|
|
991
951
|
end
|
|
@@ -1015,30 +975,15 @@ module Langfuse
|
|
|
1015
975
|
def normalize_prompt_content(prompt, type)
|
|
1016
976
|
return prompt if type == :text
|
|
1017
977
|
|
|
1018
|
-
# Normalize chat messages to use string keys
|
|
1019
978
|
prompt.map do |message|
|
|
1020
979
|
normalized = message.transform_keys(&:to_s)
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
980
|
+
if normalized["type"] == ChatPromptClient::PLACEHOLDER_TYPE
|
|
981
|
+
{ "type" => ChatPromptClient::PLACEHOLDER_TYPE, "name" => normalized["name"].to_s }
|
|
982
|
+
else
|
|
983
|
+
normalized.merge("role" => normalized["role"]&.to_s, "content" => normalized["content"])
|
|
984
|
+
end
|
|
1024
985
|
end
|
|
1025
986
|
end
|
|
1026
|
-
|
|
1027
|
-
# @api private
|
|
1028
|
-
def placeholder_prompt_content(message)
|
|
1029
|
-
{
|
|
1030
|
-
"type" => ChatPromptClient::PLACEHOLDER_TYPE,
|
|
1031
|
-
"name" => message["name"].to_s
|
|
1032
|
-
}
|
|
1033
|
-
end
|
|
1034
|
-
|
|
1035
|
-
# @api private
|
|
1036
|
-
def normalize_chat_message_content(message)
|
|
1037
|
-
message.merge(
|
|
1038
|
-
"role" => message["role"]&.to_s,
|
|
1039
|
-
"content" => message["content"]
|
|
1040
|
-
)
|
|
1041
|
-
end
|
|
1042
987
|
end
|
|
1043
988
|
# rubocop:enable Metrics/ClassLength
|
|
1044
989
|
end
|