feat: correx-managed model lifecycle slice 2 — per-stage model selection

StageConfig.modelId; InferenceRouter gains a backward-compatible model-aware
route() overload (default ignores modelId, so static routers and test fakes are
unaffected). New ManagedInferenceRouter (infra commons) resolves a target model
per stage (stage.modelId > capability match > default) and ensureLoaded()s it
before routing, returning the live managed provider — its id changes with the
resident model, so there is no stale health cache to invalidate on swap.
ModelManager.ensureLoaded default delegates to the idempotent load(). Main wires
the managed router on the [[models]] path; the static path keeps DefaultInferenceRouter.

Plan: docs/plans/2026-05-31-model-lifecycle-management.md (slice 2 of 5).
This commit is contained in:
2026-06-01 11:35:51 +04:00
parent e45a626cc4
commit 382194b498
8 changed files with 240 additions and 2 deletions
@@ -27,6 +27,18 @@ interface InferenceRouter {
stageId: StageId,
requiredCapabilities: Set<ModelCapability>,
): InferenceProvider
/**
* Resolves and selects a provider for a stage, optionally pinned to a specific model id.
* Implementations that own model lifecycle (e.g. a managed router) use [modelId] to select
* and load the target model before routing. The default implementation ignores [modelId] and
* falls back to capability-based selection, so static routers remain unaffected.
*/
suspend fun route(
stageId: StageId,
requiredCapabilities: Set<ModelCapability>,
modelId: String?,
): InferenceProvider = route(stageId, requiredCapabilities)
}
class NoEligibleProviderException(
@@ -753,7 +753,7 @@ abstract class SessionOrchestrator(
timeoutMs: Long,
responseFormat: ResponseFormat = ResponseFormat.Text,
): InferenceResult {
val provider = inferenceRouter.route(stageId, stageConfig.requiredCapabilities)
val provider = inferenceRouter.route(stageId, stageConfig.requiredCapabilities, stageConfig.modelId)
log.debug(
"[Orchestrator] inference session={} stage={} provider={} timeoutMs={}",
sessionId.value, stageId.value, provider.id.value, timeoutMs,
@@ -7,6 +7,7 @@ import com.correx.core.inference.ModelCapability
data class StageConfig(
val requiredCapabilities: Set<ModelCapability> = emptySet(),
val modelId: String? = null,
val tokenBudget: Int = 4096,
val generationConfig: GenerationConfig = GenerationConfig(
temperature = 0.7,