feat:增加可观测性的相关监听器 & 修复前端问答报错outputkey问题

This commit is contained in:
evo
2026-04-01 22:32:01 +08:00
parent 3071bfd0f9
commit ef99c540bb
31 changed files with 1034 additions and 23 deletions

View File

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

View File

@@ -76,7 +76,7 @@ public class AuthController {
@PostMapping("/login")
public R<LoginVo> 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();

View File

@@ -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

View File

@@ -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")

View File

@@ -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

View File

@@ -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.

View File

@@ -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 监听器共享提供者。
* <p>
* 供所有 {@link dev.langchain4j.model.chat.StreamingChatModel} 构建器使用,
* 将可观测性监听器注入到模型实例中。
*
* @author evo
*/
@Component
@Getter
@Lazy
public class ChatModelListenerProvider {
private final List<ChatModelListener> chatModelListeners;
private final List<EmbeddingModelListener> embeddingModelListeners;
public ChatModelListenerProvider(@Nullable List<ChatModelListener> chatModelListeners,
@Nullable List<EmbeddingModelListener> embeddingModelListeners) {
if (CollUtil.isEmpty(chatModelListeners)) {
chatModelListeners = Collections.emptyList();
}
if (CollUtil.isEmpty(embeddingModelListeners)) {
embeddingModelListeners = Collections.emptyList();
}
this.chatModelListeners = chatModelListeners;
this.embeddingModelListeners = embeddingModelListeners;
}
}

View File

@@ -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 监听器共享提供者。
* <p>
* 供所有 {@link dev.langchain4j.model.embedding.EmbeddingModel} 构建器使用,
* 将可观测性监听器注入到模型实例中。
*
* @author evo
*/
@Component
@Getter
@Lazy
public class EmbeddingModelListenerProvider {
private final List<EmbeddingModelListener> embeddingModelListeners;
public EmbeddingModelListenerProvider(@Nullable List<EmbeddingModelListener> embeddingModelListeners) {
if (CollUtil.isEmpty(embeddingModelListeners)) {
embeddingModelListeners = Collections.emptyList();
}
this.embeddingModelListeners = embeddingModelListeners;
}
}

View File

@@ -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 可观测性配置类。
* <p>
* 负责注册所有 langchain4j 的监听器:
* <ul>
* <li>{@link AiServiceListener} - AI服务级别的事件监听器通过 AiServiceListenerRegistrar 注册)</li>
* <li>{@link ChatModelListener} - ChatModel 级别的监听器(注入到模型构建器)</li>
* <li>{@link EmbeddingModelListener} - EmbeddingModel 级别的监听器(注入到模型构建器)</li>
* </ul>
*
* @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<ChatModelListener> chatModelListeners() {
return List.of(new MyChatModelListener());
}
// ==================== EmbeddingModel 监听器 ====================
@Bean
@Experimental
public EmbeddingModelListener embeddingModelListener() {
return new MyEmbeddingModelListener();
}
@Bean
@Experimental
public List<EmbeddingModelListener> embeddingModelListeners() {
return List.of(new MyEmbeddingModelListener());
}
// ==================== MCP Client 监听器 ====================
@Bean
public McpClientListener mcpClientListener() {
return new MyMcpClientListener();
}
}

View File

@@ -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 相关的所有可观测性事件,包括:
* <ul>
* <li>Agent 调用前/后的生命周期事件</li>
* <li>Agent 执行错误事件</li>
* <li>AgenticScope 的创建/销毁事件</li>
* <li>工具执行前/后的生命周期事件</li>
* </ul>
*
* @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<String, Object> 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<String, Object> 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<String, Object> 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());
}
}

View File

@@ -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<Object> result = event.result();
UUID invocationId = invocationContext.invocationId();
String aiServiceInterfaceName = invocationContext.interfaceName();
String aiServiceMethodName = invocationContext.methodName();
List<Object> 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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<List<Embedding>> response = responseContext.response();
List<Embedding> 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());
}
}

View File

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

View File

@@ -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 客户端相关的所有可观测性事件,包括:
* <ul>
* <li>MCP 工具执行的开始/成功/错误事件</li>
* <li>MCP 资源读取的开始/成功/错误事件</li>
* <li>MCP 提示词获取的开始/成功/错误事件</li>
* </ul>
*
* @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<String, Object> 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<String, Object> 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<String, Object> 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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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