opentelemetry-instrumentation-ruby_llm 0.6.0 → 0.7.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.
@@ -1,11 +1,14 @@
1
1
  require "test_helper"
2
2
 
3
3
  class InstrumentationTest < Minitest::Test
4
+ include ChatCompletionStubs
5
+
4
6
  def setup
5
7
  EXPORTER.reset
6
8
 
7
9
  RubyLLM.configure do |c|
8
10
  c.openai_api_key = "fake-key-for-testing"
11
+ c.anthropic_api_key = "fake-key-for-testing"
9
12
  end
10
13
  end
11
14
 
@@ -30,29 +33,12 @@ class InstrumentationTest < Minitest::Test
30
33
  assert_equal "1.8.0", OpenTelemetry::Instrumentation::RubyLLM::Instrumentation::MINIMUM_RUBY_LLM_VERSION
31
34
  end
32
35
 
36
+ def test_agent_minimum_ruby_llm_version_is_pinned_at_1_12_1
37
+ assert_equal "1.12.1", OpenTelemetry::Instrumentation::RubyLLM::Instrumentation::AGENT_MINIMUM_RUBY_LLM_VERSION
38
+ end
39
+
33
40
  def test_creates_span_with_attributes
34
- stub_request(:post, "https://api.openai.com/v1/chat/completions")
35
- .to_return(
36
- status: 200,
37
- headers: { "Content-Type" => "application/json" },
38
- body: {
39
- id: "chatcmpl-123",
40
- object: "chat.completion",
41
- model: "gpt-4o-mini",
42
- choices: [
43
- {
44
- index: 0,
45
- message: { role: "assistant", content: "Hello, world!" },
46
- finish_reason: "stop"
47
- }
48
- ],
49
- usage: {
50
- prompt_tokens: 10,
51
- completion_tokens: 5,
52
- total_tokens: 15
53
- }
54
- }.to_json
55
- )
41
+ stub_chat_completion
56
42
 
57
43
  chat = RubyLLM.chat(model: "gpt-4o-mini")
58
44
  chat.ask("Hi")
@@ -73,17 +59,9 @@ class InstrumentationTest < Minitest::Test
73
59
  end
74
60
 
75
61
  def test_marks_streaming_chat_requests
76
- stub_request(:post, "https://api.openai.com/v1/chat/completions")
77
- .to_return(
78
- status: 200,
79
- headers: { "Content-Type" => "application/json" },
80
- body: {
81
- id: "chatcmpl-123",
82
- model: "gpt-4o-mini",
83
- choices: [{ index: 0, message: { role: "assistant", content: "Hi" }, finish_reason: "stop" }],
84
- usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }
85
- }.to_json
86
- )
62
+ stub_chat_completion(
63
+ chat_completion_body(content: "Hi", usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 })
64
+ )
87
65
 
88
66
  chat = RubyLLM.chat(model: "gpt-4o-mini")
89
67
  chat.ask("Hi") { |_chunk| }
@@ -92,28 +70,58 @@ class InstrumentationTest < Minitest::Test
92
70
  assert_equal true, span.attributes["gen_ai.request.stream"]
93
71
  end
94
72
 
