opentelemetry-instrumentation-ruby_llm 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dfb17d4d74d31d2a53792683e0d5d070cd25e9160ee3c5018c0a1dbfb5bbeb4b
4
- data.tar.gz: 91c3b7ac277d43ddf80a6fc49e2228d8005bf40f9321cd10cd5ac2539c96dfc7
3
+ metadata.gz: 25a2701b37bc9acecc70d4ff326230cbf5e073454f47c453744ced08d468b2f8
4
+ data.tar.gz: a5885383024ecf52deb132bdf0ccb86b1cb9a0169561a714e6d3cabec8e3a64a
5
5
  SHA512:
6
- metadata.gz: df479838c253aa695c1b36b393b4f7eac31dd041a581df3d04d9f9e9ca1253d026b8582b32d2cdf398e812d53459ef0c012406654342d37a9ae45c45dc68c5e7
7
- data.tar.gz: 9c18a05253193ce23af8bc0f342cd726778c6daccff7d1010602a63b36f20d07eb13852ea6f01d6e40bf2439236fbf4b495e32e144e701d521aaeec18972f6a4
6
+ metadata.gz: 023c166358bee510b9cd75f41764179e019eba7f3df1153c8d3806a8ce7488b7781688b708a2b315b4a2207306d822cf063c2e2125874cbbe3fc912176897edd
7
+ data.tar.gz: ef4d632a49a4d22d66d751648a2c5edf6ba13d0223dc1d42901193f675e13eff065ea8dbf713bac7f29450b2af6fd510126e4bbf5a713138bf16b9a74152c642
@@ -16,6 +16,7 @@ jobs:
16
16
  - '3.4'
17
17
  - '3.3'
18
18
  - '3.2'
19
+ - '3.1'
19
20
 
20
21
  steps:
21
22
  - uses: actions/checkout@v4
@@ -15,7 +15,9 @@ module OpenTelemetry
15
15
 
16
16
  install do |_config|
17
17
  require_relative "patches/chat"
18
+ require_relative "patches/embedding"
18
19
  ::RubyLLM::Chat.prepend(Patches::Chat)
20
+ ::RubyLLM::Embedding.singleton_class.prepend(Patches::Embedding)
19
21
  end
20
22
  end
21
23
  end
@@ -47,9 +47,6 @@ module OpenTelemetry
47
47
 
48
48
  result
49
49
  end
50
- rescue StandardError => e
51
- OpenTelemetry.handle_error(exception: e)
52
- super
53
50
  end
54
51
 
55
52
  def execute_tool(tool_call)
@@ -66,9 +63,6 @@ module OpenTelemetry
66
63
  span.set_attribute("gen_ai.tool.call.result", result_str[0..500])
67
64
  result
68
65
  end
69
- rescue StandardError => e
70
- OpenTelemetry.handle_error(exception: e)
71
- super
72
66
  end
73
67
 
74
68
  private
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenTelemetry
4
+ module Instrumentation
5
+ module RubyLLM
6
+ module Patches
7
+ module Embedding
8
+ def embed(text, model: nil, provider: nil, assume_model_exists: false, context: nil, dimensions: nil)
9
+ config = context&.config || ::RubyLLM.config
10
+ resolved_model = model || config.default_embedding_model
11
+ model_obj, _provider_instance = ::RubyLLM::Models.resolve(
12
+ resolved_model, provider: provider, assume_exists: assume_model_exists, config: config
13
+ )
14
+ model_id = model_obj.id
15
+ provider_name = model_obj.provider || "unknown"
16
+
17
+ attributes = {
18
+ "gen_ai.operation.name" => "embeddings",
19
+ "gen_ai.provider.name" => provider_name,
20
+ "gen_ai.request.model" => model_id
21
+ }
22
+
23
+ tracer.in_span("embeddings #{model_id}", attributes: attributes, kind: OpenTelemetry::Trace::SpanKind::CLIENT) do |span|
24
+ begin
25
+ result = super
26
+ rescue => e
27
+ span.record_exception(e)
28
+ span.status = OpenTelemetry::Trace::Status.error(e.message)
29
+ span.set_attribute("error.type", e.class.name)
30
+ raise
31
+ end
32
+
33
+ span.set_attribute("gen_ai.response.model", result.model) if result.model
34
+ span.set_attribute("gen_ai.usage.input_tokens", result.input_tokens) if result.input_tokens&.positive?
35
+
36
+ if result.vectors.is_a?(Array)
37
+ first = result.vectors.first
38
+ vector = first.is_a?(Array) ? first : result.vectors
39
+ span.set_attribute("gen_ai.embeddings.dimension.count", vector.length) if vector.is_a?(Array)
40
+ end
41
+
42
+ result
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def tracer
49
+ RubyLLM::Instrumentation.instance.tracer
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -3,7 +3,7 @@
3
3
  module OpenTelemetry
