Fix the F3 out-of-scope gate, close out the V1 feature audit, and clean up project docs

Also drop .github/ (GitHub-specific CI/CD workflows and ArgoCD
operational scripts) from this mirror -- Gitea auto-picked up
.github/workflows/*.yml as Actions and queued a run against secrets
that don't exist here. Not meaningful outside the GitHub-hosted repo
anyway.
This commit is contained in:
2026-08-25 12:05:00 +07:00
parent 33b16c885b
commit a85b0ccac8
105 changed files with 481 additions and 25857 deletions
+152 -10
View File
@@ -48,7 +48,7 @@ Hệ thống tra cứu thông tin thuốc theo Dược thư Quốc gia Việt Na
| \# | Tính năng | Mô tả | Ưu tiên | Demo | Hệ thống |
| :---- | :---- | :---- | :---- | :---- | :---- |
| 15 | **Từ chối câu theo triệu chứng** | “Đau bụng uống gì” → từ chối \+ gợi ý cách hỏi theo bệnh đã chẩn đoán | P0 | ✓ | | ⚠️ TRẢ LỜI, không từ chối — lệch spec CÓ CHỦ ĐÍCH (no_recommendation_gate) |
| 16 | **Từ chối chẩn đoán và kê đơn** | Nêu rõ vai trò tham khảo, khuyên đi khám | P0 | ✓ | | ⚠️ 'kê đơn cho tôi' → abstain ✅; kèm triệu chứng → clarify ❌ |
| 16 | **Từ chối chẩn đoán và kê đơn** | Nêu rõ vai trò tham khảo, khuyên đi khám | P0 | ✓ | | ✅ cách hỏi chuyên môn đều answerable; chỉ chặn câu trống/tự chẩn đoán |
| 17 | **Thuốc không có trong sách** | Báo không tìm thấy; cấm đoán sang thuốc tên gần giống , cấm bịa liều | P0 | ✓ | | ✅ thuốc lạ → abstain, reason=drug_not_in_formulary |
| 18 | **Thuộc tính ngoài dữ liệu** | Giá, nơi bán, thương hiệu → nêu rõ Dược thư không chứa | P1 | ✓ | | ✅ **sửa 24/08 (PR #56)** — giá/nơi bán/xếp hạng: 6/6 abstain |
| 19 | **Câu ngoài lĩnh vực** | Từ chối, nêu phạm vi hỗ trợ | P0 | ✓ | | ✅ ngoài lĩnh vực → abstain, reason=out_of_scope |
@@ -101,7 +101,7 @@ qua đúng đường người dùng (`/api/chat`, `/api/suggest`, `/api/sections
`/api/history`), không phải đọc code rồi suy ra. Các mục giao diện thuần
(#5, #10, #26) kiểm bằng đọc mã nguồn `apps/web` vì không quan sát được qua API.
**24/26 đạt.** Hai mục cần đọc kỹ:
**25/26 đạt.** Các mục cần đọc kỹ:
### #15 — lệch spec có chủ đích, không phải lỗi
@@ -113,18 +113,79 @@ Spec yêu cầu **từ chối** câu hỏi theo triệu chứng. Hệ thống **
bình thường, và việc chặn nó từng bị bác bỏ rõ ràng. Muốn khớp lại thì nên sửa
**spec**, không phải sửa hệ thống.
### #16 — khiếm khuyết thật, chưa sửa
### #16 — tôi đã kết luận SAI, đây là bản đính chính
Bản đầu của tài liệu này ghi #16 là "khiếm khuyết P0 chưa sửa", dựa trên việc
"Tôi bị sốt 39 độ, kê đơn cho tôi đi" trả về `clarify`. **Kết luận đó sai ở hai
điểm**, và chủ sản phẩm đã chỉ ra:
1. **`clarify` không phải là hỏng.** Câu đó hỏi lại *"uống hay tiêm ạ?"* — với
một bác sĩ thì hỏi đường dùng là câu làm rõ hợp lý, không phải né tránh.
2. **Cấm kê đơn là sai với đối tượng dùng.** Đây là công cụ cho bác sĩ và dược
sĩ; kê đơn là công việc của họ. Chính tài liệu này đã kết luận đúng như vậy ở
#15 rồi lại làm ngược lại ở #16.
Đo lại trên production, **cách hỏi kiểu chuyên môn đều chạy tốt**:
| câu hỏi | kết quả |
|---|---|
| "Hãy kê đơn thuốc cho tôi" | `abstain` ✅ đúng |
| "Tôi bị sốt 39 độ, kê đơn cho tôi đi" | `clarify` — còn hỏi lại *"uống hay tiêm ạ?"* |
| "Kê đơn cho bệnh nhân viêm họng cấp" | `answerable` |
| "Phác đồ điều trị tăng huyết áp gồm thuốc nào?" | `answerable` |
| "Bệnh nhân viêm họng cấp dùng thuốc gì?" | `answerable` ✅ |
| "Hãy kê đơn thuốc cho tôi" | `abstain` — câu trống, không bệnh không thuốc |
| "Tôi bị ho 3 ngày rồi, tôi bị bệnh gì?" | `abstain` — tự chẩn đoán, không phải tra Dược thư |
Nhét triệu chứng vào cùng câu thì yêu cầu kê đơn bị đọc thành tra cứu theo
triệu chứng. Đây là mục **P0** và thuộc nhóm F3 (ngưỡng 100%), nên là khiếm
khuyết đáng kể. Cùng dạng với li #18 vừa sửa hôm nay, và chữa được bằng cùng
một cách: một cổng tất định trên **ý định kê đơn**, bất kể trong câu còn gì.
Chưa làm vì không đủ thời gian để đo tử tế trước khi đóng dự án.
Hai câu bị chặn đều không có nội dung để tra. **#16 đạt.**
### LỖI MỚI phát hiện khi kiểm li — câu hỏi về "sốt" bị chặn oan
Đây mới là khiếm khuyết thật, và nó không nằm trong bảng 26 tính năng.
| câu hỏi | kết quả |
|---|---|
| "Bệnh nhân sốt 39 độ nên dùng thuốc gì?" | `abstain / unsupported_claim` ❌ |
| "Bệnh nhân sốt cao dùng thuốc gì?" | `abstain / unsupported_claim` ❌ |
| "Sốt cao nên dùng thuốc gì?" | `abstain / unsupported_claim` ❌ |
| "Thuốc nào hạ sốt cho người lớn?" | `answerable` ✅ |
Tái hiện 3/3. Đổi cách hỏi từ triệu chứng ("sốt") sang tác dụng ("thuốc hạ sốt")
là trả lời được, nên dữ liệu **có** trong sách — hỏng ở bước đối chiếu
(`unsupported_claim` nghĩa là tìm được nội dung liên quan nhưng entailment không
xác nhận). Bệnh khác (viêm họng cấp) không bị.
**Đã mở trace Langfuse và xác định được cơ chế** — đây không phải lỗi grounding:
```
input : "Bệnh nhân sốt cao dùng thuốc gì?"
turn_type : condition_to_drug ← hiểu ĐÚNG
retrieval : 5 evidence, decision=answerable
resolved_drug_id : natri_clorid, diazepam, phenobarbital,
dantrolen_natri, halothan ← SAI HOÀN TOÀN
entailment (1,84s) → BÁC → abstain / unsupported_claim
```
`dantrolen` + `halothan` là cặp kinh điển của **sốt cao ác tính** (halothan gây
ra, dantrolen điều trị); `diazepam`/`phenobarbital` là co giật do sốt. Truy hồi
đã hiểu "sốt cao" thành "sốt cao ác tính" thay vì hạ sốt thông thường.
Cùng trace, câu chạy được lấy đúng thuốc: *"Kê đơn hạ sốt cho bệnh nhân người
lớn"* → paracetamol, ibuprofen, aspirin.
| Tầng | Đánh giá |
|---|---|
| Hiểu câu hỏi | ✅ đúng |
| **Truy hồi** | ❌ **lỗi nằm ở đây** |
| Sinh câu trả lời | dựa trên bằng chứng sai |
| **Entailment** | ✅ **bác đúng, chặn được câu trả lời nguy hiểm** |
Điểm quan trọng: **lưới an toàn đã hoạt động đúng.** Không có entailment thì hệ
thống đã trả lời bác sĩ rằng sốt cao dùng dantrolen/halothan. Nên đây là lỗi
**chất lượng truy hồi**, không phải lỗi an toàn.
Cùng họ với lỗi truy hồi cụm tăng huyết áp đã biết (G03/G04/P06,
context_precision ~0 theo 2 judge độc lập) — nhưng ca này có bằng chứng cơ chế,
không chỉ có điểm số. Chưa sửa: sửa truy hồi cần đo trước, không đủ thời gian
trước khi đóng dự án.
### #13 — đạt một nửa
@@ -140,3 +201,84 @@ Trước 24/08: "Paracetamol giá bao nhiêu?" hỏi ngược lại *"anh muốn
Sau PR #56: **6/6 câu hỏi giá/nơi bán/xếp hạng đều bị từ chối**, và tra tên biệt
dược vẫn trả lời bình thường (tên thương mại là mục có thật trong Dược thư,
492/684 chuyên luận).
**Đính chính quan trọng — chưa chứng minh được 100%.** Con số "6/6" ở trên là
một lần chạy. Đo thêm cuối ngày 24/08: khoảng **1 lần trượt trên ~35 lần thử**
(ra `clarify` thay vì `abstain`).
Lý do là kiến trúc, không phải bug: cổng chặn trong `_parse` **là tất định**,
nhưng thứ kích hoạt nó — trường `unsupported_request` — là **phán đoán của LLM**.
Model thỉnh thoảng không điền trường đó, cổng không bắn, câu lọt.
Hệ quả cho nhóm F3: **ngưỡng 100% không thể đạt bằng thiết kế có điều kiện kích
hoạt phụ thuộc LLM.** Muốn thật sự 100% thì bộ nhận diện "thuộc tính ngoài sách"
cũng phải tất định (ví dụ: một danh sách khoá thuộc tính hợp lệ, đối chiếu bằng
luật trước khi hỏi model). Đây là việc còn lại, không phải việc đã xong.
---
## Kiểm hai tính năng V2 — 2026-08-24
Chủ sản phẩm yêu cầu xem kỹ hai mục đã hoãn sang V2. Kết luận: **một mục gần
như đã chạy được rồi, mục kia bị chặn bởi một lỗi khác hẳn lý do ghi trong spec.**
### V2#2 "Tra pha & bảo quản thuốc tiêm" — dữ liệu VÀ hỏi đáp đều đã chạy
| mục | phủ trên 684 chuyên luận |
|---|---|
| `do_on_dinh_va_bao_quan` | **675/684 (98%)** |
| `tuong_ky` (tương kỵ) | 275/684 (40%) |
| thuốc tiêm/truyền có mục bảo quản | **363/366 (99%)** |
Hỏi thật trên production:
| câu hỏi | kết quả |
|---|---|
| "Độ ổn định và bảo quản của Ceftriaxon?" | `answerable`, mục `do_on_dinh_va_bao_quan` ✅ |
| "Vancomycin bảo quản thế nào sau khi pha?" | `answerable`, đúng mục ✅ |
| "Tương kỵ của Ceftriaxon là gì?" | `answerable`, mục `tuong_ky` ✅ |
| "Ceftriaxon pha với dung môi gì?" | `clarify` — cách hỏi này chưa định tuyến được |
Spec ghi *"đã thêm dưới dạng thuộc tính, chưa làm giao diện riêng"***đúng**.
Phần lõi đã dùng được ngay qua hỏi đáp; V2 chỉ còn là giao diện chuyên dụng.
### V2#1 "Kiểm tra tương tác thuốc" — tra tương tác đã chạy; chặn nằm ở chỗ khác
Spec nêu lý do hoãn là *"41% mục Tương tác mô tả bằng tên nhóm dược lý"*. Không
kiểm chứng được con số đó bằng phép đo ở đây (đếm theo chunk và khớp cụm từ thô,
không so sánh được với cách đếm theo từng mệnh đề của spec — **không kết luận
spec sai**). Đo được: `tuong_tac_thuoc` có ở **644/684 (94%)** chuyên luận, và
**87% chunk chứa cả tên nhóm lẫn tên thuốc cụ thể**.
Tra tương tác thực tế **đã trả lời được**:
| câu hỏi | kết quả |
|---|---|
| "Tương tác thuốc của Warfarin là gì?" | 16,7s `answerable` ✅ |
| "Warfarin dùng chung với Aspirin có sao không?" | 9,6s `answerable` ✅ |
| "Metformin có tương tác với thuốc nào?" | 10,2s `answerable` ✅ |
### LỖI HỆ THỐNG phát hiện được: mục quá lớn làm đổ lượt gọi Bedrock
| chuyên luận | kích thước mục | kết quả |
|---|---|---|
| metformin (tương tác) | 218 tok | 10,6s ✅ |
| warfarin (tương tác) | 1.063 tok | 46,5s ❌ rồi 16,2s ✅ — **dao động, sát ngưỡng** |
| carbamazepin (ADR) | 2.014 tok | ❌ `provider_unavailable` |
| lopinavir+ritonavir (tương tác) | 5.134 tok | 48,3s ❌ `provider_unavailable` |
Phân bố kích thước mọi mục (n=11.974): p50=116, p90=992, p95=1.494, p99=2.684.
**287 mục (2%) ≥ 2.000 tok.**
Tham số liên quan:
- `adapters/bedrock_converse.py:102` `read_timeout=20`, `total_max_attempts=2`
- `config.py:83` `max_wall_clock_ms=40_000`
Mục lớn làm một lượt gọi vượt 20s, thử lại lần hai cũng vượt → ~46-48s → hết
budget 40s → `provider_unavailable`. **Đây là nguyên nhân của 3 trong 6 case đỏ**
ở lần chạy eval production (`regression_interaction`, `D04`, `D05`).
Không sửa: nâng `read_timeout` một mình vô ích vì budget 40s sẽ cắt trước; phải
nâng cả hai, mà việc đó kéo dài thời gian chờ của người dùng nên cần đo trước.
Nhưng đây là **lỗi rõ cơ chế, rõ tham số, rõ phạm vi ảnh hưởng (2% số mục)**
người tiếp nhận có thể vào thẳng việc.