Skip to content

feat: add instrumentation for openai gem#1797

Open
xuan-cao-swi wants to merge 11 commits intoopen-telemetry:mainfrom
xuan-cao-swi:openai-ruby-instr
Open

feat: add instrumentation for openai gem#1797
xuan-cao-swi wants to merge 11 commits intoopen-telemetry:mainfrom
xuan-cao-swi:openai-ruby-instr

Conversation

@xuan-cao-swi
Copy link
Copy Markdown
Contributor

Description

This PR introduces OpenTelemetry instrumentation for the openai Ruby gem from official OpenAI Ruby SDK, enabling automatic tracing for OpenAI API calls including chat completions, streaming responses, and embeddings. The main idea is to monkey-patch the request function which every openai api call use this function with different path.

The captured content is the content of message from user and assistants. It will only log the content if it's enabled.

request function takes all different openai ruby gem api call, but for now it only support chat (with stream) and embeddings

Chat Completion

client = OpenAI::Client.new(api_key: ENV['OPENAI_API_KEY'])

response = client.chat.completions.create(
  model: 'gpt-5-nano',
  messages: [
    { role: 'system', content: 'You are a helpful assistant.' },
    { role: 'user', content: 'Explain what Ruby is in one sentence.' }
  ],
  max_completion_tokens: 1000
)