95
- def test_records_prompt_cache_tokens
96
- # RubyLLM's OpenAI provider maps `cached_tokens` ← `cache_read_tokens(usage)`
97
- # and `cache_creation_tokens` ← `cache_write_tokens(usage)`, both surfaced
98
- # on `Message#cached_tokens` / `Message#cache_creation_tokens`.
99
- stub_request(:post, "https://api.openai.com/v1/chat/completions")
73
+ def test_records_openai_prompt_cache_read_tokens
74
+ # OpenAI exposes only `cached_tokens` via `prompt_tokens_details.cached_tokens`.
75
+ # Its provider in ruby_llm does not surface a `cache_creation_tokens` value
76
+ # until 1.15.0 (we only assert the cache-read attribute here).
77
+ # The accessor itself was added in ruby_llm 1.9.0.
78
+ skip "cached_tokens accessor not available before ruby_llm 1.9.0" unless RubyLLM::Message.instance_methods.include?(:cached_tokens)
79
+ stub_chat_completion(
80
+ chat_completion_body(
81
+ content: "Hello!",
82
+ usage: {
83
+ prompt_tokens: 100,
84
+ completion_tokens: 5,
85
+ total_tokens: 105,
86
+ prompt_tokens_details: { cached_tokens: 75 }
87
+ }
88
+ )
89
+ )
90
+
91
+ chat = RubyLLM.chat(model: "gpt-4o-mini")
92
+ chat.ask("Hi")
93
+
94
+ span = EXPORTER.finished_spans.first
95
+ assert_equal 75, span.attributes["gen_ai.usage.cache_read.input_tokens"]
96
+ assert_equal 0, span.attributes["gen_ai.usage.cache_creation.input_tokens"]
97
+ end
98
+
99
+ def test_records_anthropic_prompt_cache_tokens
100
+ # Anthropic's provider surfaces both `cached_tokens` (via
101
+ # `cache_read_input_tokens`) and `cache_creation_tokens` (via
102
+ # `cache_creation_input_tokens`). Accessors were added in ruby_llm 1.9.0.
103
+ skip "cache token accessors not available before ruby_llm 1.9.0" unless RubyLLM::Message.instance_methods.include?(:cache_creation_tokens)
104
+ stub_request(:post, "https://api.anthropic.com/v1/messages")
100
105
  .to_return(
101
106
  status: 200,
102
107
  headers: { "Content-Type" => "application/json" },
103
108
  body: {
104
- id: "chatcmpl-cache",
105
- model: "gpt-4o-mini",
106
- choices: [{ index: 0, message: { role: "assistant", content: "Hello!" }, finish_reason: "stop" }],
109
+ id: "msg_cache",
110
+ type: "message",
111
+ role: "assistant",
112
+ model: "claude-3-5-sonnet-20241022",
113
+ content: [{ type: "text", text: "Hello!" }],
114
+ stop_reason: "end_turn",
107
115
  usage: {
108
- prompt_tokens: 100,
109
- completion_tokens: 5,
110
- total_tokens: 105,
111
- prompt_tokens_details: { cached_tokens: 75, cache_write_tokens: 20 }
116
+ input_tokens: 100,
117
+ output_tokens: 5,
118
+ cache_read_input_tokens: 75,
119
+ cache_creation_input_tokens: 20
112
120
  }
113
121
  }.to_json
114
122
  )
115
123
 
116
- chat = RubyLLM.chat(model: "gpt-4o-mini")
124
+ chat = RubyLLM.chat(model: "claude-3-5-sonnet-20241022")
117
125
  chat.ask("Hi")
118
126
 
119
127
  span = EXPORTER.finished_spans.first
@@ -140,22 +148,7 @@ class InstrumentationTest < Minitest::Test
140
148
  end
141
149
 
142
150
  def test_instruments_complete_called_directly
143
- stub_request(:post, "https://api.openai.com/v1/chat/completions")
144
- .to_return(
145
- status: 200,
146
- headers: { "Content-Type" => "application/json" },
147
- body: {
148
- id: "chatcmpl-123",
149
- object: "chat.completion",
150
- model: "gpt-4o-mini",
151
- choices: [{
152
- index: 0,
153
- message: { role: "assistant", content: "Hello, world!" },
154
- finish_reason: "stop"
155
- }],
156
- usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }
157
- }.to_json
158
- )
151
+ stub_chat_completion
159
152
 
160
153
  chat = RubyLLM.chat(model: "gpt-4o-mini")
161
154
  chat.add_message(role: :user, content: "Hi")
@@ -183,47 +176,20 @@ class InstrumentationTest < Minitest::Test
183
176
  end
184
177
  end
185
178
 
