mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 21:03:05 -07:00
feat(core): Support routing for subagents.
This commit is contained in:
@@ -40,6 +40,8 @@ import type {
|
|||||||
} from './types.js';
|
} from './types.js';
|
||||||
import { AgentTerminateMode } from './types.js';
|
import { AgentTerminateMode } from './types.js';
|
||||||
import { templateString } from './utils.js';
|
import { templateString } from './utils.js';
|
||||||
|
import { isAutoModel } from '../config/models.js';
|
||||||
|
import type { RoutingContext } from '../routing/routingStrategy.js';
|
||||||
import { parseThought } from '../utils/thoughtUtils.js';
|
import { parseThought } from '../utils/thoughtUtils.js';
|
||||||
import { type z } from 'zod';
|
import { type z } from 'zod';
|
||||||
import { zodToJsonSchema } from 'zod-to-json-schema';
|
import { zodToJsonSchema } from 'zod-to-json-schema';
|
||||||
@@ -589,9 +591,34 @@ export class LocalAgentExecutor<TOutput extends z.ZodTypeAny> {
|
|||||||
signal: AbortSignal,
|
signal: AbortSignal,
|
||||||
promptId: string,
|
promptId: string,
|
||||||
): Promise<{ functionCalls: FunctionCall[]; textResponse: string }> {
|
): Promise<{ functionCalls: FunctionCall[]; textResponse: string }> {
|
||||||
|
const modelConfigAlias = getModelConfigAlias(this.definition);
|
||||||
|
|
||||||
|
// Resolve the model config early to get the concrete model string (which may be `auto`).
|
||||||
|
const resolvedConfig =
|
||||||
|
this.runtimeContext.modelConfigService.getResolvedConfig({
|
||||||
|
model: modelConfigAlias,
|
||||||
|
overrideScope: this.definition.name,
|
||||||
|
});
|
||||||
|
const requestedModel = resolvedConfig.model;
|
||||||
|
|
||||||
|
let modelToUse: string;
|
||||||
|
if (isAutoModel(requestedModel)) {
|
||||||
|
const routingContext: RoutingContext = {
|
||||||
|
history: chat.getHistory(/*curated=*/ true),
|
||||||
|
request: message.parts || [],
|
||||||
|
signal,
|
||||||
|
requestedModel,
|
||||||
|
};
|
||||||
|
const router = this.runtimeContext.getModelRouterService();
|
||||||
|
const decision = await router.route(routingContext);
|
||||||
|
modelToUse = decision.model;
|
||||||
|
} else {
|
||||||
|
modelToUse = requestedModel;
|
||||||
|
}
|
||||||
|
|
||||||
const responseStream = await chat.sendMessageStream(
|
const responseStream = await chat.sendMessageStream(
|
||||||
{
|
{
|
||||||
model: getModelConfigAlias(this.definition),
|
model: modelToUse,
|
||||||
overrideScope: this.definition.name,
|
overrideScope: this.definition.name,
|
||||||
},
|
},
|
||||||
message.parts || [],
|
message.parts || [],
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
GEMINI_MODEL_ALIAS_AUTO,
|
GEMINI_MODEL_ALIAS_AUTO,
|
||||||
PREVIEW_GEMINI_FLASH_MODEL,
|
PREVIEW_GEMINI_FLASH_MODEL,
|
||||||
isPreviewModel,
|
isPreviewModel,
|
||||||
|
isAutoModel,
|
||||||
} from '../config/models.js';
|
} from '../config/models.js';
|
||||||
import type { ModelConfigAlias } from '../services/modelConfigService.js';
|
import type { ModelConfigAlias } from '../services/modelConfigService.js';
|
||||||
|
|
||||||
@@ -206,17 +207,38 @@ export class AgentRegistry {
|
|||||||
model = this.config.getModel();
|
model = this.config.getModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
const runtimeAlias: ModelConfigAlias = {
|
const generateContentConfig = {
|
||||||
modelConfig: {
|
|
||||||
model,
|
|
||||||
generateContentConfig: {
|
|
||||||
temperature: modelConfig.temp,
|
temperature: modelConfig.temp,
|
||||||
topP: modelConfig.top_p,
|
topP: modelConfig.top_p,
|
||||||
thinkingConfig: {
|
thinkingConfig: {
|
||||||
includeThoughts: true,
|
includeThoughts: true,
|
||||||
thinkingBudget: modelConfig.thinkingBudget ?? -1,
|
thinkingBudget: modelConfig.thinkingBudget ?? -1,
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isAutoModel(model)) {
|
||||||
|
this.config.modelConfigService.registerRuntimeModelOverride({
|
||||||
|
match: {
|
||||||
|
model: getModelConfigAlias(definition),
|
||||||
},
|
},
|
||||||
|
modelConfig: {
|
||||||
|
model,
|
||||||
|
generateContentConfig,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.config.modelConfigService.registerRuntimeModelOverride({
|
||||||
|
match: {
|
||||||
|
overrideScope: definition.name,
|
||||||
|
},
|
||||||
|
modelConfig: {
|
||||||
|
generateContentConfig,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const runtimeAlias: ModelConfigAlias = {
|
||||||
|
modelConfig: {
|
||||||
|
model,
|
||||||
|
generateContentConfig,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -225,6 +247,7 @@ export class AgentRegistry {
|
|||||||
runtimeAlias,
|
runtimeAlias,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a remote agent definition asynchronously.
|
* Registers a remote agent definition asynchronously.
|
||||||
|
|||||||
@@ -148,6 +148,20 @@ export function isGemini2Model(model: string): boolean {
|
|||||||
return /^gemini-2(\.|$)/.test(model);
|
return /^gemini-2(\.|$)/.test(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the model is an auto model.
|
||||||
|
*
|
||||||
|
* @param model The model name to check.
|
||||||
|
* @returns True if the model is an auto model.
|
||||||
|
*/
|
||||||
|
export function isAutoModel(model: string): boolean {
|
||||||
|
return (
|
||||||
|
model === GEMINI_MODEL_ALIAS_AUTO ||
|
||||||
|
model === PREVIEW_GEMINI_MODEL_AUTO ||
|
||||||
|
model === DEFAULT_GEMINI_MODEL_AUTO
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the model supports multimodal function responses (multimodal data nested within function response).
|
* Checks if the model supports multimodal function responses (multimodal data nested within function response).
|
||||||
* This is supported in Gemini 3.
|
* This is supported in Gemini 3.
|
||||||
|
|||||||
@@ -605,6 +605,7 @@ export class GeminiClient {
|
|||||||
history: this.getChat().getHistory(/*curated=*/ true),
|
history: this.getChat().getHistory(/*curated=*/ true),
|
||||||
request,
|
request,
|
||||||
signal,
|
signal,
|
||||||
|
requestedModel: this.config.getModel(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let modelToUse: string;
|
let modelToUse: string;
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ export interface RoutingContext {
|
|||||||
request: PartListUnion;
|
request: PartListUnion;
|
||||||
/** An abort signal to cancel an LLM call during routing. */
|
/** An abort signal to cancel an LLM call during routing. */
|
||||||
signal: AbortSignal;
|
signal: AbortSignal;
|
||||||
|
/** The model string requested for this turn, if any. */
|
||||||
|
requestedModel?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ export class ClassifierStrategy implements RoutingStrategy {
|
|||||||
const reasoning = routerResponse.reasoning;
|
const reasoning = routerResponse.reasoning;
|
||||||
const latencyMs = Date.now() - startTime;
|
const latencyMs = Date.now() - startTime;
|
||||||
const selectedModel = resolveClassifierModel(
|
const selectedModel = resolveClassifierModel(
|
||||||
config.getModel(),
|
context.requestedModel || config.getModel(),
|
||||||
routerResponse.model_choice,
|
routerResponse.model_choice,
|
||||||
config.getPreviewFeatures(),
|
config.getPreviewFeatures(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ export class FallbackStrategy implements RoutingStrategy {
|
|||||||
readonly name = 'fallback';
|
readonly name = 'fallback';
|
||||||
|
|
||||||
async route(
|
async route(
|
||||||
_context: RoutingContext,
|
context: RoutingContext,
|
||||||
config: Config,
|
config: Config,
|
||||||
_baseLlmClient: BaseLlmClient,
|
_baseLlmClient: BaseLlmClient,
|
||||||
): Promise<RoutingDecision | null> {
|
): Promise<RoutingDecision | null> {
|
||||||
const requestedModel = config.getModel();
|
const requestedModel = context.requestedModel || config.getModel();
|
||||||
const resolvedModel = resolveModel(
|
const resolvedModel = resolveModel(
|
||||||
requestedModel,
|
requestedModel,
|
||||||
config.getPreviewFeatures(),
|
config.getPreviewFeatures(),
|
||||||
|
|||||||
@@ -24,11 +24,11 @@ export class OverrideStrategy implements RoutingStrategy {
|
|||||||
readonly name = 'override';
|
readonly name = 'override';
|
||||||
|
|
||||||
async route(
|
async route(
|
||||||
_context: RoutingContext,
|
context: RoutingContext,
|
||||||
config: Config,
|
config: Config,
|
||||||
_baseLlmClient: BaseLlmClient,
|
_baseLlmClient: BaseLlmClient,
|
||||||
): Promise<RoutingDecision | null> {
|
): Promise<RoutingDecision | null> {
|
||||||
const overrideModel = config.getModel();
|
const overrideModel = context.requestedModel || config.getModel();
|
||||||
|
|
||||||
// If the model is 'auto' we should pass to the next strategy.
|
// If the model is 'auto' we should pass to the next strategy.
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ export interface _ResolvedModelConfig {
|
|||||||
|
|
||||||
export class ModelConfigService {
|
export class ModelConfigService {
|
||||||
private readonly runtimeAliases: Record<string, ModelConfigAlias> = {};
|
private readonly runtimeAliases: Record<string, ModelConfigAlias> = {};
|
||||||
|
private readonly runtimeOverrides: ModelConfigOverride[] = [];
|
||||||
|
|
||||||
// TODO(12597): Process config to build a typed alias hierarchy.
|
// TODO(12597): Process config to build a typed alias hierarchy.
|
||||||
constructor(private readonly config: ModelConfigServiceConfig) {}
|
constructor(private readonly config: ModelConfigServiceConfig) {}
|
||||||
@@ -73,6 +74,10 @@ export class ModelConfigService {
|
|||||||
this.runtimeAliases[aliasName] = alias;
|
this.runtimeAliases[aliasName] = alias;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerRuntimeModelOverride(override: ModelConfigOverride): void {
|
||||||
|
this.runtimeOverrides.push(override);
|
||||||
|
}
|
||||||
|
|
||||||
private resolveAlias(
|
private resolveAlias(
|
||||||
aliasName: string,
|
aliasName: string,
|
||||||
aliases: Record<string, ModelConfigAlias>,
|
aliases: Record<string, ModelConfigAlias>,
|
||||||
@@ -123,7 +128,11 @@ export class ModelConfigService {
|
|||||||
...customAliases,
|
...customAliases,
|
||||||
...this.runtimeAliases,
|
...this.runtimeAliases,
|
||||||
};
|
};
|
||||||
const allOverrides = [...overrides, ...customOverrides];
|
const allOverrides = [
|
||||||
|
...overrides,
|
||||||
|
...customOverrides,
|
||||||
|
...this.runtimeOverrides,
|
||||||
|
];
|
||||||
let baseModel: string | undefined = context.model;
|
let baseModel: string | undefined = context.model;
|
||||||
let resolvedConfig: GenerateContentConfig = {};
|
let resolvedConfig: GenerateContentConfig = {};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user