mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-13 07:30:52 -07:00
feat(core): implement towards policy-driven model fallback mechanism (#13781)
This commit is contained in:
@@ -64,7 +64,7 @@ describe('ModelAvailabilityService', () => {
|
||||
healthyModel,
|
||||
]);
|
||||
expect(first).toEqual({
|
||||
selected: stickyModel,
|
||||
selectedModel: stickyModel,
|
||||
attempts: 1,
|
||||
skipped: [
|
||||
{
|
||||
@@ -81,7 +81,7 @@ describe('ModelAvailabilityService', () => {
|
||||
healthyModel,
|
||||
]);
|
||||
expect(second).toEqual({
|
||||
selected: healthyModel,
|
||||
selectedModel: healthyModel,
|
||||
skipped: [
|
||||
{
|
||||
model,
|
||||
@@ -101,7 +101,7 @@ describe('ModelAvailabilityService', () => {
|
||||
healthyModel,
|
||||
]);
|
||||
expect(third).toEqual({
|
||||
selected: stickyModel,
|
||||
selectedModel: stickyModel,
|
||||
attempts: 1,
|
||||
skipped: [
|
||||
{
|
||||
|
||||
@@ -30,7 +30,7 @@ export interface ModelAvailabilitySnapshot {
|
||||
}
|
||||
|
||||
export interface ModelSelectionResult {
|
||||
selected: ModelId | null;
|
||||
selectedModel: ModelId | null;
|
||||
attempts?: number;
|
||||
skipped: Array<{
|
||||
model: ModelId;
|
||||
@@ -107,12 +107,12 @@ export class ModelAvailabilityService {
|
||||
const state = this.health.get(model);
|
||||
// A sticky model is being attempted, so note that.
|
||||
const attempts = state?.status === 'sticky_retry' ? 1 : undefined;
|
||||
return { selected: model, skipped, attempts };
|
||||
return { selectedModel: model, skipped, attempts };
|
||||
} else {
|
||||
skipped.push({ model, reason: snapshot.reason ?? 'unknown' });
|
||||
}
|
||||
}
|
||||
return { selected: null, skipped };
|
||||
return { selectedModel: null, skipped };
|
||||
}
|
||||
|
||||
resetTurn() {
|
||||
|
||||
59
packages/core/src/availability/policyHelpers.test.ts
Normal file
59
packages/core/src/availability/policyHelpers.test.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
resolvePolicyChain,
|
||||
buildFallbackPolicyContext,
|
||||
} from './policyHelpers.js';
|
||||
import { createDefaultPolicy } from './policyCatalog.js';
|
||||
import type { Config } from '../config/config.js';
|
||||
|
||||
describe('policyHelpers', () => {
|
||||
describe('resolvePolicyChain', () => {
|
||||
it('inserts the active model when missing from the catalog', () => {
|
||||
const config = {
|
||||
getPreviewFeatures: () => false,
|
||||
getUserTier: () => undefined,
|
||||
getModel: () => 'custom-model',
|
||||
isInFallbackMode: () => false,
|
||||
} as unknown as Config;
|
||||
const chain = resolvePolicyChain(config);
|
||||
expect(chain[0]?.model).toBe('custom-model');
|
||||
});
|
||||
|
||||
it('leaves catalog order untouched when active model already present', () => {
|
||||
const config = {
|
||||
getPreviewFeatures: () => false,
|
||||
getUserTier: () => undefined,
|
||||
getModel: () => 'gemini-2.5-pro',
|
||||
isInFallbackMode: () => false,
|
||||
} as unknown as Config;
|
||||
const chain = resolvePolicyChain(config);
|
||||
expect(chain[0]?.model).toBe('gemini-2.5-pro');
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildFallbackPolicyContext', () => {
|
||||
it('returns remaining candidates after the failed model', () => {
|
||||
const chain = [
|
||||
createDefaultPolicy('a'),
|
||||
createDefaultPolicy('b'),
|
||||
createDefaultPolicy('c'),
|
||||
];
|
||||
const context = buildFallbackPolicyContext(chain, 'b');
|
||||
expect(context.failedPolicy?.model).toBe('b');
|
||||
expect(context.candidates.map((p) => p.model)).toEqual(['c']);
|
||||
});
|
||||
|
||||
it('returns full chain when model is not in policy list', () => {
|
||||
const chain = [createDefaultPolicy('a'), createDefaultPolicy('b')];
|
||||
const context = buildFallbackPolicyContext(chain, 'x');
|
||||
expect(context.failedPolicy).toBeUndefined();
|
||||
expect(context.candidates).toEqual(chain);
|
||||
});
|
||||
});
|
||||
});
|
||||
66
packages/core/src/availability/policyHelpers.ts
Normal file
66
packages/core/src/availability/policyHelpers.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type { Config } from '../config/config.js';
|
||||
import type {
|
||||
FailureKind,
|
||||
FallbackAction,
|
||||
ModelPolicy,
|
||||
ModelPolicyChain,
|
||||
} from './modelPolicy.js';
|
||||
import { createDefaultPolicy, getModelPolicyChain } from './policyCatalog.js';
|
||||
import { getEffectiveModel } from '../config/models.js';
|
||||
|
||||
/**
|
||||
* Resolves the active policy chain for the given config, ensuring the
|
||||
* user-selected active model is represented.
|
||||
*/
|
||||
export function resolvePolicyChain(config: Config): ModelPolicyChain {
|
||||
const chain = getModelPolicyChain({
|
||||
previewEnabled: !!config.getPreviewFeatures(),
|
||||
userTier: config.getUserTier(),
|
||||
});
|
||||
// TODO: This will be replaced when we get rid of Fallback Modes
|
||||
const activeModel = getEffectiveModel(
|
||||
config.isInFallbackMode(),
|
||||
config.getModel(),
|
||||
config.getPreviewFeatures(),
|
||||
);
|
||||
|
||||
if (chain.some((policy) => policy.model === activeModel)) {
|
||||
return chain;
|
||||
}
|
||||
|
||||
return [createDefaultPolicy(activeModel), ...chain];
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces the failed policy (if it exists in the chain) and the list of
|
||||
* fallback candidates that follow it.
|
||||
*/
|
||||
export function buildFallbackPolicyContext(
|
||||
chain: ModelPolicyChain,
|
||||
failedModel: string,
|
||||
): {
|
||||
failedPolicy?: ModelPolicy;
|
||||
candidates: ModelPolicy[];
|
||||
} {
|
||||
const index = chain.findIndex((policy) => policy.model === failedModel);
|
||||
if (index === -1) {
|
||||
return { failedPolicy: undefined, candidates: chain };
|
||||
}
|
||||
return {
|
||||
failedPolicy: chain[index],
|
||||
candidates: chain.slice(index + 1),
|
||||
};
|
||||
}
|
||||
|
||||
export function resolvePolicyAction(
|
||||
failureKind: FailureKind,
|
||||
policy: ModelPolicy,
|
||||
): FallbackAction {
|
||||
return policy.actions?.[failureKind] ?? 'prompt';
|
||||
}
|
||||
Reference in New Issue
Block a user