From dbd2246a09f271fac06b6b4e91c61ec216cbcfee Mon Sep 17 00:00:00 2001 From: AiKrai Date: Sun, 16 Mar 2025 18:00:11 +0800 Subject: [PATCH] =?UTF-8?q?feat(demo):=20=E6=B7=BB=E5=8A=A0=E8=81=8A?= =?UTF-8?q?=E5=A4=A9=E6=A8=A1=E5=9E=8B=E5=8A=9F=E8=83=BD=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E7=94=A8=E6=88=B7=E8=B4=A6=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 vertx-demo 项目中添加了 ChatController,实现与 OpenAI聊天模型交互 - 在 Account 模型中添加了唯一索引 TableIndex - 更新了项目依赖,增加了 langchain4j相关库 --- vertx-demo/build.gradle.kts | 4 + .../kotlin/app/controller/ChatController.kt | 112 ++++++++++++++++++ .../kotlin/app/data/domain/account/Account.kt | 1 + .../aikrai/vertx/db/annotation/Annotation.kt | 17 ++- 4 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 vertx-demo/src/main/kotlin/app/controller/ChatController.kt diff --git a/vertx-demo/build.gradle.kts b/vertx-demo/build.gradle.kts index ec9f00d..07d201f 100644 --- a/vertx-demo/build.gradle.kts +++ b/vertx-demo/build.gradle.kts @@ -88,6 +88,10 @@ dependencies { implementation("io.vertx:vertx-auth-jwt:$vertxVersion") implementation("io.vertx:vertx-redis-client:$vertxVersion") + implementation("dev.langchain4j:langchain4j-open-ai:1.0.0-beta1") + implementation("dev.langchain4j:langchain4j:1.0.0-beta1") + + implementation("com.google.inject:guice:5.1.0") implementation("org.reflections:reflections:0.10.2") diff --git a/vertx-demo/src/main/kotlin/app/controller/ChatController.kt b/vertx-demo/src/main/kotlin/app/controller/ChatController.kt new file mode 100644 index 0000000..6ea0eb5 --- /dev/null +++ b/vertx-demo/src/main/kotlin/app/controller/ChatController.kt @@ -0,0 +1,112 @@ +package app.controller + +import com.google.inject.Inject +import dev.langchain4j.model.chat.request.ChatRequestParameters +import dev.langchain4j.model.chat.response.ChatResponse +import dev.langchain4j.model.chat.response.StreamingChatResponseHandler +import dev.langchain4j.model.openai.OpenAiChatModel +import dev.langchain4j.model.openai.OpenAiStreamingChatModel +import io.vertx.core.http.HttpServerResponse +import io.vertx.ext.web.RoutingContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import mu.KotlinLogging +import org.aikrai.vertx.auth.AllowAnonymous +import org.aikrai.vertx.config.Config +import org.aikrai.vertx.context.Controller +import org.aikrai.vertx.context.CustomizeResponse +import org.aikrai.vertx.context.D + + +@AllowAnonymous +@D("对话") +@Controller("/chat") +class ChatController @Inject constructor( + private val coroutineScope: CoroutineScope +) { + private val logger = KotlinLogging.logger { } + val baseUrl = Config.getKey("langchain4j.open-ai.chat-model.base-url").toString() + val apiKey = Config.getKey("langchain4j.open-ai.chat-model.api-key").toString() + val modelName = Config.getKey("langchain4j.open-ai.chat-model.model-name").toString() + + @AllowAnonymous + @D("对话测试", "详细说明......") + suspend fun chat( + @D("message", "提问") message: String, + ): String { + return withContext(Dispatchers.IO) { + val chatModel = OpenAiChatModel.builder() + .baseUrl(baseUrl) + .apiKey(apiKey) + .defaultRequestParameters( + ChatRequestParameters.builder() + .modelName(modelName) + .build() + ) + .build() + val response = chatModel.chat(message) + println(response) + + return@withContext response.toString() + } + } + + @CustomizeResponse + @AllowAnonymous + @D("对话流式响应测试", "详细说明......") + suspend fun stream( + ctx: RoutingContext, + @D("message", "提问") message: String, + ): Void? { + val response = ctx.response() + // 设置响应头,表明这是一个流式响应 + response.setChunked(true) + response.putHeader("Content-Type", "text/event-stream") + response.putHeader("Cache-Control", "no-cache") + response.putHeader("Connection", "keep-alive") + + var isResponseEnded = false // 响应结束标志 + + withContext(Dispatchers.IO) { + val chatModel = OpenAiStreamingChatModel.builder() + .baseUrl(baseUrl) + .apiKey(apiKey) + .defaultRequestParameters( + ChatRequestParameters.builder() + .modelName(modelName) + .build() + ) + .build() + + chatModel.chat(message, object : StreamingChatResponseHandler { + override fun onPartialResponse(partialResponse: String) { + if (!isResponseEnded) { + response.write("data: $partialResponse\n\n") + logger.debug { "发送部分响应: $partialResponse" } + } + } + + override fun onCompleteResponse(completeResponse: ChatResponse) { + if (!isResponseEnded) { + response.write("data: [DONE]\n\n") + response.end() // 结束响应 + isResponseEnded = true // 设置结束标志 + logger.debug { "响应完成: $completeResponse" } + } + } + + override fun onError(error: Throwable) { + if (!isResponseEnded) { + response.write("data: {\"error\": \"${error.message}\"}\n\n") + response.end() // 结束响应 + isResponseEnded = true // 设置结束标志 + logger.error(error) { "流式响应出错" } + } + } + }) + } + + return null + } +} \ No newline at end of file diff --git a/vertx-demo/src/main/kotlin/app/data/domain/account/Account.kt b/vertx-demo/src/main/kotlin/app/data/domain/account/Account.kt index a23cd6f..ebb5796 100644 --- a/vertx-demo/src/main/kotlin/app/data/domain/account/Account.kt +++ b/vertx-demo/src/main/kotlin/app/data/domain/account/Account.kt @@ -7,6 +7,7 @@ import org.aikrai.vertx.utlis.BaseEntity import java.sql.Timestamp @TableName("sys_user") +@TableIndex(name = "idx_phone", unique = true, columnNames=["phone"]) class Account : BaseEntity() { @TableId(type = IdType.ASSIGN_ID) diff --git a/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/annotation/Annotation.kt b/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/annotation/Annotation.kt index 297ced8..0c1eb7d 100644 --- a/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/annotation/Annotation.kt +++ b/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/annotation/Annotation.kt @@ -1,8 +1,19 @@ package org.aikrai.vertx.db.annotation -import java.lang.annotation.Documented -import java.lang.annotation.ElementType -import java.lang.annotation.RetentionPolicy + +@Target(AnnotationTarget.CLASS, AnnotationTarget.FIELD) +@Retention(AnnotationRetention.RUNTIME) +@Repeatable +annotation class TableIndex( + val name: String = "", + val unique: Boolean = false, + val concurrent: Boolean = false, + val columnNames: Array = emptyArray(), +// val platforms: Array = emptyArray(), + val definition: String = "" +) + + @MustBeDocumented @Retention(AnnotationRetention.RUNTIME)