diff --git a/ruoyi-admin/src/main/java/org/ruoyi/RuoYiAIApplication.java b/ruoyi-admin/src/main/java/org/ruoyi/RuoYiAIApplication.java index 8363c957..1ce9207c 100644 --- a/ruoyi-admin/src/main/java/org/ruoyi/RuoYiAIApplication.java +++ b/ruoyi-admin/src/main/java/org/ruoyi/RuoYiAIApplication.java @@ -4,6 +4,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup; +import java.net.InetSocketAddress; +import java.net.ServerSocket; + /** * 启动程序 * @@ -13,10 +16,66 @@ import org.springframework.boot.context.metrics.buffering.BufferingApplicationSt public class RuoYiAIApplication { public static void main(String[] args) { + killPortProcess(6039); SpringApplication application = new SpringApplication(RuoYiAIApplication.class); application.setApplicationStartup(new BufferingApplicationStartup(2048)); application.run(args); - System.out.println("(♥◠‿◠)ノ゙ RuoYi-AI启动成功 ლ(´ڡ`ლ)゙"); + System.out.println("(♥◠‿◠)ノ゙ RuoYi-AI启动成功 ლ(´ڡ`ლ)冢"); + } + + /** + * 检查并终止占用指定端口的进程 + * + * @param port 端口号 + */ + private static void killPortProcess(int port) { + try { + if (!isPortInUse(port)) { + return; + } + System.out.println("端口 " + port + " 已被占用,正在查找并终止进程..."); + + ProcessBuilder pb = new ProcessBuilder("netstat", "-ano"); + Process process = pb.start(); + java.io.BufferedReader reader = new java.io.BufferedReader( + new java.io.InputStreamReader(process.getInputStream())); + + String line; + while ((line = reader.readLine()) != null) { + if (line.contains(":" + port + " ") && line.contains("LISTENING")) { + String[] parts = line.trim().split("\\s+"); + String pid = parts[parts.length - 1]; + System.out.println("找到占用端口 " + port + " 的进程 PID: " + pid + ",正在终止..."); + + ProcessBuilder killPb = new ProcessBuilder("taskkill", "/F", "/PID", pid); + Process killProcess = killPb.start(); + int exitCode = killProcess.waitFor(); + if (exitCode == 0) { + System.out.println("进程 " + pid + " 已成功终止"); + } else { + System.out.println("终止进程 " + pid + " 失败,exitCode: " + exitCode); + } + break; + } + } + + // 等待一小段时间确保端口释放 + Thread.sleep(500); + } catch (Exception e) { + System.out.println("检查/终止端口进程时发生异常: " + e.getMessage()); + } + } + + /** + * 检查端口是否被占用 + */ + private static boolean isPortInUse(int port) { + try (ServerSocket socket = new ServerSocket()) { + socket.bind(new InetSocketAddress(port)); + return false; + } catch (Exception e) { + return true; + } } } diff --git a/ruoyi-admin/src/main/java/org/ruoyi/controller/AuthController.java b/ruoyi-admin/src/main/java/org/ruoyi/controller/AuthController.java index 7a801355..655821da 100644 --- a/ruoyi-admin/src/main/java/org/ruoyi/controller/AuthController.java +++ b/ruoyi-admin/src/main/java/org/ruoyi/controller/AuthController.java @@ -76,7 +76,7 @@ public class AuthController { @PostMapping("/login") public R login(@RequestBody String body) { LoginBody loginBody = JsonUtils.parseObject(body, LoginBody.class); - ValidatorUtils.validate(loginBody); +// ValidatorUtils.validate(loginBody); // 授权类型和客户端id String clientId = loginBody.getClientId(); String grantType = loginBody.getGrantType(); diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/ChartGenerationAgent.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/ChartGenerationAgent.java index ab61ee62..2de1fcea 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/ChartGenerationAgent.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/ChartGenerationAgent.java @@ -6,7 +6,7 @@ import dev.langchain4j.service.UserMessage; import dev.langchain4j.service.V; -public interface ChartGenerationAgent extends Agent { +public interface ChartGenerationAgent { @SystemMessage(""" You are a chart generation specialist. Your only task is to generate Apache ECharts diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/EchartsAgent.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/EchartsAgent.java index 436220fe..82faf524 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/EchartsAgent.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/EchartsAgent.java @@ -14,7 +14,7 @@ public interface EchartsAgent { @SystemMessage(""" You are a data visualization assistant that generates Echarts chart configurations. - + CRITICAL OUTPUT REQUIREMENTS: - Return Echarts JSON wrapped in markdown code block - Use this exact format: ```json\n{...}\n``` @@ -81,7 +81,7 @@ public interface EchartsAgent { """) @UserMessage(""" Generate an Echarts chart for: {{query}} - + IMPORTANT: Return the Echarts configuration JSON wrapped in markdown code block (```json...```). """) @Agent("Data visualization assistant that returns Echarts JSON configurations for frontend rendering") diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/SqlAgent.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/SqlAgent.java index 77b8e1ab..e21b61c1 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/SqlAgent.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/SqlAgent.java @@ -11,7 +11,7 @@ import dev.langchain4j.service.V; * and returning relevant data and analysis results. * */ -public interface SqlAgent extends Agent { +public interface SqlAgent { @SystemMessage(""" This agent is designed for MySQL 5.7 diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/WebSearchAgent.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/WebSearchAgent.java index 970e3a2b..33f8737e 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/WebSearchAgent.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/WebSearchAgent.java @@ -10,7 +10,7 @@ import dev.langchain4j.service.V; * A web search assistant that answers natural language questions by searching the internet * and returning relevant information from web pages. */ -public interface WebSearchAgent extends Agent { +public interface WebSearchAgent { @SystemMessage(""" You are a web search assistant. Answer questions by searching and retrieving web content. diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/ChatModelListenerProvider.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/ChatModelListenerProvider.java new file mode 100644 index 00000000..cb791740 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/ChatModelListenerProvider.java @@ -0,0 +1,41 @@ +package org.ruoyi.observability; + +import cn.hutool.core.collection.CollUtil; +import dev.langchain4j.model.chat.listener.ChatModelListener; +import dev.langchain4j.model.embedding.listener.EmbeddingModelListener; +import lombok.Getter; +import org.springframework.context.annotation.Lazy; +import org.springframework.lang.Nullable; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.List; + +/** + * LangChain4j 监听器共享提供者。 + *

+ * 供所有 {@link dev.langchain4j.model.chat.StreamingChatModel} 构建器使用, + * 将可观测性监听器注入到模型实例中。 + * + * @author evo + */ +@Component +@Getter +@Lazy +public class ChatModelListenerProvider { + + private final List chatModelListeners; + private final List embeddingModelListeners; + + public ChatModelListenerProvider(@Nullable List chatModelListeners, + @Nullable List embeddingModelListeners) { + if (CollUtil.isEmpty(chatModelListeners)) { + chatModelListeners = Collections.emptyList(); + } + if (CollUtil.isEmpty(embeddingModelListeners)) { + embeddingModelListeners = Collections.emptyList(); + } + this.chatModelListeners = chatModelListeners; + this.embeddingModelListeners = embeddingModelListeners; + } +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/EmbeddingModelListenerProvider.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/EmbeddingModelListenerProvider.java new file mode 100644 index 00000000..c404bd10 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/EmbeddingModelListenerProvider.java @@ -0,0 +1,34 @@ +package org.ruoyi.observability; + +import cn.hutool.core.collection.CollUtil; +import dev.langchain4j.model.embedding.listener.EmbeddingModelListener; +import lombok.Getter; +import org.springframework.context.annotation.Lazy; +import org.springframework.lang.Nullable; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.List; + +/** + * EmbeddingModel 监听器共享提供者。 + *

+ * 供所有 {@link dev.langchain4j.model.embedding.EmbeddingModel} 构建器使用, + * 将可观测性监听器注入到模型实例中。 + * + * @author evo + */ +@Component +@Getter +@Lazy +public class EmbeddingModelListenerProvider { + + private final List embeddingModelListeners; + + public EmbeddingModelListenerProvider(@Nullable List embeddingModelListeners) { + if (CollUtil.isEmpty(embeddingModelListeners)) { + embeddingModelListeners = Collections.emptyList(); + } + this.embeddingModelListeners = embeddingModelListeners; + } +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/LangChain4jObservabilityConfig.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/LangChain4jObservabilityConfig.java new file mode 100644 index 00000000..3ada96b0 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/LangChain4jObservabilityConfig.java @@ -0,0 +1,129 @@ +package org.ruoyi.observability; + +import dev.langchain4j.Experimental; +import dev.langchain4j.mcp.client.McpClientListener; +import dev.langchain4j.model.chat.listener.ChatModelListener; +import dev.langchain4j.model.embedding.listener.EmbeddingModelListener; +import dev.langchain4j.observability.api.AiServiceListenerRegistrar; +import dev.langchain4j.observability.api.listener.*; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +/** + * LangChain4j 可观测性配置类。 + *

+ * 负责注册所有 langchain4j 的监听器: + *

+ * + * @author evo + */ +@Configuration +@RequiredArgsConstructor +@Slf4j +public class LangChain4jObservabilityConfig { + + private final AiServiceListenerRegistrar registrar = AiServiceListenerRegistrar.newInstance(); + + /** + * 注册 AI 服务级别的事件监听器 + */ + @PostConstruct + public void registerAiServiceListeners() { + log.info("正在注册 LangChain4j AI Service 事件监听器..."); + registrar.register( + new MyAiServiceStartedListener(), + new MyAiServiceRequestIssuedListener(), + new MyAiServiceResponseReceivedListener(), + new MyAiServiceCompletedListener(), + new MyAiServiceErrorListener(), + new MyInputGuardrailExecutedListener(), + new MyOutputGuardrailExecutedListener(), + new MyToolExecutedEventListener() + ); + log.info("LangChain4j AI Service 事件监听器注册完成"); + } + + // ==================== AI Service 监听器 Beans ==================== + + @Bean + public AiServiceStartedListener aiServiceStartedListener() { + return new MyAiServiceStartedListener(); + } + + @Bean + public AiServiceRequestIssuedListener aiServiceRequestIssuedListener() { + return new MyAiServiceRequestIssuedListener(); + } + + @Bean + public AiServiceResponseReceivedListener aiServiceResponseReceivedListener() { + return new MyAiServiceResponseReceivedListener(); + } + + @Bean + public AiServiceCompletedListener aiServiceCompletedListener() { + return new MyAiServiceCompletedListener(); + } + + @Bean + public AiServiceErrorListener aiServiceErrorListener() { + return new MyAiServiceErrorListener(); + } + + @Bean + public InputGuardrailExecutedListener inputGuardrailExecutedListener() { + return new MyInputGuardrailExecutedListener(); + } + + @Bean + public OutputGuardrailExecutedListener outputGuardrailExecutedListener() { + return new MyOutputGuardrailExecutedListener(); + } + + @Bean + public ToolExecutedEventListener toolExecutedEventListener() { + return new MyToolExecutedEventListener(); + } + + // ==================== ChatModel 监听器 ==================== + + @Bean + public ChatModelListener chatModelListener() { + return new MyChatModelListener(); + } + + @Bean + public List chatModelListeners() { + return List.of(new MyChatModelListener()); + } + + // ==================== EmbeddingModel 监听器 ==================== + + @Bean + @Experimental + public EmbeddingModelListener embeddingModelListener() { + return new MyEmbeddingModelListener(); + } + + @Bean + @Experimental + public List embeddingModelListeners() { + return List.of(new MyEmbeddingModelListener()); + } + + // ==================== MCP Client 监听器 ==================== + + @Bean + public McpClientListener mcpClientListener() { + return new MyMcpClientListener(); + } +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyAgentListener.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyAgentListener.java new file mode 100644 index 00000000..7a548788 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyAgentListener.java @@ -0,0 +1,130 @@ +package org.ruoyi.observability; + +import dev.langchain4j.agentic.observability.AgentInvocationError; +import dev.langchain4j.agentic.observability.AgentRequest; +import dev.langchain4j.agentic.observability.AgentResponse; +import dev.langchain4j.agentic.planner.AgentInstance; +import dev.langchain4j.agentic.scope.AgenticScope; +import dev.langchain4j.service.tool.BeforeToolExecution; +import dev.langchain4j.service.tool.ToolExecution; +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; + +/** + * 自定义的 AgentListener 的监听器。 + * 监听 Agent 相关的所有可观测性事件,包括: + *
    + *
  • Agent 调用前/后的生命周期事件
  • + *
  • Agent 执行错误事件
  • + *
  • AgenticScope 的创建/销毁事件
  • + *
  • 工具执行前/后的生命周期事件
  • + *
+ * + * @author evo + */ +@Slf4j +public class MyAgentListener implements dev.langchain4j.agentic.observability.AgentListener { + + // ==================== Agent 调用生命周期 ==================== + + @Override + public void beforeAgentInvocation(AgentRequest agentRequest) { + AgentInstance agent = agentRequest.agent(); + AgenticScope scope = agentRequest.agenticScope(); + Map inputs = agentRequest.inputs(); + + log.info("【Agent调用前】Agent名称: {}", agent.name()); + log.info("【Agent调用前】Agent ID: {}", agent.agentId()); + log.info("【Agent调用前】Agent类型: {}", agent.type().getName()); + log.info("【Agent调用前】Agent描述: {}", agent.description()); + log.info("【Agent调用前】Planner类型: {}", agent.plannerType().getName()); + log.info("【Agent调用前】输出类型: {}", agent.outputType()); + log.info("【Agent调用前】输出Key: {}", agent.outputKey()); + log.info("【Agent调用前】是否为异步: {}", agent.async()); + log.info("【Agent调用前】是否为叶子节点: {}", agent.leaf()); + log.info("【Agent调用前】Agent参数列表:"); + for (var arg : agent.arguments()) { + log.info(" - 参数名: {}, 类型: {}, 默认值: {}", + arg.name(), arg.rawType().getName(), arg.defaultValue()); + } + log.info("【Agent调用前】Agent输入参数: {}", inputs); + log.info("【Agent调用前】AgenticScope memoryId: {}", scope.memoryId()); + log.info("【Agent调用前】AgenticScope当前状态: {}", scope.state()); + log.info("【Agent调用前】Agent调用历史记录数: {}", scope.agentInvocations().size()); + + // 打印嵌套的子Agent信息 + if (!agent.subagents().isEmpty()) { + log.info("【Agent调用前】子Agent列表:"); + for (AgentInstance sub : agent.subagents()) { + log.info(" - 子Agent: {} ({})", sub.name(), sub.type().getName()); + } + } + + // 打印父Agent信息 + if (agent.parent() != null) { + log.info("【Agent调用前】父Agent: {}", agent.parent().name()); + } + } + + @Override + public void afterAgentInvocation(AgentResponse agentResponse) { + AgentInstance agent = agentResponse.agent(); + Map inputs = agentResponse.inputs(); + Object output = agentResponse.output(); + + log.info("【Agent调用后】Agent名称: {}", agent.name()); + log.info("【Agent调用后】Agent ID: {}", agent.agentId()); + log.info("【Agent调用后】Agent输入参数: {}", inputs); + log.info("【Agent调用后】Agent输出结果: {}", output); + log.info("【Agent调用后】是否为叶子节点: {}", agent.leaf()); + } + + @Override + public void onAgentInvocationError(AgentInvocationError error) { + AgentInstance agent = error.agent(); + Map inputs = error.inputs(); + Throwable throwable = error.error(); + + log.error("【Agent执行错误】Agent名称: {}", agent.name()); + log.error("【Agent执行错误】Agent ID: {}", agent.agentId()); + log.error("【Agent执行错误】Agent类型: {}", agent.type().getName()); + log.error("【Agent执行错误】Agent输入参数: {}", inputs); + log.error("【Agent执行错误】错误类型: {}", throwable.getClass().getName()); + log.error("【Agent执行错误】错误信息: {}", throwable.getMessage(), throwable); + } + + // ==================== AgenticScope 生命周期 ==================== + + @Override + public void afterAgenticScopeCreated(AgenticScope agenticScope) { + log.info("【AgenticScope已创建】memoryId: {}", agenticScope.memoryId()); + log.info("【AgenticScope已创建】初始状态: {}", agenticScope.state()); + } + + @Override + public void beforeAgenticScopeDestroyed(AgenticScope agenticScope) { + log.info("【AgenticScope即将销毁】memoryId: {}", agenticScope.memoryId()); + log.info("【AgenticScope即将销毁】最终状态: {}", agenticScope.state()); + log.info("【AgenticScope即将销毁】总调用次数: {}", agenticScope.agentInvocations().size()); + } + + // ==================== 工具执行生命周期 ==================== + + @Override + public void beforeToolExecution(BeforeToolExecution beforeToolExecution) { + var toolRequest = beforeToolExecution.request(); + log.info("【工具执行前】工具请求ID: {}", toolRequest.id()); + log.info("【工具执行前】工具名称: {}", toolRequest.name()); + log.info("【工具执行前】工具参数: {}", toolRequest.arguments()); + } + + @Override + public void afterToolExecution(ToolExecution toolExecution) { + var toolRequest = toolExecution.request(); + log.info("【工具执行后】工具请求ID: {}", toolRequest.id()); + log.info("【工具执行后】工具名称: {}", toolRequest.name()); + log.info("【工具执行后】工具执行结果: {}", toolExecution.result()); + log.info("【工具执行后】工具执行是否失败: {}", toolExecution.hasFailed()); + } +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyAiServiceCompletedListener.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyAiServiceCompletedListener.java new file mode 100644 index 00000000..b90f30fe --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyAiServiceCompletedListener.java @@ -0,0 +1,41 @@ +package org.ruoyi.observability; + +import dev.langchain4j.invocation.InvocationContext; +import dev.langchain4j.observability.api.event.AiServiceCompletedEvent; +import dev.langchain4j.observability.api.listener.AiServiceCompletedListener; +import lombok.extern.slf4j.Slf4j; + +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +/** + * 自定义的 AiServiceCompletedEvent 的监听器。 + * 它表示在 AI 服务调用完成时发生的事件。 + * + * @author evo + */ +@Slf4j +public class MyAiServiceCompletedListener implements AiServiceCompletedListener { + + @Override + public void onEvent(AiServiceCompletedEvent event) { + InvocationContext invocationContext = event.invocationContext(); + Optional result = event.result(); + UUID invocationId = invocationContext.invocationId(); + String aiServiceInterfaceName = invocationContext.interfaceName(); + String aiServiceMethodName = invocationContext.methodName(); + List aiServiceMethodArgs = invocationContext.methodArguments(); + Object chatMemoryId = invocationContext.chatMemoryId(); + Instant eventTimestamp = invocationContext.timestamp(); + + log.info("【AI服务完成】调用唯一标识符: {}", invocationId); + log.info("【AI服务完成】AI服务接口名: {}", aiServiceInterfaceName); + log.info("【AI服务完成】调用的方法名: {}", aiServiceMethodName); + log.info("【AI服务完成】AI服务方法参数: {}", aiServiceMethodArgs); + log.info("【AI服务完成】聊天记忆ID: {}", chatMemoryId); + log.info("【AI服务完成】调用发生的时间: {}", eventTimestamp); + log.info("【AI服务完成】调用结果: {}", result); + } +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyAiServiceErrorListener.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyAiServiceErrorListener.java new file mode 100644 index 00000000..535946b7 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyAiServiceErrorListener.java @@ -0,0 +1,33 @@ +package org.ruoyi.observability; + +import dev.langchain4j.invocation.InvocationContext; +import dev.langchain4j.observability.api.event.AiServiceErrorEvent; +import dev.langchain4j.observability.api.listener.AiServiceErrorListener; +import lombok.extern.slf4j.Slf4j; + +import java.util.UUID; + +/** + * 自定义的 AiServiceErrorEvent 的监听器。 + * 它表示在 AI 服务调用失败时发生的事件。 + * + * @author evo + */ +@Slf4j +public class MyAiServiceErrorListener implements AiServiceErrorListener { + + @Override + public void onEvent(AiServiceErrorEvent event) { + InvocationContext invocationContext = event.invocationContext(); + UUID invocationId = invocationContext.invocationId(); + String aiServiceInterfaceName = invocationContext.interfaceName(); + String aiServiceMethodName = invocationContext.methodName(); + Throwable error = event.error(); + + log.error("【AI服务错误】调用唯一标识符: {}", invocationId); + log.error("【AI服务错误】AI服务接口名: {}", aiServiceInterfaceName); + log.error("【AI服务错误】调用的方法名: {}", aiServiceMethodName); + log.error("【AI服务错误】错误类型: {}", error.getClass().getName()); + log.error("【AI服务错误】错误信息: {}", error.getMessage(), error); + } +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyAiServiceRequestIssuedListener.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyAiServiceRequestIssuedListener.java new file mode 100644 index 00000000..ac4e506d --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyAiServiceRequestIssuedListener.java @@ -0,0 +1,33 @@ +package org.ruoyi.observability; + +import dev.langchain4j.invocation.InvocationContext; +import dev.langchain4j.model.chat.request.ChatRequest; +import dev.langchain4j.observability.api.event.AiServiceRequestIssuedEvent; +import dev.langchain4j.observability.api.listener.AiServiceRequestIssuedListener; +import lombok.extern.slf4j.Slf4j; + +import java.util.UUID; + +/** + * 自定义的 AiServiceRequestIssuedEvent 的监听器。 + * 它表示在向 LLM 发送请求之前发生的事件。 + * + * @author evo + */ +@Slf4j +public class MyAiServiceRequestIssuedListener implements AiServiceRequestIssuedListener { + + @Override + public void onEvent(AiServiceRequestIssuedEvent event) { + InvocationContext invocationContext = event.invocationContext(); + UUID invocationId = invocationContext.invocationId(); + String aiServiceInterfaceName = invocationContext.interfaceName(); + String aiServiceMethodName = invocationContext.methodName(); + ChatRequest request = event.request(); + + log.info("【请求已发出】调用唯一标识符: {}", invocationId); + log.info("【请求已发出】AI服务接口名: {}", aiServiceInterfaceName); + log.info("【请求已发出】调用的方法名: {}", aiServiceMethodName); + log.info("【请求已发出】发送给LLM的请求: {}", request); + } +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyAiServiceResponseReceivedListener.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyAiServiceResponseReceivedListener.java new file mode 100644 index 00000000..19a926b8 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyAiServiceResponseReceivedListener.java @@ -0,0 +1,37 @@ +package org.ruoyi.observability; + +import dev.langchain4j.invocation.InvocationContext; +import dev.langchain4j.model.chat.request.ChatRequest; +import dev.langchain4j.model.chat.response.ChatResponse; +import dev.langchain4j.observability.api.event.AiServiceResponseReceivedEvent; +import dev.langchain4j.observability.api.listener.AiServiceResponseReceivedListener; +import lombok.extern.slf4j.Slf4j; + +import java.util.UUID; + +/** + * 自定义的 AiServiceResponseReceivedEvent 的监听器。 + * 它表示在从 LLM 接收到响应时发生的事件。 + * 在涉及工具或 guardrail 的单个 AI 服务调用期间,可能会被调用多次。 + * + * @author evo + */ +@Slf4j +public class MyAiServiceResponseReceivedListener implements AiServiceResponseReceivedListener { + + @Override + public void onEvent(AiServiceResponseReceivedEvent event) { + InvocationContext invocationContext = event.invocationContext(); + UUID invocationId = invocationContext.invocationId(); + String aiServiceInterfaceName = invocationContext.interfaceName(); + String aiServiceMethodName = invocationContext.methodName(); + ChatRequest request = event.request(); + ChatResponse response = event.response(); + + log.info("【响应已接收】调用唯一标识符: {}", invocationId); + log.info("【响应已接收】AI服务接口名: {}", aiServiceInterfaceName); + log.info("【响应已接收】调用的方法名: {}", aiServiceMethodName); + log.info("【响应已接收】发送给LLM的请求: {}", request); + log.info("【响应已接收】从LLM收到的响应: {}", response); + } +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyAiServiceStartedListener.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyAiServiceStartedListener.java new file mode 100644 index 00000000..ba7cdb06 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyAiServiceStartedListener.java @@ -0,0 +1,38 @@ +package org.ruoyi.observability; + +import dev.langchain4j.data.message.ChatMessage; +import dev.langchain4j.data.message.SystemMessage; +import dev.langchain4j.data.message.UserMessage; +import dev.langchain4j.invocation.InvocationContext; +import dev.langchain4j.observability.api.event.AiServiceStartedEvent; +import dev.langchain4j.observability.api.listener.AiServiceStartedListener; +import lombok.extern.slf4j.Slf4j; + +import java.util.Optional; +import java.util.UUID; + +/** + * 自定义的 AiServiceStartedEvent 的监听器。 + * 它表示在 AI 服务调用开始时发生的事件。 + * + * @author evo + */ +@Slf4j +public class MyAiServiceStartedListener implements AiServiceStartedListener { + + @Override + public void onEvent(AiServiceStartedEvent event) { + InvocationContext invocationContext = event.invocationContext(); + UUID invocationId = invocationContext.invocationId(); + String aiServiceInterfaceName = invocationContext.interfaceName(); + String aiServiceMethodName = invocationContext.methodName(); + Optional systemMessage = event.systemMessage(); + UserMessage userMessage = event.userMessage(); + + log.info("【AI服务启动】调用唯一标识符: {}", invocationId); + log.info("【AI服务启动】AI服务接口名: {}", aiServiceInterfaceName); + log.info("【AI服务启动】调用的方法名: {}", aiServiceMethodName); + log.info("【AI服务启动】系统消息: {}", systemMessage.orElse(null)); + log.info("【AI服务启动】用户消息: {}", userMessage); + } +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyChatModelListener.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyChatModelListener.java new file mode 100644 index 00000000..9cc0e2ac --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyChatModelListener.java @@ -0,0 +1,43 @@ +package org.ruoyi.observability; + +import dev.langchain4j.model.chat.listener.ChatModelErrorContext; +import dev.langchain4j.model.chat.listener.ChatModelListener; +import dev.langchain4j.model.chat.listener.ChatModelRequestContext; +import dev.langchain4j.model.chat.listener.ChatModelResponseContext; +import dev.langchain4j.model.chat.request.ChatRequest; +import dev.langchain4j.model.chat.response.ChatResponse; +import lombok.extern.slf4j.Slf4j; + +/** + * 自定义的 ChatModelListener 的监听器。 + * 它监听 ChatModel 的请求、响应和错误事件。 + * + * @author evo + */ +@Slf4j +public class MyChatModelListener implements ChatModelListener { + + @Override + public void onRequest(ChatModelRequestContext requestContext) { + ChatRequest request = requestContext.chatRequest(); + log.info("【ChatModel请求】发送给模型的请求: {}", request); + log.info("【ChatModel请求】模型提供商: {}", requestContext.modelProvider()); + } + + @Override + public void onResponse(ChatModelResponseContext responseContext) { + ChatRequest request = responseContext.chatRequest(); + ChatResponse response = responseContext.chatResponse(); + log.info("【ChatModel响应】原始请求: {}", request); + log.info("【ChatModel响应】收到的响应: {}", response); + log.info("【ChatModel响应】模型提供商: {}", responseContext.modelProvider()); + } + + @Override + public void onError(ChatModelErrorContext errorContext) { + log.error("【ChatModel错误】错误类型: {}", errorContext.error().getClass().getName()); + log.error("【ChatModel错误】错误信息: {}", errorContext.error().getMessage()); + log.error("【ChatModel错误】原始请求: {}", errorContext.chatRequest()); + log.error("【ChatModel错误】模型提供商: {}", errorContext.modelProvider()); + } +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyEmbeddingModelListener.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyEmbeddingModelListener.java new file mode 100644 index 00000000..56641dce --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyEmbeddingModelListener.java @@ -0,0 +1,47 @@ +package org.ruoyi.observability; + +import dev.langchain4j.Experimental; +import dev.langchain4j.data.embedding.Embedding; +import dev.langchain4j.model.embedding.listener.EmbeddingModelErrorContext; +import dev.langchain4j.model.embedding.listener.EmbeddingModelListener; +import dev.langchain4j.model.embedding.listener.EmbeddingModelRequestContext; +import dev.langchain4j.model.embedding.listener.EmbeddingModelResponseContext; +import dev.langchain4j.model.output.Response; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + +/** + * 自定义的 EmbeddingModelListener 的监听器。 + * 它监听 EmbeddingModel 的请求、响应和错误事件。 + * + * @author evo + */ +@Slf4j +@Experimental +public class MyEmbeddingModelListener implements EmbeddingModelListener { + + @Override + public void onRequest(EmbeddingModelRequestContext requestContext) { + log.info("【EmbeddingModel请求】输入文本段落数量: {}", requestContext.textSegments().size()); + log.info("【EmbeddingModel请求】嵌入模型: {}", requestContext.embeddingModel()); + } + + @Override + public void onResponse(EmbeddingModelResponseContext responseContext) { + Response> response = responseContext.response(); + List embeddings = response.content(); + log.info("【EmbeddingModel响应】嵌入向量数量: {}", embeddings.size()); + log.info("【EmbeddingModel响应】嵌入维度: {}", embeddings.isEmpty() ? 0 : embeddings.get(0).dimension()); + log.info("【EmbeddingModel响应】嵌入模型: {}", responseContext.embeddingModel()); + log.info("【EmbeddingModel响应】输入文本段落: {}", responseContext.textSegments()); + } + + @Override + public void onError(EmbeddingModelErrorContext errorContext) { + log.error("【EmbeddingModel错误】错误类型: {}", errorContext.error().getClass().getName()); + log.error("【EmbeddingModel错误】错误信息: {}", errorContext.error().getMessage()); + log.error("【EmbeddingModel错误】输入文本段落数量: {}", errorContext.textSegments().size()); + log.error("【EmbeddingModel错误】嵌入模型: {}", errorContext.embeddingModel()); + } +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyInputGuardrailExecutedListener.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyInputGuardrailExecutedListener.java new file mode 100644 index 00000000..fe9c3b48 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyInputGuardrailExecutedListener.java @@ -0,0 +1,45 @@ +package org.ruoyi.observability; + +import dev.langchain4j.data.message.UserMessage; +import dev.langchain4j.guardrail.InputGuardrail; +import dev.langchain4j.guardrail.InputGuardrailRequest; +import dev.langchain4j.guardrail.InputGuardrailResult; +import dev.langchain4j.invocation.InvocationContext; +import dev.langchain4j.observability.api.event.InputGuardrailExecutedEvent; +import dev.langchain4j.observability.api.listener.InputGuardrailExecutedListener; +import lombok.extern.slf4j.Slf4j; + +import java.time.Duration; +import java.util.UUID; + +/** + * 自定义的 InputGuardrailExecutedEvent 的监听器。 + * 它表示在输入 guardrail 验证执行时发生的事件。 + * + * @author evo + */ +@Slf4j +public class MyInputGuardrailExecutedListener implements InputGuardrailExecutedListener { + + @Override + public void onEvent(InputGuardrailExecutedEvent event) { + InvocationContext invocationContext = event.invocationContext(); + UUID invocationId = invocationContext.invocationId(); + String aiServiceInterfaceName = invocationContext.interfaceName(); + String aiServiceMethodName = invocationContext.methodName(); + InputGuardrailRequest request = event.request(); + InputGuardrailResult result = event.result(); + Class guardrailClass = event.guardrailClass(); + Duration duration = event.duration(); + UserMessage rewrittenUserMessage = event.rewrittenUserMessage(); + + log.info("【输入Guardrail已执行】调用唯一标识符: {}", invocationId); + log.info("【输入Guardrail已执行】AI服务接口名: {}", aiServiceInterfaceName); + log.info("【输入Guardrail已执行】调用的方法名: {}", aiServiceMethodName); + log.info("【输入Guardrail已执行】Guardrail类名: {}", guardrailClass.getName()); + log.info("【输入Guardrail已执行】输入Guardrail请求: {}", request); + log.info("【输入Guardrail已执行】输入Guardrail结果: {}", result); + log.info("【输入Guardrail已执行】重写后的用户消息: {}", rewrittenUserMessage); + log.info("【输入Guardrail已执行】执行耗时: {}ms", duration.toMillis()); + } +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyMcpClientListener.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyMcpClientListener.java new file mode 100644 index 00000000..fe19d728 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyMcpClientListener.java @@ -0,0 +1,136 @@ +package org.ruoyi.observability; + +import dev.langchain4j.invocation.InvocationContext; +import dev.langchain4j.mcp.client.McpCallContext; +import dev.langchain4j.mcp.client.McpClientListener; +import dev.langchain4j.mcp.client.McpGetPromptResult; +import dev.langchain4j.mcp.client.McpReadResourceResult; +import dev.langchain4j.mcp.protocol.McpClientMessage; +import dev.langchain4j.service.tool.ToolExecutionResult; +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; + +/** + * 自定义的 McpClientListener 的监听器。 + * 监听 MCP 客户端相关的所有可观测性事件,包括: + *
    + *
  • MCP 工具执行的开始/成功/错误事件
  • + *
  • MCP 资源读取的开始/成功/错误事件
  • + *
  • MCP 提示词获取的开始/成功/错误事件
  • + *
+ * + * @author evo + */ +@Slf4j +public class MyMcpClientListener implements McpClientListener { + + // ==================== 工具执行 ==================== + + @Override + public void beforeExecuteTool(McpCallContext context) { + InvocationContext invocationContext = context.invocationContext(); + McpClientMessage message = context.message(); + + log.info("【MCP工具执行前】调用唯一标识符: {}", invocationContext.invocationId()); + log.info("【MCP工具执行前】MCP消息ID: {}", message.getId()); + log.info("【MCP工具执行前】MCP方法: {}", message.method); + } + + @Override + public void afterExecuteTool(McpCallContext context, ToolExecutionResult result, Map rawResult) { + InvocationContext invocationContext = context.invocationContext(); + McpClientMessage message = context.message(); + + log.info("【MCP工具执行后】调用唯一标识符: {}", invocationContext.invocationId()); + log.info("【MCP工具执行后】MCP消息ID: {}", message.getId()); + log.info("【MCP工具执行后】MCP方法: {}", message.method); + log.info("【MCP工具执行后】工具执行结果: {}", result); + log.info("【MCP工具执行后】原始结果: {}", rawResult); + } + + @Override + public void onExecuteToolError(McpCallContext context, Throwable error) { + InvocationContext invocationContext = context.invocationContext(); + McpClientMessage message = context.message(); + + log.error("【MCP工具执行错误】调用唯一标识符: {}", invocationContext.invocationId()); + log.error("【MCP工具执行错误】MCP消息ID: {}", message.getId()); + log.error("【MCP工具执行错误】MCP方法: {}", message.method); + log.error("【MCP工具执行错误】错误类型: {}", error.getClass().getName()); + log.error("【MCP工具执行错误】错误信息: {}", error.getMessage(), error); + } + + // ==================== 资源读取 ==================== + + @Override + public void beforeResourceGet(McpCallContext context) { + InvocationContext invocationContext = context.invocationContext(); + McpClientMessage message = context.message(); + + log.info("【MCP资源读取前】调用唯一标识符: {}", invocationContext.invocationId()); + log.info("【MCP资源读取前】MCP消息ID: {}", message.getId()); + log.info("【MCP资源读取前】MCP方法: {}", message.method); + } + + @Override + public void afterResourceGet(McpCallContext context, McpReadResourceResult result, Map rawResult) { + InvocationContext invocationContext = context.invocationContext(); + McpClientMessage message = context.message(); + + log.info("【MCP资源读取后】调用唯一标识符: {}", invocationContext.invocationId()); + log.info("【MCP资源读取后】MCP消息ID: {}", message.getId()); + log.info("【MCP资源读取后】MCP方法: {}", message.method); + log.info("【MCP资源读取后】资源内容数量: {}", result.contents() != null ? result.contents().size() : 0); + log.info("【MCP资源读取后】原始结果: {}", rawResult); + } + + @Override + public void onResourceGetError(McpCallContext context, Throwable error) { + InvocationContext invocationContext = context.invocationContext(); + McpClientMessage message = context.message(); + + log.error("【MCP资源读取错误】调用唯一标识符: {}", invocationContext.invocationId()); + log.error("【MCP资源读取错误】MCP消息ID: {}", message.getId()); + log.error("【MCP资源读取错误】MCP方法: {}", message.method); + log.error("【MCP资源读取错误】错误类型: {}", error.getClass().getName()); + log.error("【MCP资源读取错误】错误信息: {}", error.getMessage(), error); + } + + // ==================== 提示词获取 ==================== + + @Override + public void beforePromptGet(McpCallContext context) { + InvocationContext invocationContext = context.invocationContext(); + McpClientMessage message = context.message(); + + log.info("【MCP提示词获取前】调用唯一标识符: {}", invocationContext.invocationId()); + log.info("【MCP提示词获取前】MCP消息ID: {}", message.getId()); + log.info("【MCP提示词获取前】MCP方法: {}", message.method); + } + + @Override + public void afterPromptGet(McpCallContext context, McpGetPromptResult result, Map rawResult) { + InvocationContext invocationContext = context.invocationContext(); + McpClientMessage message = context.message(); + + log.info("【MCP提示词获取后】调用唯一标识符: {}", invocationContext.invocationId()); + log.info("【MCP提示词获取后】MCP消息ID: {}", message.getId()); + log.info("【MCP提示词获取后】MCP方法: {}", message.method); + log.info("【MCP提示词获取后】提示词描述: {}", result.description()); + log.info("【MCP提示词获取后】提示词消息数量: {}", result.messages() != null ? result.messages().size() : 0); + log.info("【MCP提示词获取后】原始结果: {}", rawResult); + } + + @Override + public void onPromptGetError(McpCallContext context, Throwable error) { + InvocationContext invocationContext = context.invocationContext(); + McpClientMessage message = context.message(); + + log.error("【MCP提示词获取错误】调用唯一标识符: {}", invocationContext.invocationId()); + log.error("【MCP提示词获取错误】MCP消息ID: {}", message.getId()); + log.error("【MCP提示词获取错误】MCP方法: {}", message.method); + log.error("【MCP提示词获取错误】错误类型: {}", error.getClass().getName()); + log.error("【MCP提示词获取错误】错误信息: {}", error.getMessage(), error); + } +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyOutputGuardrailExecutedListener.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyOutputGuardrailExecutedListener.java new file mode 100644 index 00000000..fda4176c --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyOutputGuardrailExecutedListener.java @@ -0,0 +1,42 @@ +package org.ruoyi.observability; + +import dev.langchain4j.guardrail.OutputGuardrail; +import dev.langchain4j.guardrail.OutputGuardrailRequest; +import dev.langchain4j.guardrail.OutputGuardrailResult; +import dev.langchain4j.invocation.InvocationContext; +import dev.langchain4j.observability.api.event.OutputGuardrailExecutedEvent; +import dev.langchain4j.observability.api.listener.OutputGuardrailExecutedListener; +import lombok.extern.slf4j.Slf4j; + +import java.time.Duration; +import java.util.UUID; + +/** + * 自定义的 OutputGuardrailExecutedEvent 的监听器。 + * 它表示在输出 guardrail 验证执行时发生的事件。 + * + * @author evo + */ +@Slf4j +public class MyOutputGuardrailExecutedListener implements OutputGuardrailExecutedListener { + + @Override + public void onEvent(OutputGuardrailExecutedEvent event) { + InvocationContext invocationContext = event.invocationContext(); + UUID invocationId = invocationContext.invocationId(); + String aiServiceInterfaceName = invocationContext.interfaceName(); + String aiServiceMethodName = invocationContext.methodName(); + OutputGuardrailRequest request = event.request(); + OutputGuardrailResult result = event.result(); + Class guardrailClass = event.guardrailClass(); + Duration duration = event.duration(); + + log.info("【输出Guardrail已执行】调用唯一标识符: {}", invocationId); + log.info("【输出Guardrail已执行】AI服务接口名: {}", aiServiceInterfaceName); + log.info("【输出Guardrail已执行】调用的方法名: {}", aiServiceMethodName); + log.info("【输出Guardrail已执行】Guardrail类名: {}", guardrailClass.getName()); + log.info("【输出Guardrail已执行】输出Guardrail请求: {}", request); + log.info("【输出Guardrail已执行】输出Guardrail结果: {}", result); + log.info("【输出Guardrail已执行】执行耗时: {}ms", duration.toMillis()); + } +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyToolExecutedEventListener.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyToolExecutedEventListener.java new file mode 100644 index 00000000..9c60a757 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyToolExecutedEventListener.java @@ -0,0 +1,38 @@ +package org.ruoyi.observability; + +import dev.langchain4j.agent.tool.ToolExecutionRequest; +import dev.langchain4j.invocation.InvocationContext; +import dev.langchain4j.observability.api.event.ToolExecutedEvent; +import dev.langchain4j.observability.api.listener.ToolExecutedEventListener; +import lombok.extern.slf4j.Slf4j; + +import java.util.UUID; + +/** + * 自定义的 ToolExecutedEvent 的监听器。 + * 它表示在工具执行完成后发生的事件。 + * 在单个 AI 服务调用期间,可能会被调用多次。 + * + * @author evo + */ +@Slf4j +public class MyToolExecutedEventListener implements ToolExecutedEventListener { + + @Override + public void onEvent(ToolExecutedEvent event) { + InvocationContext invocationContext = event.invocationContext(); + UUID invocationId = invocationContext.invocationId(); + String aiServiceInterfaceName = invocationContext.interfaceName(); + String aiServiceMethodName = invocationContext.methodName(); + ToolExecutionRequest request = event.request(); + String resultText = event.resultText(); + + log.info("【工具已执行】调用唯一标识符: {}", invocationId); + log.info("【工具已执行】AI服务接口名: {}", aiServiceInterfaceName); + log.info("【工具已执行】调用的方法名: {}", aiServiceMethodName); + log.info("【工具已执行】工具执行请求 ID: {}", request.id()); + log.info("【工具已执行】工具名称: {}", request.name()); + log.info("【工具已执行】工具参数: {}", request.arguments()); + log.info("【工具已执行】工具执行结果: {}", resultText); + } +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatServiceFacade.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatServiceFacade.java index ba1c5ac8..c0292871 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatServiceFacade.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatServiceFacade.java @@ -22,6 +22,8 @@ import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.ruoyi.agent.ChartGenerationAgent; import org.ruoyi.agent.SqlAgent; +import org.ruoyi.observability.MyAgentListener; +import org.ruoyi.observability.MyMcpClientListener; import org.ruoyi.agent.WebSearchAgent; import org.ruoyi.agent.tool.ExecuteSqlQueryTool; import org.ruoyi.agent.tool.QueryAllTablesTool; @@ -213,6 +215,7 @@ public class ChatServiceFacade implements IChatService { McpClient mcpClient = new DefaultMcpClient.Builder() .transport(transport) + .listener(new MyMcpClientListener()) .build(); ToolProvider toolProvider = McpToolProvider.builder() @@ -227,6 +230,7 @@ public class ChatServiceFacade implements IChatService { McpClient mcpClient1 = new DefaultMcpClient.Builder() .transport(transport1) + .listener(new MyMcpClientListener()) .build(); ToolProvider toolProvider1 = McpToolProvider.builder() @@ -261,6 +265,7 @@ public class ChatServiceFacade implements IChatService { .chatModel(plannerModel) .subAgents(sqlAgent, chartGenerationAgent) .responseStrategy(SupervisorResponseStrategy.LAST) + .listener(new MyAgentListener()) .build(); String invoke = supervisor.invoke(chatRequest.getContent()); diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/DeepseekServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/DeepseekServiceImpl.java index d5b1b1b9..15bf7556 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/DeepseekServiceImpl.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/DeepseekServiceImpl.java @@ -1,24 +1,31 @@ package org.ruoyi.service.chat.impl.provider; + import dev.langchain4j.model.chat.StreamingChatModel; import dev.langchain4j.model.openai.OpenAiStreamingChatModel; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ruoyi.common.chat.domain.dto.request.ChatRequest; import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; import org.ruoyi.enums.ChatModeType; +import org.ruoyi.observability.ChatModelListenerProvider; import org.ruoyi.service.chat.AbstractChatService; import org.springframework.stereotype.Service; /** - * @Author: xiaoen - * @Description: deepseek 服务调用 - * @Date: Created in 19:12 2026/3/17 + * Deepseek服务调用 + * + * @author xiaoen + * @date 2026/3/17 */ @Service @Slf4j +@RequiredArgsConstructor public class DeepseekServiceImpl implements AbstractChatService { + private final ChatModelListenerProvider listenerProvider; + @Override public StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo, ChatRequest chatRequest) { return OpenAiStreamingChatModel.builder() @@ -26,6 +33,7 @@ public class DeepseekServiceImpl implements AbstractChatService { .apiKey(chatModelVo.getApiKey()) .modelName(chatModelVo.getModelName()) .returnThinking(chatRequest.getEnableThinking()) + .listeners(listenerProvider.getChatModelListeners()) .build(); } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/OllamaServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/OllamaServiceImpl.java index c4de3427..4da7073e 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/OllamaServiceImpl.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/OllamaServiceImpl.java @@ -1,13 +1,16 @@ package org.ruoyi.service.chat.impl.provider; + import dev.langchain4j.model.chat.StreamingChatModel; import dev.langchain4j.model.ollama.OllamaStreamingChatModel; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ruoyi.enums.ChatModeType; import org.ruoyi.service.chat.AbstractChatService; import org.springframework.stereotype.Service; import org.ruoyi.common.chat.domain.dto.request.ChatRequest; import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; +import org.ruoyi.observability.ChatModelListenerProvider; /** * OllamaAI服务调用 @@ -17,13 +20,17 @@ import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; */ @Service @Slf4j +@RequiredArgsConstructor public class OllamaServiceImpl implements AbstractChatService { + private final ChatModelListenerProvider listenerProvider; + @Override public StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo, ChatRequest chatRequest) { return OllamaStreamingChatModel.builder() .baseUrl(chatModelVo.getApiHost()) .modelName(chatModelVo.getModelName()) + .listeners(listenerProvider.getChatModelListeners()) .build(); } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/OpenAIServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/OpenAIServiceImpl.java index e601fbcb..76503b89 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/OpenAIServiceImpl.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/OpenAIServiceImpl.java @@ -3,10 +3,12 @@ package org.ruoyi.service.chat.impl.provider; import dev.langchain4j.model.chat.StreamingChatModel; import dev.langchain4j.model.openai.OpenAiStreamingChatModel; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ruoyi.common.chat.domain.dto.request.ChatRequest; import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; import org.ruoyi.enums.ChatModeType; +import org.ruoyi.observability.ChatModelListenerProvider; import org.ruoyi.service.chat.AbstractChatService; import org.springframework.stereotype.Service; @@ -19,15 +21,19 @@ import org.springframework.stereotype.Service; */ @Service @Slf4j +@RequiredArgsConstructor public class OpenAIServiceImpl implements AbstractChatService { + private final ChatModelListenerProvider listenerProvider; + @Override - public StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo, ChatRequest chatRequest) { + public StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo,ChatRequest chatRequest) { return OpenAiStreamingChatModel.builder() .baseUrl(chatModelVo.getApiHost()) .apiKey(chatModelVo.getApiKey()) .modelName(chatModelVo.getModelName()) .returnThinking(chatRequest.getEnableThinking()) + .listeners(listenerProvider.getChatModelListeners()) .build(); } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/PPIOServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/PPIOServiceImpl.java index 5b36fee9..fd742abb 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/PPIOServiceImpl.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/PPIOServiceImpl.java @@ -1,24 +1,30 @@ package org.ruoyi.service.chat.impl.provider; + import dev.langchain4j.model.chat.StreamingChatModel; import dev.langchain4j.model.openai.OpenAiStreamingChatModel; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ruoyi.common.chat.domain.dto.request.ChatRequest; import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; import org.ruoyi.enums.ChatModeType; +import org.ruoyi.observability.ChatModelListenerProvider; import org.ruoyi.service.chat.AbstractChatService; import org.springframework.stereotype.Service; /** - * OPENAI服务调用 + * PPIO服务调用 * * @author ageerle@163.com * @date 2025/12/13 */ @Service @Slf4j +@RequiredArgsConstructor public class PPIOServiceImpl implements AbstractChatService { + private final ChatModelListenerProvider listenerProvider; + @Override public StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo, ChatRequest chatRequest) { return OpenAiStreamingChatModel.builder() @@ -26,6 +32,7 @@ public class PPIOServiceImpl implements AbstractChatService { .apiKey(chatModelVo.getApiKey()) .modelName(chatModelVo.getModelName()) .returnThinking(chatRequest.getEnableThinking()) + .listeners(listenerProvider.getChatModelListeners()) .build(); } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/QianWenChatServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/QianWenChatServiceImpl.java index bb7243c4..2f34899e 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/QianWenChatServiceImpl.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/QianWenChatServiceImpl.java @@ -1,11 +1,14 @@ package org.ruoyi.service.chat.impl.provider; + import dev.langchain4j.community.model.dashscope.QwenStreamingChatModel; import dev.langchain4j.model.chat.StreamingChatModel; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ruoyi.common.chat.domain.dto.request.ChatRequest; import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; import org.ruoyi.enums.ChatModeType; +import org.ruoyi.observability.ChatModelListenerProvider; import org.ruoyi.service.chat.AbstractChatService; import org.springframework.stereotype.Service; @@ -18,14 +21,17 @@ import org.springframework.stereotype.Service; */ @Service @Slf4j +@RequiredArgsConstructor public class QianWenChatServiceImpl implements AbstractChatService { - // 添加文档解析的前缀字段 + private final ChatModelListenerProvider listenerProvider; + @Override public StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo,ChatRequest chatRequest) { return QwenStreamingChatModel.builder() .apiKey(chatModelVo.getApiKey()) .modelName(chatModelVo.getModelName()) + .listeners(listenerProvider.getChatModelListeners()) .build(); } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/ZhiPuChatServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/ZhiPuChatServiceImpl.java index cf19a5a5..0cfebbfd 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/ZhiPuChatServiceImpl.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/ZhiPuChatServiceImpl.java @@ -1,11 +1,14 @@ package org.ruoyi.service.chat.impl.provider; + import dev.langchain4j.community.model.zhipu.ZhipuAiStreamingChatModel; import dev.langchain4j.model.chat.StreamingChatModel; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ruoyi.common.chat.domain.dto.request.ChatRequest; import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; import org.ruoyi.enums.ChatModeType; +import org.ruoyi.observability.ChatModelListenerProvider; import org.ruoyi.service.chat.AbstractChatService; import org.springframework.stereotype.Service; @@ -18,13 +21,17 @@ import org.springframework.stereotype.Service; */ @Service @Slf4j +@RequiredArgsConstructor public class ZhiPuChatServiceImpl implements AbstractChatService { + private final ChatModelListenerProvider listenerProvider; + @Override public StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo, ChatRequest chatRequest) { return ZhipuAiStreamingChatModel.builder() .apiKey(chatModelVo.getApiKey()) .model(chatModelVo.getModelName()) + .listeners(listenerProvider.getChatModelListeners()) .build(); } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/embed/impl/AliBaiLianBaseEmbedProvider.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/embed/impl/AliBaiLianBaseEmbedProvider.java index 083f98d6..936e0993 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/embed/impl/AliBaiLianBaseEmbedProvider.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/embed/impl/AliBaiLianBaseEmbedProvider.java @@ -4,8 +4,12 @@ package org.ruoyi.service.embed.impl; import dev.langchain4j.community.model.dashscope.QwenEmbeddingModel; import dev.langchain4j.data.embedding.Embedding; import dev.langchain4j.data.segment.TextSegment; +import dev.langchain4j.model.embedding.EmbeddingModel; +import dev.langchain4j.model.embedding.listener.EmbeddingModelListener; import dev.langchain4j.model.output.Response; import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; +import org.ruoyi.observability.EmbeddingModelListenerProvider; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.ruoyi.enums.ModalityType; @@ -20,9 +24,11 @@ import java.util.Set; @Component("alibailian") public class AliBaiLianBaseEmbedProvider extends OpenAiEmbeddingProvider { - private ChatModelVo chatModelVo; + @Autowired + private EmbeddingModelListenerProvider embeddingModelListenerProvider; + @Override public void configure(ChatModelVo config) { this.chatModelVo = config; @@ -35,12 +41,18 @@ public class AliBaiLianBaseEmbedProvider extends OpenAiEmbeddingProvider { @Override public Response> embedAll(List textSegments) { - return QwenEmbeddingModel.builder() + List listeners = embeddingModelListenerProvider.getEmbeddingModelListeners(); + EmbeddingModel model = QwenEmbeddingModel.builder() .apiKey(chatModelVo.getApiKey()) .modelName(chatModelVo.getModelName()) .dimension(chatModelVo.getModelDimension()) - .build() - .embedAll(textSegments); + .build(); + + if (!listeners.isEmpty()) { + model = model.addListeners(listeners); + } + + return model.embedAll(textSegments); } } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/embed/impl/OllamaEmbeddingProvider.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/embed/impl/OllamaEmbeddingProvider.java index 8d48a30e..79202425 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/embed/impl/OllamaEmbeddingProvider.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/embed/impl/OllamaEmbeddingProvider.java @@ -2,11 +2,16 @@ package org.ruoyi.service.embed.impl; import dev.langchain4j.data.embedding.Embedding; import dev.langchain4j.data.segment.TextSegment; +import dev.langchain4j.model.embedding.EmbeddingModel; +import dev.langchain4j.model.embedding.listener.EmbeddingModelListener; import dev.langchain4j.model.ollama.OllamaEmbeddingModel; import dev.langchain4j.model.output.Response; +import jakarta.annotation.Resource; import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; import org.ruoyi.enums.ModalityType; +import org.ruoyi.observability.EmbeddingModelListenerProvider; import org.ruoyi.service.embed.BaseEmbedModelService; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; @@ -21,6 +26,9 @@ import java.util.Set; public class OllamaEmbeddingProvider implements BaseEmbedModelService { private ChatModelVo chatModelVo; + @Resource + private EmbeddingModelListenerProvider embeddingModelListenerProvider; + @Override public void configure(ChatModelVo config) { this.chatModelVo = config; @@ -34,10 +42,16 @@ public class OllamaEmbeddingProvider implements BaseEmbedModelService { // ollama不能设置embedding维度,使用milvus时请注意!!创建向量表时需要先设定维度大小 @Override public Response> embedAll(List textSegments) { - return OllamaEmbeddingModel.builder() + List listeners = embeddingModelListenerProvider.getEmbeddingModelListeners(); + EmbeddingModel model = OllamaEmbeddingModel.builder() .baseUrl(chatModelVo.getApiHost()) .modelName(chatModelVo.getModelName()) - .build() - .embedAll(textSegments); + .build(); + + if (!listeners.isEmpty()) { + model = model.addListeners(listeners); + } + + return model.embedAll(textSegments); } } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/embed/impl/OpenAiEmbeddingProvider.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/embed/impl/OpenAiEmbeddingProvider.java index 2997d5b1..039342ee 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/embed/impl/OpenAiEmbeddingProvider.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/embed/impl/OpenAiEmbeddingProvider.java @@ -2,11 +2,15 @@ package org.ruoyi.service.embed.impl; import dev.langchain4j.data.embedding.Embedding; import dev.langchain4j.data.segment.TextSegment; +import dev.langchain4j.model.embedding.EmbeddingModel; +import dev.langchain4j.model.embedding.listener.EmbeddingModelListener; import dev.langchain4j.model.openai.OpenAiEmbeddingModel; import dev.langchain4j.model.output.Response; import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; import org.ruoyi.enums.ModalityType; +import org.ruoyi.observability.EmbeddingModelListenerProvider; import org.ruoyi.service.embed.BaseEmbedModelService; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; @@ -21,6 +25,9 @@ import java.util.Set; public class OpenAiEmbeddingProvider implements BaseEmbedModelService { protected ChatModelVo chatModelVo; + @Autowired + private EmbeddingModelListenerProvider embeddingModelListenerProvider; + @Override public void configure(ChatModelVo config) { this.chatModelVo = config; @@ -33,12 +40,18 @@ public class OpenAiEmbeddingProvider implements BaseEmbedModelService { @Override public Response> embedAll(List textSegments) { - return OpenAiEmbeddingModel.builder() + List listeners = embeddingModelListenerProvider.getEmbeddingModelListeners(); + EmbeddingModel model = OpenAiEmbeddingModel.builder() .baseUrl(chatModelVo.getApiHost()) .apiKey(chatModelVo.getApiKey()) .modelName(chatModelVo.getModelName()) .dimensions(chatModelVo.getModelDimension()) - .build() - .embedAll(textSegments); + .build(); + + if (!listeners.isEmpty()) { + model = model.addListeners(listeners); + } + + return model.embedAll(textSegments); } }