Skip to content

Commit e844a57

Browse files
authored
feat(core): fallback to chat-base when using unrecognized models for chat (#19016)
1 parent 9c285ea commit e844a57

File tree

4 files changed

+100
-11
lines changed

4 files changed

+100
-11
lines changed

packages/core/src/core/client.test.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -892,7 +892,7 @@ ${JSON.stringify(
892892
// Assert
893893
expect(ideContextStore.get).toHaveBeenCalled();
894894
expect(mockTurnRunFn).toHaveBeenCalledWith(
895-
{ model: 'default-routed-model' },
895+
{ model: 'default-routed-model', isChatModel: true },
896896
initialRequest,
897897
expect.any(AbortSignal),
898898
undefined,
@@ -1722,7 +1722,7 @@ ${JSON.stringify(
17221722
expect(mockConfig.getModelRouterService).toHaveBeenCalled();
17231723
expect(mockRouterService.route).toHaveBeenCalled();
17241724
expect(mockTurnRunFn).toHaveBeenCalledWith(
1725-
{ model: 'routed-model' },
1725+
{ model: 'routed-model', isChatModel: true },
17261726
[{ text: 'Hi' }],
17271727
expect.any(AbortSignal),
17281728
undefined,
@@ -1740,7 +1740,7 @@ ${JSON.stringify(
17401740

17411741
expect(mockRouterService.route).toHaveBeenCalledTimes(1);
17421742
expect(mockTurnRunFn).toHaveBeenCalledWith(
1743-
{ model: 'routed-model' },
1743+
{ model: 'routed-model', isChatModel: true },
17441744
[{ text: 'Hi' }],
17451745
expect.any(AbortSignal),
17461746
undefined,
@@ -1758,7 +1758,7 @@ ${JSON.stringify(
17581758
expect(mockRouterService.route).toHaveBeenCalledTimes(1);
17591759
// Should stick to the first model
17601760
expect(mockTurnRunFn).toHaveBeenCalledWith(
1761-
{ model: 'routed-model' },
1761+
{ model: 'routed-model', isChatModel: true },
17621762
[{ text: 'Continue' }],
17631763
expect.any(AbortSignal),
17641764
undefined,
@@ -1776,7 +1776,7 @@ ${JSON.stringify(
17761776

17771777
expect(mockRouterService.route).toHaveBeenCalledTimes(1);
17781778
expect(mockTurnRunFn).toHaveBeenCalledWith(
1779-
{ model: 'routed-model' },
1779+
{ model: 'routed-model', isChatModel: true },
17801780
[{ text: 'Hi' }],
17811781
expect.any(AbortSignal),
17821782
undefined,
@@ -1798,7 +1798,7 @@ ${JSON.stringify(
17981798
expect(mockRouterService.route).toHaveBeenCalledTimes(2);
17991799
// Should use the newly routed model
18001800
expect(mockTurnRunFn).toHaveBeenCalledWith(
1801-
{ model: 'new-routed-model' },
1801+
{ model: 'new-routed-model', isChatModel: true },
18021802
[{ text: 'A new topic' }],
18031803
expect.any(AbortSignal),
18041804
undefined,
@@ -1826,7 +1826,7 @@ ${JSON.stringify(
18261826
expect(mockRouterService.route).toHaveBeenCalledTimes(1);
18271827
expect(mockTurnRunFn).toHaveBeenNthCalledWith(
18281828
1,
1829-
{ model: 'original-model' },
1829+
{ model: 'original-model', isChatModel: true },
18301830
[{ text: 'Hi' }],
18311831
expect.any(AbortSignal),
18321832
undefined,
@@ -1849,7 +1849,7 @@ ${JSON.stringify(
18491849
expect(mockRouterService.route).toHaveBeenCalledTimes(2);
18501850
expect(mockTurnRunFn).toHaveBeenNthCalledWith(
18511851
2,
1852-
{ model: 'fallback-model' },
1852+
{ model: 'fallback-model', isChatModel: true },
18531853
[{ text: 'Continue' }],
18541854
expect.any(AbortSignal),
18551855
undefined,
@@ -1935,7 +1935,7 @@ ${JSON.stringify(
19351935
// First call with original request
19361936
expect(mockTurnRunFn).toHaveBeenNthCalledWith(
19371937
1,
1938-
{ model: 'default-routed-model' },
1938+
{ model: 'default-routed-model', isChatModel: true },
19391939
initialRequest,
19401940
expect.any(AbortSignal),
19411941
undefined,
@@ -1944,7 +1944,7 @@ ${JSON.stringify(
19441944
// Second call with "Please continue."
19451945
expect(mockTurnRunFn).toHaveBeenNthCalledWith(
19461946
2,
1947-
{ model: 'default-routed-model' },
1947+
{ model: 'default-routed-model', isChatModel: true },
19481948
[{ text: 'System: Please continue.' }],
19491949
expect.any(AbortSignal),
19501950
undefined,

packages/core/src/core/client.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -656,7 +656,10 @@ export class GeminiClient {
656656
}
657657

658658
// availability logic
659-
const modelConfigKey: ModelConfigKey = { model: modelToUse };
659+
const modelConfigKey: ModelConfigKey = {
660+
model: modelToUse,
661+
isChatModel: true,
662+
};
660663
const { model: finalModel } = applyModelSelection(
661664
this.config,
662665
modelConfigKey,

packages/core/src/services/modelConfigService.test.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,71 @@ describe('ModelConfigService', () => {
729729
});
730730
});
731731

732+
describe('fallback behavior', () => {
733+
it('should fallback to chat-base if the requested model is completely unknown', () => {
734+
const config: ModelConfigServiceConfig = {
735+
aliases: {
736+
'chat-base': {
737+
modelConfig: {
738+
model: 'default-fallback-model',
739+
generateContentConfig: {
740+
temperature: 0.99,
741+
},
742+
},
743+
},
744+
},
745+
};
746+
const service = new ModelConfigService(config);
747+
const resolved = service.getResolvedConfig({
748+
model: 'my-custom-model',
749+
isChatModel: true,
750+
});
751+
752+
// It preserves the requested model name, but inherits the config from chat-base
753+
expect(resolved.model).toBe('my-custom-model');
754+
expect(resolved.generateContentConfig).toEqual({
755+
temperature: 0.99,
756+
});
757+
});
758+
759+
it('should return empty config if requested model is unknown and chat-base is not defined', () => {
760+
const config: ModelConfigServiceConfig = {
761+
aliases: {},
762+
};
763+
const service = new ModelConfigService(config);
764+
const resolved = service.getResolvedConfig({
765+
model: 'my-custom-model',
766+
isChatModel: true,
767+
});
768+
769+
expect(resolved.model).toBe('my-custom-model');
770+
expect(resolved.generateContentConfig).toEqual({});
771+
});
772+
773+
it('should NOT fallback to chat-base if the requested model is completely unknown but isChatModel is false', () => {
774+
const config: ModelConfigServiceConfig = {
775+
aliases: {
776+
'chat-base': {
777+
modelConfig: {
778+
model: 'default-fallback-model',
779+
generateContentConfig: {
780+
temperature: 0.99,
781+
},
782+
},
783+
},
784+
},
785+
};
786+
const service = new ModelConfigService(config);
787+
const resolved = service.getResolvedConfig({
788+
model: 'my-custom-model',
789+
isChatModel: false,
790+
});
791+
792+
expect(resolved.model).toBe('my-custom-model');
793+
expect(resolved.generateContentConfig).toEqual({});
794+
});
795+
});
796+
732797
describe('unrecognized models', () => {
733798
it('should apply overrides to unrecognized model names', () => {
734799
const unregisteredModelName = 'my-unregistered-model-v1';

packages/core/src/services/modelConfigService.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ export interface ModelConfigKey {
2626
// This allows overrides to specify different settings (e.g., higher temperature)
2727
// specifically for retry scenarios.
2828
isRetry?: boolean;
29+
30+
// Indicates whether this request originates from the primary interactive chat model.
31+
// Enables the default fallback configuration to `chat-base` when unknown.
32+
isChatModel?: boolean;
2933
}
3034

3135
export interface ModelConfig {
@@ -122,6 +126,7 @@ export class ModelConfigService {
122126
const { aliasChain, baseModel, resolvedConfig } = this.resolveAliasChain(
123127
context.model,
124128
allAliases,
129+
context.isChatModel,
125130
);
126131

127132
const modelToLevel = this.buildModelLevelMap(aliasChain, baseModel);
@@ -159,6 +164,7 @@ export class ModelConfigService {
159164
private resolveAliasChain(
160165
requestedModel: string,
161166
allAliases: Record<string, ModelConfigAlias>,
167+
isChatModel?: boolean,
162168
): {
163169
aliasChain: string[];
164170
baseModel: string | undefined;
@@ -206,6 +212,21 @@ export class ModelConfigService {
206212
};
207213
}
208214

215+
if (isChatModel) {
216+
const fallbackAlias = 'chat-base';
217+
if (allAliases[fallbackAlias]) {
218+
const fallbackResolution = this.resolveAliasChain(
219+
fallbackAlias,
220+
allAliases,
221+
);
222+
return {
223+
aliasChain: [...fallbackResolution.aliasChain, requestedModel],
224+
baseModel: requestedModel,
225+
resolvedConfig: fallbackResolution.resolvedConfig,
226+
};
227+
}
228+
}
229+
209230
return {
210231
aliasChain: [requestedModel],
211232
baseModel: requestedModel,

0 commit comments

Comments
 (0)