From b18dcb3001b66d7e02aa8279adac088bbe34b205 Mon Sep 17 00:00:00 2001 From: thinkgem Date: Mon, 19 May 2025 17:15:33 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20AI=20=E7=BB=93=E6=9E=84?= =?UTF-8?q?=E5=8C=96=E8=BE=93=E5=87=BA=E4=BE=8B=E5=AD=90=E3=80=81=E6=96=87?= =?UTF-8?q?=E6=9C=AC=E6=A0=BC=E5=BC=8F=E3=80=81JSON=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E3=80=81Java=E5=AF=B9=E8=B1=A1=E6=A0=BC=E5=BC=8F=E3=80=81?= =?UTF-8?q?=E7=BB=93=E5=90=88Tool=20Calling=E6=96=B9=E5=BC=8F=E8=BE=93?= =?UTF-8?q?=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cms/ai/service/CmsAiChatService.java | 99 +++++++++++++++++-- .../modules/cms/ai/tools/CmsAiTools.java | 8 +- .../cms/ai/web/CmsAiChatController.java | 71 +++++++++---- 3 files changed, 152 insertions(+), 26 deletions(-) diff --git a/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/service/CmsAiChatService.java b/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/service/CmsAiChatService.java index 00da4636..e3acbe3d 100644 --- a/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/service/CmsAiChatService.java +++ b/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/service/CmsAiChatService.java @@ -9,8 +9,11 @@ import com.jeesite.common.collect.MapUtils; import com.jeesite.common.idgen.IdGen; import com.jeesite.common.lang.DateUtils; import com.jeesite.common.lang.StringUtils; +import com.jeesite.common.mapper.JsonMapper; import com.jeesite.common.service.BaseService; import com.jeesite.modules.cms.ai.properties.CmsAiProperties; +import com.jeesite.modules.sys.entity.Area; +import com.jeesite.modules.sys.utils.AreaUtils; import com.jeesite.modules.sys.utils.UserUtils; import jakarta.servlet.http.HttpServletRequest; import org.springframework.ai.chat.client.ChatClient; @@ -19,13 +22,19 @@ import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvi import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.SystemMessage; 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.chat.prompt.PromptTemplate; +import org.springframework.ai.converter.AbstractMessageOutputConverter; +import org.springframework.ai.converter.BeanOutputConverter; +import org.springframework.ai.converter.MapOutputConverter; import org.springframework.ai.vectorstore.SearchRequest; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.messaging.converter.MappingJackson2MessageConverter; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClientResponseException; import reactor.core.publisher.Flux; @@ -46,9 +55,9 @@ public class CmsAiChatService extends BaseService { private static final String[] USER_MESSAGE_REPLACE = new String[]{"\\{", "\\}"}; @Autowired - private ChatClient chatClient; + private ChatClient chatClient; @Autowired - private ChatMemory chatMemory; + private ChatMemory chatMemory; @Autowired private VectorStore vectorStore; @Autowired @@ -118,10 +127,10 @@ public class CmsAiChatService extends BaseService { */ public Flux chatStream(String conversationId, String message, HttpServletRequest request) { return chatClient.prompt() - .messages( + .messages( new UserMessage(StringUtils.replaceEach(message, USER_MESSAGE_SEARCH, USER_MESSAGE_REPLACE)) ) - .advisors( + .advisors( MessageChatMemoryAdvisor.builder(chatMemory) .conversationId(conversationId) .build(), @@ -130,9 +139,9 @@ public class CmsAiChatService extends BaseService { .promptTemplate(new PromptTemplate(properties.getDefaultPromptTemplate())) .build() ) - .stream() + .stream() .chatResponse() - .doOnNext(response -> { + .doOnNext(response -> { if (response.getResult() != null && StringUtils.isNotBlank(response.getResult().getOutput().getText())) { AssistantMessage assistantMessage = (AssistantMessage)request.getAttribute("assistantMessage"); AssistantMessage currAssistantMessage = response.getResult().getOutput(); @@ -167,6 +176,84 @@ public class CmsAiChatService extends BaseService { .generations(List.of(new Generation(assistantMessage))) .build()); }); + } + + /** + * 聊天对话,文本输出 + * @author ThinkGem + */ + public String chatText(String message) { + return chatClient.prompt() + .messages( + new UserMessage(StringUtils.replaceEach(message, USER_MESSAGE_SEARCH, USER_MESSAGE_REPLACE)) + ) + .call() + .content(); } + /** + * 聊天对话,结构化输出(Map) + * @author ThinkGem + */ + public Map chatJson(String message) { + return chatClient.prompt() + .messages( + new SystemMessage(""" + [ {name:'张三', sex:'男', age:'17'}, {name:'李四', sex:'女', age:'18'} ],返回 json。 + """), + new UserMessage(StringUtils.replaceEach(message, USER_MESSAGE_SEARCH, USER_MESSAGE_REPLACE)) + ) + .call() + .responseEntity(new AbstractMessageOutputConverter>( + new MappingJackson2MessageConverter(JsonMapper.getInstance()) + ) { + final MapOutputConverter mapOutputConverter = new MapOutputConverter(); + @Override + public Map convert(String source) { + return mapOutputConverter.convert(source); + } + @Override + public String getFormat() { + return mapOutputConverter.getFormat(); + } + } + ) + .getEntity(); + } + + /** + * 聊天对话,结构化输出(Area) + * @author ThinkGem + */ + public List chatArea(String message) { + List list = AreaUtils.getAreaAllList(); + if (list.size() > 10) list = list.subList(0, 10); + return chatClient.prompt() + .messages( + new SystemMessage(JsonMapper.toJson(list)), + new UserMessage(StringUtils.replaceEach(message, USER_MESSAGE_SEARCH, USER_MESSAGE_REPLACE)) + ) + .advisors( + QuestionAnswerAdvisor.builder(vectorStore) + .searchRequest(SearchRequest.builder().similarityThreshold(0.6F).topK(6).build()) + .promptTemplate(new PromptTemplate(properties.getDefaultPromptTemplate())) + .build() + ) + .call() + .responseEntity(new BeanOutputConverter<>(new ParameterizedTypeReference>() {}, + JsonMapper.getInstance())) + .getEntity(); + } + +// public static void main(String[] args) { +// String s = """ +// [{"id":"110000","isNewRecord":false,"createBy":"system","createDate":"2025-01-01T19:25:11Z","updateBy":"system","updateDate":"2025-01-01 19:25","childList":[{"id":"110100","isNewRecord":false,"createBy":"system","createDate":"2025-01-01 19:25","updateBy":"system","updateDate":"2025-01-01 19:25","childList":[{"id":"110101","isNewRecord":false,"areaCode":"110101","areaName":"东城区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110102","isNewRecord":false,"areaCode":"110102","areaName":"西城区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110105","isNewRecord":false,"areaCode":"110105","areaName":"朝阳区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110106","isNewRecord":false,"areaCode":"110106","areaName":"丰台区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110107","isNewRecord":false,"areaCode":"110107","areaName":"石景山区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110108","isNewRecord":false,"areaCode":"110108","areaName":"海淀区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110109","isNewRecord":false,"areaCode":"110109","areaName":"门头沟区","areaType":"3","isRoot":true,"isTreeLeaf":false},{"id":"110111","isNewRecord":false,"areaCode":"110111","areaName":"房山区","areaType":"3","isRoot":true,"isTreeLeaf":false}],"areaCode":"110100","areaName":"北京城区","areaType":"2","isRoot":true,"isTreeLeaf":false}],"areaCode":"110000","areaName":"北京市","areaType":"1","isRoot":true,"isTreeLeaf":false}] +// """; +// JsonMapper jsonMapper = JsonMapper.getInstance(); +// ParameterizedTypeReference> p = new ParameterizedTypeReference>() {}; +// List entity = jsonMapper.fromJsonString(s, jsonMapper.constructType(p.getType())); +// System.out.println(entity); +// String json = jsonMapper.toJsonString(entity); +// System.out.println(json); +// } } diff --git a/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/tools/CmsAiTools.java b/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/tools/CmsAiTools.java index b323eb6a..8f53fcfd 100644 --- a/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/tools/CmsAiTools.java +++ b/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/tools/CmsAiTools.java @@ -33,8 +33,14 @@ public class CmsAiTools { * 你可以询问:打开客厅的灯,关闭卧室的灯(需创建新对话) */ @Tool(description = "房间里的灯打开或关闭") - public void turnLight(@ToolParam(description = "房间") String roomName, @ToolParam(description = "开关") boolean on) { + public String turnLight(@ToolParam(description = "房间") String roomName, @ToolParam(description = "开关") boolean on) { String message = roomName + " 房间里的灯被 " + (on ? "打开" : "关闭"); logger.info(message + " ============== "); + return String.format(""" + message: %s + roomName: %s + on: %s + """, + roomName, on, message); } } \ No newline at end of file diff --git a/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/web/CmsAiChatController.java b/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/web/CmsAiChatController.java index 14314611..9ddac068 100644 --- a/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/web/CmsAiChatController.java +++ b/modules/cms-ai/src/main/java/com/jeesite/modules/cms/ai/web/CmsAiChatController.java @@ -7,12 +7,14 @@ package com.jeesite.modules.cms.ai.web; import com.jeesite.common.config.Global; import com.jeesite.common.web.BaseController; import com.jeesite.modules.cms.ai.service.CmsAiChatService; +import com.jeesite.modules.sys.entity.Area; import jakarta.servlet.http.HttpServletRequest; import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; @@ -37,49 +39,80 @@ public class CmsAiChatController extends BaseController { * 获取聊天对话消息 * @author ThinkGem */ - @RequestMapping("/message") - public List message(String id) { - return cmsAiChatService.getChatMessage(id); - } + @RequestMapping("/message") + public List message(String id) { + return cmsAiChatService.getChatMessage(id); + } /** * 聊天对话列表 * @author ThinkGem */ - @RequestMapping("/list") - public Collection> list() { + @RequestMapping("/list") + public Collection> list() { return cmsAiChatService.getChatCacheMap().values().stream() - .sorted(Comparator.comparing(map -> (String) map.get("id"), - Comparator.reverseOrder())).collect(Collectors.toList()); - } + .sorted(Comparator.comparing(map -> (String) map.get("id"), + Comparator.reverseOrder())).collect(Collectors.toList()); + } /** * 新建或更新聊天对话 * @author ThinkGem */ @RequestMapping("/save") - public String save(String id, String title) { + public String save(String id, String title) { Map map = cmsAiChatService.saveChatConversation(id, title); - return renderResult(Global.TRUE, "保存成功", map); - } + return renderResult(Global.TRUE, "保存成功", map); + } /** * 删除聊天对话 * @author ThinkGem */ - @RequestMapping("/delete") - public String delete(String id) { + @RequestMapping("/delete") + public String delete(@RequestParam String id) { cmsAiChatService.deleteChatConversation(id); - return renderResult(Global.TRUE, "删除成功", id); - } + return renderResult(Global.TRUE, "删除成功", id); + } /** * 聊天对话,流输出 * @author ThinkGem + * http://127.0.0.1:8980/js/a/cms/chat/stream?id=1&message=你好 */ - @RequestMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) - public Flux stream(String id, String message, HttpServletRequest request) { + @RequestMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public Flux stream(@RequestParam String id, @RequestParam String message, HttpServletRequest request) { return cmsAiChatService.chatStream(id, message, request); - } + } + + /** + * 聊天对话,文本输出 + * @author ThinkGem + * http://127.0.0.1:8980/js/a/cms/chat/text?message=你好 + */ + @RequestMapping(value = "/text") + public String text(@RequestParam String message) { + return cmsAiChatService.chatText(message); + } + + /** + * 聊天对话,结构化输出 + * @author ThinkGem + * http://127.0.0.1:8980/js/a/cms/chat/json?message=张三 + */ + @RequestMapping(value = "/json") + public Map json(@RequestParam String message) { + return cmsAiChatService.chatJson(message); + } + + /** + * 聊天对话,结构化输出 + * @author ThinkGem + * http://127.0.0.1:8980/js/a/cms/chat/entity?message=北京 + */ + @RequestMapping(value = "/entity") + public List entity(@RequestParam String message) { + return cmsAiChatService.chatArea(message); + } }