feat(chat): 尝试在对话中加入知识库

This commit is contained in:
v-zhangjc9
2025-06-03 20:23:52 +08:00
parent 536c4e9cab
commit c2af2d6365
14 changed files with 216 additions and 54 deletions

View File

@@ -1,9 +1,12 @@
package com.lanyuanxiaoyao.service.ai.chat.controller;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.lanyuanxiaoyao.service.ai.chat.entity.MessageVO;
import com.lanyuanxiaoyao.service.ai.chat.tools.DatetimeTools;
import com.lanyuanxiaoyao.service.forest.service.KnowledgeService;
import java.io.IOException;
import java.util.Optional;
import org.eclipse.collections.api.list.ImmutableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -11,10 +14,14 @@ import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.deepseek.DeepSeekAssistantMessage;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
@@ -28,26 +35,42 @@ import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
@RequestMapping("chat")
public class ChatController {
private static final Logger logger = LoggerFactory.getLogger(ChatController.class);
private static final String ROLE_ASSISTANT = "assistant";
private static final String ROLE_USER = "user";
private final ChatClient chatClient;
private final KnowledgeService knowledgeService;
public ChatController(ChatClient.Builder builder) {
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
public ChatController(ChatClient.Builder builder, KnowledgeService knowledgeService) {
this.chatClient = builder.build();
this.knowledgeService = knowledgeService;
}
private ChatClient.ChatClientRequestSpec buildRequest(ImmutableList<MessageVO> messages) {
private ChatClient.ChatClientRequestSpec buildRequest(Long knowledgeId, ImmutableList<MessageVO> messages) {
var systemPromptBuilder = new StringBuilder();
systemPromptBuilder.append("""
你是一名专业的AI运维助手负责“Hudi数据同步服务平台”的运维工作
你将会友好地帮助用户解答关于该平台运维工作的问题,你会尽可能通过各种方式获取知识和数据来解答;
对于无法通过已有知识回答的问题,你会提示用户你无法解答该问题,而不是虚构不存在的数据或答案;
对于与该平台无关的问题,你会委婉地拒绝用户,并提示无法回答;
你将始终在中文语境下进行对话。
""");
if (ObjectUtil.isNotNull(knowledgeId)) {
var vo = messages.select(message -> StrUtil.equals(message.getRole(), "user")).getLastOptional().orElseThrow();
var documents = knowledgeService.query(knowledgeId, vo.getContent());
logger.info("Knowledge id:{}, content:{}", knowledgeId, vo.getContent());
if (ObjectUtil.isNotEmpty(documents)) {
systemPromptBuilder.append("以下是与用户问题有关的外部知识,优先利用该知识回答用户的提问:\n");
systemPromptBuilder.append(documents.makeString("\n"));
}
}
return chatClient.prompt()
.system("""
你是一名专业的AI运维助手负责“Hudi数据同步服务平台”的运维工作
你将会友好地帮助用户解答关于该平台运维工作的问题,你会尽可能通过各种方式获取知识和数据来解答;
对于无法通过已有知识回答的问题,你会提示用户你无法解答该问题,而不是虚构不存在的数据或答案;
对于与该平台无关的问题,你会委婉地拒绝用户,并提示无法回答;
你将始终在中文语境下进行对话。
""")
.system(systemPromptBuilder.toString())
.tools(new DatetimeTools())
.messages(
messages
.collect(message -> StrUtil.equals(message.getRole(), "assistant")
.collect(message -> StrUtil.equals(message.getRole(), ROLE_ASSISTANT)
? new AssistantMessage(message.getContent())
: new UserMessage(message.getContent()))
.collect(message -> (Message) message)
@@ -57,23 +80,29 @@ public class ChatController {
@PostMapping("sync")
@ResponseBody
public String chatSync(@RequestBody ImmutableList<MessageVO> messages) {
String content = buildRequest(messages)
public MessageVO chatSync(
@RequestParam(value = "knowledge_id", required = false) Long knowledgeId,
@RequestBody ImmutableList<MessageVO> messages
) {
ChatResponse response = buildRequest(knowledgeId, messages)
.call()
.content();
return StrUtil.trimToEmpty(content);
.chatResponse();
return toMessage(response);
}
@PostMapping("async")
public SseEmitter chatAsync(@RequestBody ImmutableList<MessageVO> messages) {
public SseEmitter chatAsync(
@RequestParam(value = "knowledge_id", required = false) Long knowledgeId,
@RequestBody ImmutableList<MessageVO> messages
) {
SseEmitter emitter = new SseEmitter();
buildRequest(messages)
buildRequest(knowledgeId, messages)
.stream()
.content()
.chatResponse()
.subscribe(
content -> {
response -> {
try {
emitter.send(content);
emitter.send(toMessage(response));
} catch (IOException e) {
emitter.completeWithError(e);
throw new RuntimeException(e);
@@ -84,4 +113,18 @@ public class ChatController {
);
return emitter;
}
private MessageVO toMessage(ChatResponse response) {
AssistantMessage message = Optional.ofNullable(response)
.map(ChatResponse::getResult)
.map(Generation::getOutput)
.orElseThrow(() -> new RuntimeException("ChatResponse is null"));
MessageVO vo = new MessageVO();
vo.setRole(ROLE_ASSISTANT);
vo.setContent(message.getText());
if (message instanceof DeepSeekAssistantMessage deepseekMessage) {
vo.setReason(deepseekMessage.getReasoningContent());
}
return vo;
}
}

View File

@@ -7,6 +7,7 @@ package com.lanyuanxiaoyao.service.ai.chat.entity;
public class MessageVO {
private String role;
private String content;
private String reason;
public String getRole() {
return role;
@@ -24,11 +25,20 @@ public class MessageVO {
this.content = content;
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
@Override
public String toString() {
return "MessageVO{" +
"role='" + role + '\'' +
", content='" + content + '\'' +
", reason='" + reason + '\'' +
'}';
}
}

View File

@@ -4,8 +4,8 @@ spring:
profiles:
include: random-port,common,discovery,metrics,forest
ai:
openai:
base-url: http://132.121.206.65:10086
deepseek:
base-url: http://132.121.206.65:10086/v1
api-key: ENC(K+Hff9QGC+fcyi510VIDd9CaeK/IN5WBJ9rlkUsHEdDgIidW+stHHJlsK0lLPUXXREha+ToQZqqDXJrqSE+GUKCXklFhelD8bRHFXBIeP/ZzT2cxhzgKUXgjw3S0Qw2R)
chat:
options:

View File

@@ -4,7 +4,6 @@ import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import reactor.core.Disposable;
/**
* @author lanyuanxiaoyao