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:
@@ -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 `00–29` 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ệ thống.
|
||||
|
||||
Reference in New Issue
Block a user