refactor(llmx): 重构项目配置和网络请求处理
- 更新 Nacos 配置中的 IP地址 - 修改 Spring Boot项目配置,使用更具体的 USE_SPRING_BOOT_WEB 标志 - 重构 DashScopeAdapter 中的网络请求和响应处理逻辑,提高可读性和维护性 - 在 ChatController 和 LLMProvider 中添加 NDJSON 媒体类型支持
This commit is contained in:
parent
73eeaa19c1
commit
6bf046d6e0
@ -19,7 +19,7 @@ configurations.implementation {
|
|||||||
allprojects {
|
allprojects {
|
||||||
// 设置全局属性
|
// 设置全局属性
|
||||||
ext {
|
ext {
|
||||||
set(ProjectFlags.USE_SPRING_BOOT, false)
|
set(ProjectFlags.USE_SPRING_BOOT_WEB, false)
|
||||||
set(ProjectFlags.USE_LLM_CORE_SPI, false)
|
set(ProjectFlags.USE_LLM_CORE_SPI, false)
|
||||||
set(ProjectFlags.USE_SPRING_CLOUD_BOM, false)
|
set(ProjectFlags.USE_SPRING_CLOUD_BOM, false)
|
||||||
set(ProjectFlags.IS_ROOT_MODULE, false)
|
set(ProjectFlags.IS_ROOT_MODULE, false)
|
||||||
@ -67,7 +67,8 @@ allprojects {
|
|||||||
subprojects {
|
subprojects {
|
||||||
afterEvaluate {
|
afterEvaluate {
|
||||||
// springbootWeb
|
// springbootWeb
|
||||||
if (project.getPropertyByBoolean(ProjectFlags.USE_SPRING_BOOT)) {
|
// springbootWeb
|
||||||
|
if (project.getPropertyByBoolean(ProjectFlags.USE_SPRING_BOOT_WEB)) {
|
||||||
apply {
|
apply {
|
||||||
plugin(libs.plugins.spring.dependency.management.get().pluginId)
|
plugin(libs.plugins.spring.dependency.management.get().pluginId)
|
||||||
plugin(libs.plugins.spring.boot.get().pluginId)
|
plugin(libs.plugins.spring.boot.get().pluginId)
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
object ProjectFlags {
|
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_SPRING_CLOUD_BOM = "useSpringCloudBom"
|
||||||
const val USE_LLM_CORE_SPI = "useLLMCoreSPI"
|
const val USE_LLM_CORE_SPI = "useLLMCoreSPI"
|
||||||
const val IS_ROOT_MODULE = "isRootModule"
|
const val IS_ROOT_MODULE = "isRootModule"
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
extra {
|
extra {
|
||||||
// 开启springboot
|
// 开启springboot
|
||||||
setProperty(ProjectFlags.USE_SPRING_BOOT, true)
|
setProperty(ProjectFlags.USE_SPRING_BOOT_WEB, true)
|
||||||
setProperty(ProjectFlags.USE_SPRING_CLOUD_BOM,true)
|
setProperty(ProjectFlags.USE_SPRING_CLOUD_BOM,true)
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
import org.jcnc.llmx.core.service.service.impl.LLMServiceImpl
|
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.request.ChatRequest
|
||||||
import org.jcnc.llmx.core.spi.entities.response.ChatResponsePart
|
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.PostMapping
|
||||||
import org.springframework.web.bind.annotation.RequestBody
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
@ -33,7 +34,7 @@ class ChatController(
|
|||||||
* @param request 聊天请求对象,包含了发起聊天所需的各种参数和用户信息
|
* @param request 聊天请求对象,包含了发起聊天所需的各种参数和用户信息
|
||||||
* @return 返回一个Flow流,流中依次提供了聊天响应的部分数据,允许异步处理和逐步消费响应内容
|
* @return 返回一个Flow流,流中依次提供了聊天响应的部分数据,允许异步处理和逐步消费响应内容
|
||||||
*/
|
*/
|
||||||
@PostMapping("/stream")
|
@PostMapping("/stream", produces = [MediaType.APPLICATION_NDJSON_VALUE])
|
||||||
fun chat(@RequestBody request: ChatRequest): Flow<ChatResponsePart> {
|
fun chat(@RequestBody request: ChatRequest): Flow<ChatResponsePart> {
|
||||||
return llmServiceImpl.chat(request)
|
return llmServiceImpl.chat(request)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ spring:
|
|||||||
username: nacos
|
username: nacos
|
||||||
password: L4s6f9y3
|
password: L4s6f9y3
|
||||||
server-addr: 49.235.96.75:8848
|
server-addr: 49.235.96.75:8848
|
||||||
ip: 192.168.1.6
|
ip: 192.168.1.100
|
||||||
discovery:
|
discovery:
|
||||||
server-addr: ${spring.cloud.nacos.server-addr}
|
server-addr: ${spring.cloud.nacos.server-addr}
|
||||||
username: ${spring.cloud.nacos.username}
|
username: ${spring.cloud.nacos.username}
|
||||||
|
|||||||
@ -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.request.EmbeddingRequest
|
||||||
import org.jcnc.llmx.core.spi.entities.response.ChatResponsePart
|
import org.jcnc.llmx.core.spi.entities.response.ChatResponsePart
|
||||||
import org.jcnc.llmx.core.spi.entities.response.EmbeddingResponse
|
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.PostMapping
|
||||||
import org.springframework.web.bind.annotation.RequestMapping
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
@ -29,7 +30,7 @@ interface LLMProvider {
|
|||||||
* @param request 聊天请求对象,包含建立聊天所需的信息,如用户标识、会话标识等
|
* @param request 聊天请求对象,包含建立聊天所需的信息,如用户标识、会话标识等
|
||||||
* @return 返回一个Flow流,通过该流可以接收到聊天响应的部分数据,如消息、状态更新等
|
* @return 返回一个Flow流,通过该流可以接收到聊天响应的部分数据,如消息、状态更新等
|
||||||
*/
|
*/
|
||||||
@PostMapping("/chat")
|
@PostMapping("/chat", produces = [MediaType.APPLICATION_NDJSON_VALUE])
|
||||||
fun chat(request: ChatRequest): Flow<ChatResponsePart>
|
fun chat(request: ChatRequest): Flow<ChatResponsePart>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
// 开启springboot
|
// 开启springboot
|
||||||
extra[ProjectFlags.USE_SPRING_BOOT] = true
|
setProperty(ProjectFlags.USE_SPRING_BOOT_WEB, true)
|
||||||
setProperty(ProjectFlags.USE_SPRING_CLOUD_BOM,true)
|
setProperty(ProjectFlags.USE_SPRING_CLOUD_BOM,true)
|
||||||
dependencies {
|
dependencies {
|
||||||
// Nacos 服务发现和配置
|
// Nacos 服务发现和配置
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
|
|||||||
import com.gewuyou.forgeboot.core.extension.log
|
import com.gewuyou.forgeboot.core.extension.log
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.FlowCollector
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import okhttp3.Headers.Companion.toHeaders
|
import okhttp3.Headers.Companion.toHeaders
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
@ -54,52 +55,81 @@ class DashScopeAdapter(
|
|||||||
val requestJson = objectMapper.writeValueAsString(requestBody)
|
val requestJson = objectMapper.writeValueAsString(requestBody)
|
||||||
log.info("📤 请求参数: {}", requestJson)
|
log.info("📤 请求参数: {}", requestJson)
|
||||||
|
|
||||||
val request = Request.Builder()
|
val request = buildRequest(url, headers, requestJson)
|
||||||
.url(url)
|
|
||||||
.headers(headers.toHeaders())
|
|
||||||
.post(requestJson.toRequestBody("application/json".toMediaType()))
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val call = okHttpClient.newCall(request)
|
okHttpClient.newCall(request).execute().use { response ->
|
||||||
val response = call.execute()
|
if (!response.isSuccessful) {
|
||||||
|
throw RuntimeException("❌ DashScope 请求失败: HTTP ${response.code}")
|
||||||
|
}
|
||||||
|
|
||||||
if (!response.isSuccessful) {
|
val responseBody = response.body ?: throw RuntimeException("❌ DashScope 响应体为空")
|
||||||
throw RuntimeException("❌ DashScope 请求失败: HTTP ${response.code}")
|
|
||||||
}
|
|
||||||
|
|
||||||
val responseBody = response.body ?: throw RuntimeException("❌ DashScope 响应体为空")
|
responseBody.charStream().buffered().use { reader ->
|
||||||
|
val allContent = StringBuilder()
|
||||||
val bufferedReader: BufferedReader = responseBody.charStream().buffered()
|
try {
|
||||||
val allContent = StringBuilder()
|
processResponse(dispatcher, reader, extractContent, allContent)
|
||||||
|
log.info("📦 完整内容: {}", allContent)
|
||||||
try {
|
} catch (e: Exception) {
|
||||||
while (currentCoroutineContext().isActive) {
|
log.error("🚨 读取 DashScope 响应流失败: {}", e.message, e)
|
||||||
val line = withContext(dispatcher) {
|
throw e
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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<ChatResponsePart>.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<String, String>, json: String): Request {
|
||||||
|
return Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.headers(headers.toHeaders())
|
||||||
|
.post(json.toRequestBody("application/json".toMediaType()))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ spring:
|
|||||||
username: nacos
|
username: nacos
|
||||||
password: L4s6f9y3
|
password: L4s6f9y3
|
||||||
server-addr: 49.235.96.75:8848
|
server-addr: 49.235.96.75:8848
|
||||||
ip: 192.168.1.6
|
ip: 192.168.1.100
|
||||||
discovery:
|
discovery:
|
||||||
server-addr: ${spring.cloud.nacos.server-addr}
|
server-addr: ${spring.cloud.nacos.server-addr}
|
||||||
username: ${spring.cloud.nacos.username}
|
username: ${spring.cloud.nacos.username}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user