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:
@@ -60,18 +60,56 @@ def test_job_log_endpoint_serves_log_and_404s(job_env, monkeypatch):
|
||||
assert client.get("/jobs/nope/log").status_code == 404
|
||||
|
||||
|
||||
def test_model_override_set_and_cleared_around_run(job_env, monkeypatch):
|
||||
from backend import llm
|
||||
|
||||
def test_model_overrides_passed_to_classic_runner(job_env, monkeypatch):
|
||||
"""Classic mode: per-run picks travel as run_pipeline kwargs (the runner
|
||||
sets and clears llm.set_model_overrides itself)."""
|
||||
seen = {}
|
||||
|
||||
def fake_runner(pdf_path, **kwargs):
|
||||
seen["override"] = llm._model_override
|
||||
seen.update(kwargs)
|
||||
return {"source": "set.pdf", "summary": {}}
|
||||
|
||||
monkeypatch.setattr("backend.jobs.run_pipeline", fake_runner)
|
||||
jobs.create_job(str(job_env / "set.pdf"), "set.pdf",
|
||||
pipeline_mode="classic", model="openai/gpt-4o")
|
||||
pipeline_mode="classic",
|
||||
vision_model="openai/gpt-4o", text_model="openai/gpt-4o-mini")
|
||||
|
||||
assert seen["override"] == "openai/gpt-4o"
|
||||
assert llm._model_override is None # cleared after the run
|
||||
assert seen["vision_model"] == "openai/gpt-4o"
|
||||
assert seen["text_model"] == "openai/gpt-4o-mini"
|
||||
|
||||
|
||||
def test_model_overrides_set_and_cleared_around_agent_run(job_env, monkeypatch):
|
||||
"""Agent mode: the agent runner has no override params, so jobs.py sets
|
||||
them module-level for the duration of the run."""
|
||||
from backend import llm
|
||||
|
||||
seen = {}
|
||||
|
||||
def fake_agent_runner(pdf_path, **kwargs):
|
||||
seen["vision"] = llm._vision_model_override
|
||||
seen["text"] = llm._text_model_override
|
||||
return {"source": "set.pdf", "summary": {}}
|
||||
|
||||
monkeypatch.setattr("backend.jobs.run_agent_pipeline", fake_agent_runner)
|
||||
jobs.create_job(str(job_env / "set.pdf"), "set.pdf",
|
||||
pipeline_mode="agent",
|
||||
vision_model="openai/gpt-4o", text_model="openai/gpt-4o-mini")
|
||||
|
||||
assert seen["vision"] == "openai/gpt-4o"
|
||||
assert seen["text"] == "openai/gpt-4o-mini"
|
||||
assert llm._vision_model_override is None # cleared after the run
|
||||
assert llm._text_model_override is None
|
||||
|
||||
|
||||
def test_failed_run_logs_traceback(job_env, monkeypatch):
|
||||
"""A crashed job must leave the traceback in job.log, not just str(e)."""
|
||||
def boom(pdf_path, **kwargs):
|
||||
raise RuntimeError("kaboom-stage-failure")
|
||||
|
||||
monkeypatch.setattr("backend.jobs.run_pipeline", boom)
|
||||
job_id = jobs.create_job(str(job_env / "set.pdf"), "set.pdf", pipeline_mode="classic")
|
||||
|
||||
assert jobs._jobs[job_id]["status"] == "error"
|
||||
content = (job_env / job_id / "job.log").read_text()
|
||||
assert "Traceback (most recent call last)" in content
|
||||
assert "RuntimeError: kaboom-stage-failure" in content
|
||||
|
||||
Reference in New Issue
Block a user