Parcourir la source

java的代码调整优化

liuchengsen il y a 1 mois
Parent
commit
b2e5071120

+ 85 - 8
backend-java/src/main/java/com/pharmacopoeia/controller/ChatController.java

@@ -48,7 +48,7 @@ public class ChatController {
         String answer;
         // 剂量/用法/禁忌等零容错场景:直接回原文,不经过 LLM
         if ("usage_guide".equals(intent) && !docs.isEmpty()) {
-            answer = buildDirectAnswer(docs);
+            answer = buildDirectAnswer(query, docs);
         } else {
             List<Map<String, String>> messages = promptService.buildPrompt(query, docs, intent);
             String rawAnswer = llmService.chat(messages);
@@ -80,7 +80,7 @@ public class ChatController {
 
         // 剂量/用法/禁忌等零容错场景:直接回原文
         if ("usage_guide".equals(intent) && !docs.isEmpty()) {
-            final String directAnswer = buildDirectAnswer(docs);
+            final String directAnswer = buildDirectAnswer(query, docs);
             final List<Map<String, Object>> sources = buildSources(docs);
             sink.tryEmitNext(ServerSentEvent.<String>builder().event("intent").data(intent).build());
             sink.tryEmitNext(ServerSentEvent.<String>builder().data(directAnswer).build());
@@ -194,13 +194,15 @@ public class ChatController {
         return docs.stream()
                 .map(d -> {
                     String content = (String) d.getOrDefault("content", "");
-                    // 清理:去来源行、合并换行为空格、去首尾空白
+                    String storedSection = (String) d.getOrDefault("section", "");
+                    String section = realSection(content, storedSection);
+                    String drugName = extractDrugName(content);
                     content = content.replaceAll("\\s*来源:.*$", "");
                     content = content.replaceAll("[\\r\\n]+", " ").trim();
                     String excerpt = content.length() > 500 ? content.substring(0, 500) + "…" : content;
                     return Map.<String, Object>of(
-                            "name", d.getOrDefault("source", ""),
-                            "section", d.getOrDefault("section", ""),
+                            "name", drugName,
+                            "section", section,
                             "source", d.getOrDefault("source", ""),
                             "excerpt", excerpt
                     );
@@ -209,23 +211,98 @@ public class ChatController {
     }
 
     /** 剂量/用法/禁忌等零容错场景:直接用检索结果构建回答,不经过 LLM */
-    private String buildDirectAnswer(List<Map<String, Object>> docs) {
+    private String buildDirectAnswer(String query, List<Map<String, Object>> docs) {
+        // 从 query 中提取可能的药品名
+        String targetDrug = extractQueryDrugName(query);
+        // 从 query 中提取关注的 section(如"用法用量"→"用法与用量")
+        String targetSection = extractQuerySection(query);
+
+        // 过滤:优先匹配药品名 + section
+        List<Map<String, Object>> filtered = new ArrayList<>();
+        List<Map<String, Object>> fallback = new ArrayList<>();
+        for (Map<String, Object> d : docs) {
+            String content = (String) d.getOrDefault("content", "");
+            String section = (String) d.getOrDefault("section", "");
+            boolean drugMatch = targetDrug.isEmpty() || content.contains(targetDrug);
+            boolean sectionMatch = targetSection.isEmpty() || section.contains(targetSection);
+
+            if (drugMatch && sectionMatch) {
+                filtered.add(d);  // 精确匹配
+            } else if (drugMatch) {
+                fallback.add(d);  // 只匹配药品名
+            }
+        }
+
+        // 精确匹配不够,用 fallback
+        List<Map<String, Object>> selected = filtered.size() >= 1 ? filtered : fallback;
+
         StringBuilder sb = new StringBuilder();
         sb.append("【原文引用】以下内容直接来自药典原文,未经 AI 改写:\n\n");
         int count = 0;
-        for (Map<String, Object> d : docs) {
-            if (count >= 3) break;
+        for (Map<String, Object> d : selected) {
+            if (count >= 2) break;
             String content = (String) d.getOrDefault("content", "");
+            String storedSection = (String) d.getOrDefault("section", "");
+            String section = realSection(content, storedSection);
+            String drugName = extractDrugName(content);
             content = content.replaceAll("\\s*来源:.*$", "").trim();
+            if (content.length() > 300) {
+                int cut = content.indexOf('\n', 250);
+                if (cut < 0 || cut > 350) cut = 300;
+                content = content.substring(0, cut).trim() + "…";
+            }
             if (!content.isEmpty()) {
+                sb.append("【").append(drugName).append(" - ").append(section).append("】\n");
                 sb.append(content).append("\n\n");
                 count++;
             }
         }
+        if (count == 0) return "未找到匹配的原文内容。";
         sb.append("—— 以上内容直接引用自药典原文,确保准确性。\n本回答由AI生成,仅供参考。");
         return sb.toString();
     }
 
+    /** 从 query 提取药品名:"布洛芬的用法用量" → "布洛芬" */
+    private String extractQueryDrugName(String query) {
+        // 去掉常见后缀
+        String cleaned = query.replaceAll("[的之](用法|用量|副作用|禁忌|注意事项|说明书|是什么|多少钱).*$", "");
+        // 去掉纯问句前缀
+        cleaned = cleaned.replaceAll("^(什么是|什么是|怎么|如何|告诉我|请问|查询|搜索|查一下)", "");
+        return cleaned.trim();
+    }
+
+    /** 从 query 提取关注的 section */
+    private String extractQuerySection(String query) {
+        if (query.contains("用法") || query.contains("用量") || query.contains("剂量") || query.contains("怎么吃") || query.contains("怎么用")) return "用法";
+        if (query.contains("禁忌") || query.contains("禁用")) return "禁忌";
+        if (query.contains("副作用") || query.contains("不良反应")) return "不良反应";
+        if (query.contains("注意") || query.contains("慎用")) return "注意";
+        if (query.contains("贮藏") || query.contains("保存")) return "贮藏";
+        return "";
+    }
+
+    private String extractDrugName(String content) {
+        if (content == null) return "";
+        int start = content.indexOf("【");
+        int end = content.indexOf(" - ");
+        if (start >= 0 && end > start) {
+            return content.substring(start + 1, end);
+        }
+        return content.length() > 20 ? content.substring(0, 20) : content;
+    }
+
+    /** 从 content 文本中提取真实 section(兜底"正文") */
+    private String realSection(String content, String storedSection) {
+        if (!"正文".equals(storedSection) || content == null) return storedSection;
+        int sep = content.indexOf(" - ");
+        if (sep < 0) return storedSection;
+        int end = content.indexOf("】", sep);
+        if (end > sep) {
+            return content.substring(sep + 3, end).trim();
+        }
+        return storedSection;
+    }
+
     private List<Map<String, Object>> rerank(List<Map<String, Object>> docs, String query, int topK) {
         if (docs.size() <= topK) return docs;
         docs.sort((a, b) -> {

+ 49 - 11
backend-java/src/main/java/com/pharmacopoeia/service/RetrieverService.java

@@ -20,9 +20,10 @@ public class RetrieverService {
     public String classifyIntent(String query) {
         String q = query.trim();
 
-        if (anyMatch(q, "怎么吃", "吃多少", "怎么用", "孕妇", "儿童用量",
+        if (anyMatch(q, "怎么吃", "吃多少", "怎么用", "怎么服用", "孕妇", "儿童用量",
                 "副作用多大", "伤肝", "伤肾", "安全吗", "副作用", "不良反应",
-                "禁忌", "过敏", "能不能", "可以吗")) {
+                "禁忌", "过敏", "能不能", "可以吗", "用法", "用量", "剂量",
+                "用药指导", "一天几次", "一次多少", "饭前", "饭后", "空腹")) {
             return "usage_guide";
         }
 
@@ -49,16 +50,53 @@ public class RetrieverService {
                 .map(String::valueOf)
                 .collect(Collectors.joining(",", "[", "]"));
 
-        String sql = """
-            SELECT content, source, drug_id, section,
-                   1 - (vec <=> ?::vector) AS similarity
-            FROM drug_chunks
-            WHERE vec IS NOT NULL
-            ORDER BY vec <=> ?::vector
-            LIMIT ?
-            """;
+        // 尝试提取药品名,用于精确定位
+        String drugName = extractDrugName(query);
 
-        return jdbc.queryForList(sql, vecStr, vecStr, topK);
+        String sql;
+        Object[] params;
+        if (!drugName.isEmpty()) {
+            // 药品名+向量混合检索:精确匹配 或 前缀匹配(如"布洛芬"匹配"布洛芬片")
+            sql = """
+                SELECT c.content, c.source, c.drug_id, c.section,
+                       1 - (c.vec <=> ?::vector) AS similarity
+                FROM drug_chunks c
+                JOIN drugs d ON d.drug_id = c.drug_id
+                WHERE c.vec IS NOT NULL AND (d.name = ? OR d.name LIKE ?)
+                ORDER BY c.vec <=> ?::vector
+                LIMIT ?
+                """;
+            params = new Object[]{vecStr, drugName, drugName + "%", vecStr, topK};
+        } else {
+            sql = """
+                SELECT content, source, drug_id, section,
+                       1 - (vec <=> ?::vector) AS similarity
+                FROM drug_chunks
+                WHERE vec IS NOT NULL
+                ORDER BY vec <=> ?::vector
+                LIMIT ?
+                """;
+            params = new Object[]{vecStr, vecStr, topK};
+        }
+
+        return jdbc.queryForList(sql, params);
+    }
+
+    /** 从 query 中提取已知药品名:查 drugs 表精确匹配 */
+    private String extractDrugName(String query) {
+        // 去掉常见后缀词,尝试匹配药品名
+        String cleaned = query.replaceAll("[的之是](用法|用量|副作用|禁忌|注意|说明书|是什么|怎么|如何).*$", "");
+        cleaned = cleaned.trim();
+        if (cleaned.length() < 2) return "";
+
+        // 查 drugs 表精确匹配
+        try {
+            List<String> matches = jdbc.queryForList(
+                "SELECT name FROM drugs WHERE name = ? AND is_active = TRUE LIMIT 1",
+                String.class, cleaned);
+            if (!matches.isEmpty()) return matches.get(0);
+        } catch (Exception ignored) {}
+        return "";
     }
 
     private boolean anyMatch(String text, String... keywords) {