Resolve adapters by what is mounted, and say why each kind is not loaded

runtime.py resolved adapters at 7 call sites and only ever looked in the
adapter library, so an adapter attached to a cartridge (cartridge.py:230
copies it into <cartridge>/adapters/{kind}/) was never loaded by anything.
Resolution now lives in adapters.py: it reads the mounted cartridge set,
prefers a mounted cartridge's copy over the library, and answers every one
of the six kinds with a state the facts support — loaded, available,
deferred (base_model mismatch or missing weights, both models named in the
reason), missing — plus a shadowed row for each adapter that lost the
choice. A mounted cartridge is the scenario selection; neither manifest
carries a scenario field, so none was invented.

Two bugs the new tests caught: the preference sort lost reverse=True, so a
library adapter beat a mounted cartridge's copy, and the load branch had
never been executed by a test. Stub weights now prove peft is asked to
attach, that the returned model is put in eval mode, and that the endpoint
reports loaded until unload clears it.

Faces: GET /api/runtime/adapters, `ely-eye adapters`, and a log line naming
the reason whenever a generation runs without an adapter.

Gates: 222 passed; ruff clean; mypy cold cache on the 7 touched files gives
the same 2 errors as HEAD's runtime.py, and pyright gives the same 2 as
HEAD — both are qwen_vl_utils typing artifacts (process_vision_info returns
2 or 3 values depending on return_video_kwargs; the product takes 2).
This commit is contained in:
ZacharyZhang-NY committed 2026-10-07 23:09:28 -04:00
1 parent 9f0f7993e5
commit 2fbb4af96b
8 files changed
+585 -86

No files matched your search

+4 -3
View File
@@ -9,6 +9,7 @@ from __future__ import annotations
from pathlib import Path
from typing import Any
from .. import adapters
from ..compiler import ContextCompiler
from ..config import Settings
from ..db import Database
@@ -17,7 +18,7 @@ from ..evaluation_schemas import (
)
from ..evidence_graph import EvidenceGraphService
from ..profiles import runtime_profile_report
from ..runtime import QwenRuntime, resolve_runtime_adapter
from ..runtime import QwenRuntime
from ..schemas import (
RuntimeProfile,
TrainingAdapterKind,
@@ -58,7 +59,7 @@ class RuntimeChecks:
return deferred
runtime = QwenRuntime(self.settings)
status = runtime.status()
adapter = resolve_runtime_adapter(self.settings)
adapter, _adapter_state, _adapter_reason = adapters.resolve_library_adapter(self.settings)
weight_path = adapter.path / "adapter_model.safetensors" if adapter else None
payload = {
"runtime_status": status.model_dump(mode="json"),
@@ -133,7 +134,7 @@ class RuntimeChecks:
)
payload["generation_question"] = generation_question
status = runtime.status()
adapter = resolve_runtime_adapter(proof_settings)
adapter, _adapter_state, _adapter_reason = adapters.resolve_library_adapter(proof_settings)
generation = runtime.generate(generation_question, context)
answer, citations, citation_report = Verifier().verify_answer(generation.text, context)
claim_id = EvidenceGraphService(proof_settings, self.db).record_answer(