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 | 6 | |
| 7 | 7 | { |
| 8 | 8 | "conversationId": "1781604279872581649", |
| 9 | - "content": "中国好看吗?" | |
| 9 | + "content": "你是 OpenAI 么?" | |
| 10 | 10 | } |
| 11 | 11 | |
| 12 | 12 | |
| ... | ... | @@ -16,11 +16,10 @@ Content-Type: application/json |
| 16 | 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 | 23 | ### message list |
| 25 | 24 | GET {{baseUrl}}/admin-api/ai/chat/message/list-by-conversation-id?conversationId=1781604279872581649 |
| 26 | 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 | 15 | @Mapper |
| 16 | 16 | public interface AiChatConversationMapper extends BaseMapperX<AiChatConversationDO> { |
| 17 | 17 | |
| 18 | + // TODO @fan:建议这里不排序哈;交给他们前端排序 | |
| 18 | 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 | 32 | import org.springframework.stereotype.Service; |
| 33 | 33 | import org.springframework.transaction.annotation.Transactional; |
| 34 | 34 | import reactor.core.publisher.Flux; |
| 35 | +import reactor.core.scheduler.Schedulers; | |
| 35 | 36 | |
| 36 | 37 | import java.time.LocalDateTime; |
| 37 | 38 | import java.util.*; |
| ... | ... | @@ -125,9 +126,10 @@ public class AiChatServiceImpl implements AiChatService { |
| 125 | 126 | Flux<ChatResponse> streamResponse = chatClient.stream(prompt); |
| 126 | 127 | |
| 127 | 128 | // 3.3 流式返回 |
| 129 | + // 注意:Schedulers.immediate() 目的是,避免默认 Schedulers.parallel() 并发消费 chunk 导致 SSE 响应前端会乱序问题 | |
| 128 | 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 | 133 | newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况 |
| 132 | 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 | 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 | 156 | # base-url: https://api.chatanywhere.tech |
| 157 | 157 | api-key: sk-yzKea6d8e8212c3bdd99f9f44ced1cae37c097e5aa3BTS7z |
| 158 | 158 | base-url: https://api.gptsapi.net |
| 159 | +# chat: | |
| 160 | +# options: | |
| 161 | +# model: gpt-4-0125-preview | |
| 159 | 162 | |
| 160 | 163 | yudao.ai: |
| 161 | 164 | yiyan: | ... | ... |