feat: Спринт 1 — RAG-ядро, загрузка wiki и Debug UI

FastAPI + ChromaDB + E5-large + DeepSeek по паттерну work-pcs-dr-cdss,
адаптированному под пациентский контекст:

- services: embeddings (E5-large с префиксами), vectorstore (коллекция
  operators_wiki), document_processor (PDF/DOCX/TXT/MD + чанкер с FAQ-
  паттерном под wiki), llm_client (системный промпт ассистента клиники),
  rag_pipeline (одиночный вопрос → retrieval → ответ).
- routers: /health, /documents (upload, list, chunks, delete), /query.
- static/index.html: шапка со статусом, блок базы знаний с раскрытием
  чанков по клику, блок тест-вопроса с 3-колоночным ответом
  (чанки со score / собранный промпт / ответ LLM).
- Порт 8003 (8001 занят CDSS, 8002 — voicenote).

E2E проверен: загрузка wiki_test.md → 2 чанка, вопрос «как записать
ребёнка к лору?» → top score 84.8%, корректный ответ DeepSeek.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
AR 15 M4
2026-04-22 14:57:34 +05:00
parent d1e7749605
commit a7f78d71b2
21 changed files with 1641 additions and 0 deletions
View File
+9
View File
@@ -0,0 +1,9 @@
from pydantic import BaseModel, Field
class QueryRequest(BaseModel):
text: str = Field(..., description="Вопрос от лица пациента")
top_k: int = Field(5, ge=1, le=20, description="Количество чанков для retrieval")
document_ids: list[str] | None = Field(None, description="Ограничить поиск конкретными документами")
temperature: float | None = Field(None, ge=0.0, le=2.0)
max_tokens: int | None = Field(None, ge=100, le=8000)
+77
View File
@@ -0,0 +1,77 @@
from pydantic import BaseModel, Field
class DocumentInfo(BaseModel):
document_id: str
name: str
chunks_count: int
file_type: str
created_at: str
metadata: dict = Field(default_factory=dict)
class ChunkPreview(BaseModel):
index: int
section: str = ""
page_number: int = 0
text_preview: str = ""
char_length: int = 0
class DocumentUploadResponse(BaseModel):
document_id: str
name: str
chunks_count: int
status: str = "indexed"
created_at: str
chunks_preview: list[ChunkPreview] = Field(default_factory=list)
class DocumentListResponse(BaseModel):
documents: list[DocumentInfo]
total: int
class ChunkDetail(BaseModel):
index: int
section: str = ""
page_number: int = 0
text: str = ""
char_length: int = 0
class DocumentChunksResponse(BaseModel):
document_id: str
name: str
file_type: str
chunks_count: int
chunks: list[ChunkDetail] = Field(default_factory=list)
class DocumentDeleteResponse(BaseModel):
ok: bool = True
deleted_chunks: int
class SourceInfo(BaseModel):
document_id: str
document_name: str
chunk_text: str
section: str = ""
page: int = 0
relevance_score: float = 0.0
class QueryResponse(BaseModel):
answer: str
sources: list[SourceInfo]
model_used: str
assembled_prompt: str = ""
class HealthResponse(BaseModel):
status: str = "ok"
chromadb: str
embedding_model: str
documents_count: int
chunks_count: int