Documentation
Reverse-engineered from the code in this repository. Every claim here traces to a file, a command, or an artifact on disk — see DOCUMENTATION_PLAN.md for the method and for what was not verified.
Chọn tài liệu theo việc bạn cần làm
Bộ tài liệu dùng cấu trúc Diataxis: mỗi trang ưu tiên một nhu cầu của người đọc thay vì cố dạy, hướng dẫn thao tác, liệt kê reference và giải thích kiến trúc trong cùng một trang.
Học qua thực hành — Tutorial
Hoàn thành một tác vụ — How-to
- Local development
- Rebuild và publish corpus
- Chạy test và evaluation
- Deploy và rollback production
- Lần một request từ người dùng đến evidence
- Production operations
- Troubleshooting
Tra cứu dữ kiện — Reference
- Catalog toàn bộ tài liệu
- Repository structure
- API contracts
- Configuration
- Observability signals
- Known limitations
- Glossary và reason codes
Hiểu thiết kế — Explanation
- Pipeline canonical từ PDF đến chatbot
- Vì sao dùng structured RAG
- System architecture
- Query understanding
- Retrieval pipeline
- Generation and grounding
- Audit kiến trúc tài liệu
What this system is
A Vietnamese-language question-answering system over the Dược thư Quốc gia Việt Nam 2018 (Vietnamese National Drug Formulary), for doctors and pharmacists. A user asks a drug question in Vietnamese; the system resolves what was asked, retrieves the exact monograph section from a vector store, has an LLM restate it, verifies that restatement against the retrieved text, and returns it with printed-page citations — or refuses.
Two things distinguish it from a generic RAG app, and both are enforced in code:
- Retrieval decides what is true; generation only decides how it reads. A
generated answer is discarded unless every number in it appears verbatim in
the specific evidence block it cites (
rag/grounding.py) and a second LLM pass confirms the cited block actually says it (rag/answer.py). - Tables and formulas are quarantined, not linearised. Content whose numbers could not be reliably reconstructed from the PDF is never embedded as prose and never restated; it is surfaced as "check the source page".
Scope boundary: the corpus is Part 2 monographs only (printed pages 99–1496). Part 1 general chapters and Part 3 appendices are not ingested.
Architecture at a glance
flowchart LR
U[Clinician<br/>browser]
CADDY[Caddy 2<br/>TLS + reverse proxy]
WEB["web — Next.js 14<br/>chat UI + BFF routes<br/>+ in-memory rate limit"]
AI["ai-service — FastAPI<br/>RagAgent orchestrator"]
QD[("Qdrant<br/>duocthu_v1<br/>15,100 points")]
PG[("PostgreSQL 16<br/>traces · turns · feedback")]
BR["AWS Bedrock<br/>Cohere embed-v4 · Cohere rerank<br/>Converse generation"]
ING["ingestion — offline batch<br/>PDF → chunks → vectors"]
PDF[/"duoc-thu-quoc-gia-viet-nam-2018.pdf"/]
U --> CADDY --> WEB --> AI
AI --> QD
AI --> PG
AI --> BR
PDF --> ING --> QD
ING --> BR
The api-gateway, auth-service, user-service and chat-service directories
in apps/ contain only a README.md and a package.json. There is no
gateway, no authentication and no chat-service in the request path; web calls
ai-service directly. See 02-system-architecture.md.
Main technology stack
| Layer | Technology | Evidence |
|---|---|---|
| Frontend | Next.js 14 (App Router), React 18, Tailwind, framer-motion | apps/web/package.json |
| Backend | Python 3.12, FastAPI, Pydantic Settings, uvicorn | apps/ai-service/pyproject.toml, Dockerfile |
| Vector store | Qdrant (cosine, 1024-d) | adapters/qdrant.py, ingestion/load/ |
| Relational | PostgreSQL 16 (psycopg 3) |
adapters/postgres.py, migrations/ |
| Embedding | cohere.embed-v4:0 on AWS Bedrock |
adapters/embedding.py, ingestion/embed/bedrock_cohere.py |
| Generation | Bedrock Converse API (model id is config) | adapters/bedrock_converse.py |
| Rerank | cohere.rerank-v3-5:0 on Bedrock |
adapters/bedrock_converse.py |
| PDF parsing | PyMuPDF (fitz), pdfplumber for tables only |
ingestion/extract/, ingestion/tables/ |
| Observability | Prometheus, OpenTelemetry → OTel Collector → Tempo, Grafana | rag/telemetry.py, infra/docker/ |
| Runtime | Docker Compose on a single EC2 host, Caddy for TLS | infra/docker/docker-compose.prod.yml |
| Monorepo | pnpm workspaces + Turborepo (JS side only) | pnpm-workspace.yaml, turbo.json |
No RAG framework is used. There is no LangChain and no LlamaIndex anywhere in
the dependency set — the orchestration is hand-written in rag/agent.py.
Core runtime services
| Service | Language | Entrypoint | Port |
|---|---|---|---|
ai-service |
Python | apps/ai-service/main.py → app |
8000 |
web |
TypeScript | apps/web/app/ (Next.js) |
3000 |
caddy |
— | infra/docker/Caddyfile |
80/443 |
ingestion |
Python | python -m ingestion.cli, python -m ingestion.load.run |
offline, no port |
Main data stores
| Store | Holds | Live-path role |
|---|---|---|
Qdrant duocthu_v1 |
15,100 chunk points + payload | Every retrieval |
Qdrant duocthu_v1__manifest |
One point: corpus sha, model id, dimensions | Startup gate (bootstrap.py) |
| PostgreSQL | rag_retrieval_trace, rag_conversation_turn, rag_answer_feedback |
Traces + multi-turn history; both fail-open |
| Local disk | chunks.jsonl, monographs.jsonl, embedding cache |
Offline pipeline only |
Redis appears in infra/docker/docker-compose.yml (local dev) and in the
pre-existing architecture document. Nothing in the codebase imports a Redis
client. It is not deployed in production and not read or written by any code.
Main pipelines
The single canonical, end-to-end explanation is pipeline-tu-pdf-den-chatbot-production.md. The numbered pages below remain the component-level reference.
For a concise demonstration of the changes delivered from 31/07 to 14/08/2026, use ke-hoach-showcase-cai-tien-2-tuan.md.
- Ingestion (offline) — PDF → spans → monographs → chunks → embeddings → Qdrant. Seven CLI subcommands plus a separate embed/load entrypoint. Has already been run; re-running the embed step costs real Bedrock spend. → 04-ingestion-pipeline.md
- Query (live) — HTTP → understanding LLM call → deterministic route → Qdrant retrieval → generation LLM call → deterministic grounding → entailment LLM call → citations → response. → 10-rag-orchestration.md
Documentation map
Start here, in order:
- 00-project-overview.md — problem, users, boundaries
- 02-system-architecture.md — components and what is not built
- 03-data-flow.md — the two end-to-end flows in one page
For AI/RAG engineers: 08-query-understanding.md → 09-retrieval-pipeline.md → 10-rag-orchestration.md → 11-generation-and-grounding.md → 19-rag-evaluation.md. For the corpus itself: 04 → 05 → 06 → 07.
For backend engineers: 12-api-architecture.md → 14-data-stores.md → 15-configuration.md → 18-testing.md → 23-local-development.md.
For frontend engineers: 13-frontend-architecture.md → 12-api-architecture.md (the response contract) → 16-security.md (rate limiting lives in the frontend today).
For DevOps/SRE: 20-deployment.md → 22-ci-cd.md → 17-observability.md → 24-production-operations.md → 25-troubleshooting.md → 21-kubernetes-and-argocd.md (unapplied target state).
For QA: 18-testing.md → 19-rag-evaluation.md → 26-known-limitations.md.
Before planning work: 26-known-limitations.md → 27-technical-debt.md → 28-roadmap-from-code.md.
Terms: 29-glossary.md.
Historical and empirical documents
architecture.md, progress-log.md, pdf-parsing-outlier-catalog.md,
document-profile.md, and the ADRs predate the numbered set. They are retained
only for decision history and empirical PDF measurements. Completed plans and
superseded audits were removed; they are not current-state references.