Module: Foam::Otel::LLM::GeminiShim
- Defined in:
- lib/foam/otel/llm/gemini_shim.rb
Overview
Gap-filler for Google Gemini via the gemini-ai gem — the de-facto
Gemini Ruby SDK (Google ships NO official Ruby SDK; provider-parity
ruling 2026-07-26 requires Gemini coverage in every server language).
Patches the public call sites generate_content and
stream_generate_content (the gem consumes the SSE stream INSIDE the
call and returns the collected events, so both seams yield the full
response synchronously). gen_ai.provider.name uses the semconv
well-known value for the Gemini API: "gcp.gemini", and
gen_ai.operation.name / the span name use "generate_content" — the
semconv well-known operation for this API shape, exactly what the
fleet's js shim and python's official google-genai instrumentor emit
(GenAI-uniformity ruling 2026-07-26; aligned from "chat" in 1.2.1).
Defined Under Namespace
Modules: ClientPatch
Constant Summary collapse
- PROVIDER =
"gcp.gemini"- OPERATION =
"generate_content"- SUPPORTED =
Gem::Requirement.new(">= 4.0", "< 5")
- RESPONSE =
Gemini REST response (a parsed hash, or the array of SSE events) → response attributes. Candidates' content rides VERBATIM.
lambda do |result| events = (result.is_a?(Array) ? result : [result]).select { |e| e.is_a?(Hash) } next nil if events.empty? candidates = events.flat_map { |e| Array(e["candidates"]) }.select { |c| c.is_a?(Hash) } usage = events.reverse.filter_map { |e| e["usageMetadata"] }.find { |u| u.is_a?(Hash) } input_tokens = usage && usage["promptTokenCount"] output_tokens = usage && usage["candidatesTokenCount"] LLM.response_attributes( model: events.filter_map { |e| e["modelVersion"] }.first, id: events.filter_map { |e| e["responseId"] }.first, finish_reasons: candidates.filter_map { |c| c["finishReason"] }, input_tokens: input_tokens.is_a?(Numeric) ? input_tokens.to_i : nil, output_tokens: output_tokens.is_a?(Numeric) ? output_tokens.to_i : nil, output_messages: candidates.filter_map { |c| c["content"] } ) end
Class Method Summary collapse
- .install! ⇒ Object
- .installed? ⇒ Boolean
- .present? ⇒ Boolean
-
.request(client, payload) ⇒ Object
The request model lives on the CLIENT (its model address), not in the payload: "models/gemini-2.0-flash" / "publishers/google/ models/..." → the trailing segment.
- .supported_version? ⇒ Boolean
Class Method Details
.install! ⇒ Object
30 31 32 33 34 35 36 |
# File 'lib/foam/otel/llm/gemini_shim.rb', line 30 def install! return true if @installed ::Gemini::Controllers::Client.prepend(ClientPatch) Diagnostics.info("LLM shim installed: gemini-ai (generate_content, stream_generate_content)") @installed = true end |
.installed? ⇒ Boolean
38 |
# File 'lib/foam/otel/llm/gemini_shim.rb', line 38 def installed? = @installed |
.present? ⇒ Boolean
24 25 26 27 28 |
# File 'lib/foam/otel/llm/gemini_shim.rb', line 24 def present? defined?(::Gemini::Controllers::Client) && ::Gemini::Controllers::Client.method_defined?(:generate_content) && supported_version? end |
.request(client, payload) ⇒ Object
The request model lives on the CLIENT (its model address), not in the payload: "models/gemini-2.0-flash" / "publishers/google/ models/..." → the trailing segment.
58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/foam/otel/llm/gemini_shim.rb', line 58 def request(client, payload) payload = payload.is_a?(Hash) ? payload : {} model = begin client.instance_variable_get(:@model_address).to_s.split("/").last rescue StandardError nil end LLM.request_attributes( provider: PROVIDER, operation: OPERATION, model: model, input_messages: payload[:contents] || payload["contents"], system_instructions: payload[:system_instruction] || payload["system_instruction"] ) end |
.supported_version? ⇒ Boolean
40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/foam/otel/llm/gemini_shim.rb', line 40 def supported_version? spec = Gem.loaded_specs["gemini-ai"] return true if spec.nil? supported = SUPPORTED.satisfied_by?(spec.version) unless supported || @version_warned @version_warned = true Diagnostics.warn("gemini-ai #{spec.version} is outside foam's LLM-shim window (#{SUPPORTED}) " \ "— the Gemini shim stays dark; file this with foam support") end supported rescue StandardError true end |