odysseus-dev/odysseus · error · HTTPException
AI tidy failed: {e}
Error message
AI tidy failed: {e} What it means
The catch-all 500 handler for POST /api/documents/ai-tidy. Non-HTTPException errors during candidate selection, LLM calls, verdict parsing, or the delete/commit phase are rolled back, logged as 'AI tidy failed: {e}', and surfaced as HTTPException(500, f"AI tidy failed: {e}"). Note HTTPExceptions (like the invalid-response error) are re-raised unchanged; this handler covers everything else, including network failures to the LLM endpoint.
Source
Thrown at routes/document/document_routes.py:1059
db.delete(doc)
deleted += 1
else:
doc.tidy_verdict = "keep"
reviewed += 1
db.commit()
return {
"deleted": deleted,
"reviewed": reviewed,
"remaining": len(to_review) - len(batch),
"message": f"Reviewed {reviewed}, removed {deleted} junk document{'s' if deleted != 1 else ''}",
}
except HTTPException:
raise
except Exception as e:
db.rollback()
logger.error(f"AI tidy failed: {e}")
raise HTTPException(500, f"AI tidy failed: {e}")
finally:
db.close()
# ---- POST /api/document/{doc_id}/export-pdf/preview ----
@router.post("/api/document/{doc_id}/export-pdf/preview")
async def export_pdf_preview(doc_id: str, request: Request) -> Dict[str, Any]:
"""Return the field-value mapping that would be written to the PDF.
Frontend shows this in a confirmation modal so the user can spot/fix
any wrong values before triggering the actual download.
"""
from src.pdf_form_doc import find_source_upload_id, parse_markdown_to_values, load_field_sidecar
user = get_current_user(request)
db = SessionLocal()
try:
doc = db.query(Document).filter(Document.id == doc_id).first()
if not doc:View on GitHub (pinned to f9235ebbf1)
Solutions
- Read the '{e}' detail — timeout errors point at the provider, JSONDecodeError at the model output, lock errors at the DB.
- Verify the endpoint URL and headers returned by resolve_task_endpoint actually work (curl the provider).
- Shrink the batch to keep the LLM call under the 30s timeout.
- Retry later; verdicts are cached, so completed batches are skipped on the next run.
Example fix
# before
resp = requests.post(f"{base}/api/documents/ai-tidy", timeout=120)
resp.raise_for_status()
# after
for attempt in range(3):
resp = requests.post(f"{base}/api/documents/ai-tidy", timeout=120)
if resp.status_code != 500: break
time.sleep(2 ** attempt) # transient LLM/DB failures; cached verdicts make retry cheap Defensive patterns
Strategy: retry
Try / catch
try:
r = requests.post(f"{base}/api/documents/ai-tidy", timeout=120)
except requests.HTTPError as e:
detail = e.response.json().get("detail", "")
if e.response.status_code == 500 and ("timeout" in detail or "connection" in detail):
time.sleep(5); r = retry_ai_tidy() # cached verdicts make retries cheap
else:
raise Prevention
- Verify the LLM endpoint URL and auth headers before scheduling ai-tidy.
- Keep batches under the 30s LLM timeout.
- Rely on verdict caching — interrupted runs resume where they left off.
When it happens
Trigger: llm_call_async raising on connection timeout (30s limit) or auth failure; JSON parse errors if the extracted bracket text is not valid JSON; DB lock during the commit that deletes junk docs.
Common situations: LLM provider unreachable from the server (egress blocked, wrong URL); expired API key headers; slow provider exceeding the 30-second timeout on a large batch.
Related errors
- No endpoint configured for AI tidy
- AI returned invalid response
- Tidy failed: {e}
- Failed to delete calendar
- Failed to list calendars
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/6f5b149f722d1074.
Report an issue: GitHub.