Module: OmniAI::Google::Chat::UsageSerializer
- Defined in:
- lib/omniai/google/chat/usage_serializer.rb
Overview
Overrides usage serialize / deserialize.
Class Method Summary collapse
-
.deserialize(data) ⇒ OmniAI::Chat::Usage?
Returns
nilwhen the payload carries no token counts at all. -
.serialize(usage) ⇒ Hash
Gemini reports thinking separately from the answer:
candidatesTokenCountcovers the answer only, whilethoughtsTokenCountcovers internal reasoning.
Class Method Details
.deserialize(data) ⇒ OmniAI::Chat::Usage?
Returns nil when the payload carries no token counts at all. A truncated stream still assembles a
usageMetadata — Gemini sends one on every chunk, carrying only trafficType until the terminal chunk —
so the presence of the key is not the presence of usage. Building a Usage from it would report every
count as nil, which arithmetic downstream silently turns into zero.
The test is strictly "no count is present", never "the counts are falsy": a reported 0 is a count.
42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/omniai/google/chat/usage_serializer.rb', line 42 def self.deserialize(data, *) input_tokens = data["promptTokenCount"] candidates_tokens = data["candidatesTokenCount"] thinking_tokens = data["thoughtsTokenCount"] total_tokens = data["totalTokenCount"] return if [input_tokens, candidates_tokens, thinking_tokens, total_tokens].all?(&:nil?) output_tokens = (candidates_tokens || 0) + (thinking_tokens || 0) if candidates_tokens || thinking_tokens OmniAI::Chat::Usage.new(input_tokens:, output_tokens:, total_tokens:, thinking_tokens:) end |
.serialize(usage) ⇒ Hash
Gemini reports thinking separately from the answer: candidatesTokenCount covers the answer only, while
thoughtsTokenCount covers internal reasoning. Google bills both as output and totalTokenCount includes
both, so output_tokens is the sum — matching Anthropic and OpenAI, where reasoning is already folded into
the output count and reported back only as a breakdown.
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
# File 'lib/omniai/google/chat/usage_serializer.rb', line 15 def self.serialize(usage, *) thinking_tokens = usage.thinking_tokens candidates_tokens = usage.output_tokens # `thinking_tokens` is a subset of `output_tokens`, so this cannot go negative for any Usage this gem # builds. Clamp anyway: a hand-constructed Usage that violates the subset invariant should not produce a # negative token count on the wire. candidates_tokens = [candidates_tokens - thinking_tokens, 0].max if candidates_tokens && thinking_tokens data = { promptTokenCount: usage.input_tokens, candidatesTokenCount: candidates_tokens, totalTokenCount: usage.total_tokens, } # Gemini omits the key entirely when nothing was thought; only emit it when there is a value to report. data[:thoughtsTokenCount] = thinking_tokens unless thinking_tokens.nil? data end |