omniai-google 3.10.0 → 3.13.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2b8dca01238f0fabfede35ce895cb4ec7ae936d9f3296c7accd88aa0284a6bfc
4
- data.tar.gz: aa5f4c994eaac71ce5a39e6c3c0e0765eee2da33aacf2753d82b994b8c19b328
3
+ metadata.gz: 8dab5e45bcb29a2506c505023b188ff9bfab5e000529a84bf691d5fbd576f66d
4
+ data.tar.gz: 2f77225d19897c818297fdd225721d5aa412b95edcf4f4445c2d309662b3360e
5
5
  SHA512:
6
- metadata.gz: c5b8f8335542020564f81a03bb69236f3db4c2b0683353e84ca8866d69fc557b52ae353deebd272c480fe3a9f54b190a4c451550c8a1f93f5d4e2638731b48d6
7
- data.tar.gz: f5a2f43feda13a60a4fcdf7ae9e4826ffae58f527f550d258a3ba8e520421ff1b601e2f59b6affcb6560a2e59aa58452be1ce74fcbdf48c433736d2e98fdc212
6
+ metadata.gz: 8c5582f5d5a94cdac496da59f9840799ba3786a26047afe269e06deabf120886e704474b976cebb9b35ee947e31031f8b00479cd8d0f2d6ff0a551878799219c
7
+ data.tar.gz: 6c9bd1633e7deba0331cdf3134381b25b195e869c3607139797decc04b43d57b424ffbfc83234035b955cac22a37a635f34981adab55dddfc67949b586f27cb0
data/README.md CHANGED
@@ -87,7 +87,7 @@ completion.text # 'The capital of Canada is Ottawa.'
87
87
 
88
88
  #### Model
89
89
 
90
- `model` takes an optional string (default is `gemini-3.5-flash`):
90
+ `model` takes an optional string (default is `gemini-3.7-flash`):
91
91
 
