RAG System — Document Indexation and Retrieval
Audiences: developer, internal
User-facing RAG covers four corpora: session attachments (per-session ephemeral collections), knowledge base (curated clinical knowledge), patient documents (long-term doxing archive), and web/deep-search. Each ingestion path is owned by its own service; the consumer surface is a set of TUCAN tools under
tools/rag/,tools/knowledge/, andtools/deep-search/.
Business Purpose
A clinical AI is only as useful as the documents it can read. Three real use cases drive this:
- Per-visit attachments: A patient walks in with a stack of PDFs from another clinic. Upload them to the session, TUCAN embeds and queries them on the fly — answer "what did the cardiologist say about her ejection fraction?" without doctor-side reading.
- Long-term patient archive: Documents in the patient's chart (uploads, lab reports, prior letters) live in MinIO, are extracted via OCR, and indexed into Qdrant. TUCAN can query the patient's full document history.
- Curated knowledge (drug data, clinical guidelines, ICD-10 catalogs): A vetted knowledge corpus separate from per-patient data. The Knowledge Curator agent maintains it as a multi-step workflow.
This is distinct from the tool-RAG ([[tucan-rag-tools]]) which embeds tool metadata for human discovery. User-facing document RAG is what the doctor experiences as "the assistant can read my documents."
Audiences
- Investor: Per-session ephemeral collections + per-patient long-term archive + curated clinical knowledge are three distinct Qdrant lifecycles. Each is tenant-isolated. RAG quality is what closes "I asked the AI about Mrs. Garcia's chart and it answered" sales demos.
- Clinical buyer: Upload PDFs to a session, ask questions, get cited answers. Or ask a clinical question and get an answer backed by the knowledge base. The patient archive is searchable in plain language.
- Developer / partner: Three ingestion paths
(
tucan-attachment-extraction.service,tucan-session-rag.service, knowledge-curator workflow). Four query surfaces (rag/, deep-search/, knowledge/). All use the same Dudoxx embedder (port resolution athttps://embedder.home.dudoxx.com/v1/embeddings,dudoxx-embed, dim 2560). - Internal: Session collections are ephemeral — deleted with the session. Patient archive and knowledge collections are long-lived; backfill / re-embed is a manual operation.
Architecture
Source of documents
│
┌──────────────────────┼─────────────────────────┐
│ │ │
▼ ▼ ▼
Session attachments Patient archive Curated knowledge
(per session, (per patient, (global / per-tenant,
ephemeral) long-lived) curator-managed)
│ │ │
▼ ▼ ▼
TucanAttachmentExtractionService Knowledge curator
TucanSessionRagService workflow
│ │ │
│ embed (Dudoxx │ embed │ embed
│ embedder, 2560) │ │
│ │ │
▼ ▼ ▼
Qdrant (port 15333)
┌─────────────────────────────────────┐
│ tucan_session_<sessionId> │ ephemeral
│ patient_documents_<orgSlug> │ long-lived
│ knowledge_<orgSlug or 'global'> │ long-lived
│ portfolio_<orgSlug> │ portfolio search
│ diagnosis_knowledge │ shared curated
└─────────────────────────────────────┘
▲
│ (query)
│
┌─────────────────────────────────────────────────────┐
│ TUCAN tools (the consumer surface) │
│ tools/rag/ │
│ · query-session-attachments (ephemeral coll) │
│ · query-medical-documents (patient archive)│
│ · query-portfolio │
│ · search-knowledge-base │
│ · search-patient-knowledge │
│ · search-diagnosis-knowledge │
│ · search-web │
│ tools/deep-search/ │
│ · plan / search-* / synthesize / export-report │
│ tools/knowledge/ │
│ · curator-managed knowledge tools │
└─────────────────────────────────────────────────────┘
Per-session attachments — ephemeral lifecycle
TucanSessionRagService (tucan-session-rag.service.ts:37) is the
per-session RAG service. Methods (verified):
| Method | Purpose | Line |
|---|---|---|
ensureCollection(sessionId) | Create Qdrant collection for a session | 49 |
embedAndStore(sessionId, chunks, ...) | Chunk-embed-upsert | 58 |
query(sessionId, prompt, ...) | Search collection | 100 |
deleteCollection(sessionId) | Delete on session end | 128 |
collectionExists(sessionId) | Existence check | 135 |
Lifecycle: created on first attachment upload; deleted when the session is deleted. The session collection is the only ephemeral RAG corpus.
Document extraction pipeline
TucanAttachmentExtractionService
(tucan-attachment-extraction.service.ts:37) handles OCR / parse:
- PDF / image arrives via
tucan-attachments.controller.ts. - Extraction service runs OCR (Docling / native parse) → text + structured metadata.
- Text is chunked + embedded via the Dudoxx embedder.
TucanSessionRagService.embedAndStorewrites points to the session's Qdrant collection.
Knowledge curator (long-lived corpus)
Knowledge curator is a Mastra workflow under
tucan/agents/knowledge-curator/ with steps research.step.ts,
compare.step.ts, translate.step.ts, upsert.step.ts. It is
manually triggered to maintain the knowledge base — not a
per-turn flow.
Deep-search composite workflow
tools/deep-search/ is a multi-tool workflow:
plan.tool.ts— plan search across sourcessearch-clinical-guidelines.tool.ts— guidelines lookupsearch-medications.tool.ts— medication knowledgesearch-icd-codes.tool.ts— ICD-10 codessearch-patient-records.tool.ts— patient archivesearch-documents.tool.ts— generic document searchevaluate-sources.tool.ts— score retrieved sourcessynthesize.tool.ts— synthesize answer with citationsfollow-up.tool.ts— propose follow-up queriesexport-report.tool.ts— export to document
Tech Stack & Choices
| Component | Choice | Why |
|---|---|---|
| Vector store | Qdrant 1.x at port 15333 (production) | Same Qdrant deployment as tool-RAG ([[tucan-rag-tools]]); per-corpus collections. |
| Embedder | dudoxx-embed via https://embedder.home.dudoxx.com/v1/embeddings, dim 2560 | Single embedding space across docs, code, attachments, knowledge. Production CUDA endpoint. |
| Session collection naming | tucan_session_<sessionId> | Trivially tenant-scoped (sessionId is unique). |
| Patient archive collection | patient_documents_<orgSlug> | Tenant slug as discriminator; cross-clinic isolation. |
| Knowledge collection | knowledge_<orgSlug> or knowledge_global | Per-tenant + shared global; curator decides. |
| Chunking | Per-document chunker (size + overlap) | Common config for all ingestion paths. |
| OCR | Docling-based extraction with structured output | Returns text + metadata, ready for chunking. |
| Lifecycle | Session: ephemeral (delete on session end). Patient + knowledge: long-lived. | Matches the data sensitivity model. |
| Query surface | TUCAN tools, not direct DB | Tools enforce tenant isolation + audit logging. |
Alternatives rejected:
- Single global collection with payload filters — payload-only isolation is fragile; collection-per-tenant is structural.
- Vector DB-as-a-service (Pinecone / Weaviate) — adds an external dependency + network hop; we already run Qdrant.
- Per-document collection — collection-count explosion; one-per-(patient,corpus) is the right granularity.
Data Flow
Business outcome: A patient brings a stack of external lab PDFs. Receptionist uploads them to the session. The doctor later asks "what was her HbA1c six months ago?" TUCAN reads the uploaded labs and answers in one turn.
Technical mechanism:
- Receptionist uploads via
tucan-attachments.controller.ts→TucanAttachmentsService.create.- Background:
TucanAttachmentExtractionService.extract(file)runs OCR on the PDF (ddx-api/src/ai/tucan/attachments/tucan-attachment-extraction.service.ts:37).- Extracted text → chunked + embedded via
TucanSessionRagService.embedAndStore(sessionId, chunks)(ddx-api/src/ai/tucan/attachments/tucan-session-rag.service.ts:58).- Qdrant collection
tucan_session_<sessionId>now has points.- Doctor's turn: LLM picks
query-session-attachmentstool.- Tool calls
TucanSessionRagService.query(sessionId, 'HbA1c six months')(ddx-api/src/ai/tucan/attachments/tucan-session-rag.service.ts:100).- Returns top-K chunks with scores; LLM synthesizes the answer with the chunk text in context.
- SSE
metrics:toolragevent published to surface the activity in monitoring.
Business outcome (cleanup): Receptionist deletes the session at end of day. The uploaded PDFs disappear from Qdrant (GDPR).
Technical mechanism: Session delete →
TucanSessionRagService.deleteCollection(sessionId)(ddx-api/src/ai/tucan/attachments/tucan-session-rag.service.ts:128).
Implicated Code
ddx-api/src/ai/tucan/attachments/tucan-attachments.controller.ts— upload + list + delete endpoints.ddx-api/src/ai/tucan/attachments/tucan-attachments.service.ts— business logic.ddx-api/src/ai/tucan/attachments/tucan-attachment-extraction.service.ts:37— classTucanAttachmentExtractionService(OCR + parse).ddx-api/src/ai/tucan/attachments/tucan-session-rag.service.ts:37— classTucanSessionRagService.ddx-api/src/ai/tucan/attachments/tucan-session-rag.service.ts:49—ensureCollection(sessionId).ddx-api/src/ai/tucan/attachments/tucan-session-rag.service.ts:58—embedAndStore(sessionId, chunks, ...).ddx-api/src/ai/tucan/attachments/tucan-session-rag.service.ts:100—query(sessionId, prompt, ...).ddx-api/src/ai/tucan/attachments/tucan-session-rag.service.ts:128—deleteCollection(sessionId).ddx-api/src/ai/tucan/attachments/tucan-session-rag.service.ts:135—collectionExists(sessionId).ddx-api/src/ai/tucan/tools/rag/query-session-attachments.tool.ts— LLM-facing tool for session-attachment query.ddx-api/src/ai/tucan/tools/rag/query-medical-documents.tool.ts— LLM tool for patient-archive query.ddx-api/src/ai/tucan/tools/rag/query-portfolio.tool.ts— portfolio search.ddx-api/src/ai/tucan/tools/rag/search-knowledge-base.tool.ts— general knowledge search.ddx-api/src/ai/tucan/tools/rag/search-patient-knowledge.tool.ts— patient-scoped knowledge.ddx-api/src/ai/tucan/tools/rag/search-diagnosis-knowledge.tool.ts— diagnosis-engine knowledge.ddx-api/src/ai/tucan/tools/rag/search-web.tool.ts— web search.ddx-api/src/ai/tucan/tools/deep-search/plan.tool.ts— deep-search planner tool.ddx-api/src/ai/tucan/tools/deep-search/synthesize.tool.ts— deep-search synthesizer with citations.ddx-api/src/ai/tucan/tools/deep-search/evaluate-sources.tool.ts— source-quality scoring.ddx-api/src/ai/tucan/tools/knowledge/_config-curator.ts— curator agent config.ddx-api/src/ai/tucan/agents/knowledge-curator/workflow.ts— Mastra curator workflow.ddx-api/src/ai/tucan/agents/knowledge-curator/steps/research.step.ts— research step.ddx-api/src/ai/tucan/agents/knowledge-curator/steps/upsert.step.ts— upsert step.ddx-api/src/ai/tucan/tool-rag/embedding.service.ts:35— classEmbeddingService(shared with tool-RAG embedder client).ddx-api/src/ai/tucan/tool-rag/qdrant.repository.ts:44— classQdrantRepository(shared Qdrant adapter).ddx-api/src/ai/tucan/context/adapters/rag-context.adapter.ts— RAG-related context slice (collection availability).
Operational Notes
- Per-session collections must be deleted on session delete.
The session controller's
deletecascades into Prisma but Qdrant has its own lifecycle — callTucanSessionRagService.deleteCollection(sessionId). Missing this leaks ephemeral data into Qdrant. - Tenant slug, not UUID, in collection names. Patient and knowledge collections are keyed by org slug. Mirror of the SSE / pool / context-builder rule.
- Embedder is shared with tool-RAG. Single Dudoxx embedder endpoint serves both. If the embedder is down, both tool-RAG and document RAG degrade.
- Re-embed is a manual operation. When the embedder version changes (different dim, different model), every long-lived collection must be re-embedded. There is no automatic migration.
- Knowledge curator is multi-step. It is not a per-turn workflow — it is run by an admin / curator to maintain the knowledge corpus; not triggered by a clinical turn.
- Document RAG ≠ tool-RAG. [[tucan-rag-tools]] embeds tool metadata for human discovery; this page covers user-facing document/knowledge RAG. The Qdrant infra is shared; the consumer pattern is different.
search-webis an outbound tool. It hits the web; check the egress policy for the tenant before enabling.- Citations are required. The deep-search synthesizer
produces answers with
[source-i]citations linking to the retrieved chunks. Drift in this contract is a defect — file one if you see uncited answers. - OCR cost lives in the extraction service, not in the consumption pricing for the LLM turn. Don't confuse OCR cost with token cost; they are separate budget lines.
- No SSE event for index complete (today). Frontend has no
push for "your document is ready"; it polls. A
rag:index:completeevent is a known enhancement; not yet in the SSE inventory.
Related Topics
- [[tucan-rag-tools]] — sibling RAG layer for TOOL METADATA (build-time, not per-turn).
- [[ai-medical-tools]] — catalog containing the rag/* and deep-search/* and knowledge/* tools.
- [[tucan-context-builder]] —
rag-context.adapter.tsexposes RAG collection state to the context bundle. - [[tucan-orchestrator]] — deep-search workflow uses orchestrator primitives.
- [[infra-qdrant]] (W1) — Qdrant infrastructure provider.
- [[sse-event-engine]] (W1) —
metrics:toolragevents surface tool-RAG and document-RAG activity. MEMORY_DOXINGshard — document OCR + summarization historical context.