186
- stub_request(:post, "https://api.openai.com/v1/chat/completions")
187
- .to_return(
188
- {
189
- status: 200,
190
- headers: { "Content-Type" => "application/json" },
191
- body: {
192
- id: "chatcmpl-123",
193
- object: "chat.completion",
194
- model: "gpt-4o-mini",
195
- choices: [{
196
- index: 0,
197
- message: {
198
- role: "assistant",
199
- content: nil,
200
- tool_calls: [{
201
- id: "call_abc123",
202
- type: "function",
203
- function: { name: "calculator", arguments: '{"expression":"2+2"}' }
204
- }]
205
- },
206
- finish_reason: "tool_calls"
207
- }],
208
- usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }
209
- }.to_json
210
- },
211
- {
212
- status: 200,
213
- headers: { "Content-Type" => "application/json" },
214
- body: {
215
- id: "chatcmpl-456",
216
- object: "chat.completion",
217
- model: "gpt-4o-mini",
218
- choices: [{
219
- index: 0,
220
- message: { role: "assistant", content: "The answer is 4" },
221
- finish_reason: "stop"
222
- }],
223
- usage: { prompt_tokens: 20, completion_tokens: 5, total_tokens: 25 }
224
- }.to_json
225
- }
179
+ stub_chat_completion(
180
+ chat_completion_body(
181
+ content: nil,
182
+ tool_calls: [{
183
+ id: "call_abc123",
184
+ type: "function",
185
+ function: { name: "calculator", arguments: '{"expression":"2+2"}' }
186
+ }]
187
+ ),
188
+ chat_completion_body(
189
+ content: "The answer is 4",
190
+ usage: { prompt_tokens: 20, completion_tokens: 5, total_tokens: 25 }
226
191
  )
192
+ )
227
193
 
228
194
  chat = RubyLLM.chat(model: "gpt-4o-mini")
229
195
  chat.with_tool(calculator)
@@ -249,6 +215,41 @@ class InstrumentationTest < Minitest::Test
249
215
  assert_equal "function", tool_span.attributes["gen_ai.tool.type"]
250
216
  end
251
217
 
218
+ def test_truncates_tool_result_to_configured_max_length
219
+ long_value = "x" * 1000
220
+ echo = Class.new(RubyLLM::Tool) do
221
+ def self.name = "echo"
222
+ description "Echoes a long string"
223
+
224
+ define_method(:execute) { long_value }
225
+ end
226
+
227
+ stub_chat_completion(
228
+ chat_completion_body(
229
+ content: nil,
230
+ tool_calls: [{
231
+ id: "call_echo",
232
+ type: "function",
233
+ function: { name: "echo", arguments: "{}" }
234
+ }]
235
+ ),
236
+ chat_completion_body(
237
+ content: "done",
238
+ usage: { prompt_tokens: 20, completion_tokens: 5, total_tokens: 25 }
239
+ )
240
+ )
241
+
242
+ OpenTelemetry::Instrumentation::RubyLLM::Instrumentation.instance.config[:tool_result_max_length] = 700
243
+
244
+ chat = RubyLLM.chat(model: "gpt-4o-mini").with_tool(echo)
245
+ chat.ask("echo please")
246
+
247
+ tool_span = EXPORTER.finished_spans.find { |s| s.name.start_with?("execute_tool ") }
248
+ assert_equal "x" * 700, tool_span.attributes["gen_ai.tool.call.result"]
249
+ ensure
250
+ OpenTelemetry::Instrumentation::RubyLLM::Instrumentation.instance.config[:tool_result_max_length] = 500
251
+ end
252
+
252
253
  def test_records_error_when_tool_raises
253
254
  boom = Class.new(RubyLLM::Tool) do
254
255
  def self.name = "boom"
@@ -259,29 +260,17 @@ class InstrumentationTest < Minitest::Test
259
260
  end
260
261
  end
261
262
 
