Fix ai-service Dockerfile: bake in drug_entities.json, override its path

This commit is contained in:
2026-08-10 10:35:13 +07:00
parent a4b8e1c4db
commit 60b4397032
51 changed files with 4302 additions and 2087 deletions
+50 -7
View File
@@ -33,7 +33,14 @@ Quy tắc bắt buộc:
[n] ở mọi câu — gắn một lần cho một cụm cùng nguồn là đủ. Không bịa số [n].
5. Nếu BẰNG CHỨNG không đủ (thiếu đối tượng được hỏi, thiếu con số, hoặc chỉ nói
chung chung), nói rõ là không đủ và đặt evidence_sufficient=false. Đó là câu
trả lời hợp lệ. Không suy diễn để lấp chỗ trống.
trả lời hợp lệ. Không suy diễn để lấp chỗ trống. TRƯỜNG HỢP NÀY BẮT BUỘC LUÔN
điền `clarifying_question` giải thích NGẮN GỌN, CỤ THỂ vì sao — dù đó là vì
người dùng chưa nêu đủ dữ kiện (hỏi lại dữ kiện còn thiếu, như quy tắc 7), hay
đơn giản là chuyên luận KHÔNG đề cập nội dung này cho đối tượng/đường dùng
đang hỏi (nói thẳng điều đó, ví dụ "Dược thư không nêu liều dùng đường nhỏ
mắt của thuốc này"). TUYỆT ĐỐI KHÔNG để `clarifying_question`=null khi
evidence_sufficient=false — một câu giải thích cụ thể luôn hữu ích hơn cho
người đọc so với việc để trống.
6. Giữ nguyên thuật ngữ chuyên môn của sách. Không diễn giải cho người
không chuyên.
7. HỎI LẠI khi thiếu dữ kiện — ĐÂY LÀ QUY TẮC QUAN TRỌNG NHẤT, ưu tiên hơn việc
@@ -70,8 +77,11 @@ ANSWER_SCHEMA = {
"clarifying_question": {
"type": ["string", "null"],
"description": (
"Câu hỏi lại khi người dùng chưa nêu đủ dữ kiện (vd tuổi/cân nặng "
"cho câu hỏi liều có nhiều mức). null nếu đã đủ dữ kiện để trả lời."
"BẮT BUỘC khi evidence_sufficient=false: câu hỏi lại khi người dùng "
"chưa nêu đủ dữ kiện (vd tuổi/cân nặng), HOẶC — nếu vấn đề là "
"chuyên luận không có nội dung này — một câu nói thẳng điều đó (vd "
"'Dược thư không nêu liều dùng đường nhỏ mắt của thuốc này'). "
"null CHỈ khi evidence_sufficient=true."
),
},
},
@@ -94,18 +104,32 @@ Quy tắc:
- Câu hỏi KHÔNG về liều (chống chỉ định, tương tác, tác dụng phụ, giới thiệu
thuốc…) thì thường ĐỦ.
Trả về DUY NHẤT JSON: {"sufficient": bool, "clarifying_question": string|null}.
Trả về DUY NHẤT JSON: {"sufficient": bool, "clarifying_question": string|null,
"quick_replies": string[]}.
Nếu CHƯA đủ: sufficient=false và clarifying_question hỏi NGẮN GỌN tất cả dữ kiện
còn thiếu (vd: "Bé mấy tuổi, cân nặng bao nhiêu kg, dùng đường nào và để hạ sốt
hay giảm đau?"). Nếu đủ: sufficient=true, clarifying_question=null."""
hay giảm đau?"). Nếu đủ: sufficient=true, clarifying_question=null,
quick_replies=[].
quick_replies: 2-4 phương án trả lời NGẮN (dưới ~20 ký tự mỗi phương án) cho
CHÍNH câu clarifying_question vừa đặt ra, để người dùng bấm chọn thay vì gõ —
CHỈ khi câu hỏi thực sự có vài lựa chọn rời rạc, tự nhiên (vd đối tượng: "Người
lớn" / "Trẻ em"; đường dùng: "Uống" / "Tiêm"). Để mảng RỖNG nếu câu hỏi cần một
con số cụ thể không có sẵn lựa chọn ngắn (vd hỏi cân nặng chính xác) — không
được bịa ra các phương án number-ish giả."""
SUFFICIENCY_SCHEMA = {
"type": "object",
"properties": {
"sufficient": {"type": "boolean"},
"clarifying_question": {"type": ["string", "null"]},
"quick_replies": {
"type": "array",
"items": {"type": "string"},
"maxItems": 4,
},
},
"required": ["sufficient", "clarifying_question"],
"required": ["sufficient", "clarifying_question", "quick_replies"],
"additionalProperties": False,
}
@@ -180,7 +204,8 @@ def build_entailment_request(claims: list[tuple[str, str]]) -> "GenerationReques
def build_request(
question: str, evidence_texts: tuple[str, ...], intro: bool = False
question: str, evidence_texts: tuple[str, ...], intro: bool = False,
list_mode: bool = False,
) -> GenerationRequest:
"""The prompt for one question over one ordered evidence list.
@@ -191,6 +216,14 @@ def build_request(
`intro=True` is the "user typed only a drug name" case: instead of restating
a section, write a short introduction — what the drug is, its class and its
main indication — then invite a specific follow-up. Still evidence-only.
`list_mode=True` is the symptom_to_drug reverse-lookup case: each evidence
block is a DIFFERENT drug's own `chi_dinh`, not alternative phrasings of
one drug's section — found live 2026-08-07 that without this the model
picked just one drug out of 8 real matches and answered only about that
one, silently dropping the rest. Not a treatment ranking — a factual list
([[feedback_no_recommendation_gate]] already covers why a "which is best"
framing would be wrong for this audience anyway).
"""
if not evidence_texts:
raise ValueError("cannot build a grounded prompt with no evidence")
@@ -206,6 +239,16 @@ def build_request(
"lượng. Kết thúc bằng một câu mời hỏi tiếp về thuộc tính cụ thể (liều "
"dùng, chống chỉ định, thận trọng, tương tác…)."
)
elif list_mode:
task = (
f"CÂU HỎI: {question}\n\nMỖI đoạn BẰNG CHỨNG trên là chỉ định của MỘT "
"thuốc KHÁC NHAU. Hãy LIỆT KÊ TẤT CẢ các thuốc mà bằng chứng cho thấy "
"có chỉ định phù hợp với câu hỏi — không chỉ chọn một thuốc. Mỗi thuốc "
"một câu ngắn, gắn đúng số nguồn [n] của thuốc đó. Đây là liệt kê tra "
"cứu, KHÔNG phải khuyến cáo thuốc nào tốt hơn — không xếp hạng, không "
"chọn thuốc \"phù hợp nhất\". Nếu KHÔNG thuốc nào trong bằng chứng thực "
"sự phù hợp với câu hỏi, nói rõ điều đó thay vì liệt kê thuốc không liên quan."
)
else:
task = f"CÂU HỎI: {question}"
user = f"BẰNG CHỨNG:\n\n{blocks}\n\n{task}"