Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5d8c19deab | |||
| dbd2246a09 |
@ -41,16 +41,8 @@ tasks.test {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
}
|
||||
|
||||
tasks.compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
kotlin {
|
||||
jvmToolchain(17)
|
||||
}
|
||||
|
||||
spotless {
|
||||
@ -88,6 +80,9 @@ dependencies {
|
||||
implementation("io.vertx:vertx-auth-jwt:$vertxVersion")
|
||||
implementation("io.vertx:vertx-redis-client:$vertxVersion")
|
||||
|
||||
implementation("dev.langchain4j:langchain4j:1.0.0-beta1")
|
||||
implementation("dev.langchain4j:langchain4j-open-ai:1.0.0-beta1")
|
||||
implementation("dev.langchain4j:langchain4j-mcp:1.0.0-beta1")
|
||||
|
||||
implementation("com.google.inject:guice:5.1.0")
|
||||
implementation("org.reflections:reflections:0.10.2")
|
||||
|
||||
129
vertx-demo/src/main/kotlin/app/controller/ChatController.kt
Normal file
129
vertx-demo/src/main/kotlin/app/controller/ChatController.kt
Normal file
@ -0,0 +1,129 @@
|
||||
package app.controller
|
||||
|
||||
import app.port.mcp.AiClient
|
||||
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.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
|
||||
}
|
||||
|
||||
|
||||
@AllowAnonymous
|
||||
@D("MCP测试", "详细说明......")
|
||||
suspend fun chatWithMcp(
|
||||
@D("message", "提问") message: String,
|
||||
): String {
|
||||
val aiService = AiClient.getAiAssistant()
|
||||
|
||||
return withContext(Dispatchers.IO) {
|
||||
|
||||
val response = aiService.chat(message)
|
||||
println(response)
|
||||
|
||||
return@withContext response
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
|
||||
75
vertx-demo/src/main/kotlin/app/port/mcp/AiClient.kt
Normal file
75
vertx-demo/src/main/kotlin/app/port/mcp/AiClient.kt
Normal file
@ -0,0 +1,75 @@
|
||||
package app.port.mcp
|
||||
|
||||
import dev.langchain4j.mcp.McpToolProvider
|
||||
import dev.langchain4j.mcp.client.DefaultMcpClient
|
||||
import dev.langchain4j.mcp.client.McpClient
|
||||
import dev.langchain4j.mcp.client.transport.stdio.StdioMcpTransport
|
||||
import dev.langchain4j.memory.chat.MessageWindowChatMemory
|
||||
import dev.langchain4j.model.chat.ChatLanguageModel
|
||||
import dev.langchain4j.model.chat.request.ChatRequestParameters
|
||||
import dev.langchain4j.model.openai.OpenAiChatModel
|
||||
import dev.langchain4j.service.AiServices
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import mu.KotlinLogging
|
||||
import org.aikrai.vertx.config.Config
|
||||
|
||||
interface AiAssistant {
|
||||
fun chat(message: String): String
|
||||
}
|
||||
|
||||
object AiClient {
|
||||
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()
|
||||
|
||||
|
||||
private suspend fun createMcpClient(): McpClient {
|
||||
val apiKey = Config.getKey("weather.api.api-key").toString()
|
||||
return withContext(Dispatchers.IO) {
|
||||
DefaultMcpClient.Builder()
|
||||
.transport(
|
||||
// HttpMcpTransport.Builder()
|
||||
// .sseUrl("http://127.0.0.1:17080/sse")
|
||||
// .build()
|
||||
StdioMcpTransport.Builder()
|
||||
.command(
|
||||
listOf(
|
||||
"java",
|
||||
"-Dspring.ai.mcp.server.stdio=true",
|
||||
"-jar",
|
||||
"vertx-demo/src/main/resources/mcp-demo-0.0.1-SNAPSHOT.jar",
|
||||
"--weather.api.api-key=${apiKey}"
|
||||
)
|
||||
)
|
||||
.logEvents(true)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createChatClient(): ChatLanguageModel {
|
||||
return OpenAiChatModel.builder()
|
||||
.baseUrl(baseUrl)
|
||||
.apiKey(apiKey)
|
||||
.defaultRequestParameters(
|
||||
ChatRequestParameters.builder()
|
||||
.modelName(modelName)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
suspend fun getAiAssistant(): AiAssistant {
|
||||
val toolProvider = McpToolProvider.builder()
|
||||
.mcpClients(listOf(createMcpClient()))
|
||||
.build()
|
||||
return AiServices.builder(AiAssistant::class.java)
|
||||
.chatLanguageModel(createChatClient())
|
||||
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
|
||||
.toolProvider(toolProvider)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
@ -27,16 +27,8 @@ tasks.test {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
}
|
||||
|
||||
tasks.compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
kotlin {
|
||||
jvmToolchain(17)
|
||||
}
|
||||
|
||||
spotless {
|
||||
|
||||
@ -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<String> = emptyArray(),
|
||||
// val platforms: Array<Platform> = emptyArray(),
|
||||
val definition: String = ""
|
||||
)
|
||||
|
||||
|
||||
|
||||
@MustBeDocumented
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user