Make the governed RAG evidence path executable end to end
Some checks failed
verify / verify (push) Has been cancelled
Some checks failed
verify / verify (push) Has been cancelled
Separate local parsing from model indexing, bind review decisions to immutable manifests, persist vectors behind active profiles, and expose retrieval, chat, evaluation, and document workflows through the React workbench. Constraint: Live Bailian authentication currently fails for all three configured capabilities Rejected: Direct upload-to-embedding flow | bypasses local review and manifest binding Confidence: high Scope-risk: broad Directive: Keep private-data deployment blocked until authentication, RBAC, and separate database roles land Tested: make verify; fresh and replay Docker document smoke; worker recovery smoke; frozen synthetic evaluation; migration 0003-0004 roundtrip Not-tested: Successful live Bailian calls, OCR, real multi-user authorization
This commit is contained in:
89
backend/app/workers/indexing_jobs.py
Normal file
89
backend/app/workers/indexing_jobs.py
Normal file
@@ -0,0 +1,89 @@
|
||||
"""Worker handler wiring for governed document embedding jobs."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from collections.abc import Mapping, Sequence
|
||||
|
||||
from app.adapters.fake import FakeEmbeddingProvider
|
||||
from app.adapters.model_gateway import ModelGatewayAdapter
|
||||
from app.core.config import Settings
|
||||
from app.persistence.indexing import PostgresIndexingRepository
|
||||
from app.persistence.job_queue import BackgroundJob
|
||||
from app.ports.model_providers import EmbeddingResult
|
||||
from app.services.indexing import DocumentIndexingService
|
||||
from app.worker import JobHandler
|
||||
|
||||
|
||||
class InvalidIndexingJobError(RuntimeError):
|
||||
"""A safe job-envelope failure that never includes payload content."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__("EMBED_DOCUMENT job envelope is invalid")
|
||||
|
||||
|
||||
class _WorkerGatewayEmbeddingProvider:
|
||||
"""Open the internal gateway only for real provider batches.
|
||||
|
||||
Synthetic profiles are routed to ``FakeEmbeddingProvider`` by the service,
|
||||
so they never read a gateway token or create model-network traffic.
|
||||
"""
|
||||
|
||||
def __init__(self, settings: Settings) -> None:
|
||||
if settings.model_gateway_caller != "worker":
|
||||
raise ValueError("indexing handlers require MODEL_GATEWAY_CALLER=worker")
|
||||
self._settings = settings
|
||||
|
||||
async def embed_documents(self, texts: Sequence[str]) -> EmbeddingResult:
|
||||
async with ModelGatewayAdapter.from_settings(self._settings) as gateway:
|
||||
return await gateway.embed_documents(texts)
|
||||
|
||||
async def embed_query(self, text: str) -> EmbeddingResult:
|
||||
async with ModelGatewayAdapter.from_settings(self._settings) as gateway:
|
||||
return await gateway.embed_query(text)
|
||||
|
||||
|
||||
def build_embed_document_handler(service: DocumentIndexingService) -> JobHandler:
|
||||
"""Build a testable handler around one configured indexing service."""
|
||||
|
||||
async def handle(job: BackgroundJob) -> None:
|
||||
document_version_id = _document_version_id(job)
|
||||
await service.index_document_version(
|
||||
lease=job.lease,
|
||||
document_version_id=document_version_id,
|
||||
trace_id=job.id,
|
||||
)
|
||||
|
||||
return handle
|
||||
|
||||
|
||||
def build_indexing_handlers(settings: Settings) -> Mapping[str, JobHandler]:
|
||||
"""Return production handler registration without loading cloud credentials eagerly."""
|
||||
|
||||
if settings.model_gateway_caller != "worker":
|
||||
raise ValueError("indexing handlers require MODEL_GATEWAY_CALLER=worker")
|
||||
service = DocumentIndexingService(
|
||||
repository=PostgresIndexingRepository(settings),
|
||||
embedding_provider=_WorkerGatewayEmbeddingProvider(settings),
|
||||
synthetic_embedding_provider=FakeEmbeddingProvider(settings.embedding_dimension),
|
||||
)
|
||||
return {"EMBED_DOCUMENT": build_embed_document_handler(service)}
|
||||
|
||||
|
||||
def _document_version_id(job: BackgroundJob) -> uuid.UUID:
|
||||
raw_version_id = job.payload.get("document_version_id")
|
||||
if (
|
||||
job.job_type != "EMBED_DOCUMENT"
|
||||
or job.required_capability != "embedding"
|
||||
or job.resource_type != "document_version"
|
||||
or job.lease.job_id != job.id
|
||||
or not isinstance(raw_version_id, str)
|
||||
):
|
||||
raise InvalidIndexingJobError
|
||||
try:
|
||||
document_version_id = uuid.UUID(raw_version_id)
|
||||
except (AttributeError, ValueError):
|
||||
raise InvalidIndexingJobError from None
|
||||
if str(document_version_id) != raw_version_id or document_version_id != job.resource_id:
|
||||
raise InvalidIndexingJobError
|
||||
return document_version_id
|
||||
Reference in New Issue
Block a user