unsloth/tests/python/test_rag_reingest.py
Roland Tannous d9dfc7db80 Studio: re-ingest existing KBs / threads with new settings (Backfill UX)
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.
2026-05-24 12:41:55 +04:00

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