262
- stub_request(:post, "https://api.openai.com/v1/chat/completions")
263
- .to_return(
264
- status: 200,
265
- headers: { "Content-Type" => "application/json" },
266
- body: {
267
- id: "chatcmpl-boom",
268
- model: "gpt-4o-mini",
269
- choices: [{
270
- index: 0,
271
- message: {
272
- role: "assistant",
273
- content: nil,
274
- tool_calls: [{
275
- id: "call_x",
276
- type: "function",
277
- function: { name: "boom", arguments: "{}" }
278
- }]
279
- },
280
- finish_reason: "tool_calls"
281
- }],
282
- usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }
283
- }.to_json
263
+ stub_chat_completion(
264
+ chat_completion_body(
265
+ content: nil,
266
+ tool_calls: [{
267
+ id: "call_x",
268
+ type: "function",
269
+ function: { name: "boom", arguments: "{}" }
270
+ }],
271
+ usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }
284
272
  )
273
+ )
285
274
 
286
275
  chat = RubyLLM.chat(model: "gpt-4o-mini").with_tool(boom)
287
276
  assert_raises(ArgumentError) { chat.ask("trigger") }
@@ -292,22 +281,7 @@ class InstrumentationTest < Minitest::Test
292
281
  end
293
282
 
294
283
  def test_does_not_capture_content_by_default
295
- stub_request(:post, "https://api.openai.com/v1/chat/completions")
296
- .to_return(
297
- status: 200,
298
- headers: { "Content-Type" => "application/json" },
299
- body: {
300
- id: "chatcmpl-123",
301
- object: "chat.completion",
302
- model: "gpt-4o-mini",
303
- choices: [{
304
- index: 0,
305
- message: { role: "assistant", content: "Hello, world!" },
306
- finish_reason: "stop"
307
- }],
308
- usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }
309
- }.to_json
310
- )
284
+ stub_chat_completion
311
285
 
312
286
  chat = RubyLLM.chat(model: "gpt-4o-mini")
313
287
  chat.with_instructions("You are helpful")
@@ -322,22 +296,7 @@ class InstrumentationTest < Minitest::Test
322
296
  def test_captures_content_when_enabled
323
297
  OpenTelemetry::Instrumentation::RubyLLM::Instrumentation.instance.config[:capture_content] = true
324
298
 
325
- stub_request(:post, "https://api.openai.com/v1/chat/completions")
326
- .to_return(
327
- status: 200,
328
- headers: { "Content-Type" => "application/json" },
329
- body: {
330
- id: "chatcmpl-123",
331
- object: "chat.completion",
332
- model: "gpt-4o-mini",
333
- choices: [{
334
- index: 0,
335
- message: { role: "assistant", content: "Hello, world!" },
336
- finish_reason: "stop"
337
- }],
338
- usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }
339
- }.to_json
340
- )
299
+ stub_chat_completion
341
300
 
342
301
  chat = RubyLLM.chat(model: "gpt-4o-mini")
343
302
  chat.with_instructions("You are helpful")
@@ -409,22 +368,7 @@ class InstrumentationTest < Minitest::Test
409
368
  end
410
369
 
411
370
  def test_with_otel_attributes_sets_span_attributes
412
- stub_request(:post, "https://api.openai.com/v1/chat/completions")
413
- .to_return(
414
- status: 200,
415
- headers: { "Content-Type" => "application/json" },
416
- body: {
417
- id: "chatcmpl-123",
418
- object: "chat.completion",
419
- model: "gpt-4o-mini",
420
- choices: [{
421
- index: 0,
422
- message: { role: "assistant", content: "Hello!" },
423
- finish_reason: "stop"
424
- }],
425
- usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }
426
- }.to_json
427
- )
371
+ stub_chat_completion(chat_completion_body(content: "Hello!"))
428
372
 
429
373
  chat = RubyLLM.chat(model: "gpt-4o-mini")