92
92
  ```ruby
93
93
  completion = client.chat('How fast is a cheetah?', model: OmniAI::Google::Chat::Model::GEMINI_FLASH)
@@ -5,23 +5,51 @@ module OmniAI
5
5
  class Chat
6
6
  # Overrides usage serialize / deserialize.
7
7
  module UsageSerializer
8
+ # Gemini reports thinking separately from the answer: `candidatesTokenCount` covers the answer only, while
9
+ # `thoughtsTokenCount` covers internal reasoning. Google bills both as output and `totalTokenCount` includes
10
+ # both, so `output_tokens` is the sum — matching Anthropic and OpenAI, where reasoning is already folded into
11
+ # the output count and reported back only as a breakdown.
12
+ #
8
13
  # @param usage [OmniAI::Chat::Usage]
9
14
  # @return [Hash]
10
15
  def self.serialize(usage, *)
11
- {
16
+ thinking_tokens = usage.thinking_tokens
17
+ candidates_tokens = usage.output_tokens
18
+ # `thinking_tokens` is a subset of `output_tokens`, so this cannot go negative for any Usage this gem
19
+ # builds. Clamp anyway: a hand-constructed Usage that violates the subset invariant should not produce a
20
+ # negative token count on the wire.
21
+ candidates_tokens = [candidates_tokens - thinking_tokens, 0].max if candidates_tokens && thinking_tokens
22
+
23
+ data = {
12
24
  promptTokenCount: usage.input_tokens,
13
- candidatesTokenCount: usage.output_tokens,
25
+ candidatesTokenCount: candidates_tokens,
14
26
  totalTokenCount: usage.total_tokens,
15
27
  }
28
+ # Gemini omits the key entirely when nothing was thought; only emit it when there is a value to report.
29
+ data[:thoughtsTokenCount] = thinking_tokens unless thinking_tokens.nil?
30
+ data
16
31
  end
17
32
 
33
+ # Returns `nil` when the payload carries no token counts at all. A truncated stream still assembles a
34
+ # `usageMetadata` — Gemini sends one on every chunk, carrying only `trafficType` until the terminal chunk —
35
+ # so the presence of the key is not the presence of usage. Building a Usage from it would report every
36
+ # count as `nil`, which arithmetic downstream silently turns into zero.
37
+ #
38
+ # The test is strictly "no count is present", never "the counts are falsy": a reported `0` is a count.
39
+ #
18
40
  # @param data [Hash]
19
- # @return [OmniAI::Chat::Usage]
41
+ # @return [OmniAI::Chat::Usage, nil]
20
42
  def self.deserialize(data, *)
21
43
  input_tokens = data["promptTokenCount"]
22
- output_tokens = data["candidatesTokenCount"]
44
+ candidates_tokens = data["candidatesTokenCount"]
45
+ thinking_tokens = data["thoughtsTokenCount"]
23
46
  total_tokens = data["totalTokenCount"]
24
- OmniAI::Chat::Usage.new(input_tokens:, output_tokens:, total_tokens:)
47
+
48
+ return if [input_tokens, candidates_tokens, thinking_tokens, total_tokens].all?(&:nil?)
49
+
50
+ output_tokens = (candidates_tokens || 0) + (thinking_tokens || 0) if candidates_tokens || thinking_tokens
51
+
52
+ OmniAI::Chat::Usage.new(input_tokens:, output_tokens:, total_tokens:, thinking_tokens:)
25
53
  end
26
54
  end
27
55
  end
@@ -23,11 +23,13 @@ module OmniAI
23
23
  GEMINI_2_5_FLASH = "gemini-2.5-flash"
24
24
  GEMINI_3_FLASH = "gemini-3-flash-preview"
25
25
  GEMINI_3_5_FLASH = "gemini-3.5-flash"
26
+ GEMINI_3_6_FLASH = "gemini-3.6-flash"
27
+ GEMINI_3_7_FLASH = "gemini-3.7-flash"
26
28
  GEMINI_PRO = GEMINI_3_1_PRO
27
- GEMINI_FLASH = GEMINI_3_5_FLASH
29
+ GEMINI_FLASH = GEMINI_3_7_FLASH
28
30
  end
29
31
 
30
- DEFAULT_MODEL = Model::GEMINI_3_5_FLASH
32
+ DEFAULT_MODEL = Model::GEMINI_FLASH
31
33
 
32
34
  module ResponseMimeType
33
35
  JSON = "application/json"
@@ -39,7 +39,7 @@ module OmniAI
39
39
  credentials: OmniAI::Google.config.credentials,
40
40
  logger: OmniAI::Google.config.logger,
41
41
  host: OmniAI::Google.config.host,
42
- version: OmniAI::Google.config.version,
42
+ version: nil,
43
43
  timeout: OmniAI::Google.config.timeout
44
44
  )
45
45
  if api_key.nil? && credentials.nil?
@@ -51,7 +51,7 @@ module OmniAI
51
51
  @project_id = project_id
52
52
  @location_id = location_id
53
53
  @credentials = Credentials.parse(credentials)
54
- @version = version
54
+ @version = version || default_version
55
55
  end
56
56
 
57
57
  # @raise [OmniAI::Error]
@@ -128,6 +128,19 @@ module OmniAI
128
128
 
129
129
  private
130
130
 
131
+ # Vertex AI serves `v1`; the Gemini API serves `v1beta`. `OmniAI::Google.config.version` derives from the
132
+ # *config's* host, not this client's, so a client constructed with a Vertex host but an otherwise default
133
+ # config inherited `v1beta` and produced `/v1beta/projects/.../locations/...`, which 404s against every
134
+ # regional endpoint.
135
+ #
136
+ # Only the Vertex case is derived from this client's host. Any other custom host — a proxy or gateway in
137
+ # front of the Gemini API — keeps deferring to the config, so those callers are unaffected.
138
+ #
139
+ # @return [String]
140
+ def default_version
141
+ vertex? ? Config::Version::STABLE : OmniAI::Google.config.version
142
+ end
143
+
131
144
  # @return [String] e.g. "Bearer ..."
132
145
  def auth
133
146
  @credentials.fetch_access_token!
@@ -40,6 +40,13 @@ module OmniAI
40
40
  [data["embedding"]["values"]]
41
41
  end
42
42
 
43
+ # Always returns a Usage, never nil — callers rely on `response.usage` being present here without a nil check.
44
+ #
45
+ # This deliberately differs from the chat-side `UsageSerializer`, which returns nil when the payload carries
46
+ # no token counts at all (a truncated Gemini stream still sends a `usageMetadata` containing only
47
+ # `trafficType`). Embeddings have no streaming path and so no equivalent partial payload, so there is nothing
48
+ # to distinguish. Do not make this nil-safe "for consistency" with chat: it would turn an unguarded
49
+ # `response.usage.total_tokens` in a consumer into a NoMethodError.
43
50
  USAGE_METADATA_DESERIALIZER = proc do |data, *|
44
51
  prompt_tokens = data.dig("usageMetadata", "promptTokenCount")
45
52
  total_tokens = data.dig("usageMetadata", "totalTokenCount")
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OmniAI
4
4
  module Google
5
- VERSION = "3.10.0"
5
+ VERSION = "3.13.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omniai-google
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.10.0
4
+ version: 3.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Sylvestre
@@ -57,14 +57,14 @@ dependencies:
57
57
  requirements:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
- version: '3.7'
60
+ version: '3.8'
61
61
  type: :runtime
62
62
  prerelease: false
63
63
  version_requirements: !ruby/object:Gem::Requirement
64
64
  requirements:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
- version: '3.7'
67
+ version: '3.8'
68
68
  - !ruby/object:Gem::Dependency
69
69
  name: openssl
70
70
  requirement: !ruby/object:Gem::Requirement