Rewrite README for what this repo actually is

The old one was a verbatim copy of the real product's README: claimed
'live in production at realvuxbaro.me' (a different site), pointed at
docs-legacy/ (just removed), and said the team's Gitea 'has not
started' while sitting inside it. Replaced with an honest description
of this repo's actual purpose -- an isolated GitOps demo -- and its
real state.
This commit is contained in:
2026-08-25 17:37:41 +07:00
parent 454edfa492
commit 981a4968da
+44 -188
View File
@@ -1,199 +1,55 @@
# Dược Thư RAG — Medical Chatbot Platform
# Dược Thư RAG — Demo GitOps trên Gitea cá nhân
Medical chatbot grounded in the Vietnamese National Drug Formulary
(Dược thư quốc gia Việt Nam 2018), built as a microservices monorepo.
Chatbot y dược tra cứu Dược thư Quốc gia Việt Nam 2018, có căn cứ trích dẫn
trang thật cho từng câu trả lời. Repo này là **bản demo độc lập**, dựng lại để
chứng minh một điều cụ thể: toàn bộ hạ tầng có thể chạy qua GitOps đọc từ
Gitea cá nhân (`git.vinmec.tech/VuQuangBao`), thay vì GitHub — cùng kiến trúc,
khác nguồn Git, khác hạ tầng vật lý, không đụng gì tới sản phẩm chính hay hạ
tầng của team.
Start with the [canonical documentation set](docs/README.md). It is a compact,
code-verified set covering architecture, PDF ingestion, RAG/chat, local development,
operations, API, configuration, evaluation and documentation governance.
**Chạy thật tại:** https://finalduocthu.realvuxbaro.me
The former numbered `0029` material and historical plans are retained in
[`docs-legacy/`](docs-legacy/) as raw input only. Architecture decisions also remain
there until reviewed. See the canonical
[documentation policy](docs/documentation-policy.md) for source precedence.
## Trạng thái
> **Status** (2026-08-24): **live in production at
> [realvuxbaro.me](https://realvuxbaro.me)**, running on **k3s + ArgoCD**
> since the 2026-08-17 cutover — 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_v1` |
> | `apps/ai-service/` | Done — live grounded RAG (retrieval, generation, grounding, abstention, citations, traces) |
> | `apps/web/` | Done — chat UI with citation/evidence panel, optional login |
> | `apps/auth-service`, `apps/api-gateway` | **Live since 2026-08-19** (real NestJS: register/login/JWT, `/auth/*` proxy) with a `/login` page. Verified 2026-08-24: `POST /api/auth/login` with the seed `demo` account returns a session. Login stays **optional** — anonymous chat is unaffected. Seed accounts (`admin`/`demo`) still use placeholder passwords and are **not** safe to expose publicly as-is |
> | `apps/user-service`, `apps/chat-service` | **Not built** — `README.md` + `package.json` only |
> | `apps/mobile/` | **Not built** — reserved |
> | `infra/docker/` | Compose is **stopped** (was production through 2026-08-17); still used for local dev (Postgres/Qdrant/observability), not for deploying anywhere |
> | `infra/helm/medical-chatbot/` | **This is production.** Two ArgoCD Applications, `medical-chatbot-app` and `medical-chatbot-data`, both tracked in `infra/argocd/applications/` |
> | Team's Gitea + ArgoCD (ADR 0002) | **Still not started**, not abandoned — the personal k3s/ArgoCD instance above is a separate, owner-operated cluster, not the team's shared infrastructure |
>
> `apps/web`'s chat/history/feedback/sections routes still call
> `apps/ai-service` **directly** — auth-service/api-gateway exist but chat
> traffic doesn't route through the gateway yet. Login is optional and only
> gates `/admin`; anonymous chat is unaffected. See
> [canonical architecture document](docs/architecture.md) for the implemented
> topology (not yet updated for this — verify against code, not that doc,
> until it is).
| Phần | Thế nào |
|---|---|
| Corpus | 15.100 chunk, embed và nạp vào Qdrant `duocthu_v1` |
| Chat | RAG có căn cứ — chỉ định, chống chỉ định, ADR theo tần suất, tương tác thuốc, thận trọng thai kỳ, giữ ngữ cảnh nhiều lượt |
| Eval toàn sách | 673/684 (98,4%) trả lời được trong phạm vi có thể trả lời |
| ArgoCD | 4 Application: `clickhouse-operator`, `langfuse`, `medical-chatbot-app`, `medical-chatbot-data` — tất cả đọc từ repo này |
| Quan sát | Grafana (độ trễ, phân bố quyết định, lỗi provider) + Langfuse tự host (trace từng bước xử lý, chi phí thật theo token) |
## Directory map
Hai năng lực chưa có: tính liều theo cân nặng, so sánh nhiều thuốc. Đo trực
tiếp và ghi lại trung thực tại
[`apps/ai-service/evals/known_gaps.jsonl`](apps/ai-service/evals/known_gaps.jsonl),
không phải suy đoán. Xem thêm ở [`docs/rag-chat.md`](docs/rag-chat.md#giới-hạn-đã-biết).
## Đọc gì trước
Bắt đầu ở [`docs/README.md`](docs/README.md) — bộ tài liệu chuẩn của dự án
(kiến trúc, ingestion, RAG/chat, local dev, vận hành, API, cấu hình, eval).
Bộ tài liệu này mô tả **sản phẩm**, không riêng cho bản demo — máy demo chỉ
khác ở nguồn Git và hạ tầng chạy, hành vi chatbot thì y hệt.
## Cấu trúc
```
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/ canonical project documentation
docs-legacy/ raw notes, historical plans and ADRs pending review
web/ Next.js — giao diện chat + BFF
ai-service/ Python FastAPI — pipeline RAG, gọi AWS Bedrock
api-gateway/ NestJS — cổng vào công khai
auth-service/ NestJS — đăng ký/đăng nhập/JWT
packages/ DTO dùng chung, HTTP client, UI components
ingestion/ pipeline offline: PDF -> chuyên luận -> chunk -> embedding -> Qdrant
infra/helm/ Helm chart 4 Application đọc bởi ArgoCD (chart giống hệt production)
docs/ tài liệu chuẩn của dự án
```
## Prerequisites
## Vì sao repo này tồn tại
- Node.js + pnpm (JS workspace: `apps/web`, `apps/auth-service`,
`apps/api-gateway`, `packages/*`; `apps/user-service`/`apps/chat-service`
are still 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-service` still starts, but every answer abstains
rather than falling back to raw source text.
## Running it locally
```powershell
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
```
```powershell
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 --reload` on 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.
## Observability: Prometheus, Grafana and Tempo
The observability stack is provisioned in the repository and has been deployed
to the production EC2 instance since 2026-08-11.
- **Prometheus** scrapes `/metrics` from `ai-service`. It records request rate
and latency, latency for each RAG stage, routing decisions and reasons,
provider failures, trace-write failures and the existing domain counters.
- **Grafana** is the user interface for dashboards and metric queries. Its
datasource and the Dược Thư dashboard are provisioned automatically.
- **Tempo** stores OpenTelemetry traces. A trace contains the receive,
understanding, routing, retrieval, rerank/evidence, generation,
grounding/entailment, persistence and response stages. Correlation and trace
IDs follow the request from the Next.js BFF into FastAPI.
- **OpenTelemetry Collector** receives spans from `ai-service` and exports them
to Tempo. Grafana exemplars link aggregate latency metrics to an individual
Tempo trace.
For answer lineage, use the three views together:
1. The web citation/evidence panel shows which source chunks, pages and exact
evidence text were selected for the answer.
2. **Grafana -> Explore -> Tempo** shows which pipeline stages ran, their
nesting and timing, the final decision/reason, provider failures and the
persisted trace ID.
3. PostgreSQL table `rag_retrieval_trace` is the durable audit record. It stores
the query, resolved drug, decision/reason, selected citations/evidence,
correlation ID and OpenTelemetry trace ID, so a returned `trace_id` can be
joined to its Tempo trace.
This is provenance and execution tracing, not model chain-of-thought logging.
Full prompts/responses, hidden reasoning, every rejected retrieval candidate
and every ranking score are deliberately not stored today. If deeper debugging
is needed, add bounded audit fields rather than putting sensitive prompt or
patient content into metric labels or span names.
Start the local stack from the repository root:
```powershell
docker compose -f infra\docker\docker-compose.yml up -d prometheus tempo otel-collector grafana
```
Local endpoints:
| Service | Address | Use |
|---|---|---|
| Grafana | `http://localhost:3002` | Dashboards and Explore |
| Prometheus | `http://localhost:9090` | Raw targets, PromQL and metrics |
| Tempo | `http://localhost:3200` | Trace backend; normally queried through Grafana |
| ai-service metrics | `http://localhost:8079/metrics` | Raw OpenMetrics output when ai-service runs on port 8079 |
Grafana is served through the same k3s ingress as the app, at
`https://realvuxbaro.me/grafana/`. Anonymous access is on but demoted to
**Viewer** (dashboards load with no login; write actions need the admin
account). Prometheus has no public URL — use **Grafana -> Explore ->
Prometheus** for PromQL, or **Explore -> Tempo** with a returned
`X-Trace-ID` to investigate a slow request.
## Production
Live at [realvuxbaro.me](https://realvuxbaro.me), running on **k3s +
ArgoCD** (a personal, owner-operated cluster — not the team's shared
infrastructure) since the 2026-08-17 cutover. Two ArgoCD Applications:
`medical-chatbot-app` (ai-service, web, observability) and
`medical-chatbot-data` (PostgreSQL, Qdrant — a separate release so an app
redeploy or prune can never touch persistent data). Both are tracked in
`infra/argocd/applications/`; `infra/helm/medical-chatbot/` is the chart both
render from. Bedrock is reached through an IAM instance role — no long-lived
AWS keys on the box or in any manifest.
Two things deploy independently:
- **App code** (`apps/ai-service/**`, `apps/web/**`, `packages/**`) — a push
to `master` triggers `.github/workflows/build-practice-images.yml`, which
builds and pushes GHCR images tagged by commit SHA, then repoints
`medical-chatbot-app` at the new tag. `ci.yml` runs in parallel and does
**not** gate this — a red test suite does not block a deploy.
- **Chart/config** (`infra/helm/**`) — `helm-chart.yml` lints and asserts
render invariants on the PR; once merged, ArgoCD's own `selfHeal` picks up
the change automatically. No CI step applies it directly.
Rollback is `.github/workflows/rollback-k3s.yml` (`workflow_dispatch`,
`target_sha`) — see `docs/operations.md` for the full runbook, including its
current gaps (no automated rollback for a config-only change, and the
Grafana admin password still lives inline on the ArgoCD Application rather
than in a real Kubernetes Secret).
The former EC2 Docker Compose deployment (`i-039fc8f6102467a54`) is
**stopped**, not deleted — see `docs/operations.md` if it's ever needed as a
manual DNS fallback again, though its corpus/schema will drift further out
of date the longer it stays off.
The team's self-hosted **Gitea** + **ArgoCD** (per
`docs-legacy/adr/0002-argocd-gitops.md`) remains the longer-term target for
this project and is **still not started** — not abandoned, just a separate
decision from the personal-cluster cutover above. 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.
Không phải để thay thế sản phẩm chính. Team dùng `git.vinmec.tech/ai-team/*`
cho hạ tầng thật — repo này nằm trong namespace cá nhân
(`VuQuangBao/duocthu`), tách biệt hoàn toàn: EC2 riêng, cluster k3s riêng,
Postgres/Qdrant riêng, ArgoCD riêng, Langfuse riêng. Mục đích duy nhất: chứng
minh khả năng migrate GitOps sang Gitea mà không đổi gì về kiến trúc hay hành
vi hệ thng.