430
374
  chat.with_otel_attributes(
@@ -439,22 +383,7 @@ class InstrumentationTest < Minitest::Test
439
383
  end
440
384
 
441
385
  def test_with_otel_attributes_returns_self_for_chaining
442
- stub_request(:post, "https://api.openai.com/v1/chat/completions")
443
- .to_return(
444
- status: 200,
445
- headers: { "Content-Type" => "application/json" },
446
- body: {
447
- id: "chatcmpl-123",
448
- object: "chat.completion",
449
- model: "gpt-4o-mini",
450
- choices: [{
451
- index: 0,
452
- message: { role: "assistant", content: "Hello!" },
453
- finish_reason: "stop"
454
- }],
455
- usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }
456
- }.to_json
457
- )
386
+ stub_chat_completion(chat_completion_body(content: "Hello!"))
458
387
 
459
388
  chat = RubyLLM.chat(model: "gpt-4o-mini")
460
389
  result = chat.with_otel_attributes("custom.category" => "test")
@@ -463,22 +392,7 @@ class InstrumentationTest < Minitest::Test
463
392
  end
464
393
 
465
394
  def test_with_otel_attributes_evaluates_callables
466
- stub_request(:post, "https://api.openai.com/v1/chat/completions")
467
- .to_return(
468
- status: 200,
469
- headers: { "Content-Type" => "application/json" },
470
- body: {
471
- id: "chatcmpl-123",
472
- object: "chat.completion",
473
- model: "gpt-4o-mini",
474
- choices: [{
475
- index: 0,
476
- message: { role: "assistant", content: "Hello!" },
477
- finish_reason: "stop"
478
- }],
479
- usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }
480
- }.to_json
481
- )
395
+ stub_chat_completion(chat_completion_body(content: "Hello!"))
482
396
 
483
397
  chat = RubyLLM.chat(model: "gpt-4o-mini")
484
398
  chat.with_otel_attributes(
@@ -492,23 +406,29 @@ class InstrumentationTest < Minitest::Test
492
406
  assert_equal "fixed", span.attributes["custom.static"]
493
407
  end
494
408
 
495
- def test_works_without_otel_attributes
409
+ def test_with_otel_attributes_applied_on_api_failure
496
410
  stub_request(:post, "https://api.openai.com/v1/chat/completions")
497
- .to_return(
498
- status: 200,
499
- headers: { "Content-Type" => "application/json" },
500
- body: {
501
- id: "chatcmpl-123",
502
- object: "chat.completion",
503
- model: "gpt-4o-mini",
504
- choices: [{
505
- index: 0,
506
- message: { role: "assistant", content: "Hello!" },
507
- finish_reason: "stop"
508
- }],
509
- usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }
510
- }.to_json
511
- )
411
+ .to_return(status: 500, body: "Internal Server Error")
412
+
413
+ chat = RubyLLM.chat(model: "gpt-4o-mini")
414
+ chat.with_otel_attributes(
415
+ "langfuse.trace.name" => "My Agent",
416
+ "custom.last_role" => -> { chat.messages.last&.role.to_s }
417
+ )
418
+
419
+ assert_raises do
420
+ chat.ask("Hi")
421
+ end
422
+
423
+ span = EXPORTER.finished_spans.last
424
+ assert_equal "chat gpt-4o-mini", span.name
425
+ assert_equal OpenTelemetry::Trace::Status::ERROR, span.status.code
426
+ assert_equal "My Agent", span.attributes["langfuse.trace.name"]
427
+ assert_equal "user", span.attributes["custom.last_role"]
428
+ end
429
+
430
+ def test_works_without_otel_attributes
431
+ stub_chat_completion(chat_completion_body(content: "Hello!"))
512
432
 
513
433
  chat = RubyLLM.chat(model: "gpt-4o-mini")
514
434
  response = chat.ask("Hi")
@@ -516,25 +436,84 @@ class InstrumentationTest < Minitest::Test
516
436
  assert_equal "Hello!", response.content
517
437
  end
518
438
 
