|
@@ -48,7 +48,7 @@ public class ChatController {
|
|
|
String answer;
|
|
String answer;
|
|
|
// 剂量/用法/禁忌等零容错场景:直接回原文,不经过 LLM
|
|
// 剂量/用法/禁忌等零容错场景:直接回原文,不经过 LLM
|
|
|
if ("usage_guide".equals(intent) && !docs.isEmpty()) {
|
|
if ("usage_guide".equals(intent) && !docs.isEmpty()) {
|
|
|
- answer = buildDirectAnswer(docs);
|
|
|
|
|
|
|
+ answer = buildDirectAnswer(query, docs);
|
|
|
} else {
|
|
} else {
|
|
|
List<Map<String, String>> messages = promptService.buildPrompt(query, docs, intent);
|
|
List<Map<String, String>> messages = promptService.buildPrompt(query, docs, intent);
|
|
|
String rawAnswer = llmService.chat(messages);
|
|
String rawAnswer = llmService.chat(messages);
|
|
@@ -80,7 +80,7 @@ public class ChatController {
|
|
|
|
|
|
|
|
// 剂量/用法/禁忌等零容错场景:直接回原文
|
|
// 剂量/用法/禁忌等零容错场景:直接回原文
|
|
|
if ("usage_guide".equals(intent) && !docs.isEmpty()) {
|
|
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);
|
|
final List<Map<String, Object>> sources = buildSources(docs);
|
|
|
sink.tryEmitNext(ServerSentEvent.<String>builder().event("intent").data(intent).build());
|
|
sink.tryEmitNext(ServerSentEvent.<String>builder().event("intent").data(intent).build());
|
|
|
sink.tryEmitNext(ServerSentEvent.<String>builder().data(directAnswer).build());
|
|
sink.tryEmitNext(ServerSentEvent.<String>builder().data(directAnswer).build());
|
|
@@ -194,13 +194,15 @@ public class ChatController {
|
|
|
return docs.stream()
|
|
return docs.stream()
|
|
|
.map(d -> {
|
|
.map(d -> {
|
|
|
String content = (String) d.getOrDefault("content", "");
|
|
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("\\s*来源:.*$", "");
|
|
|
content = content.replaceAll("[\\r\\n]+", " ").trim();
|
|
content = content.replaceAll("[\\r\\n]+", " ").trim();
|
|
|
String excerpt = content.length() > 500 ? content.substring(0, 500) + "…" : content;
|
|
String excerpt = content.length() > 500 ? content.substring(0, 500) + "…" : content;
|
|
|
return Map.<String, Object>of(
|
|
return Map.<String, Object>of(
|
|
|
- "name", d.getOrDefault("source", ""),
|
|
|
|
|
- "section", d.getOrDefault("section", ""),
|
|
|
|
|
|
|
+ "name", drugName,
|
|
|
|
|
+ "section", section,
|
|
|
"source", d.getOrDefault("source", ""),
|
|
"source", d.getOrDefault("source", ""),
|
|
|
"excerpt", excerpt
|
|
"excerpt", excerpt
|
|
|
);
|
|
);
|
|
@@ -209,23 +211,98 @@ public class ChatController {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/** 剂量/用法/禁忌等零容错场景:直接用检索结果构建回答,不经过 LLM */
|
|
/** 剂量/用法/禁忌等零容错场景:直接用检索结果构建回答,不经过 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();
|
|
StringBuilder sb = new StringBuilder();
|
|
|
sb.append("【原文引用】以下内容直接来自药典原文,未经 AI 改写:\n\n");
|
|
sb.append("【原文引用】以下内容直接来自药典原文,未经 AI 改写:\n\n");
|
|
|
int count = 0;
|
|
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 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();
|
|
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()) {
|
|
if (!content.isEmpty()) {
|
|
|
|
|
+ sb.append("【").append(drugName).append(" - ").append(section).append("】\n");
|
|
|
sb.append(content).append("\n\n");
|
|
sb.append(content).append("\n\n");
|
|
|
count++;
|
|
count++;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+ if (count == 0) return "未找到匹配的原文内容。";
|
|
|
sb.append("—— 以上内容直接引用自药典原文,确保准确性。\n本回答由AI生成,仅供参考。");
|
|
sb.append("—— 以上内容直接引用自药典原文,确保准确性。\n本回答由AI生成,仅供参考。");
|
|
|
return sb.toString();
|
|
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) {
|
|
private List<Map<String, Object>> rerank(List<Map<String, Object>> docs, String query, int topK) {
|
|
|
if (docs.size() <= topK) return docs;
|
|
if (docs.size() <= topK) return docs;
|
|
|
docs.sort((a, b) -> {
|
|
docs.sort((a, b) -> {
|