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 +4 -4
- data/README.md +24 -0
- data/lib/omniai/google/chat.rb +22 -1
- data/lib/omniai/google/client.rb +18 -1
- data/lib/omniai/google/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9fd43c08069868c8d20d91ac6e1028baf97643eda9018a0db146c73ce0828c15
|
|
4
|
+
data.tar.gz: 04ca6d4c4789b91c194abb44f8d2d178f1e65605644fe3c72a72824e8707a9e2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
data/lib/omniai/google/chat.rb
CHANGED
|
@@ -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}"
|
data/lib/omniai/google/client.rb
CHANGED
|
@@ -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.
|
|
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
|