439
+ def test_captures_ruby_llm_content_with_attachments
440
+ OpenTelemetry::Instrumentation::RubyLLM::Instrumentation.instance.config[:capture_content] = true
441
+
442
+ stub_chat_completion(chat_completion_body(content: "A cat."))
443
+
444
+ content = RubyLLM::Content.new("What is this?", "https://example.com/cat.png")
445
+
446
+ chat = RubyLLM.chat(model: "gpt-4o-mini")
447
+ chat.add_message(role: :user, content: content)
448
+ chat.complete
449
+
450
+ span = EXPORTER.finished_spans.first
451
+ input_messages = JSON.parse(span.attributes["gen_ai.input.messages"])
452
+ assert_equal(
453
+ [
454
+ { "type" => "text", "content" => "What is this?" },
455
+ {
456
+ "type" => "uri",
457
+ "modality" => "image",
458
+ "mime_type" => "image/png",
459
+ "uri" => "https://example.com/cat.png"
460
+ }
461
+ ],
462
+ input_messages[0]["parts"]
463
+ )
464
+ ensure
465
+ OpenTelemetry::Instrumentation::RubyLLM::Instrumentation.instance.config[:capture_content] = false
466
+ end
467
+
468
+ def test_captures_ruby_llm_content_raw
469
+ skip "RubyLLM::Content::Raw not available before ruby_llm 1.9.0" unless defined?(RubyLLM::Content::Raw)
470
+ OpenTelemetry::Instrumentation::RubyLLM::Instrumentation.instance.config[:capture_content] = true
471
+
472
+ stub_chat_completion(chat_completion_body(content: "Acknowledged."))
473
+
474
+ raw = RubyLLM::Content::Raw.new([{ type: "text", text: "raw payload" }])
475
+
476
+ chat = RubyLLM.chat(model: "gpt-4o-mini")
477
+ chat.add_message(role: :user, content: raw)
478
+ chat.complete
479
+
480
+ span = EXPORTER.finished_spans.first
481
+ input_messages = JSON.parse(span.attributes["gen_ai.input.messages"])
482
+ assert_equal(
483
+ [{ "type" => "raw", "content" => [{ "type" => "text", "text" => "raw payload" }].to_json }],
484
+ input_messages[0]["parts"]
485
+ )
486
+ ensure
487
+ OpenTelemetry::Instrumentation::RubyLLM::Instrumentation.instance.config[:capture_content] = false
488
+ end
489
+
490
+ def test_captures_ruby_llm_content_raw_system_instructions
491
+ skip "RubyLLM::Content::Raw not available before ruby_llm 1.9.0" unless defined?(RubyLLM::Content::Raw)
492
+ OpenTelemetry::Instrumentation::RubyLLM::Instrumentation.instance.config[:capture_content] = true
493
+
494
+ stub_chat_completion(chat_completion_body(content: "Acknowledged."))
495
+
496
+ raw_block = RubyLLM::Content::Raw.new([{ type: "text", text: "You are helpful" }])
497
+
498
+ chat = RubyLLM.chat(model: "gpt-4o-mini")
499
+ chat.add_message(role: :system, content: raw_block)
500
+ chat.add_message(role: :user, content: "Hi")
501
+ chat.complete
502
+
503
+ span = EXPORTER.finished_spans.first
504
+ system_instructions = JSON.parse(span.attributes["gen_ai.system_instructions"])
505
+ assert_equal(
506
+ [{ "type" => "raw", "content" => [{ "type" => "text", "text" => "You are helpful" }].to_json }],
507
+ system_instructions
508
+ )
509
+ ensure
510
+ OpenTelemetry::Instrumentation::RubyLLM::Instrumentation.instance.config[:capture_content] = false
511
+ end
512
+
519
513
  def test_captures_content_when_enabled_via_env_var
520
514
  ENV["OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT"] = "true"
521
515
 
522
- stub_request(:post, "https://api.openai.com/v1/chat/completions")
523
- .to_return(
524
- status: 200,
525
- headers: { "Content-Type" => "application/json" },
526
- body: {
527
- id: "chatcmpl-123",
528
- object: "chat.completion",
529
- model: "gpt-4o-mini",
530
- choices: [{
531
- index: 0,
532
- message: { role: "assistant", content: "Hello, world!" },
533
- finish_reason: "stop"
534
- }],
535
- usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }
536
- }.to_json
537
- )
516
+ stub_chat_completion
538
517
 