# Result and Captured Content (if enabled):
I, [2025-11-22T00:04:48.965241 #99703]  INFO -- : {"event":"gen_ai.system.message","attributes":{"gen_ai.provider.name":"openai"},"body":{"content":"You are a helpful assistant."}}
I, [2025-11-22T00:04:48.965263 #99703]  INFO -- : {"event":"gen_ai.user.message","attributes":{"gen_ai.provider.name":"openai"},"body":{"content":"Explain what Ruby is in one sentence."}}
I, [2025-11-22T00:04:52.589295 #99703]  INFO -- : {"event":"gen_ai.choice","attributes":{"gen_ai.provider.name":"openai"},"body":{"index":0,"finish_reason":"stop","message":{"role":"assistant","content":"Ruby is a dynamic, open-source programming language designed for simplicity and productivity, with an elegant, human-friendly syntax."}}}

"Ruby is a dynamic, open-source programming language designed for simplicity and productivity, with an elegant, human-friendly syntax."

# Captured Span Attributes
-> gen_ai.operation.name: Str(chat)
-> gen_ai.provider.name: Str(openai)
-> gen_ai.request.model: Str(gpt-5-nano)
-> server.address: Str(api.openai.com)
-> server.port: Int(443)
-> http.request.method: Str(POST)
-> url.path: Str(chat/completions)
-> gen_ai.output.type: Str(text)
-> gen_ai.request.max_tokens: Int(1000)
-> gen_ai.response.model: Str(gpt-5-nano-2025-08-07)
-> gen_ai.response.id: Str(chatcmpl-CeVRmR6ubrhgLJOvFPvoDzm7uq0zm)
-> openai.response.service_tier: Str(default)
-> gen_ai.usage.input_tokens: Int(24)
-> gen_ai.usage.output_tokens: Int(232)
-> gen_ai.usage.total_tokens: Int(256)
-> gen_ai.response.finish_reasons: Slice(["stop"])

Streaming Chat Completion

stream = client.chat.completions.stream(
  model: 'gpt-5-nano',
  messages: [{ role: 'user', content: 'Count from 1 to 5 and say done.' }],
  max_completion_tokens: 1000
)

# Result and Captured Content (if enabled):
I, [2025-11-22T00:07:30.620543 #1730]  INFO -- : {"event":"gen_ai.system.message","attributes":{"gen_ai.provider.name":"openai"},"body":{"content":"You are a helpful assistant."}}
I, [2025-11-22T00:07:30.620575 #1730]  INFO -- : {"event":"gen_ai.user.message","attributes":{"gen_ai.provider.name":"openai"},"body":{"content":"Count from 1 to 5 and say done."}}
1, 2, 3, 4, 5. Done.
[Finished: stop]
I, [2025-11-22T00:07:33.966164 #1730]  INFO -- : {"event":"gen_ai.choice","attributes":{"gen_ai.provider.name":"openai"},"body":{"index":0,"finish_reason":"stop","message":{"role":"assistant","content":"1, 2, 3, 4, 5. Done."}}}

# Captured Span Attributes
-> gen_ai.operation.name: Str(chat)
-> gen_ai.provider.name: Str(openai)
-> gen_ai.request.model: Str(gpt-5-nano)
-> server.address: Str(api.openai.com)
-> server.port: Int(443)
-> http.request.method: Str(POST)
-> url.path: Str(chat/completions)
-> gen_ai.output.type: Str(text)
-> gen_ai.request.max_tokens: Int(1000)
-> gen_ai.response.model: Str(gpt-5-nano-2025-08-07)
-> gen_ai.response.id: Str(chatcmpl-CeVRzNeMmotPgE4f76N9DKAfS6z9g)
-> gen_ai.usage.input_tokens: Int(27)
-> gen_ai.usage.output_tokens: Int(217)
-> gen_ai.response.finish_reasons: Slice(["stop"])
-> openai.response.service_tier: Str(default)

Text Embeddings

response = client.embeddings.create(
  model: 'text-embedding-3-small',
  input: 'Ruby is a dynamic, open source programming language.'
)

# Result and Captured Content (if enabled):
I, [2025-11-22T00:08:40.827714 #2458]  INFO -- : {"event":"gen_ai.user.message","attributes":{"gen_ai.provider.name":"openai"},"body":{"content":"Ruby is a dynamic, open source programming language."}}
First 5 values: [-0.0451, 0.0145, -0.0182, -0.0009, 0.0348]
Usage: {:prompt_tokens=>10, :total_tokens=>10}
Embedding dimensions: 1536

# Captured Span Attributes
-> gen_ai.operation.name: Str(embeddings)
-> gen_ai.provider.name: Str(openai)
-> gen_ai.request.model: Str(text-embedding-3-small)
-> server.address: Str(api.openai.com)
-> server.port: Int(443)
-> http.request.method: Str(POST)
-> url.path: Str(embeddings)
-> gen_ai.output.type: Str(json)
-> gen_ai.response.model: Str(text-embedding-3-small)
-> gen_ai.usage.input_tokens: Int(10)
-> gen_ai.usage.total_tokens: Int(10)
-> gen_ai.embeddings.dimension.count: Int(1536)

@arielvalentin
Copy link
Copy Markdown
Contributor

@xuan-cao-swi did you generate this using instrumentation_generator ? There are a few changes missing like the toys release metadata, adding it to CI, patching the all gem. etc...

@github-actions
Copy link
Copy Markdown
Contributor

👋 This pull request has been marked as stale because it has been open with no activity. You can: comment on the issue or remove the stale label to hold stale off for a while, add the keep label to hold stale off permanently, or do nothing. If you do nothing this pull request will be closed eventually by the stale bot

@github-actions github-actions Bot added the stale Marks an issue/PR stale label Dec 26, 2025
@xuan-cao-swi
Copy link
Copy Markdown
Contributor Author

@arielvalentin yes, I did use instrumentation_generator. I ran the command again, I saw the changes

modified:   .github/component_owners.yml
modified:   .toys/.data/releases.yml
modified:   instrumentation/all/Gemfile
modified:   instrumentation/all/lib/opentelemetry/instrumentation/all.rb

Are we starting to include these before making the release of new gem?

@arielvalentin
Copy link
Copy Markdown
Contributor

Yes. All of those changes should be included by default unless you want to explicitly omit this instrumentation from all and defer releasing this gem.

@xuan-cao-swi
Copy link
Copy Markdown
Contributor Author

Done.

@github-actions github-actions Bot removed the stale Marks an issue/PR stale label Jan 8, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 7, 2026

👋 This pull request has been marked as stale because it has been open with no activity. You can: comment on the issue or remove the stale label to hold stale off for a while, add the keep label to hold stale off permanently, or do nothing. If you do nothing this pull request will be closed eventually by the stale bot

@github-actions github-actions Bot added the stale Marks an issue/PR stale label Feb 7, 2026
@github-actions github-actions Bot closed this Mar 9, 2026
@xuan-cao-swi xuan-cao-swi reopened this Mar 17, 2026
@github-actions github-actions Bot added ci and removed stale Marks an issue/PR stale labels Mar 17, 2026
@arielvalentin
Copy link
Copy Markdown
Contributor

@xuan-cao-swi can you sync this with main?

@arielvalentin
Copy link
Copy Markdown
Contributor

@xuan-cao-swi can I ask you to address broken tests?

Comment on lines +12 to +27
gem 'opentelemetry-instrumentation-base', path: '../base'
gem 'appraisal', '~> 2.5'
gem 'bundler', '~> 2.4'
gem 'minitest', '~> 5.0'
gem 'openai', '~> 0.36.0'
gem 'opentelemetry-sdk', '~> 1.0'
gem 'opentelemetry-test-helpers', '~> 0.3'
gem 'rake', '~> 13.0'
gem 'rubocop', '~> 1.81.1'
gem 'rubocop-minitest', '~> 0.39.0'
gem 'rubocop-rspec', '~> 3.9.0'
gem 'rubocop-rake', '~> 0.7.1'
gem 'rubocop-performance', '~> 1.26.0'
gem 'simplecov', '~> 0.17.1'
gem 'webmock', '~> 3.24'
gem 'yard', '~> 0.9'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
gem 'opentelemetry-instrumentation-base', path: '../base'
gem 'appraisal', '~> 2.5'
gem 'bundler', '~> 2.4'
gem 'minitest', '~> 5.0'
gem 'openai', '~> 0.36.0'
gem 'opentelemetry-sdk', '~> 1.0'
gem 'opentelemetry-test-helpers', '~> 0.3'
gem 'rake', '~> 13.0'
gem 'rubocop', '~> 1.81.1'
gem 'rubocop-minitest', '~> 0.39.0'
gem 'rubocop-rspec', '~> 3.9.0'
gem 'rubocop-rake', '~> 0.7.1'
gem 'rubocop-performance', '~> 1.26.0'
gem 'simplecov', '~> 0.17.1'
gem 'webmock', '~> 3.24'
gem 'yard', '~> 0.9'
gem 'opentelemetry-instrumentation-base', path: '../base'
gem 'appraisal', '~> 2.5.0'
gem 'bundler', '~> 2.4.0'
gem 'minitest', '~> 5.0'
gem 'openai', '~> 0.36.0'
gem 'opentelemetry-sdk', '~> 1.0.0'
gem 'opentelemetry-test-helpers', '~> 0.3.0'
gem 'rake', '~> 13.0.0'
gem 'rubocop', '~> 1.81.1'
gem 'rubocop-minitest', '~> 0.39.0'
gem 'rubocop-rspec', '~> 3.9.0'
gem 'rubocop-rake', '~> 0.7.1'
gem 'rubocop-performance', '~> 1.26.0'
gem 'simplecov', '~> 0.17.1'
gem 'webmock', '~> 3.24.0'
gem 'yard', '~> 0.9.0'

module OpenAI
module Patches
# OpenAIClient Patch
# rubocop:disable Metrics/ModuleLength
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move this to the rubocop config file

'gen_ai.request.model' => model,
'server.address' => uri&.host || 'api.openai.com',
'server.port' => uri&.port || 443,
'http.request.method' => req[:method].to_s.upcase,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'http.request.method' => req[:method].to_s.upcase,

'server.address' => uri&.host || 'api.openai.com',
'server.port' => uri&.port || 443,
'http.request.method' => req[:method].to_s.upcase,
'url.path' => req[:path],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'url.path' => req[:path],

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants