Merge main: dual model dropdowns + richer job logs, adapted for agent-mode.
- llm.py: set_model_overrides(vision, text) replaces the single job override;
UI picks still beat per-call agent model args, but never name the hybrid
local model (avoids main's hybrid footgun); local->cloud fallback uses the
text pick.
- jobs.py: timestamped line-split tee (job_log.py), in-memory log + log_tail
polls, full log on terminal states (done/error/needs_review/finalization_error),
log-only disk recovery, error email links to the run log, and failed runs now
append the full traceback to job.log. Keeps pipeline_mode, job.json, and the
review gate.
- models.py: vision/text split via architecture modalities, pricing kept;
/models returns {vision, text, defaults}; /check takes vision_model/text_model
(replacing model); /health adds text_model. models_catalog.py dropped.
- UI: two priced dropdowns (OpenRouter compute only) + live run-log panel.
- Tests updated for dual overrides and the /models shape; new coverage for
traceback capture and local-model immunity.
This commit is contained in:
+22
-12
@@ -23,16 +23,13 @@ _clients: Dict[str, OpenAI] = {}
|
||||
# call_json when routing a no-image (text) call. Module-global mirrors the
|
||||
# set_stage/cost pattern (single-user tool).
|
||||
_text_local = False
|
||||
|
||||
# Per-job model override (user picked a model in the UI). Same module-global
|
||||
# Per-job model overrides (user picked models in the UI). Same module-global
|
||||
# pattern: set by the job runner before the pipeline starts, cleared after.
|
||||
_model_override: Optional[str] = None
|
||||
|
||||
|
||||
def set_model_override(model: Optional[str]) -> None:
|
||||
"""Override the model for all OpenRouter calls (vision + text), or None to clear."""
|
||||
global _model_override
|
||||
_model_override = (model or "").strip() or None
|
||||
# Vision applies to image calls, text to no-image calls on OpenRouter (and to
|
||||
# the local->cloud fallback). The LOCAL endpoint's model name is never taken
|
||||
# from these overrides - hybrid local keeps LOCAL_TEXT_MODEL.
|
||||
_vision_model_override: Optional[str] = None
|
||||
_text_model_override: Optional[str] = None
|
||||
|
||||
|
||||
def set_text_backend(local: bool) -> None:
|
||||
@@ -40,6 +37,13 @@ def set_text_backend(local: bool) -> None:
|
||||
global _text_local
|
||||
_text_local = bool(local)
|
||||
|
||||
|
||||
def set_model_overrides(vision: Optional[str] = None, text: Optional[str] = None) -> None:
|
||||
"""Per-run OpenRouter vision/text model picks. None/blank clears to defaults."""
|
||||
global _vision_model_override, _text_model_override
|
||||
_vision_model_override = (vision or "").strip() or None
|
||||
_text_model_override = (text or "").strip() or None
|
||||
|
||||
# --- per-job cost accounting -------------------------------------------------
|
||||
# OpenRouter returns the real USD cost of each call when we request usage
|
||||
# accounting. We accumulate it in a module-level counter; the runner resets it
|
||||
@@ -177,17 +181,22 @@ def _resolve_backend(has_images: bool, model_override: Optional[str]) -> Dict[st
|
||||
return {
|
||||
"base_url": config.LOCAL_BASE_URL,
|
||||
"api_key": config.LOCAL_API_KEY,
|
||||
# Local model name comes from per-call args or LOCAL_TEXT_MODEL —
|
||||
# never the UI's OpenRouter picks, which a local server won't serve.
|
||||
"model": model_override or config.LOCAL_TEXT_MODEL or config.TEXT_MODEL,
|
||||
"usage": False, # local has no OpenRouter usage accounting
|
||||
"local": True,
|
||||
}
|
||||
# Vision, or text-on-OpenRouter (default / fallback). A per-job override
|
||||
# (user's UI model pick) wins over per-call and env defaults.
|
||||
default_model = config.MODEL if has_images else config.TEXT_MODEL
|
||||
if has_images:
|
||||
model = _vision_model_override or model_override or config.MODEL
|
||||
else:
|
||||
model = _text_model_override or model_override or config.TEXT_MODEL
|
||||
return {
|
||||
"base_url": config.AI_BASE_URL,
|
||||
"api_key": config.AI_API_KEY,
|
||||
"model": _model_override or model_override or default_model,
|
||||
"model": model,
|
||||
"usage": True,
|
||||
"local": False,
|
||||
}
|
||||
@@ -362,7 +371,8 @@ def call_json(
|
||||
_models["text_local"].add(be["model"])
|
||||
_models["fallback_count"] += 1
|
||||
be = {"base_url": config.AI_BASE_URL, "api_key": config.AI_API_KEY,
|
||||
"model": config.TEXT_MODEL, "usage": True, "local": False}
|
||||
"model": _text_model_override or config.TEXT_MODEL,
|
||||
"usage": True, "local": False}
|
||||
cache_key = None # don't cache fallback under the local-model key
|
||||
fell_back = True
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user