Skip to content

Commit 3d0e791

Browse files
authored
feat(models): add Gemini 4 Argon (#8970)
* feat(models): add Gemini 4 Argon * chore(models): document Gemini model ID normalization
1 parent d193173 commit 3d0e791

12 files changed

Lines changed: 277 additions & 64 deletions

File tree

‎apps/docs/content/docs/workflows/blocks/agent.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ Live tool-call chips stream for **OpenAI, Anthropic, Azure Anthropic, Google, Ve
168168
| Anthropic | Summaries only — These generations omit full thinking; Sim requests summarized thinking on streaming runs. | `claude-fable-5-1`, `claude-fable-5`, `claude-sonnet-5-5`, `claude-sonnet-5`, `claude-opus-5-5`, `claude-opus-5`, `claude-opus-4-8`, `claude-opus-4-7`, `claude-opus-4-6`, `claude-sonnet-4-6`, `claude-opus-4-5`, `claude-sonnet-4-5`, `claude-haiku-5-5`, `claude-haiku-4-5` |
169169
| Azure OpenAI | Summaries only — Requires OpenAI organization verification; falls back to no summaries. | `azure/gpt-6.1-sol`, `azure/gpt-6-sol`, `azure/gpt-6-luna`, `azure/gpt-6-astra`, `azure/gpt-5.6-sol`, `azure/gpt-5.6-terra`, `azure/gpt-5.6-luna`, `azure/gpt-5.5`, `azure/gpt-5.4-pro`, `azure/gpt-5.4`, `azure/gpt-5.4-mini`, `azure/gpt-5.4-nano`, `azure/gpt-5.2`, `azure/gpt-5.1`, `azure/gpt-5.1-codex`, `azure/gpt-5`, `azure/gpt-5-mini`, `azure/gpt-5-nano`, `azure/o3`, `azure/o4-mini` |
170170
| Azure Anthropic | Summaries only — These generations omit full thinking; Sim requests summarized thinking on streaming runs. | `azure-anthropic/claude-fable-5-1`, `azure-anthropic/claude-opus-5-5`, `azure-anthropic/claude-sonnet-5-5`, `azure-anthropic/claude-haiku-5-5`, `azure-anthropic/claude-opus-5`, `azure-anthropic/claude-opus-4-8`, `azure-anthropic/claude-opus-4-7`, `azure-anthropic/claude-opus-4-6`, `azure-anthropic/claude-opus-4-5`, `azure-anthropic/claude-sonnet-5`, `azure-anthropic/claude-sonnet-4-6`, `azure-anthropic/claude-sonnet-4-5`, `azure-anthropic/claude-opus-4-1`, `azure-anthropic/claude-haiku-4-5` |
171-
| Google | Summaries only | `gemini-3.8-flash`, `gemini-3.7-flash`, `gemini-3.6-flash`, `gemini-3.5-flash-lite`, `gemini-3.5-flash`, `gemini-3.1-pro-preview`, `gemini-3.1-flash-lite`, `gemini-3-flash-preview`, `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-2.5-flash-lite` |
171+
| Google | Summaries only | `gemini-4-argon`, `gemini-3.8-flash`, `gemini-3.7-flash`, `gemini-3.6-flash`, `gemini-3.5-flash-lite`, `gemini-3.5-flash`, `gemini-3.1-pro-preview`, `gemini-3.1-flash-lite`, `gemini-3-flash-preview`, `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-2.5-flash-lite` |
172172
| Vertex AI | Summaries only | `vertex/gemini-3.8-flash`, `vertex/gemini-3.7-flash`, `vertex/gemini-3.6-flash`, `vertex/gemini-3.5-flash-lite`, `vertex/gemini-3.5-flash`, `vertex/gemini-3.1-pro-preview`, `vertex/gemini-3.1-flash-lite`, `vertex/gemini-3-flash-preview`, `vertex/gemini-2.5-pro`, `vertex/gemini-2.5-flash`, `vertex/gemini-2.5-flash-lite` |
173173
| DeepSeek | Full thinking deltas | `deepseek-v4-pro`, `deepseek-flash`, `deepseek-v4-flash`, `deepseek-reasoner` |
174174
| xAI | Full thinking deltas | `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309` |

‎apps/sim/lib/knowledge/documents/image-extraction-gemini.ts‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { resolveProviderKey } from '@/lib/embeddings/keys'
99
import { PermanentDocumentProcessingError } from '@/lib/knowledge/documents/document-processing-error'
1010
import { readBoundedHttpErrorPayload } from '@/lib/knowledge/documents/utils'
1111
import { getProviderModels } from '@/providers/models'
12-
import { isGemini3Model } from '@/providers/utils'
12+
import { isGemini3OrLaterModel } from '@/providers/utils'
1313

1414
const logger = createLogger('GeminiImageExtraction')
1515

@@ -22,7 +22,7 @@ export const DEFAULT_IMAGE_EXTRACTION_MODEL = 'gemini-3.8-flash'
2222

2323
/**
2424
* Transcription needs little reasoning, and Gemini bills thinking as output that
25-
* also spends the output budget. Every Gemini 3 model accepts `LOW`.
25+
* also spends the output budget. Gemini 3 and later models accept `LOW`.
2626
*/
2727
const THINKING_LEVEL = 'LOW'
2828

@@ -72,19 +72,19 @@ export class GeminiImageExtractionError extends Error {
7272

7373
/**
7474
* The model from `KB_IMAGE_EXTRACTION_MODEL`, or the default when it is unset or
75-
* not a Gemini 3 model in the catalog: the request uses Gemini 3's JSON-schema
76-
* output and thinking level, and only catalog models carry the pricing that
75+
* not a Gemini 3 or later model in the catalog: the request uses modern Gemini
76+
* JSON-schema output and thinking level, and only catalog models carry the pricing that
7777
* bills each image. Never throws: a bad value must not stop indexing.
7878
*/
7979
export function resolveImageExtractionModel(): string {
8080
const configured = env.KB_IMAGE_EXTRACTION_MODEL?.trim()
8181
if (!configured) return DEFAULT_IMAGE_EXTRACTION_MODEL
8282
const catalogModel = getProviderModels('google').find(
83-
(id) => id.toLowerCase() === configured.toLowerCase() && isGemini3Model(id)
83+
(id) => id.toLowerCase() === configured.toLowerCase() && isGemini3OrLaterModel(id)
8484
)
8585
if (catalogModel) return catalogModel
8686
logger.warn(
87-
`KB_IMAGE_EXTRACTION_MODEL="${configured}" is not a supported Gemini 3 model, falling back to ${DEFAULT_IMAGE_EXTRACTION_MODEL}`
87+
`KB_IMAGE_EXTRACTION_MODEL="${configured}" is not a supported Gemini 3 or later model, falling back to ${DEFAULT_IMAGE_EXTRACTION_MODEL}`
8888
)
8989
return DEFAULT_IMAGE_EXTRACTION_MODEL
9090
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { FunctionCallingConfigMode, type GenerateContentConfig, type Schema } from '@google/genai'
2+
import { cleanSchemaForGemini } from '@/providers/google/utils'
3+
import type { ProviderRequest } from '@/providers/types'
4+
import { isGemini3OrLaterModel } from '@/providers/utils'
5+
6+
/** Applies the response schema only on turns that can accept it alongside their tool configuration. */
7+
export function applyGeminiResponseFormat(
8+
config: GenerateContentConfig,
9+
responseFormat: ProviderRequest['responseFormat'],
10+
model: string
11+
): void {
12+
if (!responseFormat) return
13+
14+
if (config.toolConfig?.functionCallingConfig?.mode === FunctionCallingConfigMode.ANY) {
15+
config.responseMimeType = undefined
16+
config.responseSchema = undefined
17+
config.responseJsonSchema = undefined
18+
return
19+
}
20+
21+
if (config.tools?.length && !isGemini3OrLaterModel(model)) return
22+
23+
config.responseMimeType = 'application/json'
24+
if (isGemini3OrLaterModel(model)) {
25+
config.responseJsonSchema = responseFormat.schema
26+
} else {
27+
config.responseSchema = cleanSchemaForGemini(responseFormat.schema) as Schema
28+
}
29+
}

‎apps/sim/providers/gemini/core.test.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ describe('Vertex Gemini request compatibility', () => {
118118
})
119119

120120
it.each([
121+
'gemini-4-argon',
121122
'vertex/gemini-3.8-flash',
122123
'vertex/gemini-3.7-flash',
123124
'vertex/gemini-3.6-flash',

‎apps/sim/providers/gemini/core.ts‎

Lines changed: 13 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
type GoogleGenAI,
88
type Interactions,
99
type Part,
10-
type Schema,
1110
type ThinkingConfig,
1211
type ToolConfig,
1312
} from '@google/genai'
@@ -21,11 +20,11 @@ import {
2120
captureProviderConversationStep,
2221
recordProviderConversationToolError,
2322
} from '@/providers/conversation-history'
23+
import { applyGeminiResponseFormat } from '@/providers/gemini/config'
2424
import { createGeminiStreamingToolLoopStream } from '@/providers/gemini/streaming-tool-loop'
2525
import { priceGeminiTokens, splitGeminiTokens, splitGeminiUsage } from '@/providers/gemini/usage'
2626
import {
2727
checkForForcedToolUsage,
28-
cleanSchemaForGemini,
2928
convertToGeminiFormat,
3029
convertUsageMetadata,
3130
createReadableStreamFromGeminiStream,
@@ -53,7 +52,7 @@ import type {
5352
} from '@/providers/types'
5453
import {
5554
isDeepResearchModel,
56-
isGemini3Model,
55+
isGemini3OrLaterModel,
5756
prepareToolExecution,
5857
prepareToolsWithUsageControl,
5958
sumToolCosts,
@@ -362,19 +361,14 @@ function buildNextConfig(
362361
if (allForcedToolsUsed && request.responseFormat) {
363362
nextConfig.tools = undefined
364363
nextConfig.toolConfig = undefined
365-
if (isGemini3Model(model)) {
366-
logger.info('Gemini 3: Stripping tools after forced tool execution, schema already set')
367-
} else {
368-
nextConfig.responseMimeType = 'application/json'
369-
nextConfig.responseSchema = cleanSchemaForGemini(request.responseFormat.schema) as Schema
370-
logger.info('Using structured output for final response after tool execution')
371-
}
364+
logger.info('Using structured output for final response after tool execution')
372365
} else if (state.currentToolConfig) {
373366
nextConfig.toolConfig = state.currentToolConfig
374367
} else {
375368
nextConfig.toolConfig = { functionCallingConfig: { mode: FunctionCallingConfigMode.AUTO } }
376369
}
377370

371+
applyGeminiResponseFormat(nextConfig, request.responseFormat, model)
378372
return nextConfig
379373
}
380374

@@ -1006,35 +1000,20 @@ export async function executeGeminiRequest(
10061000
geminiConfig.systemInstruction = systemInstruction
10071001
}
10081002

1009-
// Handle response format
1010-
if (request.responseFormat && !tools?.length) {
1011-
geminiConfig.responseMimeType = 'application/json'
1012-
geminiConfig.responseSchema = cleanSchemaForGemini(request.responseFormat.schema) as Schema
1013-
logger.info('Using Gemini native structured output format')
1014-
} else if (request.responseFormat && tools?.length && isGemini3Model(model)) {
1015-
geminiConfig.responseMimeType = 'application/json'
1016-
geminiConfig.responseJsonSchema = request.responseFormat.schema
1017-
logger.info('Using Gemini 3 structured output with tools (responseJsonSchema)')
1018-
} else if (request.responseFormat && tools?.length) {
1019-
logger.warn(
1020-
'Gemini 2 does not support responseFormat with tools. Structured output will be applied after tool execution.'
1021-
)
1022-
}
1023-
1024-
// Gemini 3.x takes thinkingLevel directly; Gemini 2.5-series rejects it and needs thinkingBudget.
1003+
// Gemini 3 and later take thinkingLevel directly; Gemini 2.5-series rejects it and needs thinkingBudget.
10251004
// includeThoughts is required for thought parts to appear; it is requested
10261005
// only on agent-events runs so legacy runs keep the pre-agent-events payload.
10271006
if (request.thinkingLevel && request.thinkingLevel !== 'none') {
10281007
const thinkingConfig: ThinkingConfig = { includeThoughts: request.agentEvents === true }
1029-
if (isGemini3Model(model)) {
1008+
if (isGemini3OrLaterModel(model)) {
10301009
thinkingConfig.thinkingLevel = mapToThinkingLevel(request.thinkingLevel)
10311010
} else {
10321011
thinkingConfig.thinkingBudget = mapToThinkingBudget(model, request.thinkingLevel)
10331012
}
10341013
geminiConfig.thinkingConfig = thinkingConfig
10351014
} else if (
10361015
request.thinkingLevel === 'none' &&
1037-
!isGemini3Model(model) &&
1016+
!isGemini3OrLaterModel(model) &&
10381017
supportsDisablingGemini25Thinking(model)
10391018
) {
10401019
// Omitting thinkingConfig falls back to the API's dynamic default (ON for gemini-2.5-flash),
@@ -1087,17 +1066,19 @@ export async function executeGeminiRequest(
10871066
}
10881067
}
10891068

1069+
applyGeminiResponseFormat(geminiConfig, request.responseFormat, model)
1070+
10901071
const initialCallTime = Date.now()
10911072
/**
10921073
* Gemini 2 cannot combine responseSchema with tools, so structured output
10931074
* is applied on a final schema-configured request after tools settle — the
10941075
* silent path does this; the live loop would break as soon as a turn has
1095-
* no calls and skip the schema. Gemini 3 carries responseJsonSchema
1096-
* alongside tools, so its live loop keeps structured output.
1076+
* no calls and skip the schema. Gemini 3 and later carry responseJsonSchema
1077+
* alongside automatic tools, or apply it after forced calls settle.
10971078
*/
10981079
const hasActiveTools = Boolean(geminiConfig.tools?.length)
10991080
const responseFormatNeedsFinalPass =
1100-
Boolean(request.responseFormat) && hasActiveTools && !isGemini3Model(model)
1081+
Boolean(request.responseFormat) && hasActiveTools && !isGemini3OrLaterModel(model)
11011082
const liveToolLoopSupported = !responseFormatNeedsFinalPass
11021083
const shouldStream = request.stream && !hasActiveTools
11031084

@@ -1262,10 +1243,7 @@ export async function executeGeminiRequest(
12621243
tools: undefined,
12631244
toolConfig: undefined,
12641245
}
1265-
if (request.responseFormat && !isGemini3Model(model)) {
1266-
finalConfig.responseMimeType = 'application/json'
1267-
finalConfig.responseSchema = cleanSchemaForGemini(request.responseFormat.schema) as Schema
1268-
}
1246+
applyGeminiResponseFormat(finalConfig, request.responseFormat, model)
12691247

12701248
const finalStartTime = Date.now()
12711249
const finalResponsePayload = await prepareConversationGeneration(request, 'gemini', {

‎apps/sim/providers/gemini/streaming-tool-loop.ts‎

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import {
2020
type GenerateContentResponse,
2121
type GoogleGenAI,
2222
type Part,
23-
type Schema,
2423
type ToolConfig,
2524
} from '@google/genai'
2625
import type { Logger } from '@sim/logger'
@@ -32,9 +31,9 @@ import {
3231
captureProviderConversationStep,
3332
recordProviderConversationToolError,
3433
} from '@/providers/conversation-history'
34+
import { applyGeminiResponseFormat } from '@/providers/gemini/config'
3535
import {
3636
checkForForcedToolUsage,
37-
cleanSchemaForGemini,
3837
convertUsageMetadata,
3938
ensureStructResponse,
4039
geminiRetryDelayMs,
@@ -51,7 +50,7 @@ import {
5150
import { ensureToolCallId } from '@/providers/tool-call-id'
5251
import { enrichLastModelSegment } from '@/providers/trace-enrichment'
5352
import type { ModelPricing, ProviderRequest, TimeSegment } from '@/providers/types'
54-
import { isGemini3Model, prepareToolExecution, sumToolCosts } from '@/providers/utils'
53+
import { prepareToolExecution, sumToolCosts } from '@/providers/utils'
5554
import type { GeminiUsage } from './types'
5655
import { priceGeminiTokens, splitGeminiUsage } from './usage'
5756

@@ -96,19 +95,14 @@ function buildNextConfig(
9695
if (allForcedToolsUsed && request.responseFormat) {
9796
nextConfig.tools = undefined
9897
nextConfig.toolConfig = undefined
99-
if (isGemini3Model(model)) {
100-
logger.info('Gemini 3: Stripping tools after forced tool execution, schema already set')
101-
} else {
102-
nextConfig.responseMimeType = 'application/json'
103-
nextConfig.responseSchema = cleanSchemaForGemini(request.responseFormat.schema) as Schema
104-
logger.info('Using structured output for final response after tool execution')
105-
}
98+
logger.info('Using structured output for final response after tool execution')
10699
} else if (currentToolConfig) {
107100
nextConfig.toolConfig = currentToolConfig
108101
} else {
109102
nextConfig.toolConfig = { functionCallingConfig: { mode: FunctionCallingConfigMode.AUTO } }
110103
}
111104

105+
applyGeminiResponseFormat(nextConfig, request.responseFormat, model)
112106
return nextConfig
113107
}
114108

@@ -310,6 +304,8 @@ export function createGeminiStreamingToolLoopStream(
310304
model
311305
)
312306

307+
applyGeminiResponseFormat(turnConfig, request.responseFormat, model)
308+
313309
const modelStart = Date.now()
314310
const turnPayload = await prepareConversationGeneration(request, 'gemini', {
315311
model,

0 commit comments

Comments
 (0)