langfuse-ruby 0.1.7 → 0.2.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/.github/workflows/ci.yml +21 -10
- data/.github/workflows/release.yml +2 -2
- data/.mise.toml +8 -0
- data/.rubocop.yml +2 -2
- data/CHANGELOG.md +78 -10
- data/CLAUDE.md +54 -16
- data/Gemfile +1 -0
- data/Gemfile.lock +15 -5
- data/Makefile +6 -5
- data/README.md +338 -89
- data/Rakefile +0 -6
- data/docs/FINAL_SUMMARY.md +12 -185
- data/docs/PUBLISH_GUIDE.md +36 -272
- data/docs/README.md +12 -22
- data/docs/RELEASE_CHECKLIST.md +20 -145
- data/docs/V4.md +159 -0
- data/examples/basic_tracing.rb +11 -10
- data/examples/simplified_usage.rb +7 -6
- data/examples/v4_otel_tracing.rb +70 -0
- data/langfuse-ruby.gemspec +2 -0
- data/lib/langfuse/client.rb +757 -276
- data/lib/langfuse/event.rb +1 -17
- data/lib/langfuse/generation.rb +71 -124
- data/lib/langfuse/null_objects.rb +4 -0
- data/lib/langfuse/otel_exporter.rb +145 -63
- data/lib/langfuse/partial_updates.rb +30 -0
- data/lib/langfuse/prompt.rb +9 -83
- data/lib/langfuse/prompt_cache.rb +65 -0
- data/lib/langfuse/span.rb +29 -153
- data/lib/langfuse/span_wrappers.rb +32 -0
- data/lib/langfuse/template_compiler.rb +56 -0
- data/lib/langfuse/trace.rb +38 -164
- data/lib/langfuse/utils.rb +54 -20
- data/lib/langfuse/version.rb +1 -1
- data/lib/langfuse.rb +61 -56
- data/scripts/release.sh +12 -12
- metadata +37 -2
data/lib/langfuse/trace.rb
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
module Langfuse
|
|
4
4
|
class Trace
|
|
5
|
+
include PartialUpdates
|
|
6
|
+
|
|
5
7
|
attr_reader :id, :name, :user_id, :session_id, :version, :release, :input, :output,
|
|
6
|
-
:metadata, :tags, :timestamp, :client
|
|
8
|
+
:metadata, :tags, :timestamp, :public, :client
|
|
7
9
|
|
|
8
10
|
def initialize(client:, id:, name: nil, user_id: nil, session_id: nil, version: nil,
|
|
9
11
|
release: nil, input: nil, output: nil, metadata: nil, tags: nil,
|
|
10
|
-
timestamp: nil, **kwargs)
|
|
12
|
+
timestamp: nil, public: nil, **kwargs)
|
|
11
13
|
@client = client
|
|
12
14
|
@id = id
|
|
13
15
|
@name = name
|
|
@@ -20,6 +22,7 @@ module Langfuse
|
|
|
20
22
|
@metadata = metadata || {}
|
|
21
23
|
@tags = tags || []
|
|
22
24
|
@timestamp = timestamp
|
|
25
|
+
@public = public
|
|
23
26
|
@kwargs = kwargs
|
|
24
27
|
|
|
25
28
|
# Create the trace
|
|
@@ -50,6 +53,7 @@ module Langfuse
|
|
|
50
53
|
# Create a child generation
|
|
51
54
|
def generation(name: nil, start_time: nil, end_time: nil, completion_start_time: nil,
|
|
52
55
|
model: nil, model_parameters: nil, input: nil, output: nil, usage: nil,
|
|
56
|
+
usage_details: nil, cost_details: nil, prompt: nil,
|
|
53
57
|
metadata: nil, level: nil, status_message: nil, parent_observation_id: nil,
|
|
54
58
|
version: nil, **kwargs)
|
|
55
59
|
@client.generation(
|
|
@@ -63,6 +67,9 @@ module Langfuse
|
|
|
63
67
|
input: input,
|
|
64
68
|
output: output,
|
|
65
69
|
usage: usage,
|
|
70
|
+
usage_details: usage_details,
|
|
71
|
+
cost_details: cost_details,
|
|
72
|
+
prompt: prompt,
|
|
66
73
|
metadata: metadata,
|
|
67
74
|
level: level,
|
|
68
75
|
status_message: status_message,
|
|
@@ -90,87 +97,11 @@ module Langfuse
|
|
|
90
97
|
)
|
|
91
98
|
end
|
|
92
99
|
|
|
93
|
-
# Convenience methods for enhanced observation types
|
|
94
|
-
|
|
95
|
-
#
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
version: nil, **kwargs)
|
|
99
|
-
span(
|
|
100
|
-
name: name,
|
|
101
|
-
start_time: start_time,
|
|
102
|
-
end_time: end_time,
|
|
103
|
-
input: input,
|
|
104
|
-
output: output,
|
|
105
|
-
metadata: metadata,
|
|
106
|
-
level: level,
|
|
107
|
-
status_message: status_message,
|
|
108
|
-
parent_observation_id: parent_observation_id,
|
|
109
|
-
version: version,
|
|
110
|
-
as_type: ObservationType::AGENT,
|
|
111
|
-
**kwargs
|
|
112
|
-
)
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
# Create a child tool observation
|
|
116
|
-
def tool(name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
|
|
117
|
-
metadata: nil, level: nil, status_message: nil, parent_observation_id: nil,
|
|
118
|
-
version: nil, **kwargs)
|
|
119
|
-
span(
|
|
120
|
-
name: name,
|
|
121
|
-
start_time: start_time,
|
|
122
|
-
end_time: end_time,
|
|
123
|
-
input: input,
|
|
124
|
-
output: output,
|
|
125
|
-
metadata: metadata,
|
|
126
|
-
level: level,
|
|
127
|
-
status_message: status_message,
|
|
128
|
-
parent_observation_id: parent_observation_id,
|
|
129
|
-
version: version,
|
|
130
|
-
as_type: ObservationType::TOOL,
|
|
131
|
-
**kwargs
|
|
132
|
-
)
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
# Create a child chain observation
|
|
136
|
-
def chain(name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
|
|
137
|
-
metadata: nil, level: nil, status_message: nil, parent_observation_id: nil,
|
|
138
|
-
version: nil, **kwargs)
|
|
139
|
-
span(
|
|
140
|
-
name: name,
|
|
141
|
-
start_time: start_time,
|
|
142
|
-
end_time: end_time,
|
|
143
|
-
input: input,
|
|
144
|
-
output: output,
|
|
145
|
-
metadata: metadata,
|
|
146
|
-
level: level,
|
|
147
|
-
status_message: status_message,
|
|
148
|
-
parent_observation_id: parent_observation_id,
|
|
149
|
-
version: version,
|
|
150
|
-
as_type: ObservationType::CHAIN,
|
|
151
|
-
**kwargs
|
|
152
|
-
)
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
# Create a child retriever observation
|
|
156
|
-
def retriever(name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
|
|
157
|
-
metadata: nil, level: nil, status_message: nil, parent_observation_id: nil,
|
|
158
|
-
version: nil, **kwargs)
|
|
159
|
-
span(
|
|
160
|
-
name: name,
|
|
161
|
-
start_time: start_time,
|
|
162
|
-
end_time: end_time,
|
|
163
|
-
input: input,
|
|
164
|
-
output: output,
|
|
165
|
-
metadata: metadata,
|
|
166
|
-
level: level,
|
|
167
|
-
status_message: status_message,
|
|
168
|
-
parent_observation_id: parent_observation_id,
|
|
169
|
-
version: version,
|
|
170
|
-
as_type: ObservationType::RETRIEVER,
|
|
171
|
-
**kwargs
|
|
172
|
-
)
|
|
173
|
-
end
|
|
100
|
+
# Convenience methods for enhanced observation types: each is a child span
|
|
101
|
+
# with a fixed as_type. (embedding keeps its own definition because it folds
|
|
102
|
+
# model/usage into metadata first.)
|
|
103
|
+
extend SpanWrappers
|
|
104
|
+
define_span_wrappers
|
|
174
105
|
|
|
175
106
|
# Create a child embedding observation
|
|
176
107
|
def embedding(name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
|
|
@@ -195,46 +126,6 @@ module Langfuse
|
|
|
195
126
|
)
|
|
196
127
|
end
|
|
197
128
|
|
|
198
|
-
# Create a child evaluator observation
|
|
199
|
-
def evaluator(name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
|
|
200
|
-
metadata: nil, level: nil, status_message: nil, parent_observation_id: nil,
|
|
201
|
-
version: nil, **kwargs)
|
|
202
|
-
span(
|
|
203
|
-
name: name,
|
|
204
|
-
start_time: start_time,
|
|
205
|
-
end_time: end_time,
|
|
206
|
-
input: input,
|
|
207
|
-
output: output,
|
|
208
|
-
metadata: metadata,
|
|
209
|
-
level: level,
|
|
210
|
-
status_message: status_message,
|
|
211
|
-
parent_observation_id: parent_observation_id,
|
|
212
|
-
version: version,
|
|
213
|
-
as_type: ObservationType::EVALUATOR,
|
|
214
|
-
**kwargs
|
|
215
|
-
)
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
# Create a child guardrail observation
|
|
219
|
-
def guardrail(name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
|
|
220
|
-
metadata: nil, level: nil, status_message: nil, parent_observation_id: nil,
|
|
221
|
-
version: nil, **kwargs)
|
|
222
|
-
span(
|
|
223
|
-
name: name,
|
|
224
|
-
start_time: start_time,
|
|
225
|
-
end_time: end_time,
|
|
226
|
-
input: input,
|
|
227
|
-
output: output,
|
|
228
|
-
metadata: metadata,
|
|
229
|
-
level: level,
|
|
230
|
-
status_message: status_message,
|
|
231
|
-
parent_observation_id: parent_observation_id,
|
|
232
|
-
version: version,
|
|
233
|
-
as_type: ObservationType::GUARDRAIL,
|
|
234
|
-
**kwargs
|
|
235
|
-
)
|
|
236
|
-
end
|
|
237
|
-
|
|
238
129
|
def score(name:, value:, data_type: nil, comment: nil, **kwargs)
|
|
239
130
|
@client.score(
|
|
240
131
|
trace_id: @id,
|
|
@@ -247,20 +138,30 @@ module Langfuse
|
|
|
247
138
|
end
|
|
248
139
|
|
|
249
140
|
def update(name: nil, user_id: nil, session_id: nil, version: nil,
|
|
250
|
-
release: nil, input: nil, output: nil, metadata: nil, tags: nil,
|
|
141
|
+
release: nil, input: nil, output: nil, metadata: nil, tags: nil,
|
|
142
|
+
public: nil, **kwargs)
|
|
251
143
|
# 更新实例变量
|
|
252
|
-
@name = name
|
|
253
|
-
@user_id = user_id
|
|
254
|
-
@session_id = session_id
|
|
255
|
-
@version = version
|
|
256
|
-
@release = release
|
|
257
|
-
@input = input
|
|
258
|
-
@output = output
|
|
259
|
-
@metadata
|
|
260
|
-
@tags = tags
|
|
144
|
+
@name = name unless name.nil?
|
|
145
|
+
@user_id = user_id unless user_id.nil?
|
|
146
|
+
@session_id = session_id unless session_id.nil?
|
|
147
|
+
@version = version unless version.nil?
|
|
148
|
+
@release = release unless release.nil?
|
|
149
|
+
@input = input unless input.nil?
|
|
150
|
+
@output = output unless output.nil?
|
|
151
|
+
@metadata.merge!(metadata) if metadata
|
|
152
|
+
@tags = tags unless tags.nil?
|
|
153
|
+
@public = public unless public.nil?
|
|
261
154
|
@kwargs.merge!(kwargs) if kwargs.any?
|
|
155
|
+
|
|
156
|
+
track_changes(
|
|
157
|
+
{ name: name, user_id: user_id, session_id: session_id, version: version,
|
|
158
|
+
release: release, input: input, output: output, metadata: metadata,
|
|
159
|
+
tags: tags, public: public },
|
|
160
|
+
kwargs.keys
|
|
161
|
+
)
|
|
262
162
|
# 触发 trace-update 事件
|
|
263
163
|
update_trace
|
|
164
|
+
self
|
|
264
165
|
end
|
|
265
166
|
|
|
266
167
|
def get_url
|
|
@@ -279,46 +180,19 @@ module Langfuse
|
|
|
279
180
|
output: @output,
|
|
280
181
|
metadata: @metadata,
|
|
281
182
|
tags: @tags,
|
|
282
|
-
timestamp: @timestamp
|
|
183
|
+
timestamp: @timestamp,
|
|
184
|
+
public: @public
|
|
283
185
|
}.merge(@kwargs).compact
|
|
284
186
|
end
|
|
285
187
|
|
|
286
188
|
private
|
|
287
189
|
|
|
288
190
|
def create_trace
|
|
289
|
-
|
|
290
|
-
id: @id,
|
|
291
|
-
name: @name,
|
|
292
|
-
user_id: @user_id,
|
|
293
|
-
session_id: @session_id,
|
|
294
|
-
version: @version,
|
|
295
|
-
release: @release,
|
|
296
|
-
input: @input,
|
|
297
|
-
output: @output,
|
|
298
|
-
metadata: @metadata,
|
|
299
|
-
tags: @tags,
|
|
300
|
-
timestamp: @timestamp
|
|
301
|
-
}.merge(@kwargs).compact
|
|
302
|
-
|
|
303
|
-
@client.enqueue_event('trace-create', data)
|
|
191
|
+
@client.enqueue_event('trace-create', to_dict)
|
|
304
192
|
end
|
|
305
193
|
|
|
306
194
|
def update_trace
|
|
307
|
-
|
|
308
|
-
id: @id,
|
|
309
|
-
name: @name,
|
|
310
|
-
user_id: @user_id,
|
|
311
|
-
session_id: @session_id,
|
|
312
|
-
version: @version,
|
|
313
|
-
release: @release,
|
|
314
|
-
input: @input,
|
|
315
|
-
output: @output,
|
|
316
|
-
metadata: @metadata,
|
|
317
|
-
tags: @tags,
|
|
318
|
-
timestamp: @timestamp
|
|
319
|
-
}.merge(@kwargs).compact
|
|
320
|
-
|
|
321
|
-
@client.enqueue_event('trace-update', data)
|
|
195
|
+
@client.enqueue_event('trace-update', update_body, trace_ref: self)
|
|
322
196
|
end
|
|
323
197
|
end
|
|
324
198
|
end
|
data/lib/langfuse/utils.rb
CHANGED
|
@@ -6,11 +6,25 @@ require 'erb'
|
|
|
6
6
|
|
|
7
7
|
module Langfuse
|
|
8
8
|
module Utils
|
|
9
|
+
# Body keys whose values must be passed through verbatim (user data),
|
|
10
|
+
# everything else nested under them keeps its original key format.
|
|
11
|
+
VERBATIM_BODY_KEYS = %w[input output metadata usageDetails costDetails modelParameters].freeze
|
|
12
|
+
|
|
9
13
|
class << self
|
|
10
14
|
def generate_id
|
|
11
15
|
SecureRandom.uuid
|
|
12
16
|
end
|
|
13
17
|
|
|
18
|
+
# W3C-compatible 32-char hex trace ID (16 bytes), used for OTel ingestion
|
|
19
|
+
def generate_hex_trace_id
|
|
20
|
+
SecureRandom.hex(16)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# W3C-compatible 16-char hex span/observation ID (8 bytes), used for OTel ingestion
|
|
24
|
+
def generate_hex_span_id
|
|
25
|
+
SecureRandom.hex(8)
|
|
26
|
+
end
|
|
27
|
+
|
|
14
28
|
def current_timestamp
|
|
15
29
|
Time.now.utc.iso8601(3)
|
|
16
30
|
end
|
|
@@ -19,34 +33,54 @@ module Langfuse
|
|
|
19
33
|
ERB::Util.url_encode(string.to_s)
|
|
20
34
|
end
|
|
21
35
|
|
|
22
|
-
def deep_symbolize_keys(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
36
|
+
def deep_symbolize_keys(object)
|
|
37
|
+
case object
|
|
38
|
+
when Hash
|
|
39
|
+
object.each_with_object({}) do |(key, value), result|
|
|
40
|
+
new_key = key.is_a?(String) ? key.to_sym : key
|
|
41
|
+
result[new_key] = deep_symbolize_keys(value)
|
|
42
|
+
end
|
|
43
|
+
when Array
|
|
44
|
+
object.map { |item| deep_symbolize_keys(item) }
|
|
45
|
+
else
|
|
46
|
+
object
|
|
29
47
|
end
|
|
30
48
|
end
|
|
31
49
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
50
|
+
# 将哈希的键名转换为小驼峰格式
|
|
51
|
+
def deep_camelize_keys(object)
|
|
52
|
+
case object
|
|
53
|
+
when Hash
|
|
54
|
+
object.each_with_object({}) do |(key, value), result|
|
|
55
|
+
new_key = camelize_key(key.to_s)
|
|
56
|
+
result[new_key] = deep_camelize_keys(value)
|
|
57
|
+
end
|
|
58
|
+
when Array
|
|
59
|
+
object.map { |item| deep_camelize_keys(item) }
|
|
60
|
+
else
|
|
61
|
+
object
|
|
39
62
|
end
|
|
40
63
|
end
|
|
41
64
|
|
|
42
|
-
#
|
|
43
|
-
|
|
44
|
-
|
|
65
|
+
# Kept for backwards compatibility: it has always camelized keys rather
|
|
66
|
+
# than only stringifying them.
|
|
67
|
+
alias deep_stringify_keys deep_camelize_keys
|
|
68
|
+
|
|
69
|
+
# Prepare an event body for the ingestion API:
|
|
70
|
+
# - top-level keys are camelized (snake_case -> camelCase)
|
|
71
|
+
# - user data values (input/output/metadata/usageDetails/costDetails/modelParameters)
|
|
72
|
+
# are passed through verbatim so user-provided keys are not mangled
|
|
73
|
+
# - the legacy `usage` object keys are camelized (prompt_tokens -> promptTokens)
|
|
74
|
+
def prepare_event_body(body)
|
|
75
|
+
return body unless body.is_a?(Hash)
|
|
45
76
|
|
|
46
|
-
|
|
77
|
+
body.each_with_object({}) do |(key, value), result|
|
|
47
78
|
new_key = camelize_key(key.to_s)
|
|
48
|
-
|
|
49
|
-
|
|
79
|
+
result[new_key] = if !VERBATIM_BODY_KEYS.include?(new_key) && value.is_a?(Hash)
|
|
80
|
+
deep_camelize_keys(value)
|
|
81
|
+
else
|
|
82
|
+
value
|
|
83
|
+
end
|
|
50
84
|
end
|
|
51
85
|
end
|
|
52
86
|
|
data/lib/langfuse/version.rb
CHANGED
data/lib/langfuse.rb
CHANGED
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'langfuse/version'
|
|
4
4
|
require_relative 'langfuse/observation_types'
|
|
5
|
+
require_relative 'langfuse/span_wrappers'
|
|
6
|
+
require_relative 'langfuse/partial_updates'
|
|
7
|
+
require_relative 'langfuse/template_compiler'
|
|
5
8
|
require_relative 'langfuse/client'
|
|
6
9
|
require_relative 'langfuse/trace'
|
|
7
10
|
require_relative 'langfuse/span'
|
|
8
11
|
require_relative 'langfuse/generation'
|
|
9
12
|
require_relative 'langfuse/event'
|
|
10
13
|
require_relative 'langfuse/prompt'
|
|
14
|
+
require_relative 'langfuse/prompt_cache'
|
|
11
15
|
require_relative 'langfuse/evaluation'
|
|
12
16
|
require_relative 'langfuse/errors'
|
|
13
17
|
require_relative 'langfuse/utils'
|
|
@@ -16,6 +20,8 @@ require_relative 'langfuse/otel_exporter'
|
|
|
16
20
|
|
|
17
21
|
# Ruby SDK for Langfuse - Open source LLM engineering platform
|
|
18
22
|
module Langfuse
|
|
23
|
+
CLIENT_MUTEX = Mutex.new
|
|
24
|
+
|
|
19
25
|
class << self
|
|
20
26
|
# Configure the Langfuse client with default settings
|
|
21
27
|
def configure
|
|
@@ -31,10 +37,10 @@ module Langfuse
|
|
|
31
37
|
Client.new(**kwargs)
|
|
32
38
|
end
|
|
33
39
|
|
|
34
|
-
# Get a thread-safe singleton client instance
|
|
40
|
+
# Get a process-wide, thread-safe singleton client instance
|
|
35
41
|
# @return [Client] Langfuse client
|
|
36
42
|
def client
|
|
37
|
-
|
|
43
|
+
@client || CLIENT_MUTEX.synchronize { @client ||= Client.new }
|
|
38
44
|
end
|
|
39
45
|
|
|
40
46
|
# Get a prompt and optionally compile it with variables
|
|
@@ -45,29 +51,23 @@ module Langfuse
|
|
|
45
51
|
# @param cache_ttl_seconds [Integer] cache TTL in seconds (default: 60)
|
|
46
52
|
# @param retries [Integer] number of retries on failure (default: 2)
|
|
47
53
|
# @return [String, Prompt, nil] compiled prompt string if variables provided, Prompt object otherwise, nil on failure
|
|
54
|
+
# `retries` is applied inside the HTTP layer, which retries only transient
|
|
55
|
+
# failures (timeout, network, 429, 5xx), honors Retry-After and backs off with
|
|
56
|
+
# jitter. A missing prompt therefore fails immediately instead of being
|
|
57
|
+
# requested three times.
|
|
48
58
|
def get_prompt(prompt_name, variables: nil, label: nil, version: nil, cache_ttl_seconds: 60, retries: 2)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
prompt.compile(variables)
|
|
57
|
-
else
|
|
58
|
-
prompt
|
|
59
|
-
end
|
|
60
|
-
rescue StandardError => e
|
|
61
|
-
if attempts <= retries
|
|
62
|
-
sleep_time = 2**(attempts - 1) * 0.1 # Exponential backoff: 0.1s, 0.2s, 0.4s...
|
|
63
|
-
warn "Langfuse prompt fetch failed (#{prompt_name}), retrying in #{sleep_time}s... (attempt #{attempts}/#{retries + 1})" if configuration.debug
|
|
64
|
-
sleep(sleep_time)
|
|
65
|
-
retry
|
|
66
|
-
end
|
|
59
|
+
prompt = client.get_prompt(
|
|
60
|
+
prompt_name,
|
|
61
|
+
label: label,
|
|
62
|
+
version: version,
|
|
63
|
+
cache_ttl_seconds: cache_ttl_seconds,
|
|
64
|
+
retries: retries
|
|
65
|
+
)
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
variables ? prompt.compile(variables) : prompt
|
|
68
|
+
rescue StandardError => e
|
|
69
|
+
warn "Langfuse prompt fetch failed (#{prompt_name}): #{e.message}" if configuration.debug
|
|
70
|
+
nil
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
# Create a trace and optionally execute a block with it
|
|
@@ -100,39 +100,35 @@ module Langfuse
|
|
|
100
100
|
# Langfuse.flush
|
|
101
101
|
#
|
|
102
102
|
def trace(name = nil, user_id: nil, session_id: nil, input: nil, output: nil,
|
|
103
|
-
metadata: nil, tags: nil, version: nil, release: nil, **kwargs
|
|
104
|
-
trace
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
input: input,
|
|
109
|
-
output: output,
|
|
110
|
-
metadata: metadata,
|
|
111
|
-
tags: tags,
|
|
112
|
-
version: version,
|
|
113
|
-
release: release,
|
|
114
|
-
**kwargs
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
if block_given?
|
|
103
|
+
metadata: nil, tags: nil, version: nil, release: nil, **kwargs)
|
|
104
|
+
# Only trace creation degrades to a NullTrace. An exception raised by the
|
|
105
|
+
# block must propagate: rescuing it here and yielding again would run the
|
|
106
|
+
# caller's block a second time (duplicate LLM calls and billing).
|
|
107
|
+
trace =
|
|
118
108
|
begin
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
109
|
+
client.trace(
|
|
110
|
+
name: name,
|
|
111
|
+
user_id: user_id,
|
|
112
|
+
session_id: session_id,
|
|
113
|
+
input: input,
|
|
114
|
+
output: output,
|
|
115
|
+
metadata: metadata,
|
|
116
|
+
tags: tags,
|
|
117
|
+
version: version,
|
|
118
|
+
release: release,
|
|
119
|
+
**kwargs
|
|
120
|
+
)
|
|
121
|
+
rescue StandardError => e
|
|
122
|
+
warn "Langfuse trace creation failed: #{e.message}" if configuration.debug
|
|
123
|
+
NullTrace.new
|
|
123
124
|
end
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if block_given?
|
|
132
|
-
null_trace = NullTrace.new
|
|
133
|
-
yield(null_trace)
|
|
134
|
-
else
|
|
135
|
-
NullTrace.new
|
|
125
|
+
|
|
126
|
+
return trace unless block_given?
|
|
127
|
+
|
|
128
|
+
begin
|
|
129
|
+
yield(trace)
|
|
130
|
+
ensure
|
|
131
|
+
flush
|
|
136
132
|
end
|
|
137
133
|
end
|
|
138
134
|
|
|
@@ -152,14 +148,15 @@ module Langfuse
|
|
|
152
148
|
|
|
153
149
|
# Reset the singleton client (mainly for testing)
|
|
154
150
|
def reset!
|
|
155
|
-
|
|
151
|
+
CLIENT_MUTEX.synchronize { @client = nil }
|
|
156
152
|
end
|
|
157
153
|
end
|
|
158
154
|
|
|
159
155
|
# Configuration class for Langfuse client settings
|
|
160
156
|
class Configuration
|
|
161
157
|
attr_accessor :public_key, :secret_key, :host, :debug, :timeout, :retries, :flush_interval, :auto_flush,
|
|
162
|
-
:ingestion_mode
|
|
158
|
+
:ingestion_mode, :environment, :sample_rate, :mask, :flush_at, :max_queue_size, :logger,
|
|
159
|
+
:shutdown_on_exit, :http_adapter
|
|
163
160
|
|
|
164
161
|
def initialize
|
|
165
162
|
@public_key = nil
|
|
@@ -171,6 +168,14 @@ module Langfuse
|
|
|
171
168
|
@flush_interval = 5
|
|
172
169
|
@auto_flush = true
|
|
173
170
|
@ingestion_mode = :legacy # :legacy or :otel
|
|
171
|
+
@environment = nil # default tracing environment for all events
|
|
172
|
+
@sample_rate = nil # 0.0..1.0, nil disables sampling
|
|
173
|
+
@mask = nil # callable applied to input/output/metadata before sending
|
|
174
|
+
@flush_at = 15 # flush as soon as this many events are queued
|
|
175
|
+
@max_queue_size = 10_000 # queue cap; the oldest events are dropped when full
|
|
176
|
+
@logger = nil # custom Logger instance
|
|
177
|
+
@shutdown_on_exit = true # register an at_exit hook that flushes pending events
|
|
178
|
+
@http_adapter = nil # Faraday adapter, e.g. :net_http_persistent for keep-alive
|
|
174
179
|
end
|
|
175
180
|
end
|
|
176
181
|
end
|
data/scripts/release.sh
CHANGED
|
@@ -49,31 +49,31 @@ if ! bundle exec rspec; then
|
|
|
49
49
|
fi
|
|
50
50
|
|
|
51
51
|
print_status "Running offline tests..."
|
|
52
|
-
if ! ruby scripts/test_offline.rb; then
|
|
52
|
+
if ! bundle exec ruby scripts/test_offline.rb; then
|
|
53
53
|
print_error "Offline tests failed. Please fix them before releasing."
|
|
54
54
|
exit 1
|
|
55
55
|
fi
|
|
56
56
|
|
|
57
57
|
# Build gem
|
|
58
58
|
print_status "Building gem..."
|
|
59
|
-
if ! gem build langfuse.gemspec; then
|
|
59
|
+
if ! gem build langfuse-ruby.gemspec; then
|
|
60
60
|
print_error "Gem build failed."
|
|
61
61
|
exit 1
|
|
62
62
|
fi
|
|
63
63
|
|
|
64
64
|
# Check if gem was built successfully
|
|
65
|
-
if [ ! -f "langfuse-${CURRENT_VERSION}.gem" ]; then
|
|
66
|
-
print_error "Gem file not found: langfuse-${CURRENT_VERSION}.gem"
|
|
65
|
+
if [ ! -f "langfuse-ruby-${CURRENT_VERSION}.gem" ]; then
|
|
66
|
+
print_error "Gem file not found: langfuse-ruby-${CURRENT_VERSION}.gem"
|
|
67
67
|
exit 1
|
|
68
68
|
fi
|
|
69
69
|
|
|
70
|
-
print_status "Gem built successfully: langfuse-${CURRENT_VERSION}.gem"
|
|
70
|
+
print_status "Gem built successfully: langfuse-ruby-${CURRENT_VERSION}.gem"
|
|
71
71
|
|
|
72
72
|
# Ask for confirmation
|
|
73
73
|
echo
|
|
74
74
|
echo "📋 Release Summary:"
|
|
75
75
|
echo " Version: $CURRENT_VERSION"
|
|
76
|
-
echo " Gem file: langfuse-${CURRENT_VERSION}.gem"
|
|
76
|
+
echo " Gem file: langfuse-ruby-${CURRENT_VERSION}.gem"
|
|
77
77
|
echo " Tests: ✅ Passed"
|
|
78
78
|
echo
|
|
79
79
|
|
|
@@ -81,7 +81,7 @@ read -p "Do you want to proceed with the release? (y/N): " -n 1 -r
|
|
|
81
81
|
echo
|
|
82
82
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
83
83
|
print_warning "Release cancelled."
|
|
84
|
-
rm -f "langfuse-${CURRENT_VERSION}.gem"
|
|
84
|
+
rm -f "langfuse-ruby-${CURRENT_VERSION}.gem"
|
|
85
85
|
exit 0
|
|
86
86
|
fi
|
|
87
87
|
|
|
@@ -91,12 +91,12 @@ git tag "v${CURRENT_VERSION}"
|
|
|
91
91
|
|
|
92
92
|
# Push to git
|
|
93
93
|
print_status "Pushing to git..."
|
|
94
|
-
git push origin
|
|
94
|
+
git push origin master
|
|
95
95
|
git push origin "v${CURRENT_VERSION}"
|
|
96
96
|
|
|
97
97
|
# Publish to RubyGems
|
|
98
98
|
print_status "Publishing to RubyGems..."
|
|
99
|
-
if gem push "langfuse-${CURRENT_VERSION}.gem"; then
|
|
99
|
+
if gem push "langfuse-ruby-${CURRENT_VERSION}.gem"; then
|
|
100
100
|
print_status "Successfully published to RubyGems!"
|
|
101
101
|
else
|
|
102
102
|
print_error "Failed to publish to RubyGems."
|
|
@@ -105,16 +105,16 @@ else
|
|
|
105
105
|
fi
|
|
106
106
|
|
|
107
107
|
# Clean up
|
|
108
|
-
rm -f "langfuse-${CURRENT_VERSION}.gem"
|
|
108
|
+
rm -f "langfuse-ruby-${CURRENT_VERSION}.gem"
|
|
109
109
|
|
|
110
110
|
print_status "Release completed successfully! 🎉"
|
|
111
111
|
echo
|
|
112
112
|
echo "📝 Next steps:"
|
|
113
|
-
echo " 1. Check https://rubygems.org/gems/langfuse"
|
|
113
|
+
echo " 1. Check https://rubygems.org/gems/langfuse-ruby"
|
|
114
114
|
echo " 2. Update documentation if needed"
|
|
115
115
|
echo " 3. Announce the release"
|
|
116
116
|
echo
|
|
117
117
|
echo "🔗 Useful links:"
|
|
118
|
-
echo " - RubyGems: https://rubygems.org/gems/langfuse"
|
|
118
|
+
echo " - RubyGems: https://rubygems.org/gems/langfuse-ruby"
|
|
119
119
|
echo " - GitHub: https://github.com/ai-firstly/langfuse-ruby"
|
|
120
120
|
echo " - Documentation: https://github.com/ai-firstly/langfuse-ruby#readme"
|