GetOrBuildClassifier looks up a built Classifier for the named router model in the registry and builds it on miss. Exported so the api/router/decide decision-oracle endpoint can share the same build-once cache that the in-band RouteModel middleware uses.
(registry *router.Registry, cfg *config.ModelConfig, deps ClassifierDeps)
| 229 | // /api/router/decide decision-oracle endpoint can share the same |
| 230 | // build-once cache that the in-band RouteModel middleware uses. |
| 231 | func GetOrBuildClassifier(registry *router.Registry, cfg *config.ModelConfig, deps ClassifierDeps) (router.Classifier, error) { |
| 232 | // Fingerprint folds the classifier model's renderer-affecting |
| 233 | // fields (chat templates + stopwords) in alongside the router |
| 234 | // config. Without this, hot-reloading the classifier model's |
| 235 | // YAML (via ReloadModelsEndpoint, /import-model, or the MCP |
| 236 | // reload_models tool) wouldn't rebuild the cached classifier — |
| 237 | // the candidates slice and renderer closure are baked at build |
| 238 | // time from those fields and would silently keep the stale |
| 239 | // stop token / template until process restart. |
| 240 | var classifierCfg *config.ModelConfig |
| 241 | if deps.ModelLookup != nil { |
| 242 | classifierCfg = deps.ModelLookup(cfg.Router.ClassifierModel) |
| 243 | } |
| 244 | fp := routerConfigFingerprint(cfg.Router, classifierCfg) |
| 245 | if cached, ok := registry.Get(cfg.Name, fp); ok { |
| 246 | return cached, nil |
| 247 | } |
| 248 | c, err := buildClassifier(cfg, deps) |
| 249 | if err != nil { |
| 250 | return nil, err |
| 251 | } |
| 252 | registry.Put(cfg.Name, fp, c) |
| 253 | return c, nil |
| 254 | } |
| 255 | |
| 256 | // routerConfigFingerprint is a stable cache key for the (router cfg, |
| 257 | // classifier model cfg) tuple. FNV-64 over the YAML form of the |
no test coverage detected