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 +4 -4
- data/.github/workflows/main.yml +1 -0
- data/lib/opentelemetry/instrumentation/ruby_llm/instrumentation.rb +2 -0
- data/lib/opentelemetry/instrumentation/ruby_llm/patches/chat.rb +0 -6
- data/lib/opentelemetry/instrumentation/ruby_llm/patches/embedding.rb +55 -0
- data/lib/opentelemetry/instrumentation/ruby_llm/version.rb +1 -1
- data/opentelemetry-instrumentation-ruby_llm.gemspec +1 -1
- data/test/instrumentation_test.rb +47 -87
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 25a2701b37bc9acecc70d4ff326230cbf5e073454f47c453744ced08d468b2f8
|
|
4
|
+
data.tar.gz: a5885383024ecf52deb132bdf0ccb86b1cb9a0169561a714e6d3cabec8e3a64a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 023c166358bee510b9cd75f41764179e019eba7f3df1153c8d3806a8ce7488b7781688b708a2b315b4a2207306d822cf063c2e2125874cbbe3fc912176897edd
|
|
7
|
+
data.tar.gz: ef4d632a49a4d22d66d751648a2c5edf6ba13d0223dc1d42901193f675e13eff065ea8dbf713bac7f29450b2af6fd510126e4bbf5a713138bf16b9a74152c642
|
data/.github/workflows/main.yml
CHANGED
|
@@ -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
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
82
|
+
version: 3.1.3
|
|
82
83
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
84
|
requirements:
|
|
84
85
|
- - ">="
|