基于SpringBoot和Dify實(shí)現(xiàn)流式響應(yīng)輸出
在使用 Dify(假設(shè)為某種生成式 AI 模型或服務(wù))結(jié)合 Spring Boot 和 WebClient 實(shí)現(xiàn)流式輸出時,我們需要確保技術(shù)棧的版本兼容性,并理解流式輸出的核心概念。以下是詳細(xì)講解:
1. 技術(shù)棧版本要求
Spring Boot 版本要求
最低推薦版本:2.7.x 或 3.x
如果需要支持 HTTP/2 或更高級別的異步處理能力,建議使用 Spring Boot 3.x。
Spring Boot 3.x 基于 Spring Framework 6.x 和 Java 17+,提供了更好的反應(yīng)式編程支持。
JDK 版本要求
最低推薦版本:Java 11
Spring Boot 2.7.x 支持 Java 8 及以上,但推薦使用 Java 11 或更高版本。
如果使用 Spring Boot 3.x,則必須使用 Java 17 或更高版本,因?yàn)?Spring Boot 3.x 已經(jīng)停止支持 Java 11 以下的版本。
2. 核心概念:流式輸出
流式輸出(Streaming Output)是指服務(wù)器以分塊的方式逐步將數(shù)據(jù)發(fā)送到客戶端,而不是一次性返回完整的結(jié)果。這種方式特別適合處理大文件傳輸、實(shí)時數(shù)據(jù)流或生成式模型的逐詞輸出。
在 Spring Boot 中,可以通過以下方式實(shí)現(xiàn)流式輸出:
- 使用 ResponseEntity<Flux<?>> 或 ResponseBodyEmitter(適用于同步場景)。
- 使用 WebClient 的反應(yīng)式編程模型來處理流式請求和響應(yīng)。
3. 實(shí)現(xiàn)步驟
3.1 添加依賴
確保在 pom.xml 中添加以下依賴項(xiàng):
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
spring-boot-starter-webflux 提供了反應(yīng)式 Web 編程的支持。
3.2 配置 WebClient
創(chuàng)建一個 WebClient 實(shí)例,主要用于設(shè)置跨域資源共享(CORS, Cross-Origin Resource Sharing)。它的作用是解決前端和后端在不同域名或端口下通信時的跨域問題。
@Configuration public class WebConfig implements WebMvcConfigurer { static final List<String> ORIGIN_LIST = Arrays.asList( // 本地 "http://localhost:8080", "http://127.0.0.1:8080", "http://localhost:8888", "http://127.0.0.1:8888", "http://localhost:8803", "http://127.0.0.1:8803" ); @Override public void addCorsMappings(CorsRegistry registry) { // 配置全局跨域規(guī)則 registry.addMapping("/**") // 允許所有路徑的請求 .allowedOrigins(ORIGIN_LIST.toArray(new String[0])) // 允許的源 .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允許的HTTP方法 .allowedHeaders("Content-Type", "Authorization") // 允許的請求頭 .allowCredentials(true); // 是否允許發(fā)送Cookie等憑證信息 } }
3.3 實(shí)現(xiàn)流式輸出控制器
@Slf4j @RestController @RequestMapping("/api") @RequiredArgsConstructor public class DifyController { @Value("${portal.chatMessages}") private String chatMessages; private final DifyService difyService; @GetMapping(value = "/chatMessagesStreaming", produces = "text/event-stream") public Flux<StreamResponse> chatMessagesStreaming(HttpServletRequest request, @RequestParam(value = "query", required = true) String query, @RequestParam(value = "userName", required = true) String userName, @RequestParam(value = "conversationId", required = false) String conversationId) throws Exception { return difyService.streamingMessage(query, conversationId, userName).doOnNext(response -> { log.info("流式結(jié)果:" + response.toString()); //workflow_finished節(jié)點(diǎn)可以獲取完整答案,進(jìn)行你的邏輯處理 if (response.getEvent().equals("workflow_finished")) { log.info("進(jìn)入workflow_finished階段"); String answer = response.getData().getOutputs().getAnswer();//完整答案 } //message_end結(jié)束節(jié)點(diǎn),進(jìn)行你的邏輯處理 if (response.getEvent().equals("message_end")) { log.info("進(jìn)入message_end"); } }); }
3.4 實(shí)現(xiàn)流式輸出服務(wù)層
java @Slf4j @Service @RequiredArgsConstructor public class DifyService { @Value("${dify.url}") private String url; @Value("${dify.key}") private String apiKey; /** * 流式調(diào)用dify. * * @param query 查詢文本 * @param conversationId id * @param userName 用戶名 * @return Flux 響應(yīng)流 */ public Flux<StreamResponse> streamingMessage(String query, String conversationId, String userName) { //1.設(shè)置請求體 DifyRequestBody body = new DifyRequestBody(); body.setInputs(new HashMap<>()); body.setQuery(query); body.setResponseMode("streaming"); body.setConversationId(""); body.setUser(userName); if (StringUtils.isNotEmpty(conversationId)) { body.setConversationId(conversationId); } //如果存在自定義入?yún)⒖梢约拥饺缦翸ap中 //Map<String, Object> commoninputs = new HashMap<>(); //commoninputs.put("search_type", searchType); //body.setInputs(commoninputs); //2.使用webclient發(fā)送post請求 return webClient.post() .uri(url) .headers(httpHeaders -> { httpHeaders.setContentType(MediaType.APPLICATION_JSON); httpHeaders.setBearerAuth(apiKey); }) .bodyValue(JSON.toJSONString(body)) .retrieve() .bodyToFlux(StreamResponse.class);//實(shí)體轉(zhuǎn)換 .filter(this::shouldInclude) // 過濾掉不需要的數(shù)據(jù)【根據(jù)需求增加】 //.map(this::convertToCustomResponseAsync) // 異步轉(zhuǎn)換【如果返回格式自定義則通過異步轉(zhuǎn)換實(shí)現(xiàn)】 .onErrorResume(throwable -> { log.info("異常輸出:"+throwable.getMessage()) }) //.concatWith(Mono.just(createCustomFinalMessage())); // 添加自定義的最終消息【根據(jù)需求增加】 } private boolean shouldInclude(StreamResponse streamResponse) { // 示例:只要message節(jié)點(diǎn)的數(shù)據(jù)和message_end節(jié)點(diǎn)的數(shù)據(jù) if (streamResponse.getEvent().equals("message") || streamResponse.getEvent().equals("message_end")) { return true; } return false; }
3.4 實(shí)現(xiàn)流式輸出數(shù)據(jù)訪問層
和dify返回流式輸出格式一致
@Data public class StreamResponse implements Serializable { /** * 不同模式下的事件類型. */ private String event; /** * agent_thought id. */ private String id; /** * 任務(wù)ID. */ private String task_id; /** * 消息唯一ID. */ private String message_id; /** * LLM 返回文本塊內(nèi)容. */ private String answer; /** * 創(chuàng)建時間戳. */ private Long created_at; /** * 會話 ID. */ private String conversation_id; private StreamResponseData data; } @Data public class StreamResponseData implements Serializable { private String id; private String workflow_id; private String status; private Long created_at; private Long finished_at; private OutputsData outputs; } @Data public class OutputsData implements Serializable { private String answer; }
4. 關(guān)鍵點(diǎn)說明
1.MediaType.TEXT_EVENT_STREAM_VALUE
表示使用 Server-Sent Events (SSE) 協(xié)議進(jìn)行流式傳輸。
客戶端可以通過瀏覽器或支持 SSE 的工具(如 Postman)接收流式數(shù)據(jù)。
2.Flux
Flux 是 Reactor 庫中的核心類型,表示一個可以包含零個或多個元素的異步序列。
在這里,F(xiàn)lux 表示從 Dify 接收到的逐詞或逐句生成的文本流。
3.WebClient 的反應(yīng)式特性
WebClient 是 Spring 提供的反應(yīng)式 HTTP 客戶端,能夠高效處理流式數(shù)據(jù)。
它不會阻塞線程,而是通過事件驅(qū)動的方式逐步處理數(shù)據(jù)
總結(jié)
通過上述步驟,我們可以使用 Spring Boot 和 WebClient 實(shí)現(xiàn)流式輸出功能。關(guān)鍵在于利用反應(yīng)式編程模型(Reactor 的 Flux 和 WebClient),以及正確配置流式傳輸協(xié)議(如 SSE)。根據(jù)需求選擇合適的 Spring Boot 和 JDK 版本,可以確保項(xiàng)目的性能和穩(wěn)定性。
到此這篇關(guān)于基于SpringBoot和Dify實(shí)現(xiàn)流式響應(yīng)輸出的文章就介紹到這了,更多相關(guān)SpringBoot Dify流式響應(yīng)輸出內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java設(shè)計(jì)模式之組合模式(Composite模式)介紹
這篇文章主要介紹了Java設(shè)計(jì)模式之組合模式(Composite模式)介紹,Composite定義:將對象以樹形結(jié)構(gòu)組織起來,以達(dá)成“部分-整體” 的層次結(jié)構(gòu),使得客戶端對單個對象和組合對象的使用具有一致性,需要的朋友可以參考下2015-03-03使用Java應(yīng)用程序添加或刪除 PDF 中的附件
當(dāng)我們在制作PDF文件或者PPT演示文稿的時候,為了讓自己的文件更全面詳細(xì),就會在文件中添加附件,那么如何添加或刪除PDF中的附件呢,今天通過本文給大家詳細(xì)講解,需要的朋友參考下吧2023-01-01Java代碼實(shí)現(xiàn)簡單酒店管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了Java代碼實(shí)現(xiàn)簡單酒店管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-06-06

Java 使用maven實(shí)現(xiàn)Jsoup簡單爬蟲案例詳解