Commit 5655ae925ccda0492d5b0c5d34efb901145a29e1

Authored by YunaiV
1 parent 7ef73b7d

【代码优化】AI:适配 Spring AI 1.0.6 对 OpenAI 的逻辑

yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java
@@ -98,7 +98,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -98,7 +98,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
98 ChatResponse chatResponse = chatModel.call(prompt); 98 ChatResponse chatResponse = chatModel.call(prompt);
99 99
100 // 3.4 段式返回 100 // 3.4 段式返回
101 - String newContent = chatResponse.getResult().getOutput().getContent(); 101 + String newContent = chatResponse.getResult().getOutput().getText();
102 chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setSegmentIds(convertList(segmentList, AiKnowledgeSegmentDO::getId)).setContent(newContent)); 102 chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setSegmentIds(convertList(segmentList, AiKnowledgeSegmentDO::getId)).setContent(newContent));
103 return new AiChatMessageSendRespVO().setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class)) 103 return new AiChatMessageSendRespVO().setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class))
104 .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class).setContent(newContent)); 104 .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class).setContent(newContent));
@@ -136,7 +136,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -136,7 +136,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
136 // TODO 注意:Schedulers.immediate() 目的是,避免默认 Schedulers.parallel() 并发消费 chunk 导致 SSE 响应前端会乱序问题 136 // TODO 注意:Schedulers.immediate() 目的是,避免默认 Schedulers.parallel() 并发消费 chunk 导致 SSE 响应前端会乱序问题
137 StringBuffer contentBuffer = new StringBuffer(); 137 StringBuffer contentBuffer = new StringBuffer();
138 return streamResponse.map(chunk -> { 138 return streamResponse.map(chunk -> {
139 - String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getContent() : null; 139 + String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getText() : null;
140 newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况 140 newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况
141 contentBuffer.append(newContent); 141 contentBuffer.append(newContent);
142 // 响应结果 142 // 响应结果
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentServiceImpl.java
@@ -60,7 +60,7 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic @@ -60,7 +60,7 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic
60 List<Document> documents = loader.get(); 60 List<Document> documents = loader.get();
61 Document document = CollUtil.getFirst(documents); 61 Document document = CollUtil.getFirst(documents);
62 // 1.2 文档记录入库 62 // 1.2 文档记录入库
63 - String content = document.getContent(); 63 + String content = document.getText();
64 AiKnowledgeDocumentDO documentDO = BeanUtils.toBean(createReqVO, AiKnowledgeDocumentDO.class) 64 AiKnowledgeDocumentDO documentDO = BeanUtils.toBean(createReqVO, AiKnowledgeDocumentDO.class)
65 .setTokens(tokenCountEstimator.estimate(content)).setWordCount(content.length()) 65 .setTokens(tokenCountEstimator.estimate(content)).setWordCount(content.length())
66 .setStatus(CommonStatusEnum.ENABLE.getStatus()).setSliceStatus(AiKnowledgeDocumentStatusEnum.SUCCESS.getStatus()); 66 .setStatus(CommonStatusEnum.ENABLE.getStatus()).setSliceStatus(AiKnowledgeDocumentStatusEnum.SUCCESS.getStatus());
@@ -77,9 +77,9 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic @@ -77,9 +77,9 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic
77 List<Document> segments = tokenTextSplitter.apply(documents); 77 List<Document> segments = tokenTextSplitter.apply(documents);
78 // 2.2 分段内容入库 78 // 2.2 分段内容入库
79 List<AiKnowledgeSegmentDO> segmentDOList = CollectionUtils.convertList(segments, 79 List<AiKnowledgeSegmentDO> segmentDOList = CollectionUtils.convertList(segments,
80 - segment -> new AiKnowledgeSegmentDO().setContent(segment.getContent()).setDocumentId(documentId) 80 + segment -> new AiKnowledgeSegmentDO().setContent(segment.getText()).setDocumentId(documentId)
81 .setKnowledgeId(createReqVO.getKnowledgeId()).setVectorId(segment.getId()) 81 .setKnowledgeId(createReqVO.getKnowledgeId()).setVectorId(segment.getId())
82 - .setTokens(tokenCountEstimator.estimate(segment.getContent())).setWordCount(segment.getContent().length()) 82 + .setTokens(tokenCountEstimator.estimate(segment.getText())).setWordCount(segment.getText().length())
83 .setStatus(CommonStatusEnum.ENABLE.getStatus())); 83 .setStatus(CommonStatusEnum.ENABLE.getStatus()));
84 segmentMapper.insertBatch(segmentDOList); 84 segmentMapper.insertBatch(segmentDOList);
85 85
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java
@@ -106,10 +106,12 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService @@ -106,10 +106,12 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService
106 VectorStore vectorStore = apiKeyService.getOrCreateVectorStore(model.getKeyId()); 106 VectorStore vectorStore = apiKeyService.getOrCreateVectorStore(model.getKeyId());
107 107
108 // 3.1 向量检索 108 // 3.1 向量检索
109 - List<Document> documentList = vectorStore.similaritySearch(SearchRequest.query(reqVO.getContent())  
110 - .withTopK(knowledge.getTopK())  
111 - .withSimilarityThreshold(knowledge.getSimilarityThreshold())  
112 - .withFilterExpression(new FilterExpressionBuilder().eq(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, reqVO.getKnowledgeId()).build())); 109 + List<Document> documentList = vectorStore.similaritySearch(SearchRequest.builder()
  110 + .query(reqVO.getContent())
  111 + .topK(knowledge.getTopK())
  112 + .similarityThreshold(knowledge.getSimilarityThreshold())
  113 + .filterExpression(new FilterExpressionBuilder().eq(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, reqVO.getKnowledgeId()).build())
  114 + .build());
113 if (CollUtil.isEmpty(documentList)) { 115 if (CollUtil.isEmpty(documentList)) {
114 return ListUtil.empty(); 116 return ListUtil.empty();
115 } 117 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/mindmap/AiMindMapServiceImpl.java
@@ -85,7 +85,7 @@ public class AiMindMapServiceImpl implements AiMindMapService { @@ -85,7 +85,7 @@ public class AiMindMapServiceImpl implements AiMindMapService {
85 // 3.2 流式返回 85 // 3.2 流式返回
86 StringBuffer contentBuffer = new StringBuffer(); 86 StringBuffer contentBuffer = new StringBuffer();
87 return streamResponse.map(chunk -> { 87 return streamResponse.map(chunk -> {
88 - String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getContent() : null; 88 + String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getText() : null;
89 newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况 89 newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况
90 contentBuffer.append(newContent); 90 contentBuffer.append(newContent);
91 // 响应结果 91 // 响应结果
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteServiceImpl.java
@@ -92,7 +92,7 @@ public class AiWriteServiceImpl implements AiWriteService { @@ -92,7 +92,7 @@ public class AiWriteServiceImpl implements AiWriteService {
92 // 3.2 流式返回 92 // 3.2 流式返回
93 StringBuffer contentBuffer = new StringBuffer(); 93 StringBuffer contentBuffer = new StringBuffer();
94 return streamResponse.map(chunk -> { 94 return streamResponse.map(chunk -> {
95 - String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getContent() : null; 95 + String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getText() : null;
96 newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况 96 newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况
97 contentBuffer.append(newContent); 97 contentBuffer.append(newContent);
98 // 响应结果 98 // 响应结果
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/OpenAIChatModelTests.java
@@ -22,11 +22,16 @@ import java.util.List; @@ -22,11 +22,16 @@ import java.util.List;
22 */ 22 */
23 public class OpenAIChatModelTests { 23 public class OpenAIChatModelTests {
24 24
25 - private final OpenAiApi openAiApi = new OpenAiApi(  
26 - "https://api.holdai.top",  
27 - "sk-dZEPiVaNcT3FHhef51996bAa0bC74806BeAb620dA5Da10Bf");  
28 - private final OpenAiChatModel chatModel = new OpenAiChatModel(openAiApi,  
29 - OpenAiChatOptions.builder().model(OpenAiApi.ChatModel.GPT_4_O).build()); 25 + private static final OpenAiChatModel chatModel = OpenAiChatModel.builder()
  26 + .openAiApi(OpenAiApi.builder()
  27 + .baseUrl("https://api.holdai.top")
  28 + .apiKey("sk-aN6nWn3fILjrgLFT0fC4Aa60B72e4253826c77B29dC94f17") // apiKey
  29 + .build())
  30 + .defaultOptions(OpenAiChatOptions.builder()
  31 + .model(OpenAiApi.ChatModel.GPT_4_O) // 模型
  32 + .temperature(0.7)
  33 + .build())
  34 + .build();
30 35
31 @Test 36 @Test
32 @Disabled 37 @Disabled
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/image/OpenAiImageModelTests.java
@@ -5,8 +5,11 @@ import org.junit.jupiter.api.Test; @@ -5,8 +5,11 @@ import org.junit.jupiter.api.Test;
5 import org.springframework.ai.image.ImageOptions; 5 import org.springframework.ai.image.ImageOptions;
6 import org.springframework.ai.image.ImagePrompt; 6 import org.springframework.ai.image.ImagePrompt;
7 import org.springframework.ai.image.ImageResponse; 7 import org.springframework.ai.image.ImageResponse;
  8 +import org.springframework.ai.openai.OpenAiChatModel;
  9 +import org.springframework.ai.openai.OpenAiChatOptions;
8 import org.springframework.ai.openai.OpenAiImageModel; 10 import org.springframework.ai.openai.OpenAiImageModel;
9 import org.springframework.ai.openai.OpenAiImageOptions; 11 import org.springframework.ai.openai.OpenAiImageOptions;
  12 +import org.springframework.ai.openai.api.OpenAiApi;
10 import org.springframework.ai.openai.api.OpenAiImageApi; 13 import org.springframework.ai.openai.api.OpenAiImageApi;
11 import org.springframework.web.client.RestClient; 14 import org.springframework.web.client.RestClient;
12 15
@@ -17,11 +20,10 @@ import org.springframework.web.client.RestClient; @@ -17,11 +20,10 @@ import org.springframework.web.client.RestClient;
17 */ 20 */
18 public class OpenAiImageModelTests { 21 public class OpenAiImageModelTests {
19 22
20 - private final OpenAiImageApi imageApi = new OpenAiImageApi(  
21 - "https://api.holdai.top",  
22 - "sk-dZEPiVaNcT3FHhef51996bAa0bC74806BeAb620dA5Da10Bf",  
23 - RestClient.builder());  
24 - private final OpenAiImageModel imageModel = new OpenAiImageModel(imageApi); 23 + private final OpenAiImageModel imageModel = new OpenAiImageModel(OpenAiImageApi.builder()
  24 + .baseUrl("https://api.holdai.top") // apiKey
  25 + .apiKey("sk-aN6nWn3fILjrgLFT0fC4Aa60B72e4253826c77B29dC94f17")
  26 + .build());
25 27
26 @Test 28 @Test
27 @Disabled 29 @Disabled
yudao-server/src/main/resources/application.yaml
@@ -163,7 +163,7 @@ spring: @@ -163,7 +163,7 @@ spring:
163 zhipuai: # 智谱 AI 163 zhipuai: # 智谱 AI
164 api-key: 32f84543e54eee31f8d56b2bd6020573.3vh9idLJZ2ZhxDEs 164 api-key: 32f84543e54eee31f8d56b2bd6020573.3vh9idLJZ2ZhxDEs
165 openai: # OpenAI 官方 165 openai: # OpenAI 官方
166 - api-key: sk-yzKea6d8e8212c3bdd99f9f44ced1cae37c097e5aa3BTS7z 166 + api-key: sk-aN6nWn3fILjrgLFT0fC4Aa60B72e4253826c77B29dC94f17
167 base-url: https://api.gptsapi.net 167 base-url: https://api.gptsapi.net
168 azure: # OpenAI 微软 168 azure: # OpenAI 微软
169 openai: 169 openai:
@@ -175,11 +175,8 @@ spring: @@ -175,11 +175,8 @@ spring:
175 model: llama3 175 model: llama3
176 stabilityai: 176 stabilityai:
177 api-key: sk-e53UqbboF8QJCscYvzJscJxJXoFcFg4iJjl1oqgE7baJETmx 177 api-key: sk-e53UqbboF8QJCscYvzJscJxJXoFcFg4iJjl1oqgE7baJETmx
178 - cloud:  
179 - ai:  
180 - tongyi: # 通义千问  
181 - tongyi:  
182 - api-key: sk-Zsd81gZYg7 178 + dashscope: # 通义千问
  179 + api-key: sk-71800982914041848008480000000000
183 180
184 yudao: 181 yudao:
185 ai: 182 ai: