infer.resolve¶
Model resolution for inference: map CLI arguments to a concrete model bundle directory, with automatic fallback and hot-reload support.
Overview¶
The resolve module bridges the gap between the CLI --model-dir option
and the model registry. At inference startup
the caller may or may not specify an explicit model directory.
resolve_model_dir applies a deterministic precedence chain to
guarantee either a valid bundle path or a descriptive error:
explicit --model-dir ──► validate path exists ──► return
│ (None)
▼
active.json exists? ──► yes ──► use active pointer
│ (no / stale)
▼
find_best_model() ──► best eligible bundle ──► self-heal active.json ──► return
│ (none eligible)
▼
raise ModelResolutionError (with exclusion reasons)
For long-running online inference loops, ActiveModelReloader watches
active.json for mtime changes and transparently reloads the model
bundle without restarting the process.
ActiveModelReloader is implemented as a dataclass; public constructor
parameters (models_dir, check_interval_s) are unchanged.
Resolution precedence¶
| Priority | Condition | Behaviour |
|---|---|---|
| 1 | model_dir argument provided |
Validate the path exists; return it directly |
| 2 | models/active.json present and valid |
Use the active pointer from the registry |
| 3 | No active pointer but eligible bundles exist | Select best by macro_f1; self-heal active.json |
| 4 | No eligible bundles | Raise ModelResolutionError with per-bundle exclusion reasons |
ModelResolutionError¶
Custom exception raised when no model can be resolved.
| Attribute | Type | Description |
|---|---|---|
message |
str | Human-readable error with actionable guidance |
report |
SelectionReport or None |
Full selection report including excluded records with per-bundle reasons (attached when resolution went through the registry) |
resolve_model_dir¶
def resolve_model_dir(
model_dir: str | None,
models_dir: Path,
policy: SelectionPolicy | None = None,
) -> Path
| Parameter | Type | Default | Description |
|---|---|---|---|
model_dir |
str or None |
— | Explicit --model-dir value from CLI; None triggers automatic resolution |
models_dir |
Path |
— | Base directory containing promoted model bundles |
policy |
SelectionPolicy or None |
None |
Selection policy override; when None, the registry uses policy v1 |
Returns: Path to the resolved model bundle directory.
Raises: ModelResolutionError when no model can be resolved. The
error message includes the list of excluded bundles and their exclusion
reasons when available.
ActiveModelReloader¶
Lightweight mtime-based watcher designed for the online inference loop.
Polls active.json at a configurable interval and, when a change is
detected, loads the new model bundle via load_model_bundle. On
failure the current model is kept and a warning is logged.
Constructor¶
| Parameter | Type | Default | Description |
|---|---|---|---|
models_dir |
Path |
— | Directory containing active.json |
check_interval_s |
float |
60.0 |
Minimum seconds between mtime checks; prevents excessive stat calls |
check_reload¶
Returns (model, metadata, cat_encoders) when a reload succeeds.
Returns None in three cases:
- The check interval has not elapsed since the last check.
- The
active.jsonmtime is unchanged. - The mtime changed but the reload failed (a warning is logged and the caller should keep the current model).
After a successful reload the internal mtime is updated, so a second
immediate call returns None.
Usage¶
Resolve at startup¶
from pathlib import Path
from taskclf.infer.resolve import resolve_model_dir, ModelResolutionError
try:
bundle_path = resolve_model_dir(
model_dir=None, # let the registry decide
models_dir=Path("models/"),
)
except ModelResolutionError as exc:
print(exc)
if exc.report and exc.report.excluded:
for rec in exc.report.excluded:
print(f" {rec.model_id}: {rec.reason}")
raise SystemExit(1)
Hot-reload in an online loop¶
from pathlib import Path
from taskclf.infer.resolve import ActiveModelReloader
reloader = ActiveModelReloader(Path("models/"), check_interval_s=30.0)
# inside the polling loop
result = reloader.check_reload()
if result is not None:
model, metadata, cat_encoders = result
# swap to the new model for subsequent predictions
See also¶
model_registry— bundle scanning, ranking, and active pointer managementcore.model_io—load_model_bundleandModelMetadatainfer.online— online inference loop that usesresolve_model_dirandActiveModelReloader
taskclf.infer.resolve
¶
Model resolution for inference: resolve --model-dir and hot-reload.
Bridges CLI arguments to the model registry, providing:
- :func:
resolve_model_dir— resolve an optional--model-dirto a concrete :class:~pathlib.Pathusing the active pointer / best-model selection fallback. Deprecated — prefer :func:resolve_inference_config. - :func:
resolve_inference_config— resolve the full inference configuration (model + calibrator + threshold) from an :class:~taskclf.core.inference_policy.InferencePolicy. - :class:
ActiveModelReloader— lightweight mtime-based watcher that detectsactive.jsonchanges and reloads the model bundle for long-running online inference loops. Deprecated — prefer :class:InferencePolicyReloader. - :class:
InferencePolicyReloader— watchesinference_policy.json(falling back toactive.json) and reloads the full inference config on change.
ModelResolutionError
dataclass
¶
Bases: Exception
Raised when no model can be resolved for inference.
Source code in src/taskclf/infer/resolve.py
ActiveModelReloader
dataclass
¶
Watch active.json and reload the model bundle on change.
.. deprecated::
Use :class:InferencePolicyReloader instead. This class only
reloads the model bundle; it does not update the calibrator
store or reject threshold when the policy changes.
Designed for the online inference loop: polls the file's mtime at a configurable interval and, when a change is detected, loads the new bundle. The caller only swaps to the new model after a successful load — on failure the current model is kept.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
models_dir
|
Path
|
Directory containing |
required |
check_interval_s
|
float
|
Minimum seconds between mtime checks. |
60.0
|
Source code in src/taskclf/infer/resolve.py
check_reload()
¶
Check whether active.json changed and reload if so.
Returns the new (model, metadata, cat_encoders) tuple when a
reload succeeds, or None when no reload is needed or the
reload fails (a warning is logged on failure).
Source code in src/taskclf/infer/resolve.py
ResolvedInferenceConfig
dataclass
¶
Fully resolved inference configuration ready for use.
Produced by :func:resolve_inference_config. Contains all
loaded artifacts so callers do not need to perform additional I/O.
Source code in src/taskclf/infer/resolve.py
InferencePolicyReloader
dataclass
¶
Watch inference_policy.json and reload the full config on change.
Falls back to watching active.json when no policy file exists.
Designed for the online inference loop: polls file mtimes at a
configurable interval and returns a :class:ResolvedInferenceConfig
when a reload is needed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
models_dir
|
Path
|
Directory containing policy / active pointer files. |
required |
check_interval_s
|
float
|
Minimum seconds between mtime checks. |
60.0
|
Source code in src/taskclf/infer/resolve.py
check_reload()
¶
Check whether the policy/active file changed and reload if so.
Returns a :class:ResolvedInferenceConfig when a reload
succeeds, or None when no reload is needed or the reload
fails.
Source code in src/taskclf/infer/resolve.py
resolve_model_dir(model_dir, models_dir, policy=None)
¶
Resolve the model directory for inference.
.. deprecated::
Use :func:resolve_inference_config instead. This function
only resolves the model bundle path; it does not load the
calibrator store or reject threshold from the inference policy.
Resolution precedence:
- If model_dir is provided, validate that it exists and return it.
- Otherwise, delegate to :func:
~taskclf.model_registry.resolve_active_modelwhich readsactive.jsonor falls back to best-model selection. - If no eligible model is found, raise :class:
ModelResolutionErrorwith a descriptive message including exclusion reasons.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model_dir
|
str | None
|
Explicit |
required |
models_dir
|
Path
|
Base directory containing promoted model bundles. |
required |
policy
|
SelectionPolicy | None
|
Selection policy override (defaults to policy v1). |
None
|
Returns:
| Type | Description |
|---|---|
Path
|
Path to the resolved model bundle directory. |
Raises:
| Type | Description |
|---|---|
ModelResolutionError
|
When no model can be resolved. |
Source code in src/taskclf/infer/resolve.py
resolve_inference_config(models_dir, *, model_dir_override=None, reject_threshold_override=None, calibrator_store_override=None, calibrator_path_override=None)
¶
Resolve the full inference configuration from policy or fallback.
Resolution precedence:
- Explicit model_dir_override — bypasses policy; uses override flags for threshold and calibrator.
models/inference_policy.json— loads model, calibrator store, and threshold from the policy. Explicit overrides still take precedence for individual fields.models/active.json+ code defaults — deprecated legacy fallback.- Best-model selection + code defaults — no-config fallback.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
models_dir
|
Path
|
The |
required |
model_dir_override
|
str | None
|
Explicit |
None
|
reject_threshold_override
|
float | None
|
Explicit threshold that overrides the policy value. |
None
|
calibrator_store_override
|
Path | None
|
Explicit calibrator store path that overrides the policy value. |
None
|
calibrator_path_override
|
Path | None
|
Explicit single-calibrator JSON path (lowest calibrator precedence). |
None
|
Returns:
| Type | Description |
|---|---|
ResolvedInferenceConfig
|
A fully resolved :class: |
Raises:
| Type | Description |
|---|---|
ModelResolutionError
|
When no model can be resolved. |
Source code in src/taskclf/infer/resolve.py
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 | |