Add per-job run logs and separate vision/text model selection.
Docker Release / build-and-push (push) Successful in 1m27s
Docker Release / release (push) Skipped

Capture pipeline stdout into job.log + API/UI so failed runs can be reviewed, and let users pick OpenRouter vision vs text models independently.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-31 14:56:48 -05:00
co-authored by Cursor
parent e30522af9a
commit a6b0c8fdfa
8 changed files with 586 additions and 44 deletions
+30 -1
View File
@@ -42,7 +42,9 @@ from backend.pipeline.risk import score_and_prioritize
from backend.pipeline.rfi import generate_rfis
from backend.pipeline.report import build_report, to_markdown
from backend.pipeline._stage import validate_issue
from backend.llm import reset_cost, get_cost, set_stage, set_text_backend
from backend.llm import (
reset_cost, get_cost, set_stage, set_text_backend, set_model_overrides,
)
def run_pipeline(
@@ -52,6 +54,8 @@ def run_pipeline(
project_input: Optional[Dict] = None,
source_name: Optional[str] = None,
text_local: bool = False,
vision_model: Optional[str] = None,
text_model: Optional[str] = None,
) -> Dict:
"""
Run the full QAQC pipeline on one PDF and return the report dict.
@@ -59,6 +63,9 @@ def run_pipeline(
project_input: optional intake fields (project_name, address, occupancy,
work_type). Cover-sheet-derived values fill any gaps; intake fields win.
vision_model / text_model: optional per-run OpenRouter (or local text)
model overrides from the UI. Blank/None keeps config defaults.
If out_dir is given, writes conflicts.json, report.md, and the intermediate
artifacts (assertions.json, clusters.json, and one json per QAQC stage).
"""
@@ -70,7 +77,29 @@ def run_pipeline(
reset_cost()
set_text_backend(text_local)
set_model_overrides(vision_model, text_model)
if vision_model or text_model:
print(f"[Runner] model overrides: vision={vision_model or '(default)'} "
f"text={text_model or '(default)'}")
try:
return _run_stages(
pdf_path, out_dir, stage, project_input, source_name, text_local,
)
finally:
# Don't leak per-run picks into a later overlapping/CLI call.
set_model_overrides(None, None)
set_text_backend(False)
def _run_stages(
pdf_path: str,
out_dir: Optional[str],
stage: Callable[[str], None],
project_input: Optional[Dict],
source_name: Optional[str],
text_local: bool,
) -> Dict:
stage("PDF -> images")
pages = convert_pdf_to_images(pdf_path)