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.
@@ -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
- # Create a child agent observation
96
- def agent(name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
97
- metadata: nil, level: nil, status_message: nil, parent_observation_id: nil,
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, **kwargs)
141
+ release: nil, input: nil, output: nil, metadata: nil, tags: nil,
142
+ public: nil, **kwargs)
251
143
  # 更新实例变量
252
- @name = name if name
253
- @user_id = user_id if user_id
254
- @session_id = session_id if session_id
255
- @version = version if version
256
- @release = release if release
257
- @input = input if input
258
- @output = output if output
259
- @metadata = metadata if metadata
260
- @tags = tags if 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
- data = {
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
- data = {
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
@@ -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(hash)
23
- return hash unless hash.is_a?(Hash)
24
-
25
- hash.each_with_object({}) do |(key, value), result|
26
- new_key = key.is_a?(String) ? key.to_sym : key
27
- new_value = value.is_a?(Hash) ? deep_symbolize_keys(value) : value
28
- result[new_key] = new_value
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
- def deep_stringify_keys(hash)
33
- return hash unless hash.is_a?(Hash)
34
-
35
- hash.each_with_object({}) do |(key, value), result|
36
- new_key = camelize_key(key.to_s)
37
- new_value = value.is_a?(Hash) ? deep_stringify_keys(value) : value
38
- result[new_key] = new_value
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
- def deep_camelize_keys(hash)
44
- return hash unless hash.is_a?(Hash)
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
- hash.each_with_object({}) do |(key, value), result|
77
+ body.each_with_object({}) do |(key, value), result|
47
78
  new_key = camelize_key(key.to_s)
48
- new_value = value.is_a?(Hash) ? deep_camelize_keys(value) : value
49
- result[new_key] = new_value
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Langfuse
4
- VERSION = '0.1.7'
4
+ VERSION = '0.2.1'
5
5
  end
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
- Thread.current[:langfuse_client] ||= Client.new
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
- attempts = 0
50
-
51
- begin
52
- attempts += 1
53
- prompt = client.get_prompt(prompt_name, label: label, version: version, cache_ttl_seconds: cache_ttl_seconds)
54
-
55
- if variables
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
- warn "Langfuse prompt fetch failed (#{prompt_name}): #{e.message}" if configuration.debug
69
- nil
70
- end
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, &block)
104
- trace = client.trace(
105
- name: name,
106
- user_id: user_id,
107
- session_id: session_id,
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
- result = yield(trace)
120
- result
121
- ensure
122
- flush
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
- else
125
- trace
126
- end
127
- rescue StandardError => e
128
- warn "Langfuse trace creation failed: #{e.message}" if configuration.debug
129
-
130
- # If block given, execute with NullTrace to ensure code continues
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
- Thread.current[:langfuse_client] = nil
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 main
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"