Add read-only production runtime audit

This commit is contained in:
2026-08-17 11:17:40 +07:00
parent 057d4ed9dc
commit a1de4715a4
106 changed files with 6869 additions and 1782 deletions
+122
View File
@@ -0,0 +1,122 @@
# Cách deploy và rollback production
## Phân loại
**Loại tài liệu:** How-to.
**Reader job:** phát hành một thay đổi lên EC2 Compose và khôi phục commit trước
nếu verification thất bại.
## Khi nào dùng hướng dẫn này
Production hiện tại là một EC2 host chạy Docker Compose. Đây không phải quy
trình Kubernetes/ArgoCD. Deploy bình thường chạy bằng `deploy.yml`; rollback có
workflow manual riêng.
## Điều kiện tiên quyết
- Thay đổi đã được review.
- CI của commit đã xanh; lưu ý deploy workflow chưa phụ thuộc CI bằng `needs`.
- GitHub secrets `EC2_HOST`, `EC2_SSH_KEY``GRAFANA_ADMIN_PASSWORD` hợp lệ.
- Biết last-known-good SHA trước khi deploy.
- Thay đổi migration đã được đánh giá vì migrations chỉ đi tới, không có down.
## Bước 1 — Xác định deploy có được trigger không
Push lên `master` chỉ trigger deploy khi thay đổi nằm trong path filter:
- `apps/ai-service/**`;
- `apps/web/**`;
- `packages/**`;
- drug entity artifact;
- `infra/docker/**`;
- chính `deploy.yml`.
Docs-only change không deploy production. Có thể dùng `workflow_dispatch` khi
cần chạy chủ động.
## Bước 2 — Ghi release context
Trước khi chạy, lưu:
```text
target SHA
last-known-good SHA
CI run URL
deploy run URL
thay đổi config/migration
người theo dõi rollout
```
Không deploy đồng thời với một corpus switch nếu chưa có kế hoạch rollback riêng
cho collection.
## Bước 3 — Chạy deploy workflow
Workflow thực hiện trên host:
1. fetch và reset checkout về `origin/master`;
2. build/start app + observability services;
3. validate/reload Caddy;
4. apply migrations;
5. kiểm tra health/readiness/web;
6. smoke một condition→drug response;
7. kiểm tra Prometheus, Tempo, Grafana và một trace cụ thể.
Theo dõi log đến khi tất cả assertion pass. Job fail không đồng nghĩa host đã tự
rollback; workflow deploy không có automatic rollback.
## Bước 4 — Verify sau deploy
Kiểm tra tối thiểu:
- `/health``/ready` trả 200;
- web tải được;
- query smoke trả `answerable` và citation `chi_dinh`;
- trace ID có trong Tempo;
- `duocthu_requests_total` query được;
- dashboard Grafana được provision;
- không có spike mới ở abstain/provider failure.
Giữ một cửa sổ quan sát trước khi tuyên bố rollout hoàn tất.
## Rollback bằng workflow
Mở workflow **Rollback production**, chọn `workflow_dispatch`, nhập
`target_sha` là last-known-good commit. Workflow:
1. verify SHA tồn tại;
2. reset checkout về SHA đó;
3. rebuild app/observability tier;
4. chạy migrations idempotent;
5. chạy health checks.
Rollback không đảo schema database. Nếu release chứa migration không tương thích
ngược, dừng và lập kế hoạch phục hồi dữ liệu/schema thay vì chạy workflow mù.
## Rollback corpus
Code rollback và corpus rollback là hai thao tác khác nhau. Nếu vừa switch
Qdrant collection:
1. đặt lại `QDRANT_COLLECTION` về collection cũ;
2. restart `ai-service`;
3. xác nhận manifest check và smoke query;
4. không xóa collection mới cho đến khi điều tra xong.
## Troubleshooting
| Triệu chứng | Kiểm tra đầu tiên | Recovery |
|---|---|---|
| Build fail sau reset | GitHub log và Docker build log trên host | Rollback workflow về SHA cũ |
| `ai-service` restart loop | `ManifestMismatch` trong container log | Sửa collection/model binding |
| Smoke answer fail | Response + 200 dòng ai-service log | Rollback nếu ảnh hưởng live path |
| Tempo chưa ready | Retry/log Tempo | Không coi rollout complete |
| Migration fail | Migration output và DB state | Dừng; không chạy reset schema tùy tiện |
## Liên quan
- [Deployment architecture](../20-deployment.md)
- [CI/CD](../22-ci-cd.md)
- [Production operations](../24-production-operations.md)
- [Troubleshooting](../25-troubleshooting.md)
@@ -0,0 +1,168 @@
# Cách rebuild và publish corpus Qdrant
## Phân loại
**Loại tài liệu:** How-to.
**Reader job:** tạo corpus mới từ PDF đã thay đổi và đưa nó vào một collection
mới mà vẫn có đường rollback.
## Khi nào dùng hướng dẫn này
Chỉ rebuild khi PDF, parsing, segmentation, chunk schema hoặc chunk text thay
đổi. Nếu chỉ chuyển corpus không đổi sang máy khác, dùng Qdrant snapshot/restore;
không re-embed.
Embedding gọi AWS Bedrock và tốn chi phí. Cần có phê duyệt cụ thể trước bước
embed/load. Các bước parser và validation local không gọi cloud.
## Điều kiện tiên quyết
- Python và dependencies của `ingestion/` đã cài.
- PDF nguồn tồn tại tại `ingestion/data/raw/`.
- Có đủ dung lượng cho artifact trong `ingestion/data/processed/`.
- Nếu publish: Qdrant target và AWS credentials đã xác định rõ.
- Đã chọn **collection mới**, ví dụ `duocthu_v2`; không ghi corpus khác vào
`duocthu_v1`.
## Bước 1 — Xác định input và lưu baseline
```powershell
Set-Location ingestion
Get-FileHash data/raw/duoc-thu-quoc-gia-viet-nam-2018.pdf -Algorithm SHA256
```
Ghi lại SHA của PDF, commit code, collection hiện tại và count point hiện tại.
Đây là baseline để audit và rollback.
## Bước 2 — Phát hiện vùng bảng
```powershell
python -m ingestion.cli detect-tables `
--pdf data/raw/duoc-thu-quoc-gia-viet-nam-2018.pdf `
--out data/processed/table_regions.json
```
Bước này chậm. Tái sử dụng artifact nếu PDF và detector không đổi.
## Bước 3 — Extract và segment
```powershell
python -m ingestion.cli run `
--pdf data/raw/duoc-thu-quoc-gia-viet-nam-2018.pdf `
--tables data/processed/table_regions.json `
--out data/processed/monographs.jsonl
```
Không bỏ qua lỗi duplicate drug ID hoặc lỗi parsing. Pipeline chủ đích dừng thay
vì tự merge hai chuyên luận không chắc chắn.
## Bước 4 — Tạo chunk
```powershell
python -m ingestion.cli chunk `
--pdf data/raw/duoc-thu-quoc-gia-viet-nam-2018.pdf `
--monographs data/processed/monographs.jsonl `
--tables data/processed/table_regions.json `
--out data/processed/chunks.jsonl
```
Chunking yêu cầu page map để mọi record có printed-page provenance.
## Bước 5 — Chạy acceptance gates
```powershell
python -m ingestion.cli chunk-ready `
--monographs data/processed/monographs.jsonl `
--chunks data/processed/chunks.jsonl
```
Chỉ tiếp tục khi exit code bằng `0`. Gate fail không phải cảnh báo để bỏ qua;
nó cho biết corpus chưa được phép embedding.
Chạy thêm diagnostics khi parsing thay đổi:
```powershell
python -m ingestion.cli validate `
--pdf data/raw/duoc-thu-quoc-gia-viet-nam-2018.pdf `
--tables data/processed/table_regions.json
python -m ingestion.cli coverage `
--pdf data/raw/duoc-thu-quoc-gia-viet-nam-2018.pdf `
--tables data/processed/table_regions.json
python -m ingestion.cli residual-ink `
--pdf data/raw/duoc-thu-quoc-gia-viet-nam-2018.pdf `
--tables data/processed/table_regions.json
```
## Bước 6 — Review diff corpus
So sánh ít nhất:
- số monograph và drug ID;
- số chunk theo `chunk_kind``section_key`;
- số chunk oversized;
- số block quarantine;
- SHA-256 của `chunks.jsonl`;
- các gate count so với baseline.
Một thay đổi count lớn không được giải thích là lý do dừng trước cloud spend.
## Bước 7 — Embed-only trước khi ghi store
Chỉ chạy sau khi được phê duyệt:
```powershell
python -m ingestion.load.run `
--chunks data/processed/chunks.jsonl `
--provider cohere-v4 `
--collection duocthu_v2 `
--qdrant-url http://localhost:6333 `
--embed-only
```
Embedding cache dùng content hash nên chunk không đổi được tái sử dụng.
## Bước 8 — Load vào collection mới
```powershell
python -m ingestion.load.run `
--chunks data/processed/chunks.jsonl `
--provider cohere-v4 `
--collection duocthu_v2 `
--qdrant-url http://localhost:6333
```
Loader kiểm tra manifest compatibility, vector dimension và point count. Không
xóa collection cũ sau bước này.
## Bước 9 — Verify runtime với collection mới
1. Đặt `QDRANT_COLLECTION=duocthu_v2` trên staging/local.
2. Restart `ai-service`; startup manifest check phải pass.
3. Chạy health/readiness.
4. Chạy routing, grounding và manual battery phù hợp.
5. Review citation page và quarantine case.
## Rollback
Đặt lại `QDRANT_COLLECTION` về collection cũ và restart `ai-service`. Vì publish
dùng tên mới, rollback không cần sửa dữ liệu. Chỉ xóa collection cũ sau thời gian
quan sát và khi có snapshot đã kiểm tra restore.
## Troubleshooting
| Lỗi | Nguyên nhân thường gặp | Cách xử lý |
|---|---|---|
| `CorpusMismatch` | Dùng lại collection cho corpus/model khác | Chọn collection mới; không bypass manifest |
| Missing printed page | Page map không xác định được provenance | Sửa extraction/page map rồi chunk lại |
| Vector dimension mismatch | Provider/config khác manifest | Dùng đúng model hoặc collection khác |
| Count gate fail | Upsert chưa đủ hoặc collection có point ngoài corpus | Dừng publish và kiểm tra report |
## Liên quan
- [Ingestion pipeline](../04-ingestion-pipeline.md)
- [Document parsing](../05-document-parsing.md)
- [Chunk schema](../06-document-model-and-chunking.md)
- [Indexing and storage](../07-indexing-and-storage.md)
+117
View File
@@ -0,0 +1,117 @@
# Cách chạy test và evaluation
## Phân loại
**Loại tài liệu:** How-to.
**Reader job:** kiểm tra một thay đổi bằng các suite phù hợp và lưu bằng chứng
không nói quá phạm vi test.
## Điều kiện tiên quyết
- Python 3.12 khuyến nghị.
- Dependencies của `apps/ai-service``ingestion` đã cài.
- Node 20, pnpm 9 cho web.
- Không cần AWS cho unit test mặc định.
## Bước 1 — Chạy AI-service checks
```powershell
Set-Location apps/ai-service
ruff check .
python -m pytest tests -q
```
`tests/conftest.py` mặc định đặt `EMBEDDING_PROVIDER=disabled` trước collection,
nên unit suite không cần Qdrant. `test_live_datastores.py` tự skip trừ khi bật
integration.
## Bước 2 — Chạy ingestion suite
```powershell
Set-Location ../../ingestion
python -m pytest tests -q
```
Suite này kiểm tra extraction, segmentation, chunking, validation, provider
adapters và loader bằng doubles/in-memory store; nó không gọi Bedrock thật.
## Bước 3 — Chạy web checks
```powershell
Set-Location ..
pnpm --filter @duoc-thu/web lint
pnpm --filter @duoc-thu/web build
```
Hiện chưa có frontend test runner. Lint/build xanh không chứng minh request
timeout, citation grouping, middleware rate limit hoặc state UI không regression.
## Bước 4 — Chạy integration datastore khi cần
Khởi động PostgreSQL và Qdrant trước, rồi:
```powershell
Set-Location apps/ai-service
$env:RUN_INTEGRATION='1'
python -m pytest tests/test_live_datastores.py -q
Remove-Item Env:RUN_INTEGRATION
```
Ghi rõ integration environment và version Qdrant/PostgreSQL trong test record.
## Bước 5 — Chạy production/manual battery
Battery gọi endpoint thật và có thể phát sinh Bedrock cost:
```powershell
Set-Location apps/ai-service
python scripts/run_manual_battery.py `
--base-url http://localhost:3000 `
--target web `
--output output/manual-battery.jsonl
```
Script là HTTP recorder với invariant checks, không phải LLM judge. Review các
failure và đối chiếu citation với PDF. Không ghi đè record cũ; tên output nên có
timestamp/commit SHA.
Để thử một subset, dùng `--start`, `--limit` hoặc `--ids` theo `--help`.
## Bước 6 — Ghi kết quả đúng phạm vi
Một test record tối thiểu gồm:
```text
commit SHA
ngày/giờ
command
environment/provider mode
passed / failed / skipped
evaluation cases đã chạy
artifact output
known exclusions
```
Không cộng `skipped` vào `passed`. Không dùng unit suite để tuyên bố chất lượng
lâm sàng hoặc live provider reliability.
## Verify
- AI-service ruff và pytest pass.
- Ingestion pytest pass.
- Web lint/build pass.
- Integration/manual result được ghi riêng nếu đã chạy.
- Không có cloud call ngoài ý muốn.
## CI hiện tại
`.github/workflows/ci.yml` chạy AI-service ruff/pytest, ingestion pytest và web
lint/build trên push và pull request. `deploy.yml` vẫn trigger độc lập; CI đỏ
không tự động chặn production deploy ở cấp workflow.
## Liên quan
- [Testing reference](../18-testing.md)
- [RAG evaluation](../19-rag-evaluation.md)
- [CI/CD](../22-ci-cd.md)
+110
View File
@@ -0,0 +1,110 @@
# Cách lần một request từ người dùng đến evidence
## Phân loại
**Loại tài liệu:** How-to.
**Reader job:** điều tra một câu trả lời chậm, abstain hoặc có citation đáng ngờ
bằng correlation ID, PostgreSQL, Tempo và Prometheus.
## Điều kiện tiên quyết
- Có ít nhất một trong ba giá trị: `trace_id`, `correlation_id`, `otel_trace_id`.
- Có quyền đọc PostgreSQL và Grafana/Tempo production.
- Biết khoảng thời gian request.
Không đưa nội dung query hoặc dữ liệu người dùng vào ticket công khai.
## Bước 1 — Thu ID từ response
API body trả:
```text
trace_id
correlation_id
otel_trace_id
decision
reason
```
Headers cũng có `X-Correlation-ID``X-Trace-ID`. Ưu tiên giữ cả body lẫn
headers để phát hiện proxy/version mismatch.
## Bước 2 — Tìm business trace trong PostgreSQL
```sql
SELECT created_at, query_text, subject_scope, query_intent, decision, reason,
resolved_drug_id, citations, correlation_id, otel_trace_id
FROM rag_retrieval_trace
WHERE trace_id = '<trace_id>'
OR correlation_id = '<correlation_id>'
OR otel_trace_id = '<otel_trace_id>'
ORDER BY created_at DESC;
```
Xác nhận server đã resolve thuốc nào, decision/reason nào và citation nào thực sự
được lưu. Không dựa riêng vào UI text.
## Bước 3 — Mở distributed trace
Trong Grafana → Explore → Tempo, tìm `otel_trace_id`. Đọc các span:
- receive;
- understanding;
- routing/retrieval;
- generation;
- grounding/entailment;
- persistence;
- response.
Xác định stage chiếm thời gian hoặc stage không xuất hiện. Provider call đang
chạy không bị RequestBudget hủy giữa chừng; tổng latency có thể vượt budget bởi
một call đã in-flight.
## Bước 4 — Đối chiếu metrics
Trong cùng time window, kiểm tra:
```promql
duocthu_requests_total
duocthu_abstention_total
duocthu_generation_rejected_total
duocthu_stage_duration_seconds
```
Reason label giúp phân biệt availability failure (`provider_unavailable`,
`request_budget_exhausted`) với content/grounding failure
(`unsupported_claim`, `ungrounded_number`).
## Bước 5 — Kiểm tra citation về source
Với từng citation:
1. lấy `chunk_id`, `drug_id`, `section_key``evidence_text`;
2. xác nhận claim trỏ đúng thuốc và đúng section;
3. mở `printed_page_start` trong PDF;
4. nếu có attachment/bbox/crop, review ảnh gốc;
5. nếu block quarantine, không cố suy số từ text flatten.
## Bước 6 — Phân loại kết luận
| Kết luận | Bằng chứng cần có |
|---|---|
| Retrieval sai | Resolved frame đúng nhưng evidence sai section/drug |
| Understanding sai | QueryFrame/route chọn sai thuốc, relation hoặc population |
| Provider outage | Span/provider error và metric availability tương ứng |
| Grounding reject đúng | Generated claim vi phạm citation/number/entailment |
| UI mapping sai | Backend response đúng nhưng message/citation render sai |
| Trace persistence lỗi | Answer trả được nhưng không có PostgreSQL record |
## Verify
Một incident note hoàn chỉnh phải ghi ID, commit/deployment version, decision,
reason, stage gây lỗi, evidence/citation liên quan và recovery đã thực hiện.
## Liên quan
- [Observability reference](../17-observability.md)
- [Production operations](../24-production-operations.md)
- [Generation and grounding](../11-generation-and-grounding.md)
- [Troubleshooting](../25-troubleshooting.md)