Commit 99b6f240923b05357b5f31f37dd9907764d6012a
1 parent
041a00b2
【修复】AI:解决 spring-ai 与 webflux 集成时,SSE 存在乱序的问题
Showing
5 changed files
with
14 additions
and
28 deletions
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.http
| @@ -6,7 +6,7 @@ Authorization: {{token}} | @@ -6,7 +6,7 @@ Authorization: {{token}} | ||
| 6 | 6 | ||
| 7 | { | 7 | { |
| 8 | "conversationId": "1781604279872581649", | 8 | "conversationId": "1781604279872581649", |
| 9 | - "content": "中国好看吗?" | 9 | + "content": "你是 OpenAI 么?" |
| 10 | } | 10 | } |
| 11 | 11 | ||
| 12 | 12 | ||
| @@ -16,11 +16,10 @@ Content-Type: application/json | @@ -16,11 +16,10 @@ Content-Type: application/json | ||
| 16 | Authorization: {{token}} | 16 | Authorization: {{token}} |
| 17 | 17 | ||
| 18 | { | 18 | { |
| 19 | - "conversationId": "1781604279872581651", | ||
| 20 | - "content": "苹果是什么颜色?" | 19 | + "conversationId": "1781604279872581690", |
| 20 | + "content": "1+1=?" | ||
| 21 | } | 21 | } |
| 22 | 22 | ||
| 23 | - | ||
| 24 | ### message list | 23 | ### message list |
| 25 | GET {{baseUrl}}/admin-api/ai/chat/message/list-by-conversation-id?conversationId=1781604279872581649 | 24 | GET {{baseUrl}}/admin-api/ai/chat/message/list-by-conversation-id?conversationId=1781604279872581649 |
| 26 | Authorization: {{token}} | 25 | Authorization: {{token}} |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatConversationMapper.java
| @@ -15,12 +15,11 @@ import java.util.List; | @@ -15,12 +15,11 @@ import java.util.List; | ||
| 15 | @Mapper | 15 | @Mapper |
| 16 | public interface AiChatConversationMapper extends BaseMapperX<AiChatConversationDO> { | 16 | public interface AiChatConversationMapper extends BaseMapperX<AiChatConversationDO> { |
| 17 | 17 | ||
| 18 | + // TODO @fan:建议这里不排序哈;交给他们前端排序 | ||
| 18 | default List<AiChatConversationDO> selectListByUserId(Long userId) { | 19 | default List<AiChatConversationDO> selectListByUserId(Long userId) { |
| 19 | - return selectList( | ||
| 20 | - new LambdaQueryWrapperX<AiChatConversationDO>() | ||
| 21 | - .eq(AiChatConversationDO::getUserId, userId) | ||
| 22 | - .orderByAsc(AiChatConversationDO::getCreateTime) | ||
| 23 | - ); | 20 | + return selectList(new LambdaQueryWrapperX<AiChatConversationDO>() |
| 21 | + .eq(AiChatConversationDO::getUserId, userId) | ||
| 22 | + .orderByAsc(AiChatConversationDO::getCreateTime)); | ||
| 24 | } | 23 | } |
| 25 | 24 | ||
| 26 | } | 25 | } |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/impl/AiChatServiceImpl.java
| @@ -32,6 +32,7 @@ import lombok.extern.slf4j.Slf4j; | @@ -32,6 +32,7 @@ import lombok.extern.slf4j.Slf4j; | ||
| 32 | import org.springframework.stereotype.Service; | 32 | import org.springframework.stereotype.Service; |
| 33 | import org.springframework.transaction.annotation.Transactional; | 33 | import org.springframework.transaction.annotation.Transactional; |
| 34 | import reactor.core.publisher.Flux; | 34 | import reactor.core.publisher.Flux; |
| 35 | +import reactor.core.scheduler.Schedulers; | ||
| 35 | 36 | ||
| 36 | import java.time.LocalDateTime; | 37 | import java.time.LocalDateTime; |
| 37 | import java.util.*; | 38 | import java.util.*; |
| @@ -125,9 +126,10 @@ public class AiChatServiceImpl implements AiChatService { | @@ -125,9 +126,10 @@ public class AiChatServiceImpl implements AiChatService { | ||
| 125 | Flux<ChatResponse> streamResponse = chatClient.stream(prompt); | 126 | Flux<ChatResponse> streamResponse = chatClient.stream(prompt); |
| 126 | 127 | ||
| 127 | // 3.3 流式返回 | 128 | // 3.3 流式返回 |
| 129 | + // 注意:Schedulers.immediate() 目的是,避免默认 Schedulers.parallel() 并发消费 chunk 导致 SSE 响应前端会乱序问题 | ||
| 128 | StringBuffer contentBuffer = new StringBuffer(); | 130 | StringBuffer contentBuffer = new StringBuffer(); |
| 129 | - return streamResponse.map(response -> { | ||
| 130 | - String newContent = response.getResult() != null ? response.getResult().getOutput().getContent() : null; | 131 | + return streamResponse.publishOn(Schedulers.immediate()).map(chunk -> { |
| 132 | + String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getContent() : null; | ||
| 131 | newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况 | 133 | newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况 |
| 132 | contentBuffer.append(newContent); | 134 | contentBuffer.append(newContent); |
| 133 | // 响应结果 | 135 | // 响应结果 |
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/openAiImage/OpenAiImageClientTests.java
| @@ -71,21 +71,4 @@ public class OpenAiImageClientTests { | @@ -71,21 +71,4 @@ public class OpenAiImageClientTests { | ||
| 71 | } | 71 | } |
| 72 | } | 72 | } |
| 73 | 73 | ||
| 74 | - public static void main(String[] args) { | ||
| 75 | -// OpenAiApi api = new OpenAiApi("https://api.gptsapi.net", "sk-yzKea6d8e8212c3bdd99f9f44ced1cae37c097e5aa3BTS7z"); | ||
| 76 | -// OpenAiApi api = new OpenAiApi("https://openkey.cloud", "sk-QmgIIPc5xiYd8lPb076b1b7774Ea49Af9eD2Ef172c8f7e43"); | ||
| 77 | - OpenAiApi api = new OpenAiApi("https://api.chatanywhere.tech", "sk-gkgfYxhX9FxyZJznwxRZSJwKeGQYNPDVWjhby2PRRf17GHeT"); | ||
| 78 | - OpenAiChatClient client = new OpenAiChatClient(api); | ||
| 79 | -// String result = client.call("未来,英文是什么?"); | ||
| 80 | -// System.out.println(result); | ||
| 81 | - Flux<String> result = client.stream("未来,英文是什么?"); | ||
| 82 | - result.map(new Function<String, String>() { | ||
| 83 | - @Override | ||
| 84 | - public String apply(String s) { | ||
| 85 | - System.out.println(s); | ||
| 86 | - return s; | ||
| 87 | - } | ||
| 88 | - }).blockLast(); | ||
| 89 | - } | ||
| 90 | - | ||
| 91 | } | 74 | } |
yudao-server/src/main/resources/application.yaml
| @@ -156,6 +156,9 @@ spring.ai: | @@ -156,6 +156,9 @@ spring.ai: | ||
| 156 | # base-url: https://api.chatanywhere.tech | 156 | # base-url: https://api.chatanywhere.tech |
| 157 | api-key: sk-yzKea6d8e8212c3bdd99f9f44ced1cae37c097e5aa3BTS7z | 157 | api-key: sk-yzKea6d8e8212c3bdd99f9f44ced1cae37c097e5aa3BTS7z |
| 158 | base-url: https://api.gptsapi.net | 158 | base-url: https://api.gptsapi.net |
| 159 | +# chat: | ||
| 160 | +# options: | ||
| 161 | +# model: gpt-4-0125-preview | ||
| 159 | 162 | ||
| 160 | yudao.ai: | 163 | yudao.ai: |
| 161 | yiyan: | 164 | yiyan: |