Module: Foam::Otel::LLM::RubyOpenAIShim

Defined in:
lib/foam/otel/llm/ruby_openai_shim.rb

Overview

Gap-filler for the ruby-openai gem (the long-standing community OpenAI client: OpenAI::Client#chat, NOT the official openai gem's resource layer OpenAIShim covers). Plenty of production Rails apps still ride it — presence-checked and version-windowed like its siblings so it is inert anywhere the gem is absent, and dark outside the supported window.

Defined Under Namespace

Modules: ChatPatch, EmbeddingsPatch

Constant Summary collapse

SUPPORTED_BELOW =
Gem::Version.new("9.0.0")
CHAT_RESPONSE =

ruby-openai returns plain parsed-JSON Hashes (string keys): "model"=>..., "choices"=>[{"message"=>{..., "finish_reason"=>...}], "usage"=>...}.

lambda do |response|
  h = response.is_a?(Hash) ? response : {}
  usage = h["usage"].is_a?(Hash) ? h["usage"] : {}
  choices = h["choices"].is_a?(Array) ? h["choices"] : []
  LLM.response_attributes(
    model: h["model"],
    id: h["id"],
    finish_reasons: choices.map { |c| c.is_a?(Hash) ? c["finish_reason"] : nil }.compact,
    input_tokens: usage["prompt_tokens"],
    output_tokens: usage["completion_tokens"],
    output_messages: choices.map { |c| c.is_a?(Hash) ? c["message"] : nil }.compact
  )
end
EMBEDDINGS_RESPONSE =

Embeddings response: usage.prompt_tokens only; the vectors are NOT content (and would be enormous) — count them, never ship them.

lambda do |response|
  h = response.is_a?(Hash) ? response : {}
  usage = h["usage"].is_a?(Hash) ? h["usage"] : {}
  LLM.response_attributes(
    model: h["model"],
    input_tokens: usage["prompt_tokens"]
  )
end

Class Method Summary collapse

Class Method Details

.chat_request(params) ⇒ Object



60
61
62
63
64
65
66
67
68
69
# File 'lib/foam/otel/llm/ruby_openai_shim.rb', line 60

def chat_request(params)
  params = params.is_a?(Hash) ? params : {}
  LLM.request_attributes(
    provider: "openai", operation: "chat",
    model: params[:model] || params["model"],
    max_tokens: params[:max_tokens] || params["max_tokens"] ||
                params[:max_completion_tokens] || params["max_completion_tokens"],
    input_messages: params[:messages] || params["messages"]
  )
end

.embeddings_request(params) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/foam/otel/llm/ruby_openai_shim.rb', line 71

def embeddings_request(params)
  params = params.is_a?(Hash) ? params : {}
  # Embeddings INPUT is deliberately NOT captured (fleet parity:
  # the python core rides contrib openai-v2, which emits no
  # embeddings input under any attribute — document chunks are a
  # classic PII carrier, and gen_ai.input.messages is defined for
  # chat messages, not document batches). Tokens + models carry
  # the signal; widening this is a fleet ruling, not a shim choice.
  LLM.request_attributes(
    provider: "openai", operation: "embeddings",
    model: params[:model] || params["model"]
  )
end

.install!Object



32
33
34
35
36
37
38
39
40
41
# File 'lib/foam/otel/llm/ruby_openai_shim.rb', line 32

def install!
  return true if @installed

  ::OpenAI::Client.prepend(ChatPatch)
  if ::OpenAI::Client.method_defined?(:embeddings)
    ::OpenAI::Client.prepend(EmbeddingsPatch)
  end
  Diagnostics.info("LLM shim installed: ruby-openai (client.chat, client.embeddings)")
  @installed = true
end

.installed?Boolean

Returns:

  • (Boolean)


43
# File 'lib/foam/otel/llm/ruby_openai_shim.rb', line 43

def installed? = @installed

.present?Boolean

The official openai gem ALSO defines OpenAI::Client#chat — but as a zero-argument RESOURCE ACCESSOR (client.chat.completions…). ruby-openai's is the request method chat(parameters:). The shape check (the parameters: keyword) is the ONLY reliable discriminator between the two gems sharing this namespace — never the bare method name.

Returns:

  • (Boolean)


22
23
24
25
26
27
28
29
30
# File 'lib/foam/otel/llm/ruby_openai_shim.rb', line 22

def present?
  return false unless defined?(::OpenAI::Client) && ::OpenAI::Client.method_defined?(:chat)

  chat = ::OpenAI::Client.instance_method(:chat)
  takes_parameters = chat.parameters.any? { |type, name| %i[key keyreq].include?(type) && name == :parameters }
  takes_parameters && supported_version?
rescue StandardError
  false
end

.supported_version?Boolean

Returns:

  • (Boolean)


45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/foam/otel/llm/ruby_openai_shim.rb', line 45

def supported_version?
  spec = Gem.loaded_specs["ruby-openai"]
  return true if spec.nil? # vendored/unknown: the shape checks decide

  supported = spec.version < SUPPORTED_BELOW
  unless supported || @version_warned
    @version_warned = true
    Diagnostics.warn("ruby-openai #{spec.version} is outside foam's LLM-shim window (< #{SUPPORTED_BELOW}) " \
                     "— the ruby-openai shim stays dark; file this with foam support")
  end
  supported
rescue StandardError
  true
end