新增 AI 结构化输出例子、文本格式、JSON格式、Java对象格式、结合Tool Calling方式输出
This commit is contained in:
@@ -9,8 +9,11 @@ import com.jeesite.common.collect.MapUtils;
|
|||||||
import com.jeesite.common.idgen.IdGen;
|
import com.jeesite.common.idgen.IdGen;
|
||||||
import com.jeesite.common.lang.DateUtils;
|
import com.jeesite.common.lang.DateUtils;
|
||||||
import com.jeesite.common.lang.StringUtils;
|
import com.jeesite.common.lang.StringUtils;
|
||||||
|
import com.jeesite.common.mapper.JsonMapper;
|
||||||
import com.jeesite.common.service.BaseService;
|
import com.jeesite.common.service.BaseService;
|
||||||
import com.jeesite.modules.cms.ai.properties.CmsAiProperties;
|
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 com.jeesite.modules.sys.utils.UserUtils;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import org.springframework.ai.chat.client.ChatClient;
|
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.memory.ChatMemory;
|
||||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||||
import org.springframework.ai.chat.messages.Message;
|
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.messages.UserMessage;
|
||||||
import org.springframework.ai.chat.model.ChatResponse;
|
import org.springframework.ai.chat.model.ChatResponse;
|
||||||
import org.springframework.ai.chat.model.Generation;
|
import org.springframework.ai.chat.model.Generation;
|
||||||
import org.springframework.ai.chat.prompt.PromptTemplate;
|
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.SearchRequest;
|
||||||
import org.springframework.ai.vectorstore.VectorStore;
|
import org.springframework.ai.vectorstore.VectorStore;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
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.stereotype.Service;
|
||||||
import org.springframework.web.reactive.function.client.WebClientResponseException;
|
import org.springframework.web.reactive.function.client.WebClientResponseException;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
@@ -46,9 +55,9 @@ public class CmsAiChatService extends BaseService {
|
|||||||
private static final String[] USER_MESSAGE_REPLACE = new String[]{"\\{", "\\}"};
|
private static final String[] USER_MESSAGE_REPLACE = new String[]{"\\{", "\\}"};
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ChatClient chatClient;
|
private ChatClient chatClient;
|
||||||
@Autowired
|
@Autowired
|
||||||
private ChatMemory chatMemory;
|
private ChatMemory chatMemory;
|
||||||
@Autowired
|
@Autowired
|
||||||
private VectorStore vectorStore;
|
private VectorStore vectorStore;
|
||||||
@Autowired
|
@Autowired
|
||||||
@@ -118,10 +127,10 @@ public class CmsAiChatService extends BaseService {
|
|||||||
*/
|
*/
|
||||||
public Flux<ChatResponse> chatStream(String conversationId, String message, HttpServletRequest request) {
|
public Flux<ChatResponse> chatStream(String conversationId, String message, HttpServletRequest request) {
|
||||||
return chatClient.prompt()
|
return chatClient.prompt()
|
||||||
.messages(
|
.messages(
|
||||||
new UserMessage(StringUtils.replaceEach(message, USER_MESSAGE_SEARCH, USER_MESSAGE_REPLACE))
|
new UserMessage(StringUtils.replaceEach(message, USER_MESSAGE_SEARCH, USER_MESSAGE_REPLACE))
|
||||||
)
|
)
|
||||||
.advisors(
|
.advisors(
|
||||||
MessageChatMemoryAdvisor.builder(chatMemory)
|
MessageChatMemoryAdvisor.builder(chatMemory)
|
||||||
.conversationId(conversationId)
|
.conversationId(conversationId)
|
||||||
.build(),
|
.build(),
|
||||||
@@ -130,9 +139,9 @@ public class CmsAiChatService extends BaseService {
|
|||||||
.promptTemplate(new PromptTemplate(properties.getDefaultPromptTemplate()))
|
.promptTemplate(new PromptTemplate(properties.getDefaultPromptTemplate()))
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
.stream()
|
.stream()
|
||||||
.chatResponse()
|
.chatResponse()
|
||||||
.doOnNext(response -> {
|
.doOnNext(response -> {
|
||||||
if (response.getResult() != null && StringUtils.isNotBlank(response.getResult().getOutput().getText())) {
|
if (response.getResult() != null && StringUtils.isNotBlank(response.getResult().getOutput().getText())) {
|
||||||
AssistantMessage assistantMessage = (AssistantMessage)request.getAttribute("assistantMessage");
|
AssistantMessage assistantMessage = (AssistantMessage)request.getAttribute("assistantMessage");
|
||||||
AssistantMessage currAssistantMessage = response.getResult().getOutput();
|
AssistantMessage currAssistantMessage = response.getResult().getOutput();
|
||||||
@@ -167,6 +176,84 @@ public class CmsAiChatService extends BaseService {
|
|||||||
.generations(List.of(new Generation(assistantMessage)))
|
.generations(List.of(new Generation(assistantMessage)))
|
||||||
.build());
|
.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<String, Object> 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<Map<String, Object>>(
|
||||||
|
new MappingJackson2MessageConverter(JsonMapper.getInstance())
|
||||||
|
) {
|
||||||
|
final MapOutputConverter mapOutputConverter = new MapOutputConverter();
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> convert(String source) {
|
||||||
|
return mapOutputConverter.convert(source);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public String getFormat() {
|
||||||
|
return mapOutputConverter.getFormat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.getEntity();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 聊天对话,结构化输出(Area)
|
||||||
|
* @author ThinkGem
|
||||||
|
*/
|
||||||
|
public List<Area> chatArea(String message) {
|
||||||
|
List<Area> 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<List<Area>>() {},
|
||||||
|
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<List<Area>> p = new ParameterizedTypeReference<List<Area>>() {};
|
||||||
|
// List<Area> entity = jsonMapper.fromJsonString(s, jsonMapper.constructType(p.getType()));
|
||||||
|
// System.out.println(entity);
|
||||||
|
// String json = jsonMapper.toJsonString(entity);
|
||||||
|
// System.out.println(json);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,8 +33,14 @@ public class CmsAiTools {
|
|||||||
* 你可以询问:打开客厅的灯,关闭卧室的灯(需创建新对话)
|
* 你可以询问:打开客厅的灯,关闭卧室的灯(需创建新对话)
|
||||||
*/
|
*/
|
||||||
@Tool(description = "房间里的灯打开或关闭")
|
@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 ? "打开" : "关闭");
|
String message = roomName + " 房间里的灯被 " + (on ? "打开" : "关闭");
|
||||||
logger.info(message + " ============== ");
|
logger.info(message + " ============== ");
|
||||||
|
return String.format("""
|
||||||
|
message: %s
|
||||||
|
roomName: %s
|
||||||
|
on: %s
|
||||||
|
""",
|
||||||
|
roomName, on, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,12 +7,14 @@ package com.jeesite.modules.cms.ai.web;
|
|||||||
import com.jeesite.common.config.Global;
|
import com.jeesite.common.config.Global;
|
||||||
import com.jeesite.common.web.BaseController;
|
import com.jeesite.common.web.BaseController;
|
||||||
import com.jeesite.modules.cms.ai.service.CmsAiChatService;
|
import com.jeesite.modules.cms.ai.service.CmsAiChatService;
|
||||||
|
import com.jeesite.modules.sys.entity.Area;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import org.springframework.ai.chat.messages.Message;
|
import org.springframework.ai.chat.messages.Message;
|
||||||
import org.springframework.ai.chat.model.ChatResponse;
|
import org.springframework.ai.chat.model.ChatResponse;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
|
|
||||||
@@ -37,49 +39,80 @@ public class CmsAiChatController extends BaseController {
|
|||||||
* 获取聊天对话消息
|
* 获取聊天对话消息
|
||||||
* @author ThinkGem
|
* @author ThinkGem
|
||||||
*/
|
*/
|
||||||
@RequestMapping("/message")
|
@RequestMapping("/message")
|
||||||
public List<Message> message(String id) {
|
public List<Message> message(String id) {
|
||||||
return cmsAiChatService.getChatMessage(id);
|
return cmsAiChatService.getChatMessage(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 聊天对话列表
|
* 聊天对话列表
|
||||||
* @author ThinkGem
|
* @author ThinkGem
|
||||||
*/
|
*/
|
||||||
@RequestMapping("/list")
|
@RequestMapping("/list")
|
||||||
public Collection<Map<String, Object>> list() {
|
public Collection<Map<String, Object>> list() {
|
||||||
return cmsAiChatService.getChatCacheMap().values().stream()
|
return cmsAiChatService.getChatCacheMap().values().stream()
|
||||||
.sorted(Comparator.comparing(map -> (String) map.get("id"),
|
.sorted(Comparator.comparing(map -> (String) map.get("id"),
|
||||||
Comparator.reverseOrder())).collect(Collectors.toList());
|
Comparator.reverseOrder())).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新建或更新聊天对话
|
* 新建或更新聊天对话
|
||||||
* @author ThinkGem
|
* @author ThinkGem
|
||||||
*/
|
*/
|
||||||
@RequestMapping("/save")
|
@RequestMapping("/save")
|
||||||
public String save(String id, String title) {
|
public String save(String id, String title) {
|
||||||
Map<String, Object> map = cmsAiChatService.saveChatConversation(id, title);
|
Map<String, Object> map = cmsAiChatService.saveChatConversation(id, title);
|
||||||
return renderResult(Global.TRUE, "保存成功", map);
|
return renderResult(Global.TRUE, "保存成功", map);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除聊天对话
|
* 删除聊天对话
|
||||||
* @author ThinkGem
|
* @author ThinkGem
|
||||||
*/
|
*/
|
||||||
@RequestMapping("/delete")
|
@RequestMapping("/delete")
|
||||||
public String delete(String id) {
|
public String delete(@RequestParam String id) {
|
||||||
cmsAiChatService.deleteChatConversation(id);
|
cmsAiChatService.deleteChatConversation(id);
|
||||||
return renderResult(Global.TRUE, "删除成功", id);
|
return renderResult(Global.TRUE, "删除成功", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 聊天对话,流输出
|
* 聊天对话,流输出
|
||||||
* @author ThinkGem
|
* @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)
|
@RequestMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||||
public Flux<ChatResponse> stream(String id, String message, HttpServletRequest request) {
|
public Flux<ChatResponse> stream(@RequestParam String id, @RequestParam String message, HttpServletRequest request) {
|
||||||
return cmsAiChatService.chatStream(id, message, 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<String, Object> 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<Area> entity(@RequestParam String message) {
|
||||||
|
return cmsAiChatService.chatArea(message);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user