langfuse 0.1.0 → 0.1.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/bin/tapioca +27 -0
- data/lib/langfuse/api_client.rb +17 -5
- data/lib/langfuse/batch_worker.rb +48 -18
- data/lib/langfuse/client.rb +63 -24
- data/lib/langfuse/configuration.rb +31 -12
- data/lib/langfuse/models/event.rb +1 -0
- data/lib/langfuse/models/generation.rb +1 -0
- data/lib/langfuse/models/ingestion_event.rb +1 -0
- data/lib/langfuse/models/score.rb +1 -0
- data/lib/langfuse/models/span.rb +1 -0
- data/lib/langfuse/models/trace.rb +1 -0
- data/lib/langfuse/models/usage.rb +1 -0
- data/lib/langfuse/version.rb +2 -1
- data/lib/langfuse.rb +51 -12
- data/lib/langfuse_context.rb +45 -5
- data/lib/langfuse_helper.rb +123 -47
- metadata +47 -5
- data/lib/langfuse/rails.rb +0 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 13d36d5500f51cce6bf73e497b4d173d62da2e8be2c9142a3eba5b563b786f3a
|
4
|
+
data.tar.gz: ffa0ca7036c84a49e771e43945db532d7382cf08b8894e8b58f71ad994e9179a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d3e236f29058f9d03879d9413e5be6ed8c8780c95ab5ceace863c1fdddb0564ec9422a194a474f4b9cbfed6656dc30f15d37917de78a73195dc3f5293f9fa2d9
|
7
|
+
data.tar.gz: 6c08d42e2feb0c900b92f1c5f7cc991afaf0d6e3eb12432d64243258ad31ab6d12e2fada6c481990563f53242ca0ebb7caed082807cf56f96e4e5fc62e88e8f2
|
data/bin/tapioca
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'tapioca' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
12
|
+
|
13
|
+
bundle_binstub = File.expand_path("bundle", __dir__)
|
14
|
+
|
15
|
+
if File.file?(bundle_binstub)
|
16
|
+
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
|
17
|
+
load(bundle_binstub)
|
18
|
+
else
|
19
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
20
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
require "rubygems"
|
25
|
+
require "bundler/setup"
|
26
|
+
|
27
|
+
load Gem.bin_path("tapioca", "tapioca")
|
data/lib/langfuse/api_client.rb
CHANGED
@@ -1,16 +1,24 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
1
3
|
require 'net/http'
|
2
4
|
require 'uri'
|
3
5
|
require 'json'
|
4
6
|
require 'base64'
|
7
|
+
require 'sorbet-runtime'
|
5
8
|
|
6
9
|
module Langfuse
|
7
10
|
class ApiClient
|
11
|
+
extend T::Sig
|
12
|
+
|
13
|
+
sig { returns(T.untyped) }
|
8
14
|
attr_reader :config
|
9
15
|
|
16
|
+
sig { params(config: T.untyped).void }
|
10
17
|
def initialize(config)
|
11
18
|
@config = config
|
12
19
|
end
|
13
20
|
|
21
|
+
sig { params(events: T::Array[T::Hash[T.untyped, T.untyped]]).returns(T::Hash[String, T.untyped]) }
|
14
22
|
def ingest(events)
|
15
23
|
uri = URI.parse("#{@config.host}/api/public/ingestion")
|
16
24
|
|
@@ -44,14 +52,18 @@ module Langfuse
|
|
44
52
|
log("Request url: #{uri}")
|
45
53
|
end
|
46
54
|
|
55
|
+
log('---') # Moved log statement before response handling to avoid affecting return value
|
56
|
+
|
47
57
|
response = http.request(request)
|
48
58
|
|
59
|
+
result = T.let(nil, T.nilable(T::Hash[String, T.untyped]))
|
60
|
+
|
49
61
|
if response.code.to_i == 207 # Partial success
|
50
62
|
log('Received 207 partial success response') if @config.debug
|
51
|
-
JSON.parse(response.body)
|
63
|
+
result = JSON.parse(response.body)
|
52
64
|
elsif response.code.to_i >= 200 && response.code.to_i < 300
|
53
65
|
log("Received successful response: #{response.code}") if @config.debug
|
54
|
-
JSON.parse(response.body)
|
66
|
+
result = JSON.parse(response.body)
|
55
67
|
else
|
56
68
|
error_msg = "API error: #{response.code} #{response.message}"
|
57
69
|
if @config.debug
|
@@ -62,7 +74,7 @@ module Langfuse
|
|
62
74
|
raise error_msg
|
63
75
|
end
|
64
76
|
|
65
|
-
|
77
|
+
result
|
66
78
|
rescue StandardError => e
|
67
79
|
log("Error during API request: #{e.message}", :error)
|
68
80
|
raise
|
@@ -70,11 +82,11 @@ module Langfuse
|
|
70
82
|
|
71
83
|
private
|
72
84
|
|
85
|
+
sig { params(message: String, level: Symbol).returns(T.untyped) }
|
73
86
|
def log(message, level = :debug)
|
74
87
|
return unless @config.debug
|
75
88
|
|
76
|
-
logger
|
77
|
-
logger.send(level, "[Langfuse] #{message}")
|
89
|
+
T.unsafe(@config.logger).send(level, "[Langfuse] #{message}")
|
78
90
|
end
|
79
91
|
end
|
80
92
|
end
|
@@ -1,57 +1,83 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require 'sorbet-runtime'
|
4
|
+
|
1
5
|
module Langfuse
|
2
6
|
class BatchWorker
|
7
|
+
extend T::Sig
|
3
8
|
# This is a placeholder class that will be defined with Sidekiq::Worker
|
4
9
|
# when Sidekiq is available.
|
5
10
|
#
|
6
11
|
# If Sidekiq is available, this will be replaced with a real worker class
|
7
12
|
# that includes Sidekiq::Worker
|
8
13
|
|
14
|
+
# Ensure return type matches the synchronous perform call
|
15
|
+
sig { params(events: T::Array[T::Hash[T.untyped, T.untyped]]).void }
|
9
16
|
def self.perform_async(events)
|
10
|
-
# When Sidekiq is not available, process synchronously
|
17
|
+
# When Sidekiq is not available, process synchronously and return result
|
11
18
|
new.perform(events)
|
12
19
|
end
|
13
20
|
|
21
|
+
sig { params(events: T::Array[T::Hash[T.untyped, T.untyped]]).returns(T::Hash[String, T.untyped]) }
|
14
22
|
def perform(events)
|
15
|
-
Langfuse
|
23
|
+
# Assuming Langfuse.configuration returns a valid config object for ApiClient
|
24
|
+
T.unsafe(Langfuse::ApiClient).new(T.unsafe(Langfuse).configuration).ingest(events)
|
16
25
|
end
|
17
26
|
end
|
18
27
|
|
19
28
|
# Define the real Sidekiq worker if Sidekiq is available
|
20
29
|
if defined?(Sidekiq)
|
21
30
|
class BatchWorker
|
31
|
+
# Re-extend T::Sig within the conditional definition
|
32
|
+
extend T::Sig
|
33
|
+
# Include Sidekiq::Worker directly - rely on T.unsafe for its methods
|
22
34
|
include Sidekiq::Worker
|
23
35
|
|
24
|
-
|
36
|
+
# Using T.unsafe for sidekiq_options DSL
|
37
|
+
T.unsafe(self).sidekiq_options queue: 'langfuse', retry: 5, backtrace: true
|
25
38
|
|
26
39
|
# Custom retry delay logic (exponential backoff)
|
27
|
-
sidekiq_retry_in
|
40
|
+
# Using T.unsafe for sidekiq_retry_in DSL
|
41
|
+
T.unsafe(self).sidekiq_retry_in do |count|
|
28
42
|
10 * (count + 1) # 10s, 20s, 30s, 40s, 50s
|
29
43
|
end
|
30
44
|
|
45
|
+
sig { params(event_hashes: T::Array[T::Hash[T.untyped, T.untyped]]).void }
|
31
46
|
def perform(event_hashes)
|
32
|
-
|
47
|
+
# Assuming Langfuse.configuration returns a valid config object
|
48
|
+
api_client = T.unsafe(ApiClient).new(T.unsafe(Langfuse).configuration)
|
33
49
|
|
34
50
|
begin
|
35
51
|
response = api_client.ingest(event_hashes)
|
36
52
|
|
37
|
-
# Check for partial failures
|
38
|
-
|
39
|
-
|
40
|
-
|
53
|
+
# Check for partial failures using standard hash access
|
54
|
+
errors = T.let(response['errors'], T.nilable(T::Array[T::Hash[String, T.untyped]]))
|
55
|
+
if errors && errors.any?
|
56
|
+
errors.each do |error|
|
57
|
+
# Use T.unsafe(self).logger provided by Sidekiq::Worker
|
58
|
+
T.unsafe(self).logger.error("Langfuse API error for event #{error['id']}: #{error['message']}")
|
41
59
|
|
42
60
|
# Store permanently failed events if needed
|
43
|
-
|
44
|
-
|
61
|
+
# Assuming error['status'] exists and can be converted to integer
|
62
|
+
status = T.let(error['status'], T.untyped)
|
63
|
+
next unless non_retryable_error?(status)
|
64
|
+
|
65
|
+
# Assuming event_hashes elements have :id key
|
66
|
+
failed_event = event_hashes.find { |e| T.unsafe(e)[:id] == error['id'] }
|
67
|
+
if failed_event
|
68
|
+
# Remove redundant T.cast
|
69
|
+
store_failed_event(failed_event, T.cast(error['message'], String))
|
45
70
|
end
|
46
71
|
end
|
47
72
|
end
|
48
73
|
rescue Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNREFUSED => e
|
49
74
|
# Network errors - Sidekiq will retry
|
50
|
-
logger.error("Langfuse network error: #{e.
|
75
|
+
T.unsafe(self).logger.error("Langfuse network error: #{e.full_message}")
|
51
76
|
raise
|
52
77
|
rescue StandardError => e
|
53
78
|
# Other errors
|
54
|
-
|
79
|
+
# Use T.unsafe(self).logger
|
80
|
+
T.unsafe(self).logger.error("Langfuse API error: #{e.message}")
|
55
81
|
|
56
82
|
# Let Sidekiq retry
|
57
83
|
raise
|
@@ -60,17 +86,21 @@ module Langfuse
|
|
60
86
|
|
61
87
|
private
|
62
88
|
|
89
|
+
sig { params(status: T.untyped).returns(T::Boolean) }
|
63
90
|
def non_retryable_error?(status)
|
64
91
|
# 4xx errors except 429 (rate limit) are not retryable
|
65
|
-
|
92
|
+
status_int = T.let(status.to_i, Integer)
|
93
|
+
status_int >= 400 && status_int < 500 && status_int != 429
|
66
94
|
end
|
67
95
|
|
68
|
-
|
96
|
+
sig { params(event: T::Hash[T.untyped, T.untyped], error_msg: String).returns(T.untyped) }
|
97
|
+
def store_failed_event(event, error_msg)
|
69
98
|
# Store in Redis for later inspection/retry
|
70
|
-
Sidekiq.redis
|
71
|
-
|
99
|
+
# Using T.unsafe for Sidekiq.redis block and redis operations
|
100
|
+
T.unsafe(Sidekiq).redis do |redis|
|
101
|
+
T.unsafe(redis).rpush('langfuse:failed_events', {
|
72
102
|
event: event,
|
73
|
-
error:
|
103
|
+
error: error_msg,
|
74
104
|
timestamp: Time.now.utc.iso8601
|
75
105
|
}.to_json)
|
76
106
|
end
|
data/lib/langfuse/client.rb
CHANGED
@@ -1,29 +1,50 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
1
3
|
require 'singleton'
|
2
4
|
require 'concurrent'
|
3
5
|
require 'logger'
|
6
|
+
require 'sorbet-runtime'
|
7
|
+
|
8
|
+
# Model requires are implicitly handled by the main langfuse.rb require
|
9
|
+
# No need for placeholder type aliases here
|
4
10
|
|
5
11
|
module Langfuse
|
6
12
|
class Client
|
13
|
+
extend T::Sig
|
7
14
|
include Singleton
|
8
15
|
|
16
|
+
sig { returns(::Langfuse::Configuration) } # Use actual Configuration type
|
17
|
+
attr_reader :config
|
18
|
+
|
19
|
+
# Use the class directly, Sorbet should handle Concurrent::Array generics
|
20
|
+
sig { returns(Concurrent::Array) }
|
21
|
+
attr_reader :events
|
22
|
+
|
23
|
+
sig { returns(T.nilable(Thread)) }
|
24
|
+
attr_reader :flush_thread
|
25
|
+
|
26
|
+
sig { void }
|
9
27
|
def initialize
|
10
|
-
@config = Langfuse.configuration
|
11
|
-
|
12
|
-
@
|
28
|
+
@config = T.let(Langfuse.configuration, ::Langfuse::Configuration)
|
29
|
+
# Let Sorbet infer the type for Concurrent::Array here
|
30
|
+
@events = T.let(Concurrent::Array.new, Concurrent::Array)
|
31
|
+
@mutex = T.let(Mutex.new, Mutex)
|
32
|
+
@flush_thread = T.let(nil, T.nilable(Thread))
|
13
33
|
|
14
|
-
|
15
|
-
schedule_periodic_flush if defined?(Rails) && Rails.server?
|
34
|
+
schedule_periodic_flush
|
16
35
|
|
17
36
|
# Register shutdown hook
|
18
37
|
return if @config.disable_at_exit_hook
|
19
38
|
|
20
|
-
at_exit { shutdown }
|
39
|
+
Kernel.at_exit { shutdown }
|
21
40
|
end
|
22
41
|
|
23
42
|
# Creates a new trace
|
43
|
+
sig { params(attributes: T::Hash[Symbol, T.untyped]).returns(T.untyped) }
|
24
44
|
def trace(attributes = {})
|
25
|
-
|
26
|
-
|
45
|
+
# Ideally Models::Trace.new would have its own signature
|
46
|
+
trace = T.unsafe(Models::Trace).new(attributes)
|
47
|
+
event = T.unsafe(Models::IngestionEvent).new(
|
27
48
|
type: 'trace-create',
|
28
49
|
body: trace
|
29
50
|
)
|
@@ -32,11 +53,12 @@ module Langfuse
|
|
32
53
|
end
|
33
54
|
|
34
55
|
# Creates a new span within a trace
|
56
|
+
sig { params(attributes: T::Hash[Symbol, T.untyped]).returns(T.untyped) }
|
35
57
|
def span(attributes = {})
|
36
58
|
raise ArgumentError, 'trace_id is required for creating a span' unless attributes[:trace_id]
|
37
59
|
|
38
|
-
span = Models::Span.new(attributes)
|
39
|
-
event = Models::IngestionEvent.new(
|
60
|
+
span = T.unsafe(Models::Span).new(attributes)
|
61
|
+
event = T.unsafe(Models::IngestionEvent).new(
|
40
62
|
type: 'span-create',
|
41
63
|
body: span
|
42
64
|
)
|
@@ -45,10 +67,15 @@ module Langfuse
|
|
45
67
|
end
|
46
68
|
|
47
69
|
# Updates an existing span
|
70
|
+
sig { params(span: T.untyped).returns(T.untyped) }
|
48
71
|
def update_span(span)
|
49
|
-
|
72
|
+
# Assuming span object has :id and :trace_id methods/attributes
|
73
|
+
unless T.unsafe(span).id && T.unsafe(span).trace_id
|
74
|
+
raise ArgumentError,
|
75
|
+
'span.id and span.trace_id are required for updating a span'
|
76
|
+
end
|
50
77
|
|
51
|
-
event = Models::IngestionEvent.new(
|
78
|
+
event = T.unsafe(Models::IngestionEvent).new(
|
52
79
|
type: 'span-update',
|
53
80
|
body: span
|
54
81
|
)
|
@@ -57,11 +84,12 @@ module Langfuse
|
|
57
84
|
end
|
58
85
|
|
59
86
|
# Creates a new generation within a trace
|
87
|
+
sig { params(attributes: T::Hash[Symbol, T.untyped]).returns(T.untyped) }
|
60
88
|
def generation(attributes = {})
|
61
89
|
raise ArgumentError, 'trace_id is required for creating a generation' unless attributes[:trace_id]
|
62
90
|
|
63
|
-
generation = Models::Generation.new(attributes)
|
64
|
-
event = Models::IngestionEvent.new(
|
91
|
+
generation = T.unsafe(Models::Generation).new(attributes)
|
92
|
+
event = T.unsafe(Models::IngestionEvent).new(
|
65
93
|
type: 'generation-create',
|
66
94
|
body: generation
|
67
95
|
)
|
@@ -70,12 +98,13 @@ module Langfuse
|
|
70
98
|
end
|
71
99
|
|
72
100
|
# Updates an existing generation
|
101
|
+
sig { params(generation: T.untyped).returns(T.untyped) }
|
73
102
|
def update_generation(generation)
|
74
|
-
unless generation.id && generation.trace_id
|
103
|
+
unless T.unsafe(generation).id && T.unsafe(generation).trace_id
|
75
104
|
raise ArgumentError, 'generation.id and generation.trace_id are required for updating a generation'
|
76
105
|
end
|
77
106
|
|
78
|
-
event = Models::IngestionEvent.new(
|
107
|
+
event = T.unsafe(Models::IngestionEvent).new(
|
79
108
|
type: 'generation-update',
|
80
109
|
body: generation
|
81
110
|
)
|
@@ -84,11 +113,12 @@ module Langfuse
|
|
84
113
|
end
|
85
114
|
|
86
115
|
# Creates a new event within a trace
|
116
|
+
sig { params(attributes: T::Hash[Symbol, T.untyped]).returns(T.untyped) }
|
87
117
|
def event(attributes = {})
|
88
118
|
raise ArgumentError, 'trace_id is required for creating an event' unless attributes[:trace_id]
|
89
119
|
|
90
|
-
event_obj = Models::Event.new(attributes)
|
91
|
-
event = Models::IngestionEvent.new(
|
120
|
+
event_obj = T.unsafe(Models::Event).new(attributes)
|
121
|
+
event = T.unsafe(Models::IngestionEvent).new(
|
92
122
|
type: 'event-create',
|
93
123
|
body: event_obj
|
94
124
|
)
|
@@ -97,11 +127,12 @@ module Langfuse
|
|
97
127
|
end
|
98
128
|
|
99
129
|
# Creates a new score
|
130
|
+
sig { params(attributes: T::Hash[Symbol, T.untyped]).returns(T.untyped) }
|
100
131
|
def score(attributes = {})
|
101
132
|
raise ArgumentError, 'trace_id is required for creating a score' unless attributes[:trace_id]
|
102
133
|
|
103
|
-
score = Models::Score.new(attributes)
|
104
|
-
event = Models::IngestionEvent.new(
|
134
|
+
score = T.unsafe(Models::Score).new(attributes)
|
135
|
+
event = T.unsafe(Models::IngestionEvent).new(
|
105
136
|
type: 'score-create',
|
106
137
|
body: score
|
107
138
|
)
|
@@ -110,8 +141,9 @@ module Langfuse
|
|
110
141
|
end
|
111
142
|
|
112
143
|
# Flushes all pending events to the API
|
144
|
+
sig { void }
|
113
145
|
def flush
|
114
|
-
events_to_process =
|
146
|
+
events_to_process = T.let([], T::Array[T.untyped])
|
115
147
|
|
116
148
|
# Atomically swap the events array to avoid race conditions
|
117
149
|
@mutex.synchronize do
|
@@ -122,15 +154,17 @@ module Langfuse
|
|
122
154
|
return if events_to_process.empty?
|
123
155
|
|
124
156
|
# Convert objects to hashes for serialization
|
157
|
+
# Assuming `to_h` exists on Models::IngestionEvent and returns T::Hash[T.untyped, T.untyped]
|
125
158
|
event_hashes = events_to_process.map(&:to_h)
|
126
159
|
|
127
160
|
log("Flushing #{event_hashes.size} events")
|
128
161
|
|
129
162
|
# Send to background worker
|
130
|
-
BatchWorker.perform_async(event_hashes)
|
163
|
+
T.unsafe(BatchWorker).perform_async(event_hashes)
|
131
164
|
end
|
132
165
|
|
133
166
|
# Gracefully shuts down the client, ensuring all events are flushed
|
167
|
+
sig { void }
|
134
168
|
def shutdown
|
135
169
|
log('Shutting down Langfuse client...')
|
136
170
|
|
@@ -145,18 +179,22 @@ module Langfuse
|
|
145
179
|
|
146
180
|
private
|
147
181
|
|
182
|
+
sig { params(event: T.untyped).void }
|
148
183
|
def enqueue_event(event)
|
149
184
|
@events << event
|
150
185
|
|
151
186
|
# Trigger immediate flush if batch size reached
|
187
|
+
# Assuming @config.batch_size is an Integer
|
152
188
|
flush if @events.size >= @config.batch_size
|
153
189
|
end
|
154
190
|
|
191
|
+
sig { returns(Thread) }
|
155
192
|
def schedule_periodic_flush
|
156
193
|
log("Starting periodic flush thread (interval: #{@config.flush_interval}s)")
|
157
194
|
|
158
195
|
@flush_thread = Thread.new do
|
159
196
|
loop do
|
197
|
+
# Assuming @config.flush_interval is Numeric
|
160
198
|
sleep @config.flush_interval
|
161
199
|
flush
|
162
200
|
rescue StandardError => e
|
@@ -166,11 +204,12 @@ module Langfuse
|
|
166
204
|
end
|
167
205
|
end
|
168
206
|
|
207
|
+
sig { params(message: String, level: Symbol).returns(T.untyped) }
|
169
208
|
def log(message, level = :debug)
|
209
|
+
# Assuming @config.debug is Boolean
|
170
210
|
return unless @config.debug
|
171
211
|
|
172
|
-
logger
|
173
|
-
logger.send(level, "[Langfuse] #{message}")
|
212
|
+
T.unsafe(@config.logger).send(level, "[Langfuse] #{message}")
|
174
213
|
end
|
175
214
|
end
|
176
215
|
end
|
@@ -1,20 +1,39 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'sorbet-runtime'
|
5
|
+
|
1
6
|
module Langfuse
|
2
7
|
class Configuration
|
3
|
-
|
4
|
-
|
5
|
-
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { returns(T.nilable(String)) }
|
11
|
+
attr_accessor :public_key, :secret_key
|
12
|
+
|
13
|
+
sig { returns(String) }
|
14
|
+
attr_accessor :host
|
15
|
+
|
16
|
+
sig { returns(Integer) }
|
17
|
+
attr_accessor :batch_size, :flush_interval, :shutdown_timeout
|
18
|
+
|
19
|
+
sig { returns(T::Boolean) }
|
20
|
+
attr_accessor :debug, :disable_at_exit_hook
|
21
|
+
|
22
|
+
sig { returns(T.untyped) }
|
23
|
+
attr_accessor :logger
|
6
24
|
|
25
|
+
sig { void }
|
7
26
|
def initialize
|
8
27
|
# Default configuration with environment variable fallbacks
|
9
|
-
@public_key = ENV['LANGFUSE_PUBLIC_KEY']
|
10
|
-
@secret_key = ENV['LANGFUSE_SECRET_KEY']
|
11
|
-
@host = ENV.fetch('LANGFUSE_HOST', 'https://us.cloud.langfuse.com')
|
12
|
-
@batch_size = ENV.fetch('LANGFUSE_BATCH_SIZE', '10').to_i
|
13
|
-
@flush_interval = ENV.fetch('LANGFUSE_FLUSH_INTERVAL', '60').to_i
|
14
|
-
@debug = ENV.fetch('LANGFUSE_DEBUG', 'false') == 'true'
|
15
|
-
@disable_at_exit_hook = false
|
16
|
-
@shutdown_timeout = ENV.fetch('LANGFUSE_SHUTDOWN_TIMEOUT', '5').to_i
|
17
|
-
@logger = Logger.new(
|
28
|
+
@public_key = T.let(ENV['LANGFUSE_PUBLIC_KEY'], T.nilable(String))
|
29
|
+
@secret_key = T.let(ENV['LANGFUSE_SECRET_KEY'], T.nilable(String))
|
30
|
+
@host = T.let(ENV.fetch('LANGFUSE_HOST', 'https://us.cloud.langfuse.com'), String)
|
31
|
+
@batch_size = T.let(ENV.fetch('LANGFUSE_BATCH_SIZE', '10').to_i, Integer)
|
32
|
+
@flush_interval = T.let(ENV.fetch('LANGFUSE_FLUSH_INTERVAL', '60').to_i, Integer)
|
33
|
+
@debug = T.let(ENV.fetch('LANGFUSE_DEBUG', 'false') == 'true', T::Boolean)
|
34
|
+
@disable_at_exit_hook = T.let(false, T::Boolean)
|
35
|
+
@shutdown_timeout = T.let(ENV.fetch('LANGFUSE_SHUTDOWN_TIMEOUT', '5').to_i, Integer)
|
36
|
+
@logger = T.let(Logger.new($stdout), Logger)
|
18
37
|
end
|
19
38
|
end
|
20
39
|
end
|
data/lib/langfuse/models/span.rb
CHANGED
data/lib/langfuse/version.rb
CHANGED
data/lib/langfuse.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
+
# typed: false
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'sorbet-runtime'
|
1
5
|
require 'langfuse/version'
|
2
6
|
require 'langfuse/configuration'
|
3
7
|
|
4
|
-
# Load models
|
8
|
+
# Load models - Use fully qualified names in sigs below
|
5
9
|
require 'langfuse/models/ingestion_event'
|
6
10
|
require 'langfuse/models/trace'
|
7
11
|
require 'langfuse/models/span'
|
@@ -13,57 +17,92 @@ require 'langfuse/models/usage'
|
|
13
17
|
# Load API client
|
14
18
|
require 'langfuse/api_client'
|
15
19
|
|
16
|
-
# Load batch worker
|
20
|
+
# Load batch worker
|
17
21
|
require 'langfuse/batch_worker'
|
18
22
|
|
19
23
|
# Load main client
|
20
24
|
require 'langfuse/client'
|
21
25
|
|
22
26
|
module Langfuse
|
27
|
+
extend T::Sig
|
28
|
+
|
23
29
|
class << self
|
30
|
+
extend T::Sig
|
31
|
+
|
32
|
+
# Sig for the writer method created by attr_writer
|
33
|
+
sig { params(configuration: ::Langfuse::Configuration).void }
|
24
34
|
attr_writer :configuration
|
25
35
|
|
36
|
+
# Sig for the reader method
|
37
|
+
sig { returns(::Langfuse::Configuration) }
|
26
38
|
def configuration
|
27
|
-
|
39
|
+
# Use T.let for clarity on initialization
|
40
|
+
@configuration = T.let(@configuration, T.nilable(::Langfuse::Configuration))
|
41
|
+
@configuration ||= ::Langfuse::Configuration.new
|
28
42
|
end
|
29
43
|
|
30
|
-
|
44
|
+
# Configuration block
|
45
|
+
sig { params(_block: T.proc.params(config: ::Langfuse::Configuration).void).void }
|
46
|
+
def configure(&_block)
|
47
|
+
# Pass the block to yield
|
31
48
|
yield(configuration)
|
32
49
|
end
|
33
50
|
|
34
|
-
# Convenience delegators to the client instance
|
51
|
+
# --- Convenience delegators to the client instance ---
|
52
|
+
|
53
|
+
# Create Trace
|
54
|
+
sig { params(attributes: T::Hash[Symbol, T.untyped]).returns(::Langfuse::Models::Trace) }
|
35
55
|
def trace(attributes = {})
|
36
|
-
Client.
|
56
|
+
# Use T.unsafe as Client returns T.untyped for Models for now
|
57
|
+
T.unsafe(Client.instance).trace(attributes)
|
37
58
|
end
|
38
59
|
|
60
|
+
# Create Span
|
61
|
+
sig { params(attributes: T::Hash[Symbol, T.untyped]).returns(::Langfuse::Models::Span) }
|
39
62
|
def span(attributes = {})
|
40
|
-
Client.instance.span(attributes)
|
63
|
+
T.unsafe(Client.instance).span(attributes)
|
41
64
|
end
|
42
65
|
|
66
|
+
# Update Span
|
67
|
+
sig { params(span: ::Langfuse::Models::Span).void }
|
43
68
|
def update_span(span)
|
44
|
-
Client.instance.update_span(span)
|
69
|
+
T.unsafe(Client.instance).update_span(span)
|
70
|
+
# Return void implicitly
|
45
71
|
end
|
46
72
|
|
73
|
+
# Create Generation
|
74
|
+
sig { params(attributes: T::Hash[Symbol, T.untyped]).returns(::Langfuse::Models::Generation) }
|
47
75
|
def generation(attributes = {})
|
48
|
-
Client.instance.generation(attributes)
|
76
|
+
T.unsafe(Client.instance).generation(attributes)
|
49
77
|
end
|
50
78
|
|
79
|
+
# Update Generation
|
80
|
+
sig { params(generation: ::Langfuse::Models::Generation).void }
|
51
81
|
def update_generation(generation)
|
52
|
-
Client.instance.update_generation(generation)
|
82
|
+
T.unsafe(Client.instance).update_generation(generation)
|
83
|
+
# Return void implicitly
|
53
84
|
end
|
54
85
|
|
86
|
+
# Create Event
|
87
|
+
sig { params(attributes: T::Hash[Symbol, T.untyped]).returns(::Langfuse::Models::Event) }
|
55
88
|
def event(attributes = {})
|
56
|
-
Client.instance.event(attributes)
|
89
|
+
T.unsafe(Client.instance).event(attributes)
|
57
90
|
end
|
58
91
|
|
92
|
+
# Create Score
|
93
|
+
sig { params(attributes: T::Hash[Symbol, T.untyped]).returns(::Langfuse::Models::Score) }
|
59
94
|
def score(attributes = {})
|
60
|
-
Client.instance.score(attributes)
|
95
|
+
T.unsafe(Client.instance).score(attributes)
|
61
96
|
end
|
62
97
|
|
98
|
+
# Flush events
|
99
|
+
sig { void }
|
63
100
|
def flush
|
64
101
|
Client.instance.flush
|
65
102
|
end
|
66
103
|
|
104
|
+
# Shutdown client
|
105
|
+
sig { void }
|
67
106
|
def shutdown
|
68
107
|
Client.instance.shutdown
|
69
108
|
end
|
data/lib/langfuse_context.rb
CHANGED
@@ -1,30 +1,70 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
3
|
+
require 'sorbet-runtime' # Ensure Sorbet is required
|
4
|
+
|
1
5
|
class LangfuseContext
|
6
|
+
extend T::Sig # Add this to enable sig blocks on class methods
|
7
|
+
|
8
|
+
# Define the type for the context hash
|
9
|
+
ContextHash = T.type_alias { T::Hash[Symbol, T.nilable(String)] }
|
10
|
+
|
11
|
+
# Gets the current context hash for the thread
|
12
|
+
sig { returns(ContextHash) }
|
2
13
|
def self.current
|
3
|
-
|
14
|
+
# T.let is used for type assertion
|
15
|
+
context = T.let(Thread.current[:langfuse_context], T.nilable(ContextHash))
|
16
|
+
# Initialize if nil
|
17
|
+
context ||= T.let({}, ContextHash)
|
18
|
+
Thread.current[:langfuse_context] = context
|
19
|
+
context
|
4
20
|
end
|
5
21
|
|
22
|
+
# Gets the current trace ID from the context
|
23
|
+
sig { returns(T.nilable(String)) }
|
6
24
|
def self.current_trace_id
|
7
25
|
current[:trace_id]
|
8
26
|
end
|
9
27
|
|
28
|
+
# Gets the current span ID from the context
|
29
|
+
sig { returns(T.nilable(String)) }
|
10
30
|
def self.current_span_id
|
11
31
|
current[:span_id]
|
12
32
|
end
|
13
33
|
|
14
|
-
|
34
|
+
# Executes a block with a specific trace context
|
35
|
+
sig do
|
36
|
+
params(
|
37
|
+
trace: T.untyped, # Use T.untyped until Models::Trace is fully typed
|
38
|
+
_block: T.proc.void
|
39
|
+
).void
|
40
|
+
end
|
41
|
+
def self.with_trace(trace, &_block)
|
15
42
|
old_context = current.dup
|
16
43
|
begin
|
17
|
-
|
44
|
+
# Assuming trace.id returns a String
|
45
|
+
trace_id = T.let(T.unsafe(trace).id, T.nilable(String))
|
46
|
+
Thread.current[:langfuse_context] = { trace_id: trace_id } if trace_id
|
18
47
|
yield
|
19
48
|
ensure
|
20
49
|
Thread.current[:langfuse_context] = old_context
|
21
50
|
end
|
22
51
|
end
|
23
52
|
|
24
|
-
|
53
|
+
# Executes a block with a specific span context (merging with existing context)
|
54
|
+
sig do
|
55
|
+
params(
|
56
|
+
span: T.untyped, # Use T.untyped until Models::Span is fully typed
|
57
|
+
_block: T.proc.void
|
58
|
+
).void
|
59
|
+
end
|
60
|
+
def self.with_span(span, &_block)
|
25
61
|
old_context = current.dup
|
26
62
|
begin
|
27
|
-
|
63
|
+
# Assuming span.id returns a String
|
64
|
+
span_id = T.let(T.unsafe(span).id, T.nilable(String))
|
65
|
+
# Merge span_id into the current context
|
66
|
+
new_context = current.merge({ span_id: span_id })
|
67
|
+
Thread.current[:langfuse_context] = new_context if span_id
|
28
68
|
yield
|
29
69
|
ensure
|
30
70
|
Thread.current[:langfuse_context] = old_context
|
data/lib/langfuse_helper.rb
CHANGED
@@ -1,10 +1,25 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
1
3
|
require_relative 'langfuse_context'
|
4
|
+
require 'sorbet-runtime' # Ensure Sorbet is required
|
2
5
|
|
3
6
|
module LangfuseHelper
|
7
|
+
extend T::Sig # Add this to enable sig blocks in the module
|
8
|
+
|
4
9
|
# Execute a block within the context of a span
|
5
|
-
|
10
|
+
sig do
|
11
|
+
params(
|
12
|
+
name: String,
|
13
|
+
trace_id: String,
|
14
|
+
parent_id: T.nilable(String),
|
15
|
+
input: T.untyped,
|
16
|
+
attributes: T::Hash[Symbol, T.untyped],
|
17
|
+
block: T.proc.params(span: ::Langfuse::Models::Span).returns(T.untyped)
|
18
|
+
).returns(T.untyped)
|
19
|
+
end
|
20
|
+
def with_span(name:, trace_id:, parent_id: nil, input: nil, **attributes, &block)
|
6
21
|
# Create the span
|
7
|
-
span = Langfuse.span(
|
22
|
+
span = T.unsafe(Langfuse).span(
|
8
23
|
name: name,
|
9
24
|
trace_id: trace_id,
|
10
25
|
parent_observation_id: parent_id,
|
@@ -12,13 +27,26 @@ module LangfuseHelper
|
|
12
27
|
**attributes
|
13
28
|
)
|
14
29
|
|
15
|
-
|
30
|
+
# Pass the block to the implementation
|
31
|
+
with_span_implementation(span, &block)
|
16
32
|
end
|
17
33
|
|
18
34
|
# Execute a block within the context of an LLM generation
|
19
|
-
|
35
|
+
sig do
|
36
|
+
params(
|
37
|
+
name: String,
|
38
|
+
trace_id: String,
|
39
|
+
model: String,
|
40
|
+
input: T.untyped,
|
41
|
+
parent_id: T.nilable(String),
|
42
|
+
model_parameters: T::Hash[T.untyped, T.untyped],
|
43
|
+
attributes: T::Hash[Symbol, T.untyped],
|
44
|
+
block: T.proc.params(generation: ::Langfuse::Models::Generation).returns(T.untyped)
|
45
|
+
).returns(T.untyped)
|
46
|
+
end
|
47
|
+
def with_generation(name:, trace_id:, model:, input:, parent_id: nil, model_parameters: {}, **attributes, &block)
|
20
48
|
# Create the generation
|
21
|
-
generation = Langfuse.generation(
|
49
|
+
generation = T.unsafe(Langfuse).generation(
|
22
50
|
name: name,
|
23
51
|
trace_id: trace_id,
|
24
52
|
parent_observation_id: parent_id,
|
@@ -28,50 +56,59 @@ module LangfuseHelper
|
|
28
56
|
**attributes
|
29
57
|
)
|
30
58
|
|
31
|
-
|
32
|
-
result = nil
|
33
|
-
error = nil
|
59
|
+
T.let(Time.now, Time)
|
60
|
+
result = T.let(nil, T.untyped)
|
61
|
+
error = T.let(nil, T.nilable(StandardError))
|
34
62
|
|
35
63
|
begin
|
36
64
|
# Execute the block with the generation passed as argument
|
37
|
-
result =
|
65
|
+
result = block.call(generation)
|
38
66
|
result
|
39
67
|
rescue StandardError => e
|
40
68
|
# Capture any error
|
41
69
|
error = e
|
42
|
-
raise
|
70
|
+
Kernel.raise # Use Kernel.raise
|
43
71
|
ensure
|
44
72
|
# Always update the generation with results
|
45
73
|
generation.end_time = Time.now.utc
|
46
|
-
generation.start_time = start_time.utc
|
74
|
+
# generation.start_time = start_time.utc # start_time is already UTC if using Time.now.utc
|
47
75
|
|
48
76
|
# Add output if there was a result and it wasn't already set
|
49
|
-
generation.output = result if result &&
|
77
|
+
generation.output = result if result && generation.output.nil?
|
50
78
|
|
51
79
|
# Add error information if there was an error
|
52
80
|
if error
|
53
81
|
generation.level = 'ERROR'
|
54
82
|
generation.status_message = error.message
|
55
83
|
generation.metadata ||= {}
|
56
|
-
|
84
|
+
backtrace = error.backtrace
|
85
|
+
generation.metadata[:error_backtrace] = backtrace.first(10) if backtrace # Check if backtrace is nil
|
57
86
|
end
|
58
87
|
|
59
88
|
# Update the generation
|
60
|
-
Langfuse.update_generation(generation)
|
89
|
+
T.unsafe(Langfuse).update_generation(generation)
|
61
90
|
end
|
62
91
|
end
|
63
92
|
|
64
93
|
# Execute a block within the context of a trace
|
65
|
-
|
94
|
+
sig do
|
95
|
+
params(
|
96
|
+
name: String,
|
97
|
+
user_id: T.nilable(String),
|
98
|
+
attributes: T::Hash[Symbol, T.untyped],
|
99
|
+
_block: T.proc.params(trace: ::Langfuse::Models::Trace).returns(T.untyped)
|
100
|
+
).returns(T.untyped)
|
101
|
+
end
|
102
|
+
def with_trace(name:, user_id: nil, **attributes, &_block)
|
66
103
|
# Create the trace
|
67
|
-
trace = Langfuse.trace(
|
104
|
+
trace = T.unsafe(Langfuse).trace(
|
68
105
|
name: name,
|
69
106
|
user_id: user_id,
|
70
107
|
**attributes
|
71
108
|
)
|
72
109
|
|
73
|
-
result = nil
|
74
|
-
error = nil
|
110
|
+
result = T.let(nil, T.untyped)
|
111
|
+
error = T.let(nil, T.nilable(StandardError))
|
75
112
|
|
76
113
|
begin
|
77
114
|
# Execute the block with the trace passed as argument
|
@@ -80,62 +117,91 @@ module LangfuseHelper
|
|
80
117
|
rescue StandardError => e
|
81
118
|
# Capture any error
|
82
119
|
error = e
|
83
|
-
raise
|
120
|
+
Kernel.raise # Use Kernel.raise
|
84
121
|
ensure
|
85
122
|
# Update trace output if available
|
86
|
-
if result &&
|
87
|
-
trace.output
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
123
|
+
if result && trace.output.nil?
|
124
|
+
# Assuming trace.output is writable and can be inferred or is T.untyped
|
125
|
+
T.unsafe(trace).output = result # Use T.unsafe if Trace model type isn't fully defined
|
126
|
+
|
127
|
+
# Create a new trace event to update the trace - Reuse trace object
|
128
|
+
# This seems incorrect, updating should likely use an update method or modify the object
|
129
|
+
# directly if it's mutable and the original trace object is used later.
|
130
|
+
# Re-creating a trace event just to update seems wrong. Commenting out for now.
|
131
|
+
# Langfuse.trace(
|
132
|
+
# id: trace.id,
|
133
|
+
# output: trace.output
|
134
|
+
# )
|
94
135
|
end
|
95
136
|
|
96
137
|
# Ensure all events are sent (only in case of error, otherwise let the automatic flushing handle it)
|
97
|
-
Langfuse.flush if error
|
138
|
+
T.unsafe(Langfuse).flush if error
|
98
139
|
end
|
99
140
|
end
|
100
141
|
|
101
142
|
# Create a trace and set it as the current context
|
102
|
-
|
103
|
-
|
143
|
+
sig do
|
144
|
+
params(
|
145
|
+
name: String,
|
146
|
+
user_id: T.nilable(String),
|
147
|
+
attributes: T::Hash[Symbol, T.untyped],
|
148
|
+
block: T.proc.params(trace: ::Langfuse::Models::Trace).void
|
149
|
+
).void
|
150
|
+
end
|
151
|
+
def with_context_trace(name:, user_id: nil, **attributes, &block)
|
152
|
+
trace = T.unsafe(Langfuse).trace(
|
104
153
|
name: name,
|
105
154
|
user_id: user_id,
|
106
155
|
**attributes
|
107
156
|
)
|
108
157
|
|
109
158
|
LangfuseContext.with_trace(trace) do
|
110
|
-
|
159
|
+
block.call(trace)
|
111
160
|
end
|
112
161
|
end
|
113
162
|
|
114
163
|
# Create a span using the current trace context
|
115
|
-
|
164
|
+
sig do
|
165
|
+
params(
|
166
|
+
name: String,
|
167
|
+
input: T.untyped,
|
168
|
+
attributes: T::Hash[Symbol, T.untyped],
|
169
|
+
block: T.proc.params(span: ::Langfuse::Models::Span).returns(T.untyped)
|
170
|
+
).returns(T.untyped)
|
171
|
+
end
|
172
|
+
def with_context_span(name:, input: nil, **attributes, &block)
|
116
173
|
# Get trace_id from context
|
117
174
|
trace_id = LangfuseContext.current_trace_id
|
118
175
|
parent_id = LangfuseContext.current_span_id
|
119
176
|
|
120
|
-
|
177
|
+
# Use Kernel.raise
|
178
|
+
Kernel.raise 'No trace context found. Make sure to call within with_context_trace' if trace_id.nil?
|
121
179
|
|
122
|
-
span = Langfuse.span(
|
180
|
+
span = T.unsafe(Langfuse).span(
|
123
181
|
name: name,
|
124
|
-
trace_id: trace_id,
|
182
|
+
trace_id: T.must(trace_id), # Must be present due to check above
|
125
183
|
parent_observation_id: parent_id,
|
126
184
|
input: input,
|
127
185
|
**attributes
|
128
186
|
)
|
129
187
|
|
130
188
|
LangfuseContext.with_span(span) do
|
131
|
-
#
|
132
|
-
with_span_implementation(span
|
189
|
+
# Pass the block to the implementation
|
190
|
+
with_span_implementation(span, &block)
|
133
191
|
end
|
134
192
|
end
|
135
193
|
|
136
194
|
# Add a score to a trace
|
195
|
+
sig do
|
196
|
+
params(
|
197
|
+
trace_id: String,
|
198
|
+
name: String,
|
199
|
+
value: T.any(Integer, Float), # Assuming score value is numeric
|
200
|
+
comment: T.nilable(String)
|
201
|
+
).void
|
202
|
+
end
|
137
203
|
def score_trace(trace_id:, name:, value:, comment: nil)
|
138
|
-
Langfuse.score(
|
204
|
+
T.unsafe(Langfuse).score(
|
139
205
|
trace_id: trace_id,
|
140
206
|
name: name,
|
141
207
|
value: value,
|
@@ -145,32 +211,42 @@ module LangfuseHelper
|
|
145
211
|
|
146
212
|
private
|
147
213
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
214
|
+
# Type the private helper method
|
215
|
+
sig do
|
216
|
+
params(
|
217
|
+
span: ::Langfuse::Models::Span,
|
218
|
+
block: T.proc.params(span: ::Langfuse::Models::Span).returns(T.untyped)
|
219
|
+
).returns(T.untyped)
|
220
|
+
end
|
221
|
+
def with_span_implementation(span, &block)
|
222
|
+
T.let(Time.now, Time) # Use start_time
|
223
|
+
result = T.let(nil, T.untyped)
|
224
|
+
error = T.let(nil, T.nilable(StandardError))
|
152
225
|
|
153
226
|
begin
|
154
227
|
# Execute the block with the span passed as argument
|
155
|
-
result =
|
228
|
+
result = block.call(span) # Pass span to block
|
156
229
|
result
|
157
230
|
rescue StandardError => e
|
158
231
|
# Capture any error
|
159
232
|
error = e
|
160
|
-
raise
|
233
|
+
Kernel.raise # Use Kernel.raise
|
161
234
|
ensure
|
162
235
|
# Update span
|
163
236
|
span.end_time = Time.now.utc
|
164
|
-
span.
|
237
|
+
# span.start_time = start_time.utc # Add start time if needed by update_span
|
238
|
+
|
239
|
+
span.output = result if result && span.output.nil?
|
165
240
|
|
166
241
|
if error
|
167
242
|
span.level = 'ERROR'
|
168
243
|
span.status_message = error.message
|
169
244
|
span.metadata ||= {}
|
170
|
-
|
245
|
+
backtrace = error.backtrace
|
246
|
+
span.metadata[:error_backtrace] = backtrace.first(10) if backtrace # Check if backtrace is nil
|
171
247
|
end
|
172
248
|
|
173
|
-
Langfuse.update_span(span)
|
249
|
+
T.unsafe(Langfuse).update_span(span)
|
174
250
|
end
|
175
251
|
end
|
176
252
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: langfuse
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Langfuse
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-04-17 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: concurrent-ruby
|
@@ -23,6 +23,20 @@ dependencies:
|
|
23
23
|
- - "~>"
|
24
24
|
- !ruby/object:Gem::Version
|
25
25
|
version: '1.2'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: sorbet-runtime
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0.5'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0.5'
|
26
40
|
- !ruby/object:Gem::Dependency
|
27
41
|
name: bundler
|
28
42
|
requirement: !ruby/object:Gem::Requirement
|
@@ -149,6 +163,34 @@ dependencies:
|
|
149
163
|
- - "~>"
|
150
164
|
- !ruby/object:Gem::Version
|
151
165
|
version: '0.22'
|
166
|
+
- !ruby/object:Gem::Dependency
|
167
|
+
name: sorbet
|
168
|
+
requirement: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - "~>"
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '0.5'
|
173
|
+
type: :development
|
174
|
+
prerelease: false
|
175
|
+
version_requirements: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - "~>"
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '0.5'
|
180
|
+
- !ruby/object:Gem::Dependency
|
181
|
+
name: tapioca
|
182
|
+
requirement: !ruby/object:Gem::Requirement
|
183
|
+
requirements:
|
184
|
+
- - "~>"
|
185
|
+
- !ruby/object:Gem::Version
|
186
|
+
version: '0.11'
|
187
|
+
type: :development
|
188
|
+
prerelease: false
|
189
|
+
version_requirements: !ruby/object:Gem::Requirement
|
190
|
+
requirements:
|
191
|
+
- - "~>"
|
192
|
+
- !ruby/object:Gem::Version
|
193
|
+
version: '0.11'
|
152
194
|
- !ruby/object:Gem::Dependency
|
153
195
|
name: timecop
|
154
196
|
requirement: !ruby/object:Gem::Requirement
|
@@ -192,9 +234,9 @@ dependencies:
|
|
192
234
|
- !ruby/object:Gem::Version
|
193
235
|
version: '3.18'
|
194
236
|
description: Langfuse is an open source observability platform for LLM applications.
|
195
|
-
This is the Ruby client for Langfuse's API.
|
237
|
+
This is the Ruby client for Langfuse's API. Rough first alpha
|
196
238
|
email:
|
197
|
-
-
|
239
|
+
- manuel@mento.co
|
198
240
|
executables: []
|
199
241
|
extensions: []
|
200
242
|
extra_rdoc_files: []
|
@@ -203,6 +245,7 @@ files:
|
|
203
245
|
- README.md
|
204
246
|
- bin/console
|
205
247
|
- bin/setup
|
248
|
+
- bin/tapioca
|
206
249
|
- lib/langfuse.rb
|
207
250
|
- lib/langfuse/api_client.rb
|
208
251
|
- lib/langfuse/batch_worker.rb
|
@@ -215,7 +258,6 @@ files:
|
|
215
258
|
- lib/langfuse/models/span.rb
|
216
259
|
- lib/langfuse/models/trace.rb
|
217
260
|
- lib/langfuse/models/usage.rb
|
218
|
-
- lib/langfuse/rails.rb
|
219
261
|
- lib/langfuse/version.rb
|
220
262
|
- lib/langfuse_context.rb
|
221
263
|
- lib/langfuse_helper.rb
|
data/lib/langfuse/rails.rb
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
require 'active_support/notifications'
|
2
|
-
|
3
|
-
module Langfuse
|
4
|
-
module Rails
|
5
|
-
class << self
|
6
|
-
def setup_notifications
|
7
|
-
ActiveSupport::Notifications.subscribe(/langfuse/) do |name, start, finish, _id, payload|
|
8
|
-
case name
|
9
|
-
when 'langfuse.trace'
|
10
|
-
Langfuse.trace(payload)
|
11
|
-
when 'langfuse.span'
|
12
|
-
# Set end_time based on notification timing if not provided
|
13
|
-
payload[:end_time] ||= finish
|
14
|
-
payload[:start_time] ||= start
|
15
|
-
Langfuse.span(payload)
|
16
|
-
when 'langfuse.generation'
|
17
|
-
# Set end_time based on notification timing if not provided
|
18
|
-
payload[:end_time] ||= finish
|
19
|
-
payload[:start_time] ||= start
|
20
|
-
Langfuse.generation(payload)
|
21
|
-
when 'langfuse.score'
|
22
|
-
Langfuse.score(payload)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
# Set up notifications if we're in a Rails environment
|
31
|
-
Langfuse::Rails.setup_notifications if defined?(::Rails)
|