|
@@ -45,9 +45,15 @@ public class ChatController {
|
|
|
List<Map<String, Object>> docs = retrieverService.search(query, intent, 20);
|
|
List<Map<String, Object>> docs = retrieverService.search(query, intent, 20);
|
|
|
docs = rerank(docs, query, 5);
|
|
docs = rerank(docs, query, 5);
|
|
|
|
|
|
|
|
- List<Map<String, String>> messages = promptService.buildPrompt(query, docs, intent);
|
|
|
|
|
- String rawAnswer = llmService.chat(messages);
|
|
|
|
|
- String answer = cleanAnswer(rawAnswer);
|
|
|
|
|
|
|
+ String answer;
|
|
|
|
|
+ // 剂量/用法/禁忌等零容错场景:直接回原文,不经过 LLM
|
|
|
|
|
+ if ("usage_guide".equals(intent) && !docs.isEmpty()) {
|
|
|
|
|
+ answer = buildDirectAnswer(docs);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ List<Map<String, String>> messages = promptService.buildPrompt(query, docs, intent);
|
|
|
|
|
+ String rawAnswer = llmService.chat(messages);
|
|
|
|
|
+ answer = cleanAnswer(rawAnswer);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
List<Map<String, Object>> sources = buildSources(docs);
|
|
List<Map<String, Object>> sources = buildSources(docs);
|
|
|
persistenceService.saveMessage(cid, "user", query, intent, null);
|
|
persistenceService.saveMessage(cid, "user", query, intent, null);
|
|
@@ -72,6 +78,23 @@ public class ChatController {
|
|
|
|
|
|
|
|
Sinks.Many<ServerSentEvent<String>> sink = Sinks.many().unicast().onBackpressureBuffer();
|
|
Sinks.Many<ServerSentEvent<String>> sink = Sinks.many().unicast().onBackpressureBuffer();
|
|
|
|
|
|
|
|
|
|
+ // 剂量/用法/禁忌等零容错场景:直接回原文
|
|
|
|
|
+ if ("usage_guide".equals(intent) && !docs.isEmpty()) {
|
|
|
|
|
+ final String directAnswer = buildDirectAnswer(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());
|
|
|
|
|
+ try {
|
|
|
|
|
+ String meta = new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(Map.of(
|
|
|
|
|
+ "intent", intent, "sources", sources));
|
|
|
|
|
+ sink.tryEmitNext(ServerSentEvent.<String>builder().event("meta").data(meta).build());
|
|
|
|
|
+ } catch (Exception e) {}
|
|
|
|
|
+ persistenceService.saveMessage(cid, "user", query, intent, null);
|
|
|
|
|
+ persistenceService.saveMessage(cid, "assistant", directAnswer, intent, sources);
|
|
|
|
|
+ sink.tryEmitComplete();
|
|
|
|
|
+ return sink.asFlux();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
try {
|
|
try {
|
|
|
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().event("status").data("Retrieving...").build());
|
|
sink.tryEmitNext(ServerSentEvent.<String>builder().event("status").data("Retrieving...").build());
|
|
@@ -171,7 +194,8 @@ 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", "");
|
|
|
- // 截取前 500 字作为原文片段
|
|
|
|
|
|
|
+ // 清理格式:去掉首尾空白、压缩多余换行
|
|
|
|
|
+ content = content.replace("\r\n", "\n").replaceAll("\\n{2,}", "\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", ""),
|
|
"name", d.getOrDefault("source", ""),
|
|
@@ -183,6 +207,25 @@ public class ChatController {
|
|
|
.collect(Collectors.toList());
|
|
.collect(Collectors.toList());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /** 剂量/用法/禁忌等零容错场景:直接用检索结果构建回答,不经过 LLM */
|
|
|
|
|
+ private String buildDirectAnswer(List<Map<String, Object>> docs) {
|
|
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
|
|
+ sb.append("【原文引用】以下内容直接来自药典原文,未经 AI 改写:\n\n");
|
|
|
|
|
+ int count = 0;
|
|
|
|
|
+ for (Map<String, Object> d : docs) {
|
|
|
|
|
+ if (count >= 3) break;
|
|
|
|
|
+ String content = (String) d.getOrDefault("content", "");
|
|
|
|
|
+ // 去掉来源行
|
|
|
|
|
+ content = content.replaceAll("\\n*来源:.*$", "").trim();
|
|
|
|
|
+ if (!content.isEmpty()) {
|
|
|
|
|
+ sb.append(content).append("\n\n");
|
|
|
|
|
+ count++;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ sb.append("—— 以上内容直接引用自药典原文,确保准确性。\n本回答由AI生成,仅供参考。");
|
|
|
|
|
+ return sb.toString();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
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) -> {
|