Dược Thư RAG — Medical Chatbot Platform
Medical chatbot grounded in the Vietnamese National Drug Formulary (Dược thư quốc gia Việt Nam 2018), built as a microservices monorepo.
See docs/architecture.md for the full design (service responsibilities, data stores, RAG ingestion strategy, safety guardrails), docs/adr for architecture decision records, docs/pdf-parsing-outlier-catalog.md for a reusable checklist of confirmed PDF-parsing risks (useful for this book and any similarly-structured PDF), and docs/progress-log.md for a running log of what's been done and what's next.
Dated planning and audit documents (docs/v1-delivery-plan.md,
docs/rag-rebuild-plan.md, docs/current-rag-pipeline-audit.md,
docs/answer-experience-implementation-plan.md) record what was known on
their date and are kept for their reasoning rather than as current status —
this README and git log are the better reference for where things stand
today.
Status (2026-08-11): live in production at realvuxbaro.me — a real RAG chatbot over the whole formulary, not a scaffold. What exists and what does not:
Part State ingestion/Done — 15,100 chunks embedded and loaded into Qdrant duocthu_v1apps/ai-service/Done — live grounded RAG (retrieval, generation, grounding, abstention, citations, traces) apps/web/Done — chat UI with citation/evidence panel apps/api-gateway,auth-service,user-service,chat-serviceNot built — README.md+package.jsononlyapps/mobile/Not built — reserved infra/docker/Done — this is what production actually runs infra/k8s,helm,terraform,argocdNot built yet — empty scaffold. Still the target (ADR 0002), not abandoned: the plan is the team's self-hosted Gitea + ArgoCD; the current EC2/Compose setup is an interim stopgap Because the gateway and auth services do not exist,
apps/webtalks directly toapps/ai-service; there is no authentication layer. See the build roadmap indocs/architecture.md.
Directory map
apps/
web/ Next.js frontend (also hosts the BFF route the browser calls)
ai-service/ Python FastAPI — RAG orchestration + AWS Bedrock calls
api-gateway/ NestJS — public entry point, routes to internal services
auth-service/ NestJS — signup/login/JWT
user-service/ NestJS — profile/preferences
chat-service/ NestJS — chat session + message history
mobile/ reserved for a future mobile app
packages/
shared-types/ TS DTOs shared across Node services + web
api-client/ typed HTTP client for web
ui/ shared React components
config/ shared eslint/tsconfig presets
ingestion/ offline batch pipeline: PDF -> monographs -> chunks -> embeddings -> Qdrant
infra/ docker-compose, k8s/Helm, Terraform, CI
docs/ architecture docs and ADRs
Prerequisites
- Node.js + pnpm (JS workspace:
apps/web,packages/*; the NestJS service directories are unbuilt placeholders) - Python 3.11+ (
apps/ai-service,ingestion) - Docker (local Postgres + Qdrant via
infra/docker/docker-compose.yml) - AWS credentials with Bedrock invoke permission, for anything that generates
an answer. Without them
ai-servicestill starts, but every answer abstains rather than falling back to raw source text.
Running it locally
docker compose -f infra\docker\docker-compose.yml up -d postgres qdrant
cd apps\ai-service
python -m migrate
python -m uvicorn main:app --port 8079 # NOT --reload, see below
pnpm install
pnpm --filter web dev # http://localhost:3000
ai-service needs a populated Qdrant collection to serve answers: it verifies
a duocthu_v1__manifest sidecar at startup and refuses to run against a corpus
whose sha/model/dimensions do not match. A fresh machine either restores a
Qdrant snapshot or re-runs ingestion/ (the latter costs real Bedrock spend).
Prefer a plain restart over
uvicorn --reloadon Windows here. The reloader has been observed serving the previous code after an edit on this project, which makes it hard to tell whether a change took effect.
Tests: cd apps/ai-service && python -m pytest -q — 230 pass. test_api.py and
test_live_datastores.py need Postgres and Qdrant actually running; skip them
with --ignore when the stack is down. apps/web has no test setup at all,
so a green suite says nothing about the frontend — drive it in a browser.
Production
Live at realvuxbaro.me: a single EC2 t3.large
running infra/docker/docker-compose.prod.yml (postgres, qdrant, ai-service,
web, Caddy for automatic Let's Encrypt TLS). Bedrock is reached through an IAM
instance role — there are no long-lived AWS keys on the box or in any env file.
Pushing to master deploys: .github/workflows/deploy.yml SSHes in, resets to
the pushed commit, rebuilds only ai-service/web, runs migrations and
health-checks both. Postgres/Qdrant/Caddy are left untouched, so the vector
data survives deploys (it lives in a named volume, not the container).
This is interim infrastructure, not the end state. The intended target is
still the team's self-hosted Gitea (company domain) plus their ArgoCD
instance, per docs/adr/0002-argocd-gitops.md — that work is not started,
not cancelled. Until it is deliberately started, the project stays on private
GitHub, and the team's existing git.vinmec.tech/ai-team/gitops repository is
reference-only: never push this project into it.