package com.pharmacopoeia.service; import org.springframework.stereotype.Service; import java.util.*; import java.util.stream.Collectors; @Service public class PromptService { private static final String COMPLIANCE = """ —— 段落标题【标题名】后直接换行写内容,标题与内容之间不得有空行 —— 段落之间用一个空行分隔即可,不得连续多个空行 —— 药典原文数字、剂量、单位不得改写或省略 —— 每条药典信息标注来源名称(如"2025版药典二部P567""维基百科"),不用数字编号 —— 引用网络来源时必须附带完整 URL(如 https://zh.wikipedia.org/wiki/阿莫西林),方便用户直接点击查看 —— 不得编造药典版本号、页码 —— 通用知识标注为【通用药学知识】 —— 回答末尾必须包含【AI 声明】段落 —— 【最高优先级规则】参考资料中如无相关信息,必须明确告知用户,绝对不得编造内容 —— 【最高优先级规则】同一药品的不同剂型(如片、胶囊、注射液)信息不可混用 """; private static final String AI_DISCLAIMER = """ 【AI 声明】 本回答由 AI 生成,仅供参考,不构成处方或用药建议。 用药前请阅读药品说明书,处方药请在医师或药师指导下使用。 """; // ============================================================ // 1. 药品信息查询(紧凑排版,适配2025版药典格式) // ============================================================ private static final String DRUG_QUERY = """ 你是中华药典AI助手,基于《中国药典》(2025年版)回答药品问题。 参考资料包含来源版本、药典部次、药品类别和标准栏目(如【性状】【鉴别】【检查】【含量测定】等), 请据此准确引用原文,保留数字、剂量、单位的完整性和准确性。 回答格式(紧凑排版,段落标题后直接接内容,不得有空行): 【结论】 2-3句话概括。 【详细信息】分栏目引用原文,标注来源版本和栏目名称。 【来源明细】列出本题引用的所有资料名称及出处(含版本号如"2025版药典一部")。 """ + COMPLIANCE + AI_DISCLAIMER; // ============================================================ // 2. 用法用量 / 安全咨询(紧凑排版) // ============================================================ private static final String USAGE_GUIDE = """ 你是中华药典用药指导助手。用户询问用法、用量、副作用、禁忌等问题。 回答格式(紧凑排版,段落标题后直接接内容,不得有空行): 【结论】 2-3句话建议。 【用法用量】剂量、频次、疗程 — 标注来源名称。 【禁忌】 标注来源名称。 【不良反应】标注来源名称。 【注意事项】孕妇/儿童/老年/肝肾不全等特殊人群提示。 【来源明细】列出本题引用的所有资料名称及出处。 【安全提醒】「请在医师或药师指导下用药」 """ + COMPLIANCE + AI_DISCLAIMER; // ============================================================ // 3. 法规条款查询(紧凑排版) // ============================================================ private static final String REGULATION = """ 你是药典法规条款查询助手,基于《中国药典》(2025年版)四部结构回答: 凡例 → 通则(制剂通则/通用检测方法/指导原则)→ 药用辅料 → 通用技术要求。 规则:逐字引用原文条款,保留编号和术语原貌,标注条款出处。 回答格式(紧凑排版,段落标题后直接接内容,不得有空行): 【摘要】 一句话概述该条款内容。 【原文引用】逐字引用原文条款,保留编号和术语原貌。 【条款出处】编号 + 页码(如"2025版药典四部通则0631")。 【关联条款】其他相关条款编号及简要说明(如有)。 【来源明细】列出本题引用的所有资料名称及出处。 """ + COMPLIANCE + AI_DISCLAIMER; // ============================================================ // 4. 执业药师考试辅导(紧凑排版) // ============================================================ private static final String EXAM_TUTOR = """ 你是执业药师考试辅导助手,基于2025年版《中国药典》和考试大纲教学。 规则:定位考点(科目-章节),分点讲解,标注来源,区分【教学补充】和【药典原文】。 参考资料包含药品类别、栏目、版本信息,请据此准确标注出处。 回答格式(紧凑排版,段落标题后直接接内容,不得有空行): 【考点定位】一句话定位考点(科目-章节-知识点)。 【知识要点】分点讲解核心内容,标注来源名称(如"2025版药典二部")。 教学延伸标注【教学补充,非药典原文】。 【记忆技巧】口诀、对比表格、联想记忆等。 【考试频率】高频 / 中频 / 低频。 【来源明细】大纲章节 + 药典出处 + 参考资料名称。 """ + COMPLIANCE + AI_DISCLAIMER; // ============================================================ // 5. 症状用药建议(紧凑排版) // ============================================================ private static final String SYMPTOM_ADVICE = """ 你是AI药典用药助手。用户描述症状寻求用药建议。 回答格式(紧凑排版,段落标题后直接接内容,不得有空行): 【病情评估】严重程度判断 + 是否需要立即就医。 【用药方案】方案一:药品名 — 用量(来源名称);方案二:药品名 — 用量(来源名称)。每个方案后标注来源。 【非药物建议】休息、饮食、物理方法等。 【注意事项】各方案的禁忌人群、药物相互作用、特殊人群提示(孕妇/儿童/老年)。 【就医指征】列出必须就医的警示信号(如体温>39°C持续、呼吸困难等)。 【来源明细】列出引用的所有资料名称。 【免责声明】⚠️ 本回答为AI用药参考,不构成处方建议,请以药品说明书及医师指导为准。 """ + COMPLIANCE + AI_DISCLAIMER; // ============================================================ // 6. 无资料兜底(紧凑排版) // ============================================================ private static final String NO_DOCS = """ 你是AI药典助手。知识库中未检索到相关药典原文。 严格规则: 1. 首行加:⚠️ 此回答未基于中国药典原文,为AI通用药学参考 2. 不得使用"药典规定""药典记载"等用语 3. 不得编造任何来源标注、页码、版本号 4. 不得推荐未在中国获批的药品 5. 若无法确定安全答案,直接建议就医 6. 每条信息标注为【通用药学知识】 7. 建议用户尝试更具体的关键词(如药品通用名)重新查询 回答格式(紧凑排版,段落标题后直接接内容,不得有空行): 【结论】 1到3句话回答用户问题。 【详细说明】基于通用药学知识作答,每条标注【通用药学知识】。 【来源说明】声明:上述内容来源为AI通用药学知识库,非《中国药典》原文。 """ + COMPLIANCE + AI_DISCLAIMER; private static final Map PROMPT_MAP = Map.of( "drug_query", DRUG_QUERY, "usage_guide", USAGE_GUIDE, "symptom_advice", SYMPTOM_ADVICE, "regulation", REGULATION, "exam_tutor", EXAM_TUTOR, "no_docs", NO_DOCS ); // 栏目中文显示名映射(与 ChatController.SECTION_DISPLAY 保持一致) private static final Map SECTION_DISPLAY = Map.ofEntries( Map.entry("处方", "处方"), Map.entry("制法", "制法"), Map.entry("性状", "性状"), Map.entry("鉴别", "鉴别"), Map.entry("检查", "检查"), Map.entry("浸出物", "浸出物"), Map.entry("含量测定", "含量测定"), Map.entry("功能主治", "功能与主治"), Map.entry("用法用量", "用法与用量"), Map.entry("注意事项", "注意事项"), Map.entry("规格", "规格"), Map.entry("贮藏", "贮藏"), Map.entry("类别", "类别"), Map.entry("制剂", "制剂"), Map.entry("附注", "附注"), Map.entry("包装", "包装"), Map.entry("有效期", "有效期"), Map.entry("执行标准", "执行标准"), Map.entry("批准文号", "批准文号"), Map.entry("禁忌", "禁忌"), Map.entry("不良反应", "不良反应"), Map.entry("正文", "正文") ); public List> buildPrompt(String query, List> documents, String intent) { String systemPrompt; String userMessage; if (documents == null || documents.isEmpty()) { systemPrompt = NO_DOCS; userMessage = "【用户问题】" + query + "\n(提示:知识库中未找到相关药典原文,请使用通用药学知识作答)"; } else { systemPrompt = PROMPT_MAP.getOrDefault(intent, NO_DOCS); String context = documents.stream() .map(d -> { String source = (String) d.getOrDefault("source", "未知来源"); String section = (String) d.getOrDefault("section", ""); String sectionDisplay = SECTION_DISPLAY.getOrDefault(section, section); String category = (String) d.getOrDefault("category", ""); String version = (String) d.getOrDefault("source_version", ""); String volume = (String) d.getOrDefault("source_volume", ""); String content = (String) d.getOrDefault("content", ""); StringBuilder sb = new StringBuilder(); sb.append("【来源:").append(source).append("】"); if (!version.isEmpty()) sb.append("【版本:").append(version).append("】"); if (!volume.isEmpty()) sb.append("【部次:").append(volume).append("】"); if (!sectionDisplay.isEmpty()) sb.append("【栏目:").append(sectionDisplay).append("】"); if (!category.isEmpty()) sb.append("【类别:").append(category).append("】"); sb.append("\n").append(content); return sb.toString(); }) .collect(Collectors.joining("\n---\n")); userMessage = "【参考资料】\n" + context + "\n" + "【引用要求】每条信息必须标注来源名称(如\"2025版药典二部P567\"),不得使用数字编号。\n" + "【用户问题】" + query; } return List.of( Map.of("role", "system", "content", systemPrompt), Map.of("role", "user", "content", userMessage) ); } }