16 KiB
Audit pipeline RAG hội thoại hiện tại
Phạm vi: worktree
D:\VSF-DUOCTHUngày 2026-08-10. Báo cáo phản ánh implementation thật đang có trong worktree, bao gồm các thay đổi chưa commit.EXISTSkhông có nghĩa là đã đạt chất lượng production; nó chỉ nghĩa là đã tìm thấy implementation live tương đương.Cập nhật 2026-08-11 — đây là bản ghi theo ngày 2026-08-10. Đo lại trên production ngày 2026-08-11 cho hai kết quả khác:
- §7 ghi "Sildenafil ADR vẫn fail
ungrounded_number". Hai lần chạy lại cho hai kết quả khác nhau (46,8sabstain/unsupported_claim; 25,1sanswerable/grounded, 2 citation), không lần nào làungrounded_number. Nhiều khả năng là nhiễu ở tầng entailment/generation hơn là một lỗi xác định, nên nếu xử lý thì nên tiếp cận theo hướng đó.- §7 ghi "Pytest: 226 passed, 5 skipped". Số hiện tại là 230 passed (bỏ
test_api.py/test_live_datastores.pyvốn cần datastore sống).Một phần §6/§7 đã được xử lý ngày 2026-08-11:
incomplete_answerở đường completeness-repair phần lớn đến từ việc cạn budget, nay tách thànhrequest_budget_exhausted/provider_unavailable. Xem mục 2026-08-11 trongdocs/progress-log.md.
1. Request path đã xác minh
ChatPanel.handleSendMessage
-> POST /api/chat (Next.js BFF)
-> POST /v1/rag/query (FastAPI)
-> RagAgent.handle
-> history + prior QueryFrame
-> LlmQueryUnderstander.understand
-> RagAgent._route
-> RetrievalService.retrieve_framed / retrieve_by_indication
-> Qdrant metadata route hoặc bounded fallback
-> parent hydration + dedupe + evidence policy
-> GroundedAnswerService.answer_from_result
-> structured claim generation
-> deterministic number/citation grounding
-> semantic support + completeness verifier
-> RagQueryResponse (answer blocks + claims + source ids)
-> /api/chat mapping sang ChatMessage
-> ChatBubble + CitationCard/Evidence panel
Đường live không dùng QueryRoutingService để fuzzy-resolve thuốc; class này còn
được giữ cho retrieval-only fallback. Live agent dùng candidate-bounded
LlmQueryUnderstander rồi truyền drug_id và section_key đã resolve vào
RetrievalService.retrieve_framed.
Trace live đã chạy
| Query | Kết quả | Latency quan sát | Đối chiếu raw |
|---|---|---|---|
Levetiracetam cần tránh những điều kiện môi trường nào khi cất giữ? qua browser localhost:3000 |
answerable; block Bảo quản; 1 source, tr. 888 |
10,9 s end-to-end | Đủ 20–25 °C, tránh ánh sáng, dung dịch uống giữ trong bao bì ban đầu |
Nêu đầy đủ tác dụng không mong muốn của Medroxyprogesteron acetat... qua API |
answerable; 14 nhóm ADR | 14,6 s | Tên ADR và điều kiện chính đủ; hierarchy tần suất kế thừa vẫn cần regression test chặt hơn |
Bảo quản Levetiracetam thế nào? |
abstain provider_unavailable |
17,9 s | Không có answer để chấm; không tính pass |
| Medroxy completeness repair | abstain incomplete_answer |
23,5–26,8 s | Cho thấy repair path có thể chạm budget/provider và làm latency xấu |
Số mẫu trên chưa đủ để gọi là p50/p95. TTFB bằng gần toàn bộ latency vì response hiện là JSON nguyên khối, không có streaming.
2. Capability matrix
| Capability | Status | Evidence implementation | Quyết định |
|---|---|---|---|
| Conversation state | PARTIAL | rag/agent.py:RagAgent._get_history/_remember; adapters/postgres.py:PostgresConversationStore |
EXTEND: raw lines có window 6 turns; _last_frame chỉ in-process, không bền qua restart/multi-worker |
| Context resolution | PARTIAL | LlmQueryUnderstander.understand, _known_facts_block, _merge_with_prior_frame |
EXTEND: merge có code backstop chủ yếu cho clarify continuation; topic switching vẫn phụ thuộc model |
| Standalone query rewrite | PARTIAL | rag/agent.py:_synthesize_query |
EXTEND: đã fold population/age/weight/route/indication nhưng không lưu standalone_query first-class trong frame/trace |
| Active entity tracking | PARTIAL | QueryFrame.drugs; RagAgent._last_frame |
EXTEND persistence/isolation; active frame hiện mất khi process restart |
| Intent/facet detection | EXISTS | QueryFrame.turn_type, attribute, population, route, indication; closed vocab trong understanding.py |
REUSE; mở rộng multi-facet/reasoning mode, không thêm classifier call riêng |
| Metadata routing | EXISTS | RetrievalService.retrieve_framed; QdrantRetriever.find_by_section/find_by_drug |
KEEP: known entity + facet đi thẳng đúng section |
| Dense retrieval | EXISTS | QdrantRetriever.search/search_indication |
KEEP bounded fallback; không dùng cho mọi query |
| Sparse/lexical retrieval | PARTIAL | QdrantRetriever.search_lexical |
EXTEND nếu cần: term-overlap/BM25-style, không phải một sparse vector/BM25 index đầy đủ |
| Hybrid/RRF | PARTIAL | rag/fusion.py:reciprocal_rank_fusion có testable primitive nhưng live RetrievalService chưa gọi |
Không quảng cáo là live hybrid; chỉ wire sau eval chứng minh lợi ích |
| Reranker | PARTIAL | RetrievalService._rerank; BedrockCohereReranker trong bootstrap |
KEEP: chỉ overview/similarity fallback; explicit section route cố ý không rerank |
| Parent/sibling expansion | PARTIAL | RetrievalService._hydrate parent hydration; _pooled_neighbour_hits bounded cross-section |
KEEP bounded; không có generic sibling expansion cho mọi query |
| Evidence selector | PARTIAL | _hydrate dedupe, provenance/quarantine policy, pack_evidence token budget |
EXTEND: chưa có explicit selected/rejected reason trace theo population/route relevance |
| Evidence sufficiency | PARTIAL | generation evidence_sufficient; _check_sufficiency; completeness verifier |
EXTEND thành supported/partial/insufficient/conflicting; hiện boolean và fail toàn answer |
| Multi-section retrieval | PARTIAL | interaction gom evidence nhiều thuốc; than_trong opt-in lexical neighbor |
EXTEND cho multi-facet có kế hoạch; không mở cross-section pooling toàn cục |
| Reasoning/multi-step logic | MISSING | Không có premise/conclusion representation hoặc bounded decomposition path | ADD sau P0–P2; không dùng agent loop cho simple lookup |
| Structured claims | EXISTS | prompt.py:ANSWER_SCHEMA; answer.py:_parse_claims |
KEEP |
| Claim-to-evidence mapping | EXISTS | claim citation indices được map sang stable source_ids; response blocks giữ mapping |
KEEP; bổ sung claim id/support status khi cần inference/partial |
| Grounding validation | EXISTS | grounding.verify; _verify_entailment |
KEEP; completeness judge cần eval để giảm false positive/negative |
| Abstention | EXISTS | EvidenceDecision; granular reject reasons; provider/malformed/grounding guards |
KEEP |
| Answer planning | PARTIAL | generation instruction + _answer_mode theo claim count + _build_blocks theo section |
REPLACE heuristic bằng compact plan trong cùng generation call; không thêm LLM call |
| Adaptive verbosity | PARTIAL | _answer_mode chỉ dựa claim count; prompt phân biệt broad/specific |
EXTEND theo query complexity/answer mode, không chỉ số claim |
| Response composition | PARTIAL | AnswerBlock/AnswerClaim và BFF DTO |
EXTEND: hiện block granularity còn section-centric; chưa có lead/limitation/group hierarchy |
| SSE/streaming | MISSING | ChatPanel dùng await res.json(); FastAPI trả RagQueryResponse, không StreamingResponse |
ADD sau correctness; hiện không được nói là streaming |
| Source rendering | EXISTS | CitationCard, evidence pane, printed/physical page, raw snippet |
KEEP provenance; giảm chip lặp dưới từng claim |
| Semantic response components | PARTIAL | ChatBubble render AnswerBlock.kind; citation panel |
EXTEND nhỏ; không biến mỗi paragraph/section thành card |
| Follow-up handling | PARTIAL | history, prior frame merge, latest-clarify quick replies, clarify circuit breaker | EXTEND và eval 50–100 turns; history window hiện 6 turns nên long chat chưa được chứng minh |
| Prometheus/Grafana | PARTIAL | /metrics, PrometheusMetrics, provisioned Grafana dashboard |
KEEP aggregate counters; stack chưa được xác minh running trong audit này |
| Full request trace | PARTIAL | Postgres rag_retrieval_trace chỉ lưu query/decision/reason/resolved drug/citations |
EXTEND stage timing/frame/route/evidence/guard verdict; không đưa lên user UI |
3. Actual pipeline so với target
Phần nên giữ:
- candidate-bounded entity understanding;
- structured
QueryFramevà deterministic metadata route; - whole-section retrieval cho explicit facet;
- parent hydration, dedupe, provenance và quarantine;
- structured claims, deterministic numeric grounding và semantic verifier;
- Postgres conversation/trace, Prometheus counter và evidence panel.
Khoảng trống có tác động lớn nhất:
- active frame không durable và standalone meaning không phải first-class output;
- một
attributeduy nhất không biểu diễn multi-facet query; - evidence selection/sufficiency chưa biểu diễn partial/conflicting;
- chưa có direct/synthesis/inference mode và premise mapping;
- answer plan chỉ là heuristic, renderer hiện quá card-heavy/source-heavy;
- không streaming; latency 9–27 s và provider availability là lỗi backend thực;
- trace chưa đủ stage timing để drill-down từ Grafana.
4. Failure taxonomy theo layer
| Layer | Failure đã thấy hoặc có code path | Không được ngụy trang thành |
|---|---|---|
| Understanding/provider | timeout/throttle/malformed frame | user clarification |
| Context | stale entity, mất constraint, clarify loop | retrieval miss |
| Routing | sai facet, single-facet collapse | generator hallucination |
| Retrieval | wrong section, dense weak neighbor, parent missing | answer-style problem |
| Evidence | duplicate, mất heading/condition, token truncation | citation success |
| Generation | unsupported/partial/incomplete claim | “đã grounded” |
| Composition | hierarchy bị làm phẳng, source chip lặp | RAG correctness |
| Availability | provider unavailable, request budget exhausted | “không có trong Dược thư” |
| Observability | thiếu stage timing/selected-rejected evidence | user-facing technical trace |
5. Smallest coherent change-set
Không dựng pipeline thứ hai. Mở rộng các abstraction đang có theo thứ tự:
- P0 evidence/response contract: giữ structured claims, thêm answer plan nhỏ
trong cùng generation call; hỗ trợ
lead, semantic group và limitation; verifier trả support/completeness rõ, partial không bị trình bày như full. - P1 context: đưa
standalone_queryvàdepends_on_previous_turnvàoQueryFrame; persist active frame cùng conversation store thay vì dict local. - P2 retrieval planning: cho frame mang nhiều facets; gọi
retrieve_framedtheo từng facet có giới hạn rồi dùng cùngdecide/provenance policy. Không bật generic RRF/cross-section pooling nếu eval chưa chứng minh. - Composition/UI: prose/list là mặc định; warning/dosage/table chỉ khi plan
yêu cầu; một affordance
Xem căn cứtheo group/message, không chip dưới mọi dòng; bỏ dashboard chrome trong mỗi answer. - Trace/latency: stage timing và call counts vào internal trace/metrics; sau khi correctness ổn mới thiết kế safe streaming commit-by-verified-claim.
6. Những gì chưa được gọi là pass
- Batch 30 thuốc đã chạy xong nhưng không pass: chỉ 11/30 trả lời, 11/30
abstain và 8/30 hỏi lại. Đây là baseline trước bản sửa
section_overviewvà evidence-quoted completeness bên dưới, không được dùng làm số sau-fix. - Hội thoại dài đã chạy qua BFF; xem kết quả và giới hạn encoding ở mục 7.
- Prometheus/Grafana chưa được mở và xác minh trong phiên audit này.
- Không có p50/p95/p99 đủ mẫu.
- Grounded inference chưa được implement.
- Medroxy đã tốt hơn nhưng hierarchy tần suất cần test machine-checkable và browser review sau khi answer-plan contract hoàn thiện.
7. Kết quả triển khai và kiểm chứng ngày 2026-08-10
Thay đổi nhỏ trên đúng pipeline hiện hữu, không tạo pipeline thứ hai:
QueryFramecóstandalone_query,depends_on_previous_turnvàsection_overview. Tra toàn mục được tách khỏi yêu cầu chọn một liều cho ca bệnh; drug + facet rõ không còn bị classifier tự ý biến thành chip thu hẹp.- Answer plan compact (
verbosity,layout,reasoning_mode, heading/warning) được lập trước generation bằng code, không thêm model call. - Completeness objection phải kèm
evidence_quote; code kiểm tra quote tồn tại trong raw và thật sự hỗ trợ mô tả “bị thiếu”. Judge không còn có thể loại câu bảo quản chỉ vì câu hỏi nhắc “độ ẩm” trong khi raw không nêu độ ẩm. - Renderer dùng prose/list mặc định, một
Xem căn cứcho group, không[1] [2]trong câu trả lời và không card cho từng claim.
Baseline random 30 trước-fix theo facet:
| Facet | Answer | Abstain | Clarify | Nhận xét |
|---|---|---|---|---|
| Bảo quản | 4 | 2 | 0 | completeness false-positive |
| Tương tác | 6 | 0 | 0 | tốt nhất trong mẫu |
| ADR | 1 | 5 | 0 | incomplete/provider/grounding gây fail |
| Liều/cách dùng | 0 | 2 | 4 | ép population cho cả truy vấn toàn mục |
| Thận trọng | 0 | 2 | 4 | classifier hỏi lại dù facet đã rõ |
Retest có đối chiếu raw:
- Levetiracetam sau-fix: answerable 9,3 giây; đủ
20–25 °C, tránh ánh sáng, dung dịch uống giữ bao bì ban đầu. Browser localhost sau hot path: 6,1 giây. - Ergotamin tartrat: answerable 6,8 giây; giữ đúng nhiệt độ riêng theo dạng dùng.
- Isosorbid dinitrat toàn mục liều: answerable 19,9 giây thay vì chip; giữ nhãn chỉ định/đường dùng/liều, nhưng latency chưa đạt.
- Sildenafil ADR vẫn fail
ungrounded_number; đây là fail đúng của safety gate, không được đổi nhãn thành pass. - Ganciclovir, Glipizid, Vancomycin, Isradipin từng gặp
provider_unavailable; availability/provider vẫn là blocker thực.
Validation code hiện tại:
- Ruff: pass.
- Pytest:
226 passed, 5 skipped. - TypeScript
--noEmit: pass. - Next.js production build: pass.
- UI browser: pass về request/render; ảnh review xác nhận hết bullet kép và câu trả lời không còn citation marker nội tuyến.
Long conversation:
- Một conversation ID chạy 50 request liên tiếp qua
localhost:3000/api/chat, không có HTTP error. Runner đầu làm mất dấu tiếng Việt trong user lines khi đi qua PowerShell nên không dùng 6 lượt cuối của lần này làm kết luận context. - Giữ nguyên conversation đó và chạy sạch lượt 51–56 bằng chuỗi không lỗi encoding: Levetiracetam → follow-up chống chỉ định → đổi sang Isradipin → follow-up bảo quản → đổi sang Zolpidem → follow-up ADR. Cả ba follow-up đều bám đúng thuốc gần nhất; không rò Levetiracetam sang Isradipin/Zolpidem.
- Isradipin thận trọng ở lượt 53 bị
incomplete_answer, nhưng lượt 54 vẫn resolve “thuốc này” đúng Isradipin và trả bảo quản dưới 30 °C, lọ kín, tránh sáng/ẩm. - Latency lượt sạch 51–56: 6,9–18,8 giây; correctness context đạt trong kịch bản này nhưng tốc độ và provider/completeness availability chưa đạt.