Files
chunking_strategies_evaluation/src/documents/routes.py
Mahdi Bazrafshan 3f9677f6aa feat(jobs): add background job support for benchmarks and processing
Why:
- Long benchmark and process runs block HTTP clients; task #20 required async execution.

Changes:
- SQLite jobs table + GET /jobs and GET /jobs/{id}
- POST /benchmarks?background=true and POST /documents/{id}/process?background=true return 202 + job_id
- FastAPI BackgroundTasks execute work in-process; sync paths unchanged

Impact:
- Jobs are lost on server restart (Option A, no external queue)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-22 15:32:33 +03:30

113 lines
3.8 KiB
Python

"""Document and strategy API routes.
Endpoints:
POST /documents Upload a .docx / .doc / .pdf file
POST /documents/{id}/process Run chunking strategies; ?background=true enqueues a job
DELETE /documents/{id} Remove document + vectors
GET /strategies List available chunking strategies
"""
from fastapi import APIRouter, BackgroundTasks, File, Query, UploadFile
from fastapi.responses import JSONResponse
from src.core.exceptions import DocumentProcessingError
from src.documents.models import (
DeleteResponse,
DocumentResponse,
DocumentListResponse,
ProcessRequest,
ProcessResponse,
StrategiesResponse,
StrategyInfo,
)
from src.documents.parser import SUPPORTED_SUFFIXES
from src.documents import service
from src.jobs import service as jobs_service
from src.jobs.models import JobCreatedResponse
from src.storage import sqlite as db
router = APIRouter()
@router.get("/documents", response_model=DocumentListResponse)
async def list_documents(offset: int = 0, limit: int = 50):
"""List all uploaded documents with pagination."""
result = service.list_documents(offset=offset, limit=limit)
return DocumentListResponse(
items=[DocumentResponse(**doc) for doc in result["items"]],
total=result["total"],
offset=result["offset"],
limit=result["limit"],
)
@router.post("/documents", response_model=DocumentResponse, status_code=201)
async def upload_document(file: UploadFile = File(...)):
"""Upload a document. Parses it, stores the document tree in SQLite."""
if not file.filename:
raise DocumentProcessingError("No filename provided")
lower = file.filename.lower()
if not any(lower.endswith(s) for s in SUPPORTED_SUFFIXES):
raise DocumentProcessingError(
f"Only {', '.join(SUPPORTED_SUFFIXES)} files are supported"
)
content = await file.read()
if not content:
raise DocumentProcessingError("Empty file")
doc = service.upload_document(file.filename, content)
return DocumentResponse(
id=doc["id"],
filename=doc["filename"],
paragraph_count=doc.get("paragraph_count", 0),
chunk_counts=doc.get("chunk_counts", {}),
created_at=doc["created_at"],
)
@router.post("/documents/{doc_id}/process")
async def process_document(
doc_id: str,
request: ProcessRequest,
background_tasks: BackgroundTasks,
background: bool = Query(
False, description="Run in background; returns job_id (HTTP 202)"
),
):
"""Run selected chunking strategies on an uploaded document.
Pass ``background=true`` to enqueue and poll ``GET /jobs/{job_id}``.
"""
if background:
if db.get_document(doc_id) is None:
raise DocumentProcessingError(f"Document not found: {doc_id}")
job = jobs_service.enqueue_process(doc_id, request)
background_tasks.add_task(jobs_service.run_process_job, job["id"])
body = JobCreatedResponse(
job_id=job["id"],
job_type=job["job_type"],
status=job["status"],
poll_url=f"/jobs/{job['id']}",
)
return JSONResponse(status_code=202, content=body.model_dump())
return service.process_document(doc_id, request)
@router.delete("/documents/{doc_id}", response_model=DeleteResponse)
async def delete_document(doc_id: str):
"""Delete a document and all its chunk vectors from Qdrant."""
deleted = service.delete_document(doc_id)
return DeleteResponse(deleted=deleted, document_id=doc_id)
@router.get("/strategies", response_model=StrategiesResponse)
async def list_strategies():
"""List available chunking strategies with descriptions."""
strategies = service.list_strategies()
return StrategiesResponse(
strategies=[StrategyInfo(**s) for s in strategies]
)