4
4
  module Instrumentation
5
5
  module RubyLLM
6
- VERSION = "0.3.0"
6
+ VERSION = "0.4.0"
7
7
  end
8
8
  end
9
9
  end
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
  spec.description = "Adds OpenTelemetry tracing to RubyLLM chat operations"
12
12
  spec.homepage = "https://github.com/thoughtbot/opentelemetry-instrumentation-ruby_llm"
13
13
 
14
- spec.required_ruby_version = ">= 3.2.0"
14
+ spec.required_ruby_version = ">= 3.1.3"
15
15
 
16
16
  spec.metadata["homepage_uri"] = spec.homepage
17
17
 
@@ -67,31 +67,6 @@ class InstrumentationTest < Minitest::Test
67
67
  assert_equal OpenTelemetry::Trace::Status::ERROR, span.status.code
68
68
  end
69
69
 
70
- def test_complete_still_works_when_instrumentation_fails
71
- stub_request(:post, "https://api.openai.com/v1/chat/completions")
72
- .to_return(
73
- status: 200,
74
- headers: { "Content-Type" => "application/json" },
75
- body: {
76
- id: "chatcmpl-123",
77
- object: "chat.completion",
78
- model: "gpt-4o-mini",
79
- choices: [{
80
- index: 0,
81
- message: { role: "assistant", content: "Hello!" },
82
- finish_reason: "stop"
83
- }],
84
- usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }
85
- }.to_json
86
- )
87
-
88
- chat = RubyLLM.chat(model: "gpt-4o-mini")
89
- chat.define_singleton_method(:tracer) { raise StandardError, "instrumentation bug" }
90
-
91
- response = chat.ask("Hi")
92
- assert_equal "Hello!", response.content
93
- end
94
-
95
70
  def test_instruments_complete_called_directly
96
71
  stub_request(:post, "https://api.openai.com/v1/chat/completions")
97
72
  .to_return(
@@ -200,68 +175,6 @@ class InstrumentationTest < Minitest::Test
200
175
  assert_equal "function", tool_span.attributes["gen_ai.tool.type"]
201
176
  end
202
177
 
203
- def test_execute_tool_still_works_when_instrumentation_fails
204
- calculator = Class.new(RubyLLM::Tool) do
205
- def self.name = "calculator"
206
- description "Performs math"
207
- param :expression, type: "string", desc: "Math expression"
208
-
209
- def execute(expression:)
210
- eval(expression).to_s
211
- end
212
- end
213
-
214
- stub_request(:post, "https://api.openai.com/v1/chat/completions")
215
- .to_return(
216
- {
217
- status: 200,
218
- headers: { "Content-Type" => "application/json" },
219
- body: {
220
- id: "chatcmpl-123",
221
- object: "chat.completion",
222
- model: "gpt-4o-mini",
223
- choices: [{
224
- index: 0,
225
- message: {
226
- role: "assistant",
227
- content: nil,
228
- tool_calls: [{
229
- id: "call_abc123",
230
- type: "function",
231
- function: { name: "calculator", arguments: '{"expression":"2+2"}' }
232
- }]
233
- },
234
- finish_reason: "tool_calls"
235
- }],
236
- usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }
237
- }.to_json
238
- },
239
- {
240
- status: 200,
241
- headers: { "Content-Type" => "application/json" },
242
- body: {
243
- id: "chatcmpl-456",
244
- object: "chat.completion",
245
- model: "gpt-4o-mini",
246
- choices: [{
247
- index: 0,
248
- message: { role: "assistant", content: "The answer is 4" },
249
- finish_reason: "stop"
250
- }],
251
- usage: { prompt_tokens: 20, completion_tokens: 5, total_tokens: 25 }
252
- }.to_json
253
- }
254
- )
255
-
256
- chat = RubyLLM.chat(model: "gpt-4o-mini")
257
- chat.with_tool(calculator)
258
-
259
- chat.define_singleton_method(:tracer) { raise StandardError, "instrumentation bug" }
260
-
261
- response = chat.ask("What is 2+2?")
262
- assert_equal "The answer is 4", response.content
263
- end
264
-
265
178
  def test_does_not_capture_content_by_default
