From ef99c540bb9ada7e2ae6010650e64f57ca63760a Mon Sep 17 00:00:00 2001 From: evo <446796145@qq.com> Date: Wed, 1 Apr 2026 22:32:01 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat=EF=BC=9A=E5=A2=9E=E5=8A=A0=E5=8F=AF?= =?UTF-8?q?=E8=A7=82=E6=B5=8B=E6=80=A7=E7=9A=84=E7=9B=B8=E5=85=B3=E7=9B=91?= =?UTF-8?q?=E5=90=AC=E5=99=A8=20&=20=E4=BF=AE=E5=A4=8D=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E9=97=AE=E7=AD=94=E6=8A=A5=E9=94=99outputkey=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/ruoyi/RuoYiAIApplication.java | 61 +++++++- .../org/ruoyi/controller/AuthController.java | 2 +- .../org/ruoyi/agent/ChartGenerationAgent.java | 2 +- .../java/org/ruoyi/agent/EchartsAgent.java | 4 +- .../main/java/org/ruoyi/agent/SqlAgent.java | 2 +- .../java/org/ruoyi/agent/WebSearchAgent.java | 2 +- .../ChatModelListenerProvider.java | 41 ++++++ .../EmbeddingModelListenerProvider.java | 34 +++++ .../LangChain4jObservabilityConfig.java | 129 +++++++++++++++++ .../ruoyi/observability/MyAgentListener.java | 130 +++++++++++++++++ .../MyAiServiceCompletedListener.java | 41 ++++++ .../MyAiServiceErrorListener.java | 33 +++++ .../MyAiServiceRequestIssuedListener.java | 33 +++++ .../MyAiServiceResponseReceivedListener.java | 37 +++++ .../MyAiServiceStartedListener.java | 38 +++++ .../observability/MyChatModelListener.java | 43 ++++++ .../MyEmbeddingModelListener.java | 47 ++++++ .../MyInputGuardrailExecutedListener.java | 45 ++++++ .../observability/MyMcpClientListener.java | 136 ++++++++++++++++++ .../MyOutputGuardrailExecutedListener.java | 42 ++++++ .../MyToolExecutedEventListener.java | 38 +++++ .../service/chat/impl/ChatServiceFacade.java | 5 + .../impl/provider/DeepseekServiceImpl.java | 14 +- .../chat/impl/provider/OllamaServiceImpl.java | 7 + .../chat/impl/provider/OpenAIServiceImpl.java | 8 +- .../chat/impl/provider/PPIOServiceImpl.java | 9 +- .../impl/provider/QianWenChatServiceImpl.java | 8 +- .../impl/provider/ZhiPuChatServiceImpl.java | 7 + .../impl/AliBaiLianBaseEmbedProvider.java | 20 ++- .../embed/impl/OllamaEmbeddingProvider.java | 20 ++- .../embed/impl/OpenAiEmbeddingProvider.java | 19 ++- 31 files changed, 1034 insertions(+), 23 deletions(-) create mode 100644 ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/ChatModelListenerProvider.java create mode 100644 ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/EmbeddingModelListenerProvider.java create mode 100644 ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/LangChain4jObservabilityConfig.java create mode 100644 ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyAgentListener.java create mode 100644 ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyAiServiceCompletedListener.java create mode 100644 ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyAiServiceErrorListener.java create mode 100644 ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyAiServiceRequestIssuedListener.java create mode 100644 ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyAiServiceResponseReceivedListener.java create mode 100644 ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyAiServiceStartedListener.java create mode 100644 ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyChatModelListener.java create mode 100644 ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyEmbeddingModelListener.java create mode 100644 ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyInputGuardrailExecutedListener.java create mode 100644 ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyMcpClientListener.java create mode 100644 ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyOutputGuardrailExecutedListener.java create mode 100644 ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/observability/MyToolExecutedEventListener.java 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); } } From 3cfb185dded952414d37588a6831232f38137f3d Mon Sep 17 00:00:00 2001 From: evo <446796145@qq.com> Date: Wed, 1 Apr 2026 23:11:54 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat=EF=BC=9A=E5=A2=9E=E5=8A=A0=E5=8F=AF?= =?UTF-8?q?=E8=A7=82=E6=B5=8B=E6=80=A7=E7=9B=91=E5=90=AC=E5=99=A8=20?= =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=80=9D=E8=80=83=E8=BE=93=E5=87=BA=E7=9B=91?= =?UTF-8?q?=E5=90=AC=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ruoyi/observability/MyAgentListener.java | 15 ++++++ .../service/chat/impl/ChatServiceFacade.java | 53 ++++++++++++++++--- 2 files changed, 62 insertions(+), 6 deletions(-) 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 index 7a548788..96223687 100644 --- 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 @@ -10,6 +10,7 @@ import dev.langchain4j.service.tool.ToolExecution; import lombok.extern.slf4j.Slf4j; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; /** * 自定义的 AgentListener 的监听器。 @@ -26,6 +27,13 @@ import java.util.Map; @Slf4j public class MyAgentListener implements dev.langchain4j.agentic.observability.AgentListener { + /** 最终捕获到的思考结果(主 Agent 完成后写入,供外部获取) */ + private final AtomicReference sharedOutputRef = new AtomicReference<>(); + + public String getCapturedResult() { + return sharedOutputRef.get(); + } + // ==================== Agent 调用生命周期 ==================== @Override @@ -72,12 +80,19 @@ public class MyAgentListener implements dev.langchain4j.agentic.observability.Ag AgentInstance agent = agentResponse.agent(); Map inputs = agentResponse.inputs(); Object output = agentResponse.output(); + String outputStr = output != null ? output.toString() : ""; 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()); + + // 捕获主 Agent 的最终输出,供外部获取 + if ("invoke".equals(agent.agentId()) && !outputStr.isEmpty()) { + sharedOutputRef.set(outputStr); + log.info("【Agent调用后】已捕获主Agent输出: {}", outputStr); + } } @Override 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 c0292871..62b90728 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 @@ -122,7 +122,7 @@ public class ChatServiceFacade implements IChatService { List contextMessages = buildContextMessages(chatRequest); // 3. 处理特殊聊天模式(工作流、人机交互恢复、思考模式) - SseEmitter specialResult = handleSpecialChatModes(chatRequest, contextMessages, chatModelVo, emitter); + SseEmitter specialResult = handleSpecialChatModes(chatRequest, contextMessages, chatModelVo, emitter, userId, tokenValue); if (specialResult != null) { return specialResult; } @@ -151,10 +151,13 @@ public class ChatServiceFacade implements IChatService { * @param contextMessages 上下文消息列表(可能被修改) * @param chatModelVo 聊天模型配置 * @param emitter SSE发射器 + * @param userId 用户ID + * @param tokenValue 会话令牌 * @return 如果需要提前返回则返回SseEmitter,否则返回null */ private SseEmitter handleSpecialChatModes(ChatRequest chatRequest, List contextMessages, - ChatModelVo chatModelVo, SseEmitter emitter) { + ChatModelVo chatModelVo, SseEmitter emitter, + Long userId, String tokenValue) { // 处理工作流对话 if (chatRequest.getEnableWorkFlow()) { log.info("处理工作流对话,会话: {}", chatRequest.getSessionId()); @@ -193,7 +196,16 @@ public class ChatServiceFacade implements IChatService { // 处理思考模式 if (chatRequest.getEnableThinking()) { - handleThinkingMode(chatRequest, contextMessages, chatModelVo); + String thinkingResult = handleThinkingMode(chatRequest, contextMessages, chatModelVo, userId, tokenValue); + // 思考模式产生了有效结果,通过 SSE 发送给前端后结束 + if (thinkingResult != null && !thinkingResult.isBlank()) { + SseMessageUtils.sendDone(userId); + SseMessageUtils.completeConnection(userId, tokenValue); + log.info("思考模式完成,结果已发送: {}", thinkingResult); + return emitter; + } + // 思考结果为空,继续走普通聊天流程 + log.warn("思考模式未产生有效结果,继续普通聊天"); } return null; @@ -205,8 +217,12 @@ public class ChatServiceFacade implements IChatService { * @param chatRequest 聊天请求 * @param contextMessages 上下文消息列表 * @param chatModelVo 聊天模型配置 + * @param userId 用户ID + * @param tokenValue 会话令牌 + * @return 思考结果字符串,如果无结果则返回空字符串 */ - private void handleThinkingMode(ChatRequest chatRequest, List contextMessages, ChatModelVo chatModelVo) { + private String handleThinkingMode(ChatRequest chatRequest, List contextMessages, + ChatModelVo chatModelVo, Long userId, String tokenValue) { // 步骤1: 配置MCP传输层 - 连接到bing-cn-mcp服务器 McpTransport transport = new StdioMcpTransport.Builder() .command(List.of("C:\\Program Files\\nodejs\\npx.cmd", "-y", "bing-cn-mcp")) @@ -263,13 +279,38 @@ public class ChatServiceFacade implements IChatService { // 构建监督者Agent SupervisorAgent supervisor = AgenticServices.supervisorBuilder() .chatModel(plannerModel) - .subAgents(sqlAgent, chartGenerationAgent) + .subAgents(sqlAgent, searchAgent, chartGenerationAgent) .responseStrategy(SupervisorResponseStrategy.LAST) .listener(new MyAgentListener()) .build(); + // 调用 supervisor String invoke = supervisor.invoke(chatRequest.getContent()); - contextMessages.add(AiMessage.from(invoke)); + log.info("【思考模式】supervisor.invoke() 返回: {}", invoke); + + // 如果有有效结果,通过 SSE 发送给前端并保存到数据库 + if (invoke != null && !invoke.isBlank()) { + try { + // 通过 SSE 实时发送思考结果 + SseMessageUtils.sendContent(userId, invoke); + log.info("【思考模式】结果已发送至SSE: {}", invoke); + + // 保存用户消息 + chatMessageService.saveChatMessage(userId, chatRequest.getSessionId(), + chatRequest.getContent(), RoleType.USER.getName(), chatRequest.getModel()); + + // 保存助手思考结果消息 + chatMessageService.saveChatMessage(userId, chatRequest.getSessionId(), + invoke, RoleType.ASSISTANT.getName(), chatRequest.getModel()); + + // 将思考结果添加到上下文,供后续流程使用(如果需要) + contextMessages.add(AiMessage.from(invoke)); + } catch (Exception e) { + log.error("【思考模式】发送结果或保存消息失败: {}", e.getMessage(), e); + } + } + + return invoke != null ? invoke : ""; } /** From 4e38f853f3d21eaa9e3665de72497c1330aa7cec Mon Sep 17 00:00:00 2001 From: evo <446796145@qq.com> Date: Thu, 2 Apr 2026 10:07:26 +0800 Subject: [PATCH 3/5] =?UTF-8?q?feat=EF=BC=9A=E4=BF=AE=E5=A4=8D=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E6=A0=A1=E9=AA=8C=20&=20=E8=B0=83=E6=95=B4=E4=B8=BB?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E7=B1=BB=E7=9A=84kill=20port=20=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-admin/src/main/java/org/ruoyi/RuoYiAIApplication.java | 2 +- .../src/main/java/org/ruoyi/controller/AuthController.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ruoyi-admin/src/main/java/org/ruoyi/RuoYiAIApplication.java b/ruoyi-admin/src/main/java/org/ruoyi/RuoYiAIApplication.java index 1ce9207c..ffca4ef5 100644 --- a/ruoyi-admin/src/main/java/org/ruoyi/RuoYiAIApplication.java +++ b/ruoyi-admin/src/main/java/org/ruoyi/RuoYiAIApplication.java @@ -16,7 +16,7 @@ import java.net.ServerSocket; public class RuoYiAIApplication { public static void main(String[] args) { - killPortProcess(6039); + // killPortProcess(6039); SpringApplication application = new SpringApplication(RuoYiAIApplication.class); application.setApplicationStartup(new BufferingApplicationStartup(2048)); application.run(args); 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 655821da..7a801355 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(); From d2005cfa4847f94704c46a090dd55eb3d4223fd1 Mon Sep 17 00:00:00 2001 From: evo <446796145@qq.com> Date: Sun, 5 Apr 2026 21:34:41 +0800 Subject: [PATCH 4/5] =?UTF-8?q?feat=EF=BC=9A=E8=B0=83=E6=95=B4=E5=8F=AF?= =?UTF-8?q?=E8=A7=82=E6=B5=8B=E6=80=A7=E7=9B=91=E5=90=AC=E5=99=A8=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ruoyi/agent/tool/ExecuteSqlQueryTool.java | 2 +- .../ruoyi/factory/EmbeddingModelFactory.java | 6 +- .../ruoyi/observability/MyAgentListener.java | 2 +- .../service/chat/impl/ChatServiceFacade.java | 69 ++++++------------- .../impl/provider/DeepseekServiceImpl.java | 7 +- .../chat/impl/provider/OllamaServiceImpl.java | 11 +-- .../chat/impl/provider/OpenAIServiceImpl.java | 7 +- .../chat/impl/provider/PPIOServiceImpl.java | 8 +-- .../impl/provider/QianWenChatServiceImpl.java | 5 +- .../impl/provider/ZhiPuChatServiceImpl.java | 8 +-- .../impl/AliBaiLianBaseEmbedProvider.java | 13 +--- .../embed/impl/OllamaEmbeddingProvider.java | 12 ---- .../embed/impl/OpenAiEmbeddingProvider.java | 11 --- 13 files changed, 56 insertions(+), 105 deletions(-) diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/ExecuteSqlQueryTool.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/ExecuteSqlQueryTool.java index 7fb56ad6..1b9079f1 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/ExecuteSqlQueryTool.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/ExecuteSqlQueryTool.java @@ -43,7 +43,7 @@ public class ExecuteSqlQueryTool implements BuiltinToolProvider { @Tool("Execute a SELECT SQL query and return the results. Example: SELECT * FROM sys_user") public String executeSql(String sql) { // 2. 手动推入数据源上下文 - DynamicDataSourceContextHolder.push("agent"); +// DynamicDataSourceContextHolder.push("agent"); if (sql == null || sql.trim().isEmpty()) { return "Error: SQL query cannot be empty"; } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/EmbeddingModelFactory.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/EmbeddingModelFactory.java index a4d93725..b123e6bb 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/EmbeddingModelFactory.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/EmbeddingModelFactory.java @@ -2,8 +2,9 @@ package org.ruoyi.factory; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.ruoyi.common.chat.service.chat.IChatModelService; import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; +import org.ruoyi.common.chat.service.chat.IChatModelService; +import org.ruoyi.observability.EmbeddingModelListenerProvider; import org.ruoyi.service.embed.BaseEmbedModelService; import org.ruoyi.service.embed.MultiModalEmbedModelService; import org.springframework.beans.factory.NoSuchBeanDefinitionException; @@ -27,6 +28,7 @@ public class EmbeddingModelFactory { private final ApplicationContext applicationContext; private final IChatModelService chatModelService; + private final EmbeddingModelListenerProvider embeddingModelListenerProvider; // 模型缓存,使用ConcurrentHashMap保证线程安全 private final Map modelCache = new ConcurrentHashMap<>(); @@ -109,6 +111,8 @@ public class EmbeddingModelFactory { BaseEmbedModelService model = applicationContext.getBean(factory, BaseEmbedModelService.class); // 配置模型参数 model.configure(config); + // 增加嵌入模型监听器 + model.addListeners(embeddingModelListenerProvider.getEmbeddingModelListeners()); log.info("成功创建嵌入模型: factory={}, modelId={}", config.getProviderCode(), config.getId()); return model; } catch (NoSuchBeanDefinitionException e) { 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 index 96223687..299ea6e9 100644 --- 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 @@ -46,7 +46,7 @@ public class MyAgentListener implements dev.langchain4j.agentic.observability.Ag 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调用前】Planner类型: {}", agent.plannerType()); log.info("【Agent调用前】输出类型: {}", agent.outputType()); log.info("【Agent调用前】输出Key: {}", agent.outputKey()); log.info("【Agent调用前】是否为异步: {}", agent.async()); 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 62b90728..ed906b0d 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 @@ -1,11 +1,13 @@ package org.ruoyi.service.chat.impl; import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.util.StrUtil; import dev.langchain4j.agentic.AgenticServices; import dev.langchain4j.agentic.supervisor.SupervisorAgent; import dev.langchain4j.agentic.supervisor.SupervisorResponseStrategy; -import dev.langchain4j.community.model.dashscope.QwenChatModel; -import dev.langchain4j.data.message.*; +import dev.langchain4j.data.message.AiMessage; +import dev.langchain4j.data.message.ChatMessage; +import dev.langchain4j.data.message.UserMessage; import dev.langchain4j.mcp.McpToolProvider; import dev.langchain4j.mcp.client.DefaultMcpClient; import dev.langchain4j.mcp.client.McpClient; @@ -22,8 +24,6 @@ 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; @@ -45,6 +45,9 @@ import org.ruoyi.domain.bo.vector.QueryVectorBo; import org.ruoyi.domain.vo.knowledge.KnowledgeInfoVo; import org.ruoyi.factory.ChatServiceFactory; import org.ruoyi.mcp.service.core.ToolProviderFactory; +import org.ruoyi.observability.MyAgentListener; +import org.ruoyi.observability.MyChatModelListener; +import org.ruoyi.observability.MyMcpClientListener; import org.ruoyi.service.chat.AbstractChatService; import org.ruoyi.service.chat.IChatMessageService; import org.ruoyi.service.chat.impl.memory.PersistentChatMemoryStore; @@ -196,16 +199,7 @@ public class ChatServiceFacade implements IChatService { // 处理思考模式 if (chatRequest.getEnableThinking()) { - String thinkingResult = handleThinkingMode(chatRequest, contextMessages, chatModelVo, userId, tokenValue); - // 思考模式产生了有效结果,通过 SSE 发送给前端后结束 - if (thinkingResult != null && !thinkingResult.isBlank()) { - SseMessageUtils.sendDone(userId); - SseMessageUtils.completeConnection(userId, tokenValue); - log.info("思考模式完成,结果已发送: {}", thinkingResult); - return emitter; - } - // 思考结果为空,继续走普通聊天流程 - log.warn("思考模式未产生有效结果,继续普通聊天"); + handleThinkingMode(chatRequest, contextMessages, chatModelVo, userId); } return null; @@ -214,15 +208,13 @@ public class ChatServiceFacade implements IChatService { /** * 处理思考模式 * - * @param chatRequest 聊天请求 - * @param contextMessages 上下文消息列表 - * @param chatModelVo 聊天模型配置 - * @param userId 用户ID - * @param tokenValue 会话令牌 - * @return 思考结果字符串,如果无结果则返回空字符串 + * @param chatRequest 聊天请求 + * @param contextMessages 上下文消息列表 + * @param chatModelVo 聊天模型配置 + * @param userId 用户ID */ - private String handleThinkingMode(ChatRequest chatRequest, List contextMessages, - ChatModelVo chatModelVo, Long userId, String tokenValue) { + private void handleThinkingMode(ChatRequest chatRequest, List contextMessages, + ChatModelVo chatModelVo, Long userId) { // 步骤1: 配置MCP传输层 - 连接到bing-cn-mcp服务器 McpTransport transport = new StdioMcpTransport.Builder() .command(List.of("C:\\Program Files\\nodejs\\npx.cmd", "-y", "bing-cn-mcp")) @@ -257,60 +249,40 @@ public class ChatServiceFacade implements IChatService { OpenAiChatModel plannerModel = OpenAiChatModel.builder() .baseUrl(chatModelVo.getApiHost()) .apiKey(chatModelVo.getApiKey()) + .listeners(List.of(new MyChatModelListener())) .modelName(chatModelVo.getModelName()) .build(); // 构建各Agent SqlAgent sqlAgent = AgenticServices.agentBuilder(SqlAgent.class) .chatModel(plannerModel) + .listener(new MyAgentListener()) .tools(new QueryAllTablesTool(), new QueryTableSchemaTool(), new ExecuteSqlQueryTool()) .build(); WebSearchAgent searchAgent = AgenticServices.agentBuilder(WebSearchAgent.class) .chatModel(plannerModel) + .listener(new MyAgentListener()) .toolProvider(toolProvider) .build(); ChartGenerationAgent chartGenerationAgent = AgenticServices.agentBuilder(ChartGenerationAgent.class) .chatModel(plannerModel) + .listener(new MyAgentListener()) .toolProvider(toolProvider1) .build(); // 构建监督者Agent SupervisorAgent supervisor = AgenticServices.supervisorBuilder() .chatModel(plannerModel) + .listener(new MyAgentListener()) .subAgents(sqlAgent, searchAgent, chartGenerationAgent) .responseStrategy(SupervisorResponseStrategy.LAST) - .listener(new MyAgentListener()) .build(); // 调用 supervisor String invoke = supervisor.invoke(chatRequest.getContent()); - log.info("【思考模式】supervisor.invoke() 返回: {}", invoke); - - // 如果有有效结果,通过 SSE 发送给前端并保存到数据库 - if (invoke != null && !invoke.isBlank()) { - try { - // 通过 SSE 实时发送思考结果 - SseMessageUtils.sendContent(userId, invoke); - log.info("【思考模式】结果已发送至SSE: {}", invoke); - - // 保存用户消息 - chatMessageService.saveChatMessage(userId, chatRequest.getSessionId(), - chatRequest.getContent(), RoleType.USER.getName(), chatRequest.getModel()); - - // 保存助手思考结果消息 - chatMessageService.saveChatMessage(userId, chatRequest.getSessionId(), - invoke, RoleType.ASSISTANT.getName(), chatRequest.getModel()); - - // 将思考结果添加到上下文,供后续流程使用(如果需要) - contextMessages.add(AiMessage.from(invoke)); - } catch (Exception e) { - log.error("【思考模式】发送结果或保存消息失败: {}", e.getMessage(), e); - } - } - - return invoke != null ? invoke : ""; + log.info("supervisor.invoke() 返回: {}", invoke); } /** @@ -348,6 +320,7 @@ public class ChatServiceFacade implements IChatService { // 7. 发起对话 StreamingChatModel streamingChatModel = chatService.buildStreamingChatModel(chatModelVo, chatRequest); + streamingChatModel.listeners().add(new MyChatModelListener()); streamingChatModel.chat(chatRequest.getContent(), combinedHandler); } 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 15bf7556..7ecf4368 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 @@ -9,9 +9,12 @@ 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.observability.MyChatModelListener; import org.ruoyi.service.chat.AbstractChatService; import org.springframework.stereotype.Service; +import java.util.List; + /** * Deepseek服务调用 @@ -24,16 +27,14 @@ import org.springframework.stereotype.Service; @RequiredArgsConstructor public class DeepseekServiceImpl implements AbstractChatService { - private final ChatModelListenerProvider listenerProvider; - @Override public StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo, ChatRequest chatRequest) { return OpenAiStreamingChatModel.builder() .baseUrl(chatModelVo.getApiHost()) .apiKey(chatModelVo.getApiKey()) .modelName(chatModelVo.getModelName()) + .listeners(List.of(new MyChatModelListener())) .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 4da7073e..a9346538 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 @@ -5,12 +5,15 @@ 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.enums.ChatModeType; import org.ruoyi.observability.ChatModelListenerProvider; +import org.ruoyi.observability.MyChatModelListener; +import org.ruoyi.service.chat.AbstractChatService; +import org.springframework.stereotype.Service; + +import java.util.List; /** * OllamaAI服务调用 @@ -30,7 +33,7 @@ public class OllamaServiceImpl implements AbstractChatService { return OllamaStreamingChatModel.builder() .baseUrl(chatModelVo.getApiHost()) .modelName(chatModelVo.getModelName()) - .listeners(listenerProvider.getChatModelListeners()) + .listeners(List.of(new MyChatModelListener())) .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 76503b89..eb3ed3ae 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 @@ -9,9 +9,12 @@ 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.observability.MyChatModelListener; import org.ruoyi.service.chat.AbstractChatService; import org.springframework.stereotype.Service; +import java.util.List; + /** * OPENAI服务调用 @@ -24,16 +27,14 @@ import org.springframework.stereotype.Service; @RequiredArgsConstructor public class OpenAIServiceImpl implements AbstractChatService { - private final ChatModelListenerProvider listenerProvider; - @Override public StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo,ChatRequest chatRequest) { return OpenAiStreamingChatModel.builder() .baseUrl(chatModelVo.getApiHost()) .apiKey(chatModelVo.getApiKey()) .modelName(chatModelVo.getModelName()) + .listeners(List.of(new MyChatModelListener())) .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 fd742abb..b07c21d1 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 @@ -8,10 +8,12 @@ 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.observability.MyChatModelListener; import org.ruoyi.service.chat.AbstractChatService; import org.springframework.stereotype.Service; +import java.util.List; + /** * PPIO服务调用 * @@ -23,16 +25,14 @@ import org.springframework.stereotype.Service; @RequiredArgsConstructor public class PPIOServiceImpl implements AbstractChatService { - private final ChatModelListenerProvider listenerProvider; - @Override public StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo, ChatRequest chatRequest) { return OpenAiStreamingChatModel.builder() .baseUrl(chatModelVo.getApiHost()) .apiKey(chatModelVo.getApiKey()) .modelName(chatModelVo.getModelName()) + .listeners(List.of(new MyChatModelListener())) .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 2f34899e..73289939 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 @@ -9,9 +9,12 @@ 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.observability.MyChatModelListener; import org.ruoyi.service.chat.AbstractChatService; import org.springframework.stereotype.Service; +import java.util.List; + /** * qianWenAI服务调用 @@ -31,7 +34,7 @@ public class QianWenChatServiceImpl implements AbstractChatService { return QwenStreamingChatModel.builder() .apiKey(chatModelVo.getApiKey()) .modelName(chatModelVo.getModelName()) - .listeners(listenerProvider.getChatModelListeners()) + .listeners(List.of(new MyChatModelListener())) .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 0cfebbfd..9e1b504d 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 @@ -8,10 +8,12 @@ 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.observability.MyChatModelListener; import org.ruoyi.service.chat.AbstractChatService; import org.springframework.stereotype.Service; +import java.util.List; + /** * 智谱AI服务调用 @@ -24,14 +26,12 @@ import org.springframework.stereotype.Service; @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()) + .listeners(List.of(new MyChatModelListener())) .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 936e0993..01709e45 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 @@ -5,13 +5,10 @@ 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; +import org.springframework.stereotype.Component; import java.util.List; import java.util.Set; @@ -26,9 +23,6 @@ public class AliBaiLianBaseEmbedProvider extends OpenAiEmbeddingProvider { private ChatModelVo chatModelVo; - @Autowired - private EmbeddingModelListenerProvider embeddingModelListenerProvider; - @Override public void configure(ChatModelVo config) { this.chatModelVo = config; @@ -41,17 +35,12 @@ public class AliBaiLianBaseEmbedProvider extends OpenAiEmbeddingProvider { @Override public Response> embedAll(List textSegments) { - List listeners = embeddingModelListenerProvider.getEmbeddingModelListeners(); EmbeddingModel model = QwenEmbeddingModel.builder() .apiKey(chatModelVo.getApiKey()) .modelName(chatModelVo.getModelName()) .dimension(chatModelVo.getModelDimension()) .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 79202425..905b9e73 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 @@ -3,15 +3,11 @@ 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; @@ -26,9 +22,6 @@ 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; @@ -42,16 +35,11 @@ public class OllamaEmbeddingProvider implements BaseEmbedModelService { // ollama不能设置embedding维度,使用milvus时请注意!!创建向量表时需要先设定维度大小 @Override public Response> embedAll(List textSegments) { - List listeners = embeddingModelListenerProvider.getEmbeddingModelListeners(); EmbeddingModel model = OllamaEmbeddingModel.builder() .baseUrl(chatModelVo.getApiHost()) .modelName(chatModelVo.getModelName()) .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 039342ee..6c583f6f 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 @@ -3,14 +3,11 @@ 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; @@ -25,9 +22,6 @@ 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; @@ -40,7 +34,6 @@ public class OpenAiEmbeddingProvider implements BaseEmbedModelService { @Override public Response> embedAll(List textSegments) { - List listeners = embeddingModelListenerProvider.getEmbeddingModelListeners(); EmbeddingModel model = OpenAiEmbeddingModel.builder() .baseUrl(chatModelVo.getApiHost()) .apiKey(chatModelVo.getApiKey()) @@ -48,10 +41,6 @@ public class OpenAiEmbeddingProvider implements BaseEmbedModelService { .dimensions(chatModelVo.getModelDimension()) .build(); - if (!listeners.isEmpty()) { - model = model.addListeners(listeners); - } - return model.embedAll(textSegments); } } From 2f39fa0f53790b99f0d20f92eb06a6b6505d6a44 Mon Sep 17 00:00:00 2001 From: evo <446796145@qq.com> Date: Sun, 5 Apr 2026 21:36:53 +0800 Subject: [PATCH 5/5] =?UTF-8?q?feat=EF=BC=9A=E8=B0=83=E6=95=B4=E5=8F=AF?= =?UTF-8?q?=E8=A7=82=E6=B5=8B=E6=80=A7=E7=9B=91=E5=90=AC=E5=99=A8=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/ruoyi/service/chat/impl/ChatServiceFacade.java | 1 - 1 file changed, 1 deletion(-) 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 ed906b0d..10955902 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 @@ -320,7 +320,6 @@ public class ChatServiceFacade implements IChatService { // 7. 发起对话 StreamingChatModel streamingChatModel = chatService.buildStreamingChatModel(chatModelVo, chatRequest); - streamingChatModel.listeners().add(new MyChatModelListener()); streamingChatModel.chat(chatRequest.getContent(), combinedHandler); }