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:
+15
-5
@@ -35,6 +35,7 @@ _FRONTEND_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "
|
||||
@app.get("/health")
|
||||
def health():
|
||||
return {"status": "ok", "model": config.MODEL,
|
||||
"text_model": config.TEXT_MODEL,
|
||||
"version": config.APP_VERSION,
|
||||
"build": config.APP_BUILD,
|
||||
"key_configured": bool(config.AI_API_KEY),
|
||||
@@ -43,13 +44,15 @@ def health():
|
||||
|
||||
@app.get("/models")
|
||||
def list_models():
|
||||
"""Available OpenRouter models with per-1M-token pricing for the UI picker."""
|
||||
from backend.models import fetch_models
|
||||
"""Vision/text OpenRouter model lists with pricing for the UI dropdowns."""
|
||||
from backend.models import fetch_models, split_vision_text
|
||||
models = fetch_models()
|
||||
if models is None:
|
||||
raise HTTPException(status_code=502,
|
||||
detail="Could not fetch the model list from OpenRouter")
|
||||
return {"models": models, "default": config.MODEL, "default_text": config.TEXT_MODEL}
|
||||
vision, text = split_vision_text(models)
|
||||
return {"vision": vision, "text": text,
|
||||
"defaults": {"vision": config.MODEL, "text": config.TEXT_MODEL}}
|
||||
|
||||
|
||||
@app.get("/jobs/{job_id}/log")
|
||||
@@ -72,7 +75,8 @@ async def check(
|
||||
work_type: Optional[str] = Form(None),
|
||||
text_local: bool = Form(False),
|
||||
pipeline_mode: str = Form("classic"),
|
||||
model: Optional[str] = Form(None),
|
||||
vision_model: Optional[str] = Form(None),
|
||||
text_model: Optional[str] = Form(None),
|
||||
):
|
||||
"""
|
||||
Accept a PDF, start a background conflict check, and return a job_id
|
||||
@@ -81,6 +85,9 @@ async def check(
|
||||
Optional intake fields (project_name/address/occupancy/work_type) feed the
|
||||
Stage 0 jurisdiction profile; anything left blank is derived from the cover
|
||||
sheet.
|
||||
|
||||
vision_model / text_model override the configured defaults for this run
|
||||
(vision always OpenRouter; text follows the OpenRouter vs hybrid choice).
|
||||
"""
|
||||
if not file.filename.lower().endswith(".pdf"):
|
||||
raise HTTPException(status_code=400, detail="Please upload a PDF.")
|
||||
@@ -106,9 +113,12 @@ async def check(
|
||||
}.items()
|
||||
if v and v.strip()
|
||||
}
|
||||
v_model = (vision_model or "").strip() or None
|
||||
t_model = (text_model or "").strip() or None
|
||||
job_id = create_job(tmp_path, source_filename=file.filename, email=email,
|
||||
project_input=project_input, text_local=text_local,
|
||||
pipeline_mode=pipeline_mode, model=model)
|
||||
pipeline_mode=pipeline_mode, vision_model=v_model,
|
||||
text_model=t_model)
|
||||
return JSONResponse({
|
||||
"job_id": job_id,
|
||||
"status": "queued",
|
||||
|
||||
Reference in New Issue
Block a user