omniai-google 3.15.0 → 3.17.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: dc72ad3bc8cfe3181908f748f705708827ea8f10a3c533b9d1d4352d6f5d9df0
4
- data.tar.gz: c29f890a61470dc3ddce22d5753ca4410a04ba91b93faab6381411805e41fdbd
3
+ metadata.gz: 9fd43c08069868c8d20d91ac6e1028baf97643eda9018a0db146c73ce0828c15
4
+ data.tar.gz: 04ca6d4c4789b91c194abb44f8d2d178f1e65605644fe3c72a72824e8707a9e2
5
5
  SHA512:
6
- metadata.gz: bdee9a2925e8ea39873fb8e34b94378aa0549d0b1443a3c20b55bef586dda1208514e042ee63c6780ed6ec8f457b24a7af85f9def73d82c1c77e4a6ca84a6db2
7
- data.tar.gz: 3402943f6994d0489fc2382731e41e116667e3e93b09908dc6549c1f890f7ba2bdb397e27af9d4999fb925a7be19489c2f1566dae6e56dcf9cdb61bf731e20c2
6
+ metadata.gz: 3b2076e4c7f504e899015fbd0c8fb4f3290d347d2191051cdad1a6e079f45b6086bf8b6725693d073c45e02adf85bc43be7aace65a37fce282a163ed338596c6
7
+ data.tar.gz: ebf586d81abd71f7959f12f85159a27b3a5a53cd4966adcc2e05bec009341378fc306140ca0f94e129a3a4df1875ba2b5433e1b2b738e2c78c6a2eaf24ab0e87
data/README.md CHANGED
@@ -118,6 +118,30 @@ end
118
118
  client.chat('Be poetic.', stream:)
119
119
  ```
120
120
 
121
+ #### Max Output Tokens
122
+
123
+ `max_tokens:` caps the response, mapping to Gemini's `generationConfig.maxOutputTokens`:
124
+
125
+ ```ruby
126
+ client.chat("Summarize this page.", model: "gemini-3.7-flash", max_tokens: 8_000)
127
+ ```
128
+
129
+ It can also be set globally, and a per-call value wins:
130
+
131
+ ```ruby
132
+ OmniAI::Google.configure do |config|
133
+ config.chat_options[:max_tokens] = 8_000
134
+ end
135
+ ```
136
+
137
+ The value is passed through unchanged — no floor is imposed, so the number you ask for is the number that reaches the wire. When the cap is hit, `response.finish_reason.reason` is `:length`.
138
+
139
+ **Size it as thinking headroom plus expected answer.** Unlike Anthropic's answer-only ceiling, Gemini spends this budget on thinking *before* emitting an answer. A cap sized to the expected answer alone gets consumed by thinking on any request the model reasons about:
140
+
141
+ ```
142
+ max_tokens: 200 -> finish_reason :length, 196 output tokens, 115 of them thinking, 81 characters of answer
143
+ ```
144
+
121
145
  #### Extended Thinking
122
146
 
123
147
  Gemini models support extended thinking, which shows the model's reasoning process.
@@ -93,9 +93,15 @@ module OmniAI
93
93
  }.compact, json: payload)
94
94
  end
95
95
 
96
+ # `chat_options` is forwarded verbatim, so a key this class builds itself must be excluded or it is sent
97
+ # twice — and `max_tokens` is OmniAI's normalized name, not one Gemini knows. Left in, it reaches the
98
+ # wire as an unknown top-level field and the request fails with
99
+ # `Invalid JSON payload received. Unknown name "max_tokens"`. `#generation_config` consumes it and
100
+ # emits `maxOutputTokens` instead.
101
+ #
96
102
  # @return [Hash]
97
103
  def payload
98
- OmniAI::Google.config.chat_options.merge({
104
+ OmniAI::Google.config.chat_options.except(:max_tokens).merge({
99
105
  system_instruction: @prompt.messages.find(&:system?)&.serialize(context:),
100
106
  contents: @prompt.messages.reject(&:system?).map { |message| message.serialize(context:) },
101
107
  tools:,
@@ -138,11 +144,26 @@ module OmniAI
138
144
 
139
145
  data[:temperature] = @temperature if @temperature
140
146
  data[:thinkingConfig] = thinking_config if @options[:thinking]
147
+ data[:maxOutputTokens] = max_tokens if max_tokens
141
148
 
142
149
  data = data.compact
143
150
  data unless data.empty?
144
151
  end
145
152
 
153
+ # A per-call `max_tokens:`, falling back to `config.chat_options[:max_tokens]` so a cap can also be set
154
+ # globally — the same precedence omniai-anthropic applies. The value is passed through unchanged: no
155
+ # floor is imposed, so the number a caller asks for is the number that reaches the wire.
156
+ #
157
+ # Note that Gemini spends this budget on thinking BEFORE emitting an answer, unlike Anthropic's
158
+ # answer-only ceiling. A cap sized to the expected answer alone will be consumed by thinking on any
159
+ # request the model reasons about, returning `finishReason: MAX_TOKENS` with little or no text. Size it
160
+ # as thinking headroom plus expected answer.
161
+ #
162
+ # @return [Integer, nil]
163
+ def max_tokens
164
+ @options[:max_tokens] || OmniAI::Google.config.chat_options[:max_tokens]
165
+ end
166
+
146
167
  # @return [String]
147
168
  def path
148
169
  "#{@client.path}/models/#{@model}:#{operation}"
@@ -121,9 +121,26 @@ module OmniAI
121
121
  !@credentials.nil?
122
122
  end
123
123
 
124
+ # Vertex AI is served from three host shapes under `googleapis.com`: the global `aiplatform`, a
125
+ # region-prefixed `<region>-aiplatform`, and the multi-region `aiplatform.<geo>.rep`. Only the first two
126
+ # contain the literal "aiplatform.googleapis.com", so a substring test misses the multi-region endpoint.
127
+ #
128
+ # Deliberately wider than those three shapes: any labels are accepted between `aiplatform` and
129
+ # `googleapis.com`, so a future multi-region shape needs no change here. Everything it accepts is still
130
+ # under `googleapis.com`.
131
+ #
132
+ # Matched against the parsed hostname rather than the raw host, so a proxy whose path or query merely
133
+ # mentions a Vertex host is not treated as Vertex. A host given with no scheme has no hostname to parse, so
134
+ # the raw value is matched instead; the pattern is anchored, which keeps that safe — note that a schemeless
135
+ # host carrying a path therefore does not match.
136
+ VERTEX_HOSTNAME = /\A(?:[a-z0-9-]+-)?aiplatform(?:\.[a-z0-9-]+)*\.googleapis\.com\z/
137
+
124
138
  # @return [Boolean]
125
139
  def vertex?
126
- @host.include?("aiplatform.googleapis.com")
140
+ hostname = URI.parse(@host).hostname || @host
141
+ VERTEX_HOSTNAME.match?(hostname.downcase)
142
+ rescue URI::InvalidURIError
143
+ false
127
144
  end
128
145
 
129
146
  private
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OmniAI
4
4
  module Google
5
- VERSION = "3.15.0"
5
+ VERSION = "3.17.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.15.0
4
+ version: 3.17.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Sylvestre