routes/llm.py's _classify_model_tier() references the bare name_MODEL_TIER_MAP directly. That name is declared in this module's
PEP-562 late-binding list (_LATE = ['_MODEL_TIER_MAP', ...]), which
lazily resolves attribute access via a module-level __getattr__ --import routes.llm; routes.llm._MODEL_TIER_MAP would correctly trigger
it. But _classify_model_tier()'s own reference is a bare in-module name,
which Python resolves directly against the module's globals() dict, NOT
through __getattr__ -- so the late-binding mechanism never fires for it.
Nothing anywhere else in the shipped codebase ever touchesroutes.llm._MODEL_TIER_MAP as an attribute, so globals() never gets
populated, and every call is a guaranteed NameError.
groq_chat() (the function behind Studio's /api/chat terminal chat)
calls _classify_model_tier() unconditionally while building its return
value, in all three of its branches (success, HTTPError, generic
exception) -- and the outbound call to the model provider (_model_post)
has already completed by that point. /api/chat's handler
(routes/dispatch_llm.py _handle_chat) calls guarded_chat() ->groq_chat() with no try/except around it at all, so the NameError
propagates as an unhandled server error on every message.
Reproduction steps:
- Extract a clean station-v0.73 tarball, sys.path.insert(0, "workbench").
- import routes.llm as L
- Confirm by grep that
_MODEL_TIER_MAPis referenced only at its
definition (studio_server.py), its _LATE list entry, and the bare
usage inside _classify_model_tier() -- nothing else ever accesses it
as a module attribute.
- Stub L._model_post to return a fake successful response; stub
L.groq_key to return a fake key (BYOK path, the only path that calls
_classify_model_tier at all).
- Call L.groq_chat([{"role": "user", "content": "hello, build me a
workflow"}]).
Expected: a normal {"reply": ..., "model_name": ..., "model_tier": ...}
dict, exactly like the function's own docstring/shape promises.
Actual: NameError: name '_MODEL_TIER_MAP' is not defined, raised from
inside the success branch, re-raised from inside the generic-exception
branch's own dict construction, and never caught anywhere before reaching/api/chat's route handler.
Root cause: routes/llm.py, _classify_model_tier() (~line 58-62):
def _classify_model_tier(model_name):
return _MODEL_TIER_MAP.get(model_name, "FALLBACK_LOW_FIDELITY")
with _MODEL_TIER_MAP only ever defined in studio_server.py and declared
in routes/llm.py's _LATE PEP-562 list -- a mechanism that does not cover
bare intra-module name references.
Suggested fix: change the bare reference to an explicit module-qualified
lookup that actually goes through __getattr__ (e.g. `import sys;
sys.modules[__name__]._MODEL_TIER_MAP` from inside the function, or
simpler: import the real dict directly from studio_server at the top of_classify_model_tier() instead of relying on _LATE), or just define_MODEL_TIER_MAP directly in routes/llm.py and have studio_server.py
import it from there instead of the reverse.