mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 05:12:55 -07:00
tests
This commit is contained in:
@@ -57,8 +57,12 @@ import { AgentTerminateMode } from './types.js';
|
|||||||
import type { AnyDeclarativeTool, AnyToolInvocation } from '../tools/tools.js';
|
import type { AnyDeclarativeTool, AnyToolInvocation } from '../tools/tools.js';
|
||||||
import { CompressionStatus } from '../core/turn.js';
|
import { CompressionStatus } from '../core/turn.js';
|
||||||
import { ChatCompressionService } from '../services/chatCompressionService.js';
|
import { ChatCompressionService } from '../services/chatCompressionService.js';
|
||||||
import type { ModelConfigKey } from '../services/modelConfigService.js';
|
import type {
|
||||||
|
ModelConfigKey,
|
||||||
|
ResolvedModelConfig,
|
||||||
|
} from '../services/modelConfigService.js';
|
||||||
import { getModelConfigAlias } from './registry.js';
|
import { getModelConfigAlias } from './registry.js';
|
||||||
|
import type { ModelRouterService } from '../routing/modelRouterService.js';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
mockSendMessageStream,
|
mockSendMessageStream,
|
||||||
@@ -1192,6 +1196,101 @@ describe('LocalAgentExecutor', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Model Routing', () => {
|
||||||
|
it('should use model routing when the agent model is "auto"', async () => {
|
||||||
|
const definition = createTestDefinition();
|
||||||
|
definition.modelConfig.model = 'auto';
|
||||||
|
|
||||||
|
const mockRouter = {
|
||||||
|
route: vi.fn().mockResolvedValue({
|
||||||
|
model: 'routed-model',
|
||||||
|
metadata: { source: 'test', reasoning: 'test' },
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
vi.spyOn(mockConfig, 'getModelRouterService').mockReturnValue(
|
||||||
|
mockRouter as unknown as ModelRouterService,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Mock resolved config to return 'auto'
|
||||||
|
vi.spyOn(
|
||||||
|
mockConfig.modelConfigService,
|
||||||
|
'getResolvedConfig',
|
||||||
|
).mockReturnValue({
|
||||||
|
model: 'auto',
|
||||||
|
generateContentConfig: {},
|
||||||
|
} as unknown as ResolvedModelConfig);
|
||||||
|
|
||||||
|
const executor = await LocalAgentExecutor.create(
|
||||||
|
definition,
|
||||||
|
mockConfig,
|
||||||
|
onActivity,
|
||||||
|
);
|
||||||
|
|
||||||
|
mockModelResponse([
|
||||||
|
{
|
||||||
|
name: TASK_COMPLETE_TOOL_NAME,
|
||||||
|
args: { finalResult: 'done' },
|
||||||
|
id: 'call1',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await executor.run({ goal: 'test' }, signal);
|
||||||
|
|
||||||
|
expect(mockRouter.route).toHaveBeenCalled();
|
||||||
|
expect(mockSendMessageStream).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ model: 'routed-model' }),
|
||||||
|
expect.any(Array),
|
||||||
|
expect.any(String),
|
||||||
|
expect.any(AbortSignal),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should NOT use model routing when the agent model is NOT "auto"', async () => {
|
||||||
|
const definition = createTestDefinition();
|
||||||
|
definition.modelConfig.model = 'concrete-model';
|
||||||
|
|
||||||
|
const mockRouter = {
|
||||||
|
route: vi.fn(),
|
||||||
|
};
|
||||||
|
vi.spyOn(mockConfig, 'getModelRouterService').mockReturnValue(
|
||||||
|
mockRouter as unknown as ModelRouterService,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Mock resolved config to return 'concrete-model'
|
||||||
|
vi.spyOn(
|
||||||
|
mockConfig.modelConfigService,
|
||||||
|
'getResolvedConfig',
|
||||||
|
).mockReturnValue({
|
||||||
|
model: 'concrete-model',
|
||||||
|
generateContentConfig: {},
|
||||||
|
} as unknown as ResolvedModelConfig);
|
||||||
|
|
||||||
|
const executor = await LocalAgentExecutor.create(
|
||||||
|
definition,
|
||||||
|
mockConfig,
|
||||||
|
onActivity,
|
||||||
|
);
|
||||||
|
|
||||||
|
mockModelResponse([
|
||||||
|
{
|
||||||
|
name: TASK_COMPLETE_TOOL_NAME,
|
||||||
|
args: { finalResult: 'done' },
|
||||||
|
id: 'call1',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await executor.run({ goal: 'test' }, signal);
|
||||||
|
|
||||||
|
expect(mockRouter.route).not.toHaveBeenCalled();
|
||||||
|
expect(mockSendMessageStream).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ model: 'concrete-model' }),
|
||||||
|
expect.any(Array),
|
||||||
|
expect.any(String),
|
||||||
|
expect.any(AbortSignal),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('run (Termination Conditions)', () => {
|
describe('run (Termination Conditions)', () => {
|
||||||
const mockWorkResponse = (id: string) => {
|
const mockWorkResponse = (id: string) => {
|
||||||
mockModelResponse([{ name: LS_TOOL_NAME, args: { path: '.' }, id }]);
|
mockModelResponse([{ name: LS_TOOL_NAME, args: { path: '.' }, id }]);
|
||||||
|
|||||||
@@ -244,6 +244,42 @@ describe('AgentRegistry', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('registration logic', () => {
|
describe('registration logic', () => {
|
||||||
|
it('should register runtime overrides when the model is "auto"', async () => {
|
||||||
|
const autoAgent: LocalAgentDefinition = {
|
||||||
|
...MOCK_AGENT_V1,
|
||||||
|
name: 'AutoAgent',
|
||||||
|
modelConfig: { ...MOCK_AGENT_V1.modelConfig, model: 'auto' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const registerOverrideSpy = vi.spyOn(
|
||||||
|
mockConfig.modelConfigService,
|
||||||
|
'registerRuntimeModelOverride',
|
||||||
|
);
|
||||||
|
|
||||||
|
await registry.testRegisterAgent(autoAgent);
|
||||||
|
|
||||||
|
// Should register two overrides: one for the alias and one for the agent name (scope)
|
||||||
|
expect(registerOverrideSpy).toHaveBeenCalledTimes(2);
|
||||||
|
|
||||||
|
// Check alias override
|
||||||
|
expect(registerOverrideSpy).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
match: { model: getModelConfigAlias(autoAgent) },
|
||||||
|
modelConfig: expect.objectContaining({ model: 'auto' }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check scope override
|
||||||
|
expect(registerOverrideSpy).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
match: { overrideScope: autoAgent.name },
|
||||||
|
modelConfig: expect.objectContaining({
|
||||||
|
generateContentConfig: expect.any(Object),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('should register a valid agent definition', async () => {
|
it('should register a valid agent definition', async () => {
|
||||||
await registry.testRegisterAgent(MOCK_AGENT_V1);
|
await registry.testRegisterAgent(MOCK_AGENT_V1);
|
||||||
expect(registry.getDefinition('MockAgent')).toEqual(MOCK_AGENT_V1);
|
expect(registry.getDefinition('MockAgent')).toEqual(MOCK_AGENT_V1);
|
||||||
|
|||||||
@@ -281,4 +281,30 @@ describe('ClassifierStrategy', () => {
|
|||||||
);
|
);
|
||||||
consoleWarnSpy.mockRestore();
|
consoleWarnSpy.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should respect requestedModel from context in resolveClassifierModel', async () => {
|
||||||
|
const requestedModel = DEFAULT_GEMINI_MODEL; // Pro model
|
||||||
|
const mockApiResponse = {
|
||||||
|
reasoning: 'Choice is flash',
|
||||||
|
model_choice: 'flash',
|
||||||
|
};
|
||||||
|
vi.mocked(mockBaseLlmClient.generateJson).mockResolvedValue(
|
||||||
|
mockApiResponse,
|
||||||
|
);
|
||||||
|
|
||||||
|
const contextWithRequestedModel = {
|
||||||
|
...mockContext,
|
||||||
|
requestedModel,
|
||||||
|
} as RoutingContext;
|
||||||
|
|
||||||
|
const decision = await strategy.route(
|
||||||
|
contextWithRequestedModel,
|
||||||
|
mockConfig,
|
||||||
|
mockBaseLlmClient,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(decision).not.toBeNull();
|
||||||
|
// Since requestedModel is Pro, and choice is flash, it should resolve to Flash
|
||||||
|
expect(decision?.model).toBe(DEFAULT_GEMINI_FLASH_MODEL);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -108,4 +108,25 @@ describe('FallbackStrategy', () => {
|
|||||||
// Important: check that it queried snapshot with the RESOLVED model, not 'auto'
|
// Important: check that it queried snapshot with the RESOLVED model, not 'auto'
|
||||||
expect(mockService.snapshot).toHaveBeenCalledWith(DEFAULT_GEMINI_MODEL);
|
expect(mockService.snapshot).toHaveBeenCalledWith(DEFAULT_GEMINI_MODEL);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should respect requestedModel from context', async () => {
|
||||||
|
const requestedModel = 'requested-model';
|
||||||
|
const configModel = 'config-model';
|
||||||
|
vi.mocked(mockConfig.getModel).mockReturnValue(configModel);
|
||||||
|
vi.mocked(mockService.snapshot).mockReturnValue({ available: true });
|
||||||
|
|
||||||
|
const contextWithRequestedModel = {
|
||||||
|
requestedModel,
|
||||||
|
} as RoutingContext;
|
||||||
|
|
||||||
|
const decision = await strategy.route(
|
||||||
|
contextWithRequestedModel,
|
||||||
|
mockConfig,
|
||||||
|
mockClient,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(decision).toBeNull();
|
||||||
|
// Should check availability of the requested model from context
|
||||||
|
expect(mockService.snapshot).toHaveBeenCalledWith(requestedModel);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -56,4 +56,25 @@ describe('OverrideStrategy', () => {
|
|||||||
expect(decision).not.toBeNull();
|
expect(decision).not.toBeNull();
|
||||||
expect(decision?.model).toBe(overrideModel);
|
expect(decision?.model).toBe(overrideModel);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should respect requestedModel from context', async () => {
|
||||||
|
const requestedModel = 'requested-model';
|
||||||
|
const configModel = 'config-model';
|
||||||
|
const mockConfig = {
|
||||||
|
getModel: () => configModel,
|
||||||
|
getPreviewFeatures: () => false,
|
||||||
|
} as Config;
|
||||||
|
const contextWithRequestedModel = {
|
||||||
|
requestedModel,
|
||||||
|
} as RoutingContext;
|
||||||
|
|
||||||
|
const decision = await strategy.route(
|
||||||
|
contextWithRequestedModel,
|
||||||
|
mockConfig,
|
||||||
|
mockClient,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(decision).not.toBeNull();
|
||||||
|
expect(decision?.model).toBe(requestedModel);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -577,6 +577,81 @@ describe('ModelConfigService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('runtime overrides', () => {
|
||||||
|
it('should resolve a simple runtime-registered override', () => {
|
||||||
|
const config: ModelConfigServiceConfig = {
|
||||||
|
aliases: {},
|
||||||
|
overrides: [],
|
||||||
|
};
|
||||||
|
const service = new ModelConfigService(config);
|
||||||
|
|
||||||
|
service.registerRuntimeModelOverride({
|
||||||
|
match: { model: 'gemini-pro' },
|
||||||
|
modelConfig: {
|
||||||
|
generateContentConfig: {
|
||||||
|
temperature: 0.99,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const resolved = service.getResolvedConfig({ model: 'gemini-pro' });
|
||||||
|
|
||||||
|
expect(resolved.model).toBe('gemini-pro');
|
||||||
|
expect(resolved.generateContentConfig.temperature).toBe(0.99);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should prioritize runtime overrides over default overrides when they have the same specificity', () => {
|
||||||
|
const config: ModelConfigServiceConfig = {
|
||||||
|
aliases: {},
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
match: { model: 'gemini-pro' },
|
||||||
|
modelConfig: { generateContentConfig: { temperature: 0.1 } },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const service = new ModelConfigService(config);
|
||||||
|
|
||||||
|
service.registerRuntimeModelOverride({
|
||||||
|
match: { model: 'gemini-pro' },
|
||||||
|
modelConfig: { generateContentConfig: { temperature: 0.9 } },
|
||||||
|
});
|
||||||
|
|
||||||
|
const resolved = service.getResolvedConfig({ model: 'gemini-pro' });
|
||||||
|
|
||||||
|
// Runtime overrides are appended after overrides/customOverrides, so they should win.
|
||||||
|
expect(resolved.generateContentConfig.temperature).toBe(0.9);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should still respect specificity with runtime overrides', () => {
|
||||||
|
const config: ModelConfigServiceConfig = {
|
||||||
|
aliases: {},
|
||||||
|
overrides: [],
|
||||||
|
};
|
||||||
|
const service = new ModelConfigService(config);
|
||||||
|
|
||||||
|
// Register a more specific runtime override
|
||||||
|
service.registerRuntimeModelOverride({
|
||||||
|
match: { model: 'gemini-pro', overrideScope: 'my-agent' },
|
||||||
|
modelConfig: { generateContentConfig: { temperature: 0.1 } },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register a less specific runtime override later
|
||||||
|
service.registerRuntimeModelOverride({
|
||||||
|
match: { model: 'gemini-pro' },
|
||||||
|
modelConfig: { generateContentConfig: { temperature: 0.9 } },
|
||||||
|
});
|
||||||
|
|
||||||
|
const resolved = service.getResolvedConfig({
|
||||||
|
model: 'gemini-pro',
|
||||||
|
overrideScope: 'my-agent',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Specificity should win over order
|
||||||
|
expect(resolved.generateContentConfig.temperature).toBe(0.1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('custom aliases', () => {
|
describe('custom aliases', () => {
|
||||||
it('should resolve a custom alias', () => {
|
it('should resolve a custom alias', () => {
|
||||||
const config: ModelConfigServiceConfig = {
|
const config: ModelConfigServiceConfig = {
|
||||||
|
|||||||
Reference in New Issue
Block a user