Add read-only production runtime audit
This commit is contained in:
@@ -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` và `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` và `/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` và `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)
|
||||
@@ -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` và `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)
|
||||
@@ -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` và `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` và `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)
|
||||
Reference in New Issue
Block a user