539
518
  chat = RubyLLM.chat(model: "gpt-4o-mini")
540
519
  chat.ask("Hi")
data/test/test_helper.rb CHANGED
@@ -6,6 +6,36 @@ require "ruby_llm"
6
6
  require "opentelemetry/sdk"
7
7
  require "opentelemetry-instrumentation-ruby_llm"
8
8
 
9
+ module ChatCompletionStubs
10
+ DEFAULT_USAGE = { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }.freeze
11
+
12
+ def chat_completion_body(content: "Hello, world!", model: "gpt-4o-mini", tool_calls: nil, usage: DEFAULT_USAGE)
13
+ message = { role: "assistant", content: content }
14
+ message[:tool_calls] = tool_calls if tool_calls
15
+
16
+ {
17
+ id: "chatcmpl-123",
18
+ object: "chat.completion",
19
+ model: model,
20
+ choices: [{
21
+ index: 0,
22
+ message: message,
23
+ finish_reason: tool_calls ? "tool_calls" : "stop"
24
+ }],
25
+ usage: usage
26
+ }.to_json
27
+ end
28
+
29
+ def stub_chat_completion(*bodies)
30
+ bodies = [chat_completion_body] if bodies.empty?
31
+ responses = bodies.map do |body|
32
+ { status: 200, headers: { "Content-Type" => "application/json" }, body: body }
33
+ end
34
+
35
+ stub_request(:post, "https://api.openai.com/v1/chat/completions").to_return(*responses)
36
+ end
37
+ end
38
+
9
39
  EXPORTER = OpenTelemetry::SDK::Trace::Export::InMemorySpanExporter.new
10
40
  span_processor = OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(EXPORTER)
11
41
 
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.6.0
4
+ version: 0.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Clarissa Borges
@@ -61,14 +61,20 @@ files:
61
61
  - example/trace_demonstration_with_langfuse_and_tools.rb
62
62
  - example/trace_demonstration_with_langfuse_ingredient_search.rb
63
63
  - example/trace_demonstration_with_tools.rb
64
+ - gemfiles/ruby_llm_1.12.1.gemfile
64
65
  - gemfiles/ruby_llm_1.8.0.gemfile
65
66
  - gemfiles/ruby_llm_1_latest.gemfile
66
67
  - lib/opentelemetry-instrumentation-ruby_llm.rb
67
68
  - lib/opentelemetry/instrumentation/ruby_llm/instrumentation.rb
69
+ - lib/opentelemetry/instrumentation/ruby_llm/message_formatter.rb
70
+ - lib/opentelemetry/instrumentation/ruby_llm/patches/agent.rb
68
71
  - lib/opentelemetry/instrumentation/ruby_llm/patches/chat.rb
72
+ - lib/opentelemetry/instrumentation/ruby_llm/patches/chat_methods.rb
69
73
  - lib/opentelemetry/instrumentation/ruby_llm/patches/embedding.rb
70
74
  - lib/opentelemetry/instrumentation/ruby_llm/version.rb
71
75
  - opentelemetry-instrumentation-ruby_llm.gemspec
76
+ - test/active_record_chat_methods_test.rb
77
+ - test/agent_instrumentation_test.rb
72
78
  - test/instrumentation_test.rb
73
79
  - test/test_helper.rb
74
80
  homepage: https://github.com/thoughtbot/opentelemetry-instrumentation-ruby_llm
@@ -90,7 +96,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
90
96
  - !ruby/object:Gem::Version
91
97
  version: '0'
92
98
  requirements: []
93
- rubygems_version: 3.7.1
99
+ rubygems_version: 4.0.6
94
100
  specification_version: 4
95
101
  summary: OpenTelemetry instrumentation for RubyLLM
96
102
  test_files: []