From 6bf046d6e06dec02d5df94e4418dccab1ff99a91 Mon Sep 17 00:00:00 2001 From: gewuyou Date: Wed, 7 May 2025 22:59:39 +0800 Subject: [PATCH] =?UTF-8?q?refactor(llmx):=20=E9=87=8D=E6=9E=84=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E9=85=8D=E7=BD=AE=E5=92=8C=E7=BD=91=E7=BB=9C=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新 Nacos 配置中的 IP地址 - 修改 Spring Boot项目配置,使用更具体的 USE_SPRING_BOOT_WEB 标志 - 重构 DashScopeAdapter 中的网络请求和响应处理逻辑,提高可读性和维护性 - 在 ChatController 和 LLMProvider 中添加 NDJSON 媒体类型支持 --- build.gradle.kts | 5 +- buildSrc/src/main/kotlin/ProjectFlags.kt | 2 +- llmx-core/llmx-core-service/build.gradle.kts | 2 +- .../core/service/controller/ChatController.kt | 3 +- .../src/main/resources/bootstrap-dev.yml | 2 +- .../llmx/core/spi/provider/LLMProvider.kt | 3 +- llmx-impl/llmx-impl-bailian/build.gradle.kts | 2 +- .../impl/baiLian/adapter/DashScopeAdapter.kt | 112 +++++++++++------- .../src/main/resources/bootstrap-dev.yml | 2 +- 9 files changed, 83 insertions(+), 50 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 526774d..77531ba 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,7 +19,7 @@ configurations.implementation { allprojects { // 设置全局属性 ext { - set(ProjectFlags.USE_SPRING_BOOT, false) + set(ProjectFlags.USE_SPRING_BOOT_WEB, false) set(ProjectFlags.USE_LLM_CORE_SPI, false) set(ProjectFlags.USE_SPRING_CLOUD_BOM, false) set(ProjectFlags.IS_ROOT_MODULE, false) @@ -67,7 +67,8 @@ allprojects { subprojects { afterEvaluate { // springbootWeb - if (project.getPropertyByBoolean(ProjectFlags.USE_SPRING_BOOT)) { + // springbootWeb + if (project.getPropertyByBoolean(ProjectFlags.USE_SPRING_BOOT_WEB)) { apply { plugin(libs.plugins.spring.dependency.management.get().pluginId) plugin(libs.plugins.spring.boot.get().pluginId) diff --git a/buildSrc/src/main/kotlin/ProjectFlags.kt b/buildSrc/src/main/kotlin/ProjectFlags.kt index eb7999b..db93d4a 100644 --- a/buildSrc/src/main/kotlin/ProjectFlags.kt +++ b/buildSrc/src/main/kotlin/ProjectFlags.kt @@ -1,5 +1,5 @@ object ProjectFlags { - const val USE_SPRING_BOOT = "useSpringBoot" + const val USE_SPRING_BOOT_WEB = "useSpringBootWeb" const val USE_SPRING_CLOUD_BOM = "useSpringCloudBom" const val USE_LLM_CORE_SPI = "useLLMCoreSPI" const val IS_ROOT_MODULE = "isRootModule" diff --git a/llmx-core/llmx-core-service/build.gradle.kts b/llmx-core/llmx-core-service/build.gradle.kts index c35e025..1dd6a57 100644 --- a/llmx-core/llmx-core-service/build.gradle.kts +++ b/llmx-core/llmx-core-service/build.gradle.kts @@ -1,6 +1,6 @@ extra { // 开启springboot - setProperty(ProjectFlags.USE_SPRING_BOOT, true) + setProperty(ProjectFlags.USE_SPRING_BOOT_WEB, true) setProperty(ProjectFlags.USE_SPRING_CLOUD_BOM,true) } dependencies { diff --git a/llmx-core/llmx-core-service/src/main/kotlin/org/jcnc/llmx/core/service/controller/ChatController.kt b/llmx-core/llmx-core-service/src/main/kotlin/org/jcnc/llmx/core/service/controller/ChatController.kt index ce5ea4c..bf3570a 100644 --- a/llmx-core/llmx-core-service/src/main/kotlin/org/jcnc/llmx/core/service/controller/ChatController.kt +++ b/llmx-core/llmx-core-service/src/main/kotlin/org/jcnc/llmx/core/service/controller/ChatController.kt @@ -5,6 +5,7 @@ import kotlinx.coroutines.flow.Flow import org.jcnc.llmx.core.service.service.impl.LLMServiceImpl import org.jcnc.llmx.core.spi.entities.request.ChatRequest import org.jcnc.llmx.core.spi.entities.response.ChatResponsePart +import org.springframework.http.MediaType import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody @@ -33,7 +34,7 @@ class ChatController( * @param request 聊天请求对象,包含了发起聊天所需的各种参数和用户信息 * @return 返回一个Flow流,流中依次提供了聊天响应的部分数据,允许异步处理和逐步消费响应内容 */ - @PostMapping("/stream") + @PostMapping("/stream", produces = [MediaType.APPLICATION_NDJSON_VALUE]) fun chat(@RequestBody request: ChatRequest): Flow { return llmServiceImpl.chat(request) } diff --git a/llmx-core/llmx-core-service/src/main/resources/bootstrap-dev.yml b/llmx-core/llmx-core-service/src/main/resources/bootstrap-dev.yml index d1e2592..37654f9 100644 --- a/llmx-core/llmx-core-service/src/main/resources/bootstrap-dev.yml +++ b/llmx-core/llmx-core-service/src/main/resources/bootstrap-dev.yml @@ -4,7 +4,7 @@ spring: username: nacos password: L4s6f9y3 server-addr: 49.235.96.75:8848 - ip: 192.168.1.6 + ip: 192.168.1.100 discovery: server-addr: ${spring.cloud.nacos.server-addr} username: ${spring.cloud.nacos.username} diff --git a/llmx-core/llmx-core-spi/src/main/kotlin/org/jcnc/llmx/core/spi/provider/LLMProvider.kt b/llmx-core/llmx-core-spi/src/main/kotlin/org/jcnc/llmx/core/spi/provider/LLMProvider.kt index f390c4b..404c127 100644 --- a/llmx-core/llmx-core-spi/src/main/kotlin/org/jcnc/llmx/core/spi/provider/LLMProvider.kt +++ b/llmx-core/llmx-core-spi/src/main/kotlin/org/jcnc/llmx/core/spi/provider/LLMProvider.kt @@ -5,6 +5,7 @@ import org.jcnc.llmx.core.spi.entities.request.ChatRequest import org.jcnc.llmx.core.spi.entities.request.EmbeddingRequest import org.jcnc.llmx.core.spi.entities.response.ChatResponsePart import org.jcnc.llmx.core.spi.entities.response.EmbeddingResponse +import org.springframework.http.MediaType import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestMapping @@ -29,7 +30,7 @@ interface LLMProvider { * @param request 聊天请求对象,包含建立聊天所需的信息,如用户标识、会话标识等 * @return 返回一个Flow流,通过该流可以接收到聊天响应的部分数据,如消息、状态更新等 */ - @PostMapping("/chat") + @PostMapping("/chat", produces = [MediaType.APPLICATION_NDJSON_VALUE]) fun chat(request: ChatRequest): Flow /** diff --git a/llmx-impl/llmx-impl-bailian/build.gradle.kts b/llmx-impl/llmx-impl-bailian/build.gradle.kts index 12471f2..c27bbf7 100644 --- a/llmx-impl/llmx-impl-bailian/build.gradle.kts +++ b/llmx-impl/llmx-impl-bailian/build.gradle.kts @@ -1,6 +1,6 @@ // 开启springboot -extra[ProjectFlags.USE_SPRING_BOOT] = true +setProperty(ProjectFlags.USE_SPRING_BOOT_WEB, true) setProperty(ProjectFlags.USE_SPRING_CLOUD_BOM,true) dependencies { // Nacos 服务发现和配置 diff --git a/llmx-impl/llmx-impl-bailian/src/main/kotlin/org/jcnc/llmx/impl/baiLian/adapter/DashScopeAdapter.kt b/llmx-impl/llmx-impl-bailian/src/main/kotlin/org/jcnc/llmx/impl/baiLian/adapter/DashScopeAdapter.kt index d1b31c0..64e229a 100644 --- a/llmx-impl/llmx-impl-bailian/src/main/kotlin/org/jcnc/llmx/impl/baiLian/adapter/DashScopeAdapter.kt +++ b/llmx-impl/llmx-impl-bailian/src/main/kotlin/org/jcnc/llmx/impl/baiLian/adapter/DashScopeAdapter.kt @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.gewuyou.forgeboot.core.extension.log import kotlinx.coroutines.* import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.flow import okhttp3.Headers.Companion.toHeaders import okhttp3.MediaType.Companion.toMediaType @@ -54,52 +55,81 @@ class DashScopeAdapter( val requestJson = objectMapper.writeValueAsString(requestBody) log.info("📤 请求参数: {}", requestJson) - val request = Request.Builder() - .url(url) - .headers(headers.toHeaders()) - .post(requestJson.toRequestBody("application/json".toMediaType())) - .build() + val request = buildRequest(url, headers, requestJson) - val call = okHttpClient.newCall(request) - val response = call.execute() + okHttpClient.newCall(request).execute().use { response -> + if (!response.isSuccessful) { + throw RuntimeException("❌ DashScope 请求失败: HTTP ${response.code}") + } - if (!response.isSuccessful) { - throw RuntimeException("❌ DashScope 请求失败: HTTP ${response.code}") - } + val responseBody = response.body ?: throw RuntimeException("❌ DashScope 响应体为空") - val responseBody = response.body ?: throw RuntimeException("❌ DashScope 响应体为空") - - val bufferedReader: BufferedReader = responseBody.charStream().buffered() - val allContent = StringBuilder() - - try { - while (currentCoroutineContext().isActive) { - val line = withContext(dispatcher) { - bufferedReader.readLine() - } ?: break - log.info("📥 接收到行: {}", line) - - if (line.startsWith("data:")) { - val jsonPart = line.removePrefix("data:").trim() - try { - val part = extractContent(jsonPart) - allContent.append(part.content) - log.info("✅ 提取内容: {}", part) - emit(part) - } catch (e: Exception) { - log.warn("⚠️ 无法解析 JSON: {}", jsonPart, e) - } + responseBody.charStream().buffered().use { reader -> + val allContent = StringBuilder() + try { + processResponse(dispatcher, reader, extractContent, allContent) + log.info("📦 完整内容: {}", allContent) + } catch (e: Exception) { + log.error("🚨 读取 DashScope 响应流失败: {}", e.message, e) + throw e } } - log.info("📦 完整内容: {}", allContent) - } catch (e: Exception) { - log.error("🚨 读取 DashScope 响应流失败: {}", e.message, e) - throw e - } finally { - withContext(dispatcher) { - bufferedReader.close() - } - response.close() } } + + /** + * 处理响应流 + * + * 本函数读取HTTP响应中的数据行,解析并提取内容。 + * 它在循环中读取每一行,并使用提供的函数提取内容。 + * + * @param dispatcher 协程调度器 + * @param reader 响应体的BufferedReader + * @param extractContent 一个函数,用于从JSON响应中提取内容 + * @param allContent 保存所有提取内容的StringBuilder + */ + private suspend fun FlowCollector.processResponse( + dispatcher: CoroutineDispatcher, + reader: BufferedReader, + extractContent: (String) -> ChatResponsePart, + allContent: StringBuilder + ) { + while (currentCoroutineContext().isActive) { + val line = withContext(dispatcher) { + reader.readLine() + } ?: break + + log.debug("📥 接收到行: {}", line) + + if (line.startsWith("data:")) { + val jsonPart = line.removePrefix("data:").trim() + try { + val part = extractContent(jsonPart) + allContent.append(part.content) + log.debug("✅ 提取内容: {}", part) + emit(part) + } catch (e: Exception) { + log.warn("⚠️ 无法解析 JSON: {}", jsonPart, e) + } + } + } + } + + /** + * 构建请求 + * + * 本函数构建一个OkHttp请求对象,用于发送聊天请求。 + * + * @param url 请求的URL + * @param headers 请求的头部信息 + * @param json 请求的主体内容的JSON字符串 + * @return 返回构建好的Request对象 + */ + private fun buildRequest(url: String, headers: Map, json: String): Request { + return Request.Builder() + .url(url) + .headers(headers.toHeaders()) + .post(json.toRequestBody("application/json".toMediaType())) + .build() + } } diff --git a/llmx-impl/llmx-impl-bailian/src/main/resources/bootstrap-dev.yml b/llmx-impl/llmx-impl-bailian/src/main/resources/bootstrap-dev.yml index d1e2592..37654f9 100644 --- a/llmx-impl/llmx-impl-bailian/src/main/resources/bootstrap-dev.yml +++ b/llmx-impl/llmx-impl-bailian/src/main/resources/bootstrap-dev.yml @@ -4,7 +4,7 @@ spring: username: nacos password: L4s6f9y3 server-addr: 49.235.96.75:8848 - ip: 192.168.1.6 + ip: 192.168.1.100 discovery: server-addr: ${spring.cloud.nacos.server-addr} username: ${spring.cloud.nacos.username}