feat:调整可观测性监听器逻辑

This commit is contained in:
evo
2026-04-05 21:34:41 +08:00
parent 4e38f853f3
commit d2005cfa48
13 changed files with 56 additions and 105 deletions

View File

@@ -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";
}

View File

@@ -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<String, BaseEmbedModelService> 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) {

View File

@@ -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());

View File

@@ -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<ChatMessage> contextMessages,
ChatModelVo chatModelVo, Long userId, String tokenValue) {
private void handleThinkingMode(ChatRequest chatRequest, List<ChatMessage> 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);
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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<List<Embedding>> embedAll(List<TextSegment> textSegments) {
List<EmbeddingModelListener> 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);
}

View File

@@ -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<List<Embedding>> embedAll(List<TextSegment> textSegments) {
List<EmbeddingModelListener> 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);
}
}

View File

@@ -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<List<Embedding>> embedAll(List<TextSegment> textSegments) {
List<EmbeddingModelListener> 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);
}
}