266
179
  stub_request(:post, "https://api.openai.com/v1/chat/completions")
267
180
  .to_return(
@@ -332,6 +245,53 @@ class InstrumentationTest < Minitest::Test
332
245
  OpenTelemetry::Instrumentation::RubyLLM::Instrumentation.instance.config[:capture_content] = false
333
246
  end
334
247
 
248
+ def test_creates_span_for_embedding
249
+ stub_request(:post, "https://api.openai.com/v1/embeddings")
250
+ .to_return(
251
+ status: 200,
252
+ headers: { "Content-Type" => "application/json" },
253
+ body: {
254
+ object: "list",
255
+ model: "text-embedding-3-small",
256
+ data: [
257
+ { object: "embedding", index: 0, embedding: [0.1, 0.2, 0.3] }
258
+ ],
259
+ usage: { prompt_tokens: 8, total_tokens: 8 }
260
+ }.to_json
261
+ )
262
+
263
+ RubyLLM.embed("Hello, world!", model: "text-embedding-3-small")
264
+
265
+ spans = EXPORTER.finished_spans
266
+ assert_equal 1, spans.length
267
+
268
+ span = spans.first
269
+ assert_equal OpenTelemetry::Trace::SpanKind::CLIENT, span.kind
270
+ assert_equal "embeddings text-embedding-3-small", span.name
271
+ assert_equal "embeddings", span.attributes["gen_ai.operation.name"]
272
+ assert_equal "openai", span.attributes["gen_ai.provider.name"]
273
+ assert_equal "text-embedding-3-small", span.attributes["gen_ai.request.model"]
274
+ assert_equal "text-embedding-3-small", span.attributes["gen_ai.response.model"]
275
+ assert_equal 8, span.attributes["gen_ai.usage.input_tokens"]
276
+ assert_equal 3, span.attributes["gen_ai.embeddings.dimension.count"]
277
+ end
278
+
279
+ def test_records_error_on_embedding_api_failure
280
+ stub_request(:post, "https://api.openai.com/v1/embeddings")
281
+ .to_return(status: 500, body: "Internal Server Error")
282
+
283
+ assert_raises do
284
+ RubyLLM.embed("Hello", model: "text-embedding-3-small")
285
+ end
286
+
287
+ spans = EXPORTER.finished_spans
288
+ span = spans.last
289
+
290
+ assert_equal "embeddings text-embedding-3-small", span.name
291
+ assert span.attributes["error.type"]
292
+ assert_equal OpenTelemetry::Trace::Status::ERROR, span.status.code
293
+ end
294
+
335
295
  def test_captures_content_when_enabled_via_env_var
336
296
  ENV["OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT"] = "true"
337
297
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opentelemetry-instrumentation-ruby_llm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Clarissa Borges
@@ -62,6 +62,7 @@ files:
62
62
  - lib/opentelemetry-instrumentation-ruby_llm.rb
63
63
  - lib/opentelemetry/instrumentation/ruby_llm/instrumentation.rb
64
64
  - lib/opentelemetry/instrumentation/ruby_llm/patches/chat.rb
65
+ - lib/opentelemetry/instrumentation/ruby_llm/patches/embedding.rb
65
66
  - lib/opentelemetry/instrumentation/ruby_llm/version.rb
66
67
  - opentelemetry-instrumentation-ruby_llm.gemspec
67
68
  - test/instrumentation_test.rb
@@ -78,7 +79,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
78
79
  requirements:
79
80
  - - ">="
80
81
  - !ruby/object:Gem::Version
81
- version: 3.2.0
82
+ version: 3.1.3
82
83
  required_rubygems_version: !ruby/object:Gem::Requirement
83
84
  requirements:
84
85
  - - ">="