Closes the upgrade-path gap from Phase 3: a KB or thread whose chunks
were ingested under one strategy can now be rebuilt under a different
one without losing the uploaded files.
Backend
- routes/rag.py:
- POST /api/rag/knowledge-bases/{kb_id}/reingest takes optional
chunking_strategy / mode / embedding_model in the body. Validates
the (multimodal, late) constraint via _validate_mode_combo, updates
the rag_knowledge_bases row, wipes scope artifacts (sqlite chunks
via cascade, Qdrant collection, bm25), and re-INSERTs a fresh
rag_documents row + ingestion job per stored file. Returns the new
job IDs so callers can stream progress via the existing SSE.
- POST /api/rag/threads/{thread_id}/reingest is the simpler thread
variant — no body, rebuilds with current defaults.
- Shared _reingest_scope helper strips the UUID upload prefix when
re-naming docs so users see the original filenames again.
Frontend
- rag-api.ts: reingestKnowledgeBase(kbId, opts) and
reingestThreadDocuments(threadId) wrappers + ReingestResponse type.
- rag-store.ts: reingestKB / reingestThread actions refresh the KB +
doc lists and subscribe to every returned job so the existing
IngestionProgress chips render without further wiring.
- kb-reconfigure-dialog.tsx (new): mirrors KBCreateDialog but
pre-fills with the KB's current strategy / mode / embedder, enforces
the same (multimodal + late) constraint with disabled options, and
confirms before submitting. Submit label flips between "Re-index"
(no settings change) and "Reconfigure & re-index".
- kb-detail-panel.tsx: header gains the chunking + mode summary and
a "Reconfigure…" button that opens the dialog. Button is disabled
when the KB has no documents.
- chat-settings-sheet.tsx Retrieval section: "Re-index" button beside
the existing "Clear thread index" when the thread has documents.
Tests
- test_rag_reingest.py: ReingestKBRequest accepts optional fields,
rejects unknown enum values via Pydantic, and the shared mode-combo
guard still bites on the reingest path.
57 lines
1.7 KiB
Python
57 lines
1.7 KiB
Python
"""Reingest endpoint tests (Backfill UX).
|
|
|
|
Full end-to-end reingest needs a running studio + a real embedder; that's
|
|
covered manually via the curl smoke flow in the plan. Here we cover the
|
|
parts that are testable without external models: payload validation and
|
|
the (multimodal, late) constraint propagation.
|
|
"""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
STUDIO_BACKEND = REPO_ROOT / "studio" / "backend"
|
|
if str(STUDIO_BACKEND) not in sys.path:
|
|
sys.path.insert(0, str(STUDIO_BACKEND))
|
|
|
|
|
|
def test_reingest_request_accepts_all_optional_fields():
|
|
from routes.rag import ReingestKBRequest
|
|
|
|
empty = ReingestKBRequest()
|
|
assert empty.chunking_strategy is None
|
|
assert empty.mode is None
|
|
assert empty.embedding_model is None
|
|
|
|
partial = ReingestKBRequest(chunking_strategy = "late")
|
|
assert partial.chunking_strategy == "late"
|
|
assert partial.mode is None
|
|
|
|
|
|
def test_reingest_request_rejects_unknown_strategy():
|
|
from routes.rag import ReingestKBRequest
|
|
from pydantic import ValidationError
|
|
|
|
with pytest.raises(ValidationError):
|
|
ReingestKBRequest(chunking_strategy = "telekinetic")
|
|
|
|
|
|
def test_reingest_request_rejects_unknown_mode():
|
|
from routes.rag import ReingestKBRequest
|
|
from pydantic import ValidationError
|
|
|
|
with pytest.raises(ValidationError):
|
|
ReingestKBRequest(mode = "augmented")
|
|
|
|
|
|
def test_constraint_still_enforced_for_reingest_combos():
|
|
"""The combination guard is shared with create — verify it still bites."""
|
|
from fastapi import HTTPException
|
|
|
|
from routes.rag import _validate_mode_combo
|
|
|
|
with pytest.raises(HTTPException) as excinfo:
|
|
_validate_mode_combo("multimodal", "late")
|
|
assert excinfo.value.status_code == 400
|