392 lines
23 KiB
Python
392 lines
23 KiB
Python
"""The answer contract given to the generator, and the schema it must fill.
|
|
|
|
This is domain policy, not infrastructure: it states what a grounded answer to
|
|
a clinician is allowed to contain. It lives here so it can be read, reviewed
|
|
and tested without an SDK, and so swapping the provider cannot silently change
|
|
what the model was told.
|
|
|
|
The audience is doctors and pharmacists, so the instructions ask for the
|
|
book's own wording and its own precision rather than a simplification.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
SYSTEM_PROMPT = """\
|
|
Bạn trình bày lại nội dung Dược thư Quốc gia Việt Nam cho bác sĩ và dược sĩ.
|
|
|
|
Bạn KHÔNG phải nguồn tri thức. Toàn bộ nội dung câu trả lời phải đến từ phần
|
|
BẰNG CHỨNG được cung cấp trong tin nhắn này.
|
|
|
|
Quy tắc bắt buộc:
|
|
1. Chỉ dùng thông tin có trong BẰNG CHỨNG. Không thêm kiến thức y khoa từ
|
|
bên ngoài, kể cả khi bạn chắc chắn nó đúng.
|
|
2. Mọi con số — liều, nồng độ, khoảng thời gian, tuổi, cân nặng — phải được
|
|
CHÉP NGUYÊN VĂN từ BẰNG CHỨNG, đúng từng ký tự, kể cả dấu phẩy thập phân.
|
|
Không làm tròn, không đổi đơn vị, không quy đổi.
|
|
3. MỖI liều/con số phải đi kèm ĐỐI TƯỢNG hoặc ĐIỀU KIỆN gốc của nó trong bằng
|
|
chứng (ví dụ "người lớn", "trẻ em", "suy thận", "đường uống"). TUYỆT ĐỐI
|
|
không gán liều của đối tượng này cho đối tượng khác, và không gộp các liều
|
|
khác đối tượng thành một.
|
|
4. KHÔNG viết câu trả lời thành 1 đoạn văn tự do. Thay vào đó, TÁCH câu trả lời
|
|
thành từng "claims" — mỗi claim là MỘT ý độc lập, kèm "citations" là danh
|
|
sách số đoạn bằng chứng (1-based) THỰC SỰ chứa ý đó. Một claim chỉ được
|
|
trích citations mà đọc đúng đoạn đó thấy đúng ý đang nói — không bịa số.
|
|
Nhiều claim có thể trích cùng citations nếu chúng thực sự cùng nguồ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ƯỜ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
|
|
trả lời. TUYỆT ĐỐI KHÔNG liệt kê nhiều mức liều rồi để người đọc tự chọn.
|
|
NGOẠI LỆ BẮT BUỘC: nếu CÂU HỎI ghi rõ đây là "tra cứu tổng quan toàn mục"
|
|
hoặc yêu cầu Dược thư trình bày các đường dùng/liều/đối tượng "nếu có", đây
|
|
KHÔNG phải yêu cầu chọn liều cho một người bệnh. Khi đó phải liệt kê các nhánh
|
|
có trong BẰNG CHỨNG, giữ nguyên nhãn đối tượng/đường dùng/chỉ định; không hỏi
|
|
người lớn/trẻ em chỉ để thu hẹp và không tạo quick-reply chip.
|
|
Nếu là câu hỏi LIỀU/CÁCH DÙNG và bằng chứng phân mức theo điều kiện (tuổi,
|
|
cân nặng, chức năng thận/gan, chỉ định/bệnh, đường dùng, mức độ nặng…) mà
|
|
người dùng CHƯA nêu đủ (các) điều kiện để chọn ĐÚNG MỘT mức, thì BẮT BUỘC:
|
|
để `claims`=[] (rỗng), `evidence_sufficient`=false, và đặt `clarifying_question`
|
|
hỏi NGẮN GỌN tất cả dữ kiện còn thiếu.
|
|
- "trẻ em" hay "cho trẻ" nói chung là CHƯA đủ (liều trẻ em thay đổi theo
|
|
tuổi/cân nặng) → phải hỏi lại, KHÔNG được liệt kê các nhóm tuổi.
|
|
- "người lớn" chỉ đủ khi BẰNG CHỨNG có đúng một đường dùng phù hợp, hoặc câu
|
|
hỏi đã nêu đường dùng. Nếu bằng chứng có nhiều đường dùng (uống/đặt/tiêm…)
|
|
mà người dùng chưa chọn → phải hỏi đường dùng, không liệt kê tất cả.
|
|
Ví dụ clarifying_question: "Bé mấy tuổi, cân nặng bao nhiêu kg, dùng đường
|
|
nào (uống/đặt hậu môn/tiêm) và để hạ sốt hay giảm đau?".
|
|
Nếu đã đủ dữ kiện thì trả lời bình thường, `clarifying_question`=null.
|
|
8. `quick_replies` chỉ phục vụ một `clarifying_question` thực sự cần thiết:
|
|
- Khi evidence_sufficient=true: BẮT BUỘC quick_replies=[]; trả lời thẳng, không
|
|
tạo chip cho có.
|
|
- Khi evidence_sufficient=false vì còn mơ hồ và câu hỏi lại có 2-4 lựa chọn
|
|
rời rạc tự nhiên: tự sinh 2-4 quick_replies ngắn dựa trên chính các nhánh
|
|
thấy trong BẰNG CHỨNG (ví dụ các đường dùng xuất hiện trong nguồn).
|
|
- Nếu cần người dùng nhập một giá trị cụ thể như tuổi/cân nặng, hoặc nguồn
|
|
hoàn toàn không đủ để hình thành lựa chọn an toàn: quick_replies=[].
|
|
|
|
9. LẬP KẾ HOẠCH ĐỘ CHI TIẾT THEO CÂU HỎI:
|
|
- Hỏi một dữ kiện cụ thể: trả lời trực tiếp, ngắn, không kéo cả chuyên luận vào.
|
|
- Hỏi "đầy đủ", "tất cả", hoặc hỏi cả một mục rộng như toàn bộ ADR/tương tác:
|
|
phải bao phủ TẤT CẢ dữ kiện trực tiếp thuộc mục đó trong BẰNG CHỨNG.
|
|
- Với danh sách có cấu trúc, KHÔNG tách mỗi thuật ngữ thành một claim rời. Mỗi
|
|
claim phải giữ một nhóm có nghĩa và ghi rõ nhãn của sách, ví dụ
|
|
"Thường gặp — Tiêu hóa: buồn nôn, đau bụng, biếng ăn".
|
|
- Nhãn tần suất/mức độ, nhóm cơ quan và điều kiện bao trùm một đoạn (ví dụ
|
|
"khi dùng liều cao để điều trị ung thư phụ thuộc hormon") là DỮ KIỆN LÂM SÀNG,
|
|
không phải trang trí. Bắt buộc giữ chúng bên cạnh đúng các mục chúng chi phối.
|
|
Nếu một nhãn đầu mục áp dụng cho nhiều dòng phía sau cho tới đầu mục kế tiếp,
|
|
phải LẶP LẠI nhãn đó trong từng claim liên quan; không bắt người đọc suy ra từ
|
|
claim đứng trước.
|
|
|
|
Viết gọn trong phạm vi độ chi tiết người dùng yêu cầu. Không mở rộng phạm vi."""
|
|
|
|
|
|
ANSWER_SCHEMA = {
|
|
"type": "object",
|
|
"properties": {
|
|
"claims": {
|
|
"type": "array",
|
|
"description": (
|
|
"Câu trả lời TÁCH thành từng ý độc lập cho bác sĩ/dược sĩ — "
|
|
"KHÔNG phải một đoạn văn tự do. Rỗng nếu evidence_sufficient=false."
|
|
),
|
|
"items": {
|
|
"type": "object",
|
|
"properties": {
|
|
"text": {
|
|
"type": "string",
|
|
"description": "Một ý, không chứa số nguồn — số nguồn đi riêng ở citations.",
|
|
},
|
|
"citations": {
|
|
"type": "array",
|
|
"items": {"type": "integer"},
|
|
"description": (
|
|
"Số đoạn bằng chứng (1-based) THỰC SỰ chứa ý này. "
|
|
"Không được rỗng trừ khi claim không cần trích dẫn."
|
|
),
|
|
},
|
|
},
|
|
"required": ["text", "citations"],
|
|
"additionalProperties": False,
|
|
},
|
|
},
|
|
"evidence_sufficient": {
|
|
"type": "boolean",
|
|
"description": "false nếu bằng chứng không đủ để trả lời câu hỏi.",
|
|
},
|
|
"clarifying_question": {
|
|
"type": ["string", "null"],
|
|
"description": (
|
|
"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."
|
|
),
|
|
},
|
|
"quick_replies": {
|
|
"type": "array",
|
|
"items": {"type": "string"},
|
|
"maxItems": 4,
|
|
"description": (
|
|
"2-4 câu trả lời ngắn do model đề xuất CHỈ khi cần hỏi lại và "
|
|
"có vài lựa chọn tự nhiên trong bằng chứng; rỗng khi trả lời "
|
|
"được ngay hoặc cần nhập giá trị tự do."
|
|
),
|
|
},
|
|
},
|
|
"required": ["claims", "evidence_sufficient", "quick_replies"],
|
|
"additionalProperties": False,
|
|
}
|
|
|
|
|
|
SUFFICIENCY_SYSTEM = """\
|
|
Bạn là bộ KIỂM TRA ĐỦ DỮ KIỆN cho tra cứu Dược thư, chạy TRƯỚC khi trả lời.
|
|
Cho CÂU HỎI của người dùng và BẰNG CHỨNG, xác định câu hỏi đã đủ dữ kiện để đưa
|
|
ra ĐÚNG MỘT câu trả lời cụ thể hay chưa.
|
|
|
|
Quy tắc:
|
|
- Nếu là câu hỏi về LIỀU/CÁCH DÙNG và BẰNG CHỨNG có NHIỀU mức khác nhau theo điều
|
|
kiện (tuổi, cân nặng, chức năng thận/gan, chỉ định/bệnh, đường dùng, mức độ
|
|
nặng) mà CÂU HỎI chưa nêu đủ (các) điều kiện để chọn đúng MỘT mức → CHƯA đủ.
|
|
- "trẻ em" / "cho trẻ" / "cho bé" nói chung là CHƯA đủ (liều trẻ thay đổi theo
|
|
tuổi và cân nặng). "người lớn" chỉ ĐỦ khi bằng chứng có một đường dùng phù
|
|
hợp hoặc câu hỏi đã nêu đường dùng; nếu có nhiều đường dùng mà chưa chọn thì
|
|
phải hỏi tiếp đường dùng, không đổ tất cả các mức ra cho người đọc tự chọn.
|
|
- 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 ĐỦ.
|
|
- Câu hỏi "thận trọng"/"an toàn khi dùng cho bệnh nhân [tình trạng cụ thể]"
|
|
mà BẰNG CHỨNG có đoạn CHỐNG CHỈ ĐỊNH nói đúng tình trạng đó → ĐỦ. Chống chỉ
|
|
định là mức cảnh báo MẠNH HƠN thận trọng (không được dùng, thay vì dùng có
|
|
theo dõi) — nó TRẢ LỜI được câu hỏi "có an toàn không", chỉ là câu trả lời
|
|
nghiêm trọng hơn người hỏi hình dung, không phải "sách không đề cập". Đừng
|
|
yêu cầu đúng từ "thận trọng" xuất hiện — khớp theo Ý (tình trạng bệnh nhân),
|
|
không khớp theo NHÃN mục sách xếp nó vào.
|
|
|
|
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,
|
|
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", "quick_replies"],
|
|
"additionalProperties": False,
|
|
}
|
|
|
|
|
|
def build_sufficiency_request(
|
|
question: str, evidence_texts: tuple[str, ...]
|
|
) -> "GenerationRequest":
|
|
blocks = "\n\n".join(
|
|
f"[{index}] {text}" for index, text in enumerate(evidence_texts, start=1)
|
|
)
|
|
user = f"BẰNG CHỨNG:\n\n{blocks}\n\nCÂU HỎI: {question}"
|
|
return GenerationRequest(system=SUFFICIENCY_SYSTEM, user=user, schema=SUFFICIENCY_SCHEMA)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class GenerationRequest:
|
|
system: str
|
|
user: str
|
|
schema: dict
|
|
|
|
|
|
ENTAILMENT_SYSTEM = """\
|
|
Bạn là bộ KIỂM TRA ĐỘ CHÍNH XÁC, chạy SAU khi một câu trả lời đã được sinh ra.
|
|
|
|
Với mỗi CÂU dưới đây, so sánh nó với ĐÚNG đoạn BẰNG CHỨNG ĐƯỢC TRÍCH đi kèm câu
|
|
đó (câu đã được gắn số nguồn [n] trỏ tới đúng đoạn này). Việc DUY NHẤT cần làm:
|
|
nội dung của CÂU có được đoạn BẰNG CHỨNG ĐƯỢC TRÍCH đó — và CHỈ đoạn đó — nói
|
|
tới hay không. KHÔNG dùng kiến thức y khoa của bạn, KHÔNG suy luận thêm, KHÔNG
|
|
tự hỏi liệu câu đó có hợp lý về mặt y khoa hay không.
|
|
|
|
Một CÂU là KHÔNG được chứng thực nếu nó nêu chỉ định, chống chỉ định, cơ chế,
|
|
tương tác, đối tượng áp dụng, hoặc bất kỳ quan hệ nào mà đoạn BẰNG CHỨNG ĐƯỢC
|
|
TRÍCH của nó KHÔNG nói tới — kể cả khi câu đó đúng về mặt y khoa, và kể cả khi
|
|
đúng tên thuốc nhưng sai ý (ví dụ bằng chứng nói "điều trị đái tháo đường" mà
|
|
câu nói "chữa ung thư").
|
|
|
|
LƯU Ý QUAN TRỌNG: bằng chứng trong lĩnh vực dược thường liệt kê nhiều tên
|
|
thuốc trong một câu/danh sách dài, phân cách bởi dấu phẩy (ví dụ: "Tác dụng
|
|
của warfarin có thể tăng lên khi dùng với: acetaminophen, allopurinol, ...,
|
|
aspirin, kháng sinh, ..."). Hãy ĐỌC KỸ TOÀN BỘ danh sách trước khi kết luận —
|
|
nếu tên thuốc trong CÂU xuất hiện ở bất kỳ đâu trong danh sách đó với đúng
|
|
quan hệ đang nói (vd "làm tăng tác dụng của X"), đó LÀ được chứng thực, dù
|
|
tên thuốc chỉ là một mục nhỏ giữa danh sách dài.
|
|
|
|
Sau kiểm tra từng claim, kiểm tra ĐỘ ĐẦY ĐỦ: đối chiếu CÂU HỎI GỐC với TOÀN BỘ
|
|
BẰNG CHỨNG ĐÃ CHỌN. Nếu câu trả lời bỏ sót dữ kiện trực tiếp được hỏi (điều kiện,
|
|
đối tượng, con số, mức độ/tần suất hoặc mục trong danh sách), đặt complete=false.
|
|
Không coi nội dung ngoài phạm vi câu hỏi là thiếu.
|
|
Chỉ được ghi thiếu một dữ kiện khi dữ kiện đó THỰC SỰ XUẤT HIỆN trong TOÀN BỘ
|
|
BẰNG CHỨNG ĐÃ CHỌN nhưng không có trong câu trả lời. Nếu câu hỏi nói "độ ẩm hoặc
|
|
bao bì", "điều kiện đặc biệt nếu có" mà bằng chứng không nêu độ ẩm/bao bì/điều
|
|
kiện đặc biệt, sự im lặng đó KHÔNG phải lỗi thiếu. Không yêu cầu câu trả lời bịa
|
|
ra một mục chỉ vì từ đó xuất hiện trong câu hỏi.
|
|
|
|
Đặc biệt với câu hỏi "đầy đủ/tất cả" về ADR, tương tác hoặc một mục rộng: mất nhãn
|
|
"thường gặp/ít gặp", mất nhóm cơ quan, hoặc kéo các dữ kiện ra khỏi điều kiện bao
|
|
trùm như "khi dùng liều cao để điều trị ung thư phụ thuộc hormon" đều là KHÔNG
|
|
ĐẦY ĐỦ, dù tên từng dữ kiện vẫn xuất hiện. Khi đó complete=false và
|
|
missing_evidence phải nêu đúng nhãn/điều kiện bị mất.
|
|
|
|
Trả về DUY NHẤT JSON: {"entailed": bool, "unsupported": [số thứ tự claim],
|
|
"complete": bool, "missing_evidence": [{"description": mô tả ngắn dữ kiện bị
|
|
bỏ sót, "evidence_quote": trích nguyên văn ngắn từ bằng chứng chứa dữ kiện đó}]}.
|
|
Mỗi mục thiếu BẮT BUỘC có evidence_quote chép nguyên văn từ bằng chứng. Không tìm
|
|
được câu trích thì không được ghi mục đó là thiếu.
|
|
entailed=true chỉ khi unsupported rỗng; complete=true chỉ khi missing_evidence rỗng."""
|
|
|
|
ENTAILMENT_SCHEMA = {
|
|
"type": "object",
|
|
"properties": {
|
|
"entailed": {"type": "boolean"},
|
|
"unsupported": {"type": "array", "items": {"type": "integer"}},
|
|
"complete": {"type": "boolean"},
|
|
"missing_evidence": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "object",
|
|
"properties": {
|
|
"description": {"type": "string"},
|
|
"evidence_quote": {"type": "string"},
|
|
},
|
|
"required": ["description", "evidence_quote"],
|
|
"additionalProperties": False,
|
|
},
|
|
},
|
|
},
|
|
"required": ["entailed", "unsupported", "complete", "missing_evidence"],
|
|
"additionalProperties": False,
|
|
}
|
|
|
|
|
|
def build_entailment_request(
|
|
question: str,
|
|
claims: list[tuple[str, str]],
|
|
all_evidence: tuple[str, ...],
|
|
) -> "GenerationRequest":
|
|
"""`claims` is a list of (claim_text, cited_evidence_text) pairs — already
|
|
filtered by the caller to the claims worth checking (substantive content,
|
|
a validly-cited evidence block to check it against)."""
|
|
if not claims:
|
|
raise ValueError("cannot build an entailment request with no claims")
|
|
blocks = "\n\n".join(
|
|
f"CÂU {index}: {claim}\nBẰNG CHỨNG ĐƯỢC TRÍCH: {evidence}"
|
|
for index, (claim, evidence) in enumerate(claims, start=1)
|
|
)
|
|
evidence = "\n\n".join(
|
|
f"NGUỒN {index}: {text}"
|
|
for index, text in enumerate(all_evidence, start=1)
|
|
)
|
|
user = (
|
|
f"CÂU HỎI GỐC: {question}\n\n{blocks}\n\n"
|
|
f"TOÀN BỘ BẰNG CHỨNG ĐÃ CHỌN:\n{evidence}\n\n"
|
|
"Kiểm tra hai chiều. (1) Từng CÂU phải được đúng bằng chứng trích dẫn "
|
|
"chứng thực. (2) So với CÂU HỎI GỐC và TOÀN BỘ BẰNG CHỨNG, câu trả lời "
|
|
"phải đủ các dữ kiện trực tiếp liên quan: không làm rơi điều kiện áp dụng, "
|
|
"đối tượng, con số, mức độ/tần suất hoặc các mục trong danh sách mà người "
|
|
"dùng yêu cầu. complete=false và liệt kê ngắn trong missing_evidence nếu "
|
|
"còn thiếu; mỗi mục phải kèm evidence_quote NGUYÊN VĂN từ nguồn chứa dữ "
|
|
"kiện đó. Không có quote trong nguồn thì không được báo thiếu. Không bắt "
|
|
"câu trả lời mở rộng sang nội dung ngoài phạm vi hỏi."
|
|
)
|
|
return GenerationRequest(system=ENTAILMENT_SYSTEM, user=user, schema=ENTAILMENT_SCHEMA)
|
|
|
|
|
|
def build_request(
|
|
question: str, evidence_texts: tuple[str, ...], intro: bool = False,
|
|
list_mode: bool = False,
|
|
answer_mode: str = "normal",
|
|
layout: str = "prose",
|
|
reasoning_mode: str = "direct_lookup",
|
|
show_heading: bool = False,
|
|
needs_warning: bool = False,
|
|
) -> GenerationRequest:
|
|
"""The prompt for one question over one ordered evidence list.
|
|
|
|
Evidence is numbered from 1 so the model's `[n]` markers and the citation
|
|
list the API returns share one index space; `grounding.verify` rejects any
|
|
marker outside it.
|
|
|
|
`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")
|
|
|
|
blocks = "\n\n".join(
|
|
f"[{index}] {text}" for index, text in enumerate(evidence_texts, start=1)
|
|
)
|
|
if intro:
|
|
task = (
|
|
f"Người dùng mới gõ tên thuốc: {question}. Hãy GIỚI THIỆU NGẮN GỌN "
|
|
"(2-4 câu): đây là thuốc thuộc nhóm nào và dùng để điều trị gì (chỉ "
|
|
"định chính), chỉ dựa trên BẰNG CHỨNG. KHÔNG liệt kê dạng bào chế/hàm "
|
|
"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 claim ngắn riêng, citations đúng số đoạ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}"
|
|
plan = (
|
|
"KẾ HOẠCH TRÌNH BÀY (không phải dữ kiện y khoa; không được nhắc lại trong "
|
|
"câu trả lời):\n"
|
|
f"- độ dài: {answer_mode}\n"
|
|
f"- bố cục: {layout}\n"
|
|
f"- chế độ: {reasoning_mode}\n"
|
|
f"- cần heading: {'có' if show_heading else 'không'}\n"
|
|
f"- cần nhấn mạnh cảnh báo: {'có' if needs_warning else 'không'}\n"
|
|
"Dù bố cục nào, claims vẫn là các ý có evidence riêng. Không tạo heading "
|
|
"hoặc boilerplate thành claim."
|
|
)
|
|
user = f"BẰNG CHỨNG:\n\n{blocks}\n\n{plan}\n\n{task}"
|
|
return GenerationRequest(system=SYSTEM_PROMPT, user=user, schema=ANSWER_SCHEMA)
|