feat(core,ui): Add experiment-gated support for gemini flash 3.1 lite (#23794)

This commit is contained in:
christine betts
2026-03-25 16:28:49 -04:00
committed by GitHub
parent 012740b68f
commit 3ada29fb51
30 changed files with 354 additions and 52 deletions
+43 -1
View File
@@ -1820,6 +1820,10 @@ export class Config implements McpContext, AgentLoopContext {
const primaryModel = resolveModel(
this.getModel(),
this.getGemini31LaunchedSync(),
this.getGemini31FlashLiteLaunchedSync(),
this.getUseCustomToolModelSync(),
this.getHasAccessToPreviewModel(),
this,
);
return this.modelQuotas.get(primaryModel)?.remaining;
}
@@ -1832,6 +1836,10 @@ export class Config implements McpContext, AgentLoopContext {
const primaryModel = resolveModel(
this.getModel(),
this.getGemini31LaunchedSync(),
this.getGemini31FlashLiteLaunchedSync(),
this.getUseCustomToolModelSync(),
this.getHasAccessToPreviewModel(),
this,
);
return this.modelQuotas.get(primaryModel)?.limit;
}
@@ -1844,6 +1852,10 @@ export class Config implements McpContext, AgentLoopContext {
const primaryModel = resolveModel(
this.getModel(),
this.getGemini31LaunchedSync(),
this.getGemini31FlashLiteLaunchedSync(),
this.getUseCustomToolModelSync(),
this.getHasAccessToPreviewModel(),
this,
);
return this.modelQuotas.get(primaryModel)?.resetTime;
}
@@ -2907,7 +2919,7 @@ export class Config implements McpContext, AgentLoopContext {
}
/**
* Returns whether Gemini 3.1 has been launched.
* Returns whether Gemini 3.1 Pro has been launched.
* This method is async and ensures that experiments are loaded before returning the result.
*/
async getGemini31Launched(): Promise<boolean> {
@@ -2915,6 +2927,15 @@ export class Config implements McpContext, AgentLoopContext {
return this.getGemini31LaunchedSync();
}
/**
* Returns whether Gemini 3.1 Flash Lite has been launched.
* This method is async and ensures that experiments are loaded before returning the result.
*/
async getGemini31FlashLiteLaunched(): Promise<boolean> {
await this.ensureExperimentsLoaded();
return this.getGemini31FlashLiteLaunchedSync();
}
/**
* Returns whether the custom tool model should be used.
*/
@@ -2956,6 +2977,27 @@ export class Config implements McpContext, AgentLoopContext {
);
}
/**
* Returns whether Gemini 3.1 Flash Lite has been launched.
*
* Note: This method should only be called after startup, once experiments have been loaded.
* If you need to call this during startup or from an async context, use
* getGemini31FlashLiteLaunched instead.
*/
getGemini31FlashLiteLaunchedSync(): boolean {
const authType = this.contentGeneratorConfig?.authType;
if (
authType === AuthType.USE_GEMINI ||
authType === AuthType.USE_VERTEX_AI
) {
return true;
}
return (
this.experiments?.flags[ExperimentFlags.GEMINI_3_1_FLASH_LITE_LAUNCHED]
?.boolValue ?? false
);
}
private async ensureExperimentsLoaded(): Promise<void> {
if (!this.experimentsPromise) {
return;
@@ -218,6 +218,11 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
model: 'gemini-3-flash-preview',
},
},
'chat-compression-3.1-flash-lite': {
modelConfig: {
model: 'gemini-3.1-flash-lite-preview',
},
},
'chat-compression-2.5-pro': {
modelConfig: {
model: 'gemini-2.5-pro',
@@ -436,6 +441,15 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
'auto-gemini-2.5': {
default: 'gemini-2.5-pro',
},
'gemini-3.1-flash-lite-preview': {
default: 'gemini-3.1-flash-lite-preview',
contexts: [
{
condition: { useGemini3_1FlashLite: false },
target: 'gemini-2.5-flash-lite',
},
],
},
flash: {
default: 'gemini-3-flash-preview',
contexts: [
@@ -447,6 +461,12 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
},
'flash-lite': {
default: 'gemini-2.5-flash-lite',
contexts: [
{
condition: { useGemini3_1FlashLite: true },
target: 'gemini-3.1-flash-lite-preview',
},
],
},
},
classifierIdResolutions: {
+94 -29
View File
@@ -21,6 +21,7 @@ import {
supportsMultimodalFunctionResponse,
GEMINI_MODEL_ALIAS_PRO,
GEMINI_MODEL_ALIAS_FLASH,
GEMINI_MODEL_ALIAS_FLASH_LITE,
GEMINI_MODEL_ALIAS_AUTO,
PREVIEW_GEMINI_FLASH_MODEL,
PREVIEW_GEMINI_MODEL_AUTO,
@@ -61,9 +62,26 @@ describe('Dynamic Configuration Parity', () => {
];
const flagCombos = [
{ useGemini3_1: false, useCustomToolModel: false },
{ useGemini3_1: true, useCustomToolModel: false },
{ useGemini3_1: true, useCustomToolModel: true },
{
useGemini3_1: false,
useGemini3_1FlashLite: false,
useCustomToolModel: false,
},
{
useGemini3_1: true,
useGemini3_1FlashLite: false,
useCustomToolModel: false,
},
{
useGemini3_1: true,
useGemini3_1FlashLite: true,
useCustomToolModel: false,
},
{
useGemini3_1: true,
useGemini3_1FlashLite: true,
useCustomToolModel: true,
},
];
it('resolveModel should match legacy behavior when dynamicModelConfiguration flag enabled.', () => {
@@ -84,6 +102,7 @@ describe('Dynamic Configuration Parity', () => {
const legacy = resolveModel(
model,
flags.useGemini3_1,
flags.useGemini3_1FlashLite,
flags.useCustomToolModel,
hasAccess,
mockLegacyConfig,
@@ -91,6 +110,7 @@ describe('Dynamic Configuration Parity', () => {
const dynamic = resolveModel(
model,
flags.useGemini3_1,
flags.useGemini3_1FlashLite,
flags.useCustomToolModel,
hasAccess,
mockDynamicConfig,
@@ -129,6 +149,7 @@ describe('Dynamic Configuration Parity', () => {
anchor,
tier,
flags.useGemini3_1,
flags.useGemini3_1FlashLite,
flags.useCustomToolModel,
hasAccess,
mockLegacyConfig,
@@ -137,6 +158,7 @@ describe('Dynamic Configuration Parity', () => {
anchor,
tier,
flags.useGemini3_1,
flags.useGemini3_1FlashLite,
flags.useCustomToolModel,
hasAccess,
mockDynamicConfig,
@@ -369,7 +391,7 @@ describe('resolveModel', () => {
});
it('should return Gemini 3.1 Pro Custom Tools when auto-gemini-3 is requested, useGemini3_1 is true, and useCustomToolModel is true', () => {
const model = resolveModel(PREVIEW_GEMINI_MODEL_AUTO, true, true);
const model = resolveModel(PREVIEW_GEMINI_MODEL_AUTO, true, false, true);
expect(model).toBe(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL);
});
@@ -378,6 +400,16 @@ describe('resolveModel', () => {
expect(model).toBe(DEFAULT_GEMINI_MODEL);
});
it('should return the Default Flash-Lite model when flash-lite is requested', () => {
const model = resolveModel(GEMINI_MODEL_ALIAS_FLASH_LITE);
expect(model).toBe(DEFAULT_GEMINI_FLASH_LITE_MODEL);
});
it('should return the Preview Flash-Lite model when flash-lite is requested and useGemini3_1FlashLite is true', () => {
const model = resolveModel(GEMINI_MODEL_ALIAS_FLASH_LITE, false, true);
expect(model).toBe(PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL);
});
it('should return the requested model as-is for explicit specific models', () => {
expect(resolveModel(DEFAULT_GEMINI_MODEL)).toBe(DEFAULT_GEMINI_MODEL);
expect(resolveModel(DEFAULT_GEMINI_FLASH_MODEL)).toBe(
@@ -397,39 +429,45 @@ describe('resolveModel', () => {
describe('hasAccessToPreview logic', () => {
it('should return default model when access to preview is false and preview model is requested', () => {
expect(resolveModel(PREVIEW_GEMINI_MODEL, false, false, false)).toBe(
DEFAULT_GEMINI_MODEL,
);
expect(
resolveModel(PREVIEW_GEMINI_MODEL, false, false, false, false),
).toBe(DEFAULT_GEMINI_MODEL);
});
it('should return default flash model when access to preview is false and preview flash model is requested', () => {
expect(
resolveModel(PREVIEW_GEMINI_FLASH_MODEL, false, false, false),
resolveModel(PREVIEW_GEMINI_FLASH_MODEL, false, false, false, false),
).toBe(DEFAULT_GEMINI_FLASH_MODEL);
});
it('should return default flash lite model when access to preview is false and preview flash lite model is requested', () => {
expect(
resolveModel(PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL, false, false, false),
resolveModel(
PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL,
false,
false,
false,
false,
),
).toBe(DEFAULT_GEMINI_FLASH_LITE_MODEL);
});
it('should return default model when access to preview is false and auto-gemini-3 is requested', () => {
expect(resolveModel(PREVIEW_GEMINI_MODEL_AUTO, false, false, false)).toBe(
DEFAULT_GEMINI_MODEL,
);
expect(
resolveModel(PREVIEW_GEMINI_MODEL_AUTO, false, false, false, false),
).toBe(DEFAULT_GEMINI_MODEL);
});
it('should return default model when access to preview is false and Gemini 3.1 is requested', () => {
expect(resolveModel(PREVIEW_GEMINI_MODEL_AUTO, true, false, false)).toBe(
DEFAULT_GEMINI_MODEL,
);
expect(
resolveModel(PREVIEW_GEMINI_MODEL_AUTO, true, false, false, false),
).toBe(DEFAULT_GEMINI_MODEL);
});
it('should still return default model when access to preview is false and auto-gemini-2.5 is requested', () => {
expect(resolveModel(DEFAULT_GEMINI_MODEL_AUTO, false, false, false)).toBe(
DEFAULT_GEMINI_MODEL,
);
expect(
resolveModel(DEFAULT_GEMINI_MODEL_AUTO, false, false, false, false),
).toBe(DEFAULT_GEMINI_MODEL);
});
});
});
@@ -521,6 +559,7 @@ describe('resolveClassifierModel', () => {
PREVIEW_GEMINI_MODEL_AUTO,
GEMINI_MODEL_ALIAS_PRO,
true,
false,
true,
),
).toBe(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL);
@@ -532,7 +571,11 @@ describe('isActiveModel', () => {
expect(isActiveModel(DEFAULT_GEMINI_MODEL)).toBe(true);
expect(isActiveModel(PREVIEW_GEMINI_MODEL)).toBe(true);
expect(isActiveModel(DEFAULT_GEMINI_FLASH_MODEL)).toBe(true);
expect(isActiveModel(PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL)).toBe(true);
});
it('should return false for Gemini 3.1 models when Gemini 3.1 is not launched', () => {
expect(isActiveModel(PREVIEW_GEMINI_3_1_MODEL)).toBe(false);
expect(isActiveModel(PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL)).toBe(false);
});
it('should return true for unknown models and aliases', () => {
@@ -546,31 +589,53 @@ describe('isActiveModel', () => {
it('should return true for other valid models when useGemini3_1 is true', () => {
expect(isActiveModel(DEFAULT_GEMINI_MODEL, true)).toBe(true);
expect(isActiveModel(PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL, true)).toBe(true);
});
it('should return true for PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL only when useGemini3_1FlashLite is true', () => {
expect(
isActiveModel(PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL, false, true),
).toBe(true);
expect(isActiveModel(PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL, true, true)).toBe(
true,
);
expect(
isActiveModel(PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL, true, false),
).toBe(false);
});
it('should correctly filter Gemini 3.1 models based on useCustomToolModel when useGemini3_1 is true', () => {
// When custom tools are preferred, standard 3.1 should be inactive
expect(isActiveModel(PREVIEW_GEMINI_3_1_MODEL, true, true)).toBe(false);
expect(isActiveModel(PREVIEW_GEMINI_3_1_MODEL, true, false, true)).toBe(
false,
);
expect(
isActiveModel(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, true, true),
isActiveModel(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, true, false, true),
).toBe(true);
// When custom tools are NOT preferred, custom tools 3.1 should be inactive
expect(isActiveModel(PREVIEW_GEMINI_3_1_MODEL, true, false)).toBe(true);
expect(isActiveModel(PREVIEW_GEMINI_3_1_MODEL, true, false, false)).toBe(
true,
);
expect(
isActiveModel(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, true, false),
isActiveModel(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, true, false, false),
).toBe(false);
});
it('should return false for both Gemini 3.1 models when useGemini3_1 is false', () => {
expect(isActiveModel(PREVIEW_GEMINI_3_1_MODEL, false, true)).toBe(false);
expect(isActiveModel(PREVIEW_GEMINI_3_1_MODEL, false, false)).toBe(false);
it('should return false for Gemini 3.1 models when useGemini3_1 and useGemini3_1FlashLite are false', () => {
expect(isActiveModel(PREVIEW_GEMINI_3_1_MODEL, false, false, true)).toBe(
false,
);
expect(isActiveModel(PREVIEW_GEMINI_3_1_MODEL, false, false, false)).toBe(
false,
);
expect(
isActiveModel(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, false, true),
isActiveModel(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, false, false, true),
).toBe(false);
expect(
isActiveModel(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, false, false),
isActiveModel(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, false, false, false),
).toBe(false);
expect(
isActiveModel(PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL, false, false),
).toBe(false);
});
});
+23 -3
View File
@@ -6,6 +6,7 @@
export interface ModelResolutionContext {
useGemini3_1?: boolean;
useGemini3_1FlashLite?: boolean;
useCustomTools?: boolean;
hasAccessToPreview?: boolean;
requestedModel?: string;
@@ -97,6 +98,7 @@ export const DEFAULT_THINKING_MODE = 8192;
export function resolveModel(
requestedModel: string,
useGemini3_1: boolean = false,
useGemini3_1FlashLite: boolean = false,
useCustomToolModel: boolean = false,
hasAccessToPreview: boolean = true,
config?: ModelCapabilityContext,
@@ -104,6 +106,7 @@ export function resolveModel(
if (config?.getExperimentalDynamicModelConfiguration?.() === true) {
const resolved = config.modelConfigService.resolveModelId(requestedModel, {
useGemini3_1,
useGemini3_1FlashLite,
useCustomTools: useCustomToolModel,
hasAccessToPreview,
});
@@ -146,7 +149,9 @@ export function resolveModel(
break;
}
case GEMINI_MODEL_ALIAS_FLASH_LITE: {
resolved = DEFAULT_GEMINI_FLASH_LITE_MODEL;
resolved = useGemini3_1FlashLite
? PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL
: DEFAULT_GEMINI_FLASH_LITE_MODEL;
break;
}
default: {
@@ -160,6 +165,8 @@ export function resolveModel(
switch (resolved) {
case PREVIEW_GEMINI_FLASH_MODEL:
return DEFAULT_GEMINI_FLASH_MODEL;
case PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL:
return DEFAULT_GEMINI_FLASH_LITE_MODEL;
case PREVIEW_GEMINI_MODEL:
case PREVIEW_GEMINI_3_1_MODEL:
case PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL:
@@ -193,6 +200,7 @@ export function resolveClassifierModel(
requestedModel: string,
modelAlias: string,
useGemini3_1: boolean = false,
useGemini3_1FlashLite: boolean = false,
useCustomToolModel: boolean = false,
hasAccessToPreview: boolean = true,
config?: ModelCapabilityContext,
@@ -203,6 +211,7 @@ export function resolveClassifierModel(
requestedModel,
{
useGemini3_1,
useGemini3_1FlashLite,
useCustomTools: useCustomToolModel,
hasAccessToPreview,
},
@@ -224,7 +233,12 @@ export function resolveClassifierModel(
}
return resolveModel(GEMINI_MODEL_ALIAS_FLASH);
}
return resolveModel(requestedModel, useGemini3_1, useCustomToolModel);
return resolveModel(
requestedModel,
useGemini3_1,
useGemini3_1FlashLite,
useCustomToolModel,
);
}
export function getDisplayString(
@@ -249,6 +263,8 @@ export function getDisplayString(
return PREVIEW_GEMINI_FLASH_MODEL;
case PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL:
return PREVIEW_GEMINI_3_1_MODEL;
case PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL:
return PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL;
default:
return model;
}
@@ -347,7 +363,7 @@ export function isCustomModel(
config?: ModelCapabilityContext,
): boolean {
if (config?.getExperimentalDynamicModelConfiguration?.() === true) {
const resolved = resolveModel(model, false, false, true, config);
const resolved = resolveModel(model, false, false, false, true, config);
return (
config.modelConfigService.getModelDefinition(resolved)?.tier ===
'custom' || !resolved.startsWith('gemini-')
@@ -420,11 +436,15 @@ export function supportsMultimodalFunctionResponse(
export function isActiveModel(
model: string,
useGemini3_1: boolean = false,
useGemini3_1FlashLite: boolean = false,
useCustomToolModel: boolean = false,
): boolean {
if (!VALID_GEMINI_MODELS.has(model)) {
return false;
}
if (model === PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL) {
return useGemini3_1FlashLite;
}
if (useGemini3_1) {
if (model === PREVIEW_GEMINI_MODEL) {
return false;