mirror of
https://gitcode.com/ageerle/ruoyi-ai.git
synced 2026-04-03 15:06:11 +00:00
@@ -3728,3 +3728,26 @@ INSERT INTO `test_tree` VALUES (12, '000000', 10, 108, 3, '子节点88', 0, 103,
|
||||
INSERT INTO `test_tree` VALUES (13, '000000', 10, 108, 3, '子节点99', 0, 103, '2026-02-03 05:14:54', 1, NULL, NULL, 0);
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
INSERT INTO `t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (17, '5cd68dccbbb411f0bb7840c2ba9a7fbc', 'Start', '开始', '流程由此开始', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000');
|
||||
INSERT INTO `t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (18, '5cd6ac69bbb411f0bb7840c2ba9a7fbc', 'End', '结束', '流程由此结束', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000');
|
||||
INSERT INTO `t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (19, '5cd6c8eabbb411f0bb7840c2ba9a7fbc', 'Answer', '生成回答', '调用大语言模型回答问题', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000');
|
||||
INSERT INTO `t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (20, '0b4369bb60dc46d6bd84ceb4e36184dc', 'KeywordExtractor', '关键词提取', '从文本中提取关键词', 0, 1, '2025-12-26 16:30:05', '2025-12-26 16:30:05', 0, '000000');
|
||||
INSERT INTO `t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (21, 'bb00fc2f52c74fec82ee3f99725b56bb', 'Switcher', '条件分支', '根据条件执行不同分支', 0, 1, '2025-12-26 16:30:46', '2025-12-26 16:30:46', 0, '000000');
|
||||
INSERT INTO `t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (22, 'f37dbcb8f0d5464d90fbb22774490a56', 'HumanFeedback', '人类', '人机沟通', 0, 1, '2025-12-30 17:37:14', '2025-12-30 17:37:14', 0, '000000');
|
||||
INSERT INTO `t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (23, 'af9d6d7b9c9b47f990ad25ec84912b73', 'Tongyiwanx', '阿里图像生成', '使用通义万相生成图像', 0, 1, '2025-12-26 16:32:25', '2025-12-26 16:32:25', 0, '000000');
|
||||
INSERT INTO `t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (24, 'a1e2c9d4b8f04e1a9c3d6f8e2a7b1c9d', 'MailSend', '发送邮箱', '发送邮箱', 0, 1, '2025-12-30 17:37:14', '2025-12-30 17:37:14', 0, '000000');
|
||||
INSERT INTO `t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (25, 'f1e2d3c4b5a67890f1e2d3c4b5a6f1e2', 'HttpRequest', '请求节点', '请求节点', 0, 1, '2025-12-30 17:37:14', '2025-12-30 17:37:14', 0, '000000');
|
||||
INSERT INTO `chat_model` (`id`, `category`, `model_name`, `provider_code`, `model_describe`, `model_price`, `model_type`, `model_show`, `model_free`, `priority`, `api_host`, `api_key`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`, `tenant_id`) VALUES (2022565766560468994, 'image', 'wan2.5-t2i-preview', 'Tongyiwanx', 'wan2.5-t2i-preview', 1, '1', 'Y', 'Y', 1, 'https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation', 'skxxxx', 103, 1, '2026-02-14 14:57:11', 1, '2026-02-14 14:57:11', '通义万相文生图', 0);
|
||||
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query_param`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2021046920636690433, '流程管理', 0, 0, 'flow', '', NULL, 1, 0, 'M', '0', '0', NULL, 'ph:user-fill', 103, 1, '2026-02-10 10:21:50', 1, '2026-02-10 15:59:28', '');
|
||||
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query_param`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2021047050391678978, '工作流编排', 2021046920636690433, 0, 'aiflowengine', 'aiflow/index', NULL, 1, 0, 'C', '0', '0', '', 'ph:user-fill', 103, 1, '2026-02-10 10:22:21', 1, '2026-02-10 16:04:41', '');
|
||||
INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027192921483309058, '000000', 'HTTP请求节点响应模板', 'node.httpRequest.template', '✅ HTTP请求节点:结束响应 - ', 'Y', 103, 1, '2026-02-27 09:23:51', 1, '2026-02-27 09:31:41', NULL);
|
||||
INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027193296990957569, '000000', '文生图节点响应模板', 'node.image.template', '🎨 文生图节点:结束响应 - 图片URL: ', 'Y', 103, 1, '2026-02-27 09:25:20', 1, '2026-02-27 09:31:52', NULL);
|
||||
INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027193820393959425, '000000', '发送邮箱节点响应模板', 'node.mailsend.template', '📧 发送邮箱节点:结束响应 - ', 'Y', 103, 1, '2026-02-27 09:27:25', 1, '2026-02-27 09:32:05', NULL);
|
||||
INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027194134438277122, '000000', '结束节点响应模板', 'node.end.template', '🔚 流程已执行完毕,如果您有其他需求,请随时重新发起请求。', 'Y', 103, 1, '2026-02-27 09:28:40', 1, '2026-02-27 09:32:53', NULL);
|
||||
INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027206492573335554, '000000', '人机交互节点响应模板', 'node.humanFeedback.template', '👤 人机交互节点:等待用户操作 - ', 'Y', 103, 1, '2026-02-27 10:17:46', 1, '2026-02-27 10:17:46', NULL);
|
||||
INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027208880369647617, '000000', '条件分支节点响应模板', 'node.switch.template', '🔀 条件分支节点:触发 -> 跳转到节点 ', 'Y', 103, 1, '2026-02-27 10:27:15', 1, '2026-02-27 10:35:54', NULL);
|
||||
INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027213914603995137, '000000', '大模型回答节点响应模板', 'node.llmAnswer.template', '🤖 LLM 节点 生成回答:', 'Y', 103, 1, '2026-02-27 10:47:16', 1, '2026-02-27 10:52:40', NULL);
|
||||
INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027214387000066050, '000000', '关键词提取响应模板', 'node.keywordExtractor.template', '🔑 关键词提取节点 处理完成 : ', 'Y', 103, 1, '2026-02-27 10:49:08', 1, '2026-02-27 10:52:08', NULL);
|
||||
INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027217577397391361, '000000', '工作流异常响应模板', 'node.exception.template', '🛑 工作流发生异常:', 'N', 103, 1, '2026-02-27 11:01:49', 1, '2026-02-27 11:02:01', NULL);
|
||||
|
||||
|
||||
17
pom.xml
17
pom.xml
@@ -56,11 +56,10 @@
|
||||
<!-- AI 相关依赖 -->
|
||||
<langchain4j.version>1.11.0</langchain4j.version>
|
||||
<langchain4j.community.version>1.11.0-beta19</langchain4j.community.version>
|
||||
<langchain4j.community.zhipu.ai.version>1.1.0-beta7</langchain4j.community.zhipu.ai.version>
|
||||
<langgraph4j.version>1.5.3</langgraph4j.version>
|
||||
<weaviate.version>1.19.6</weaviate.version>
|
||||
<dify.version>1.0.7</dify.version>
|
||||
<!-- Apache Commons Compress - 用于POI处理ZIP格式 -->
|
||||
<commons-compress.version>1.27.1</commons-compress.version>
|
||||
|
||||
|
||||
<avatar-generator.version>1.1.0</avatar-generator.version>
|
||||
@@ -397,13 +396,6 @@
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- MCP模块 -->
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-mcp</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 企业微信SDK -->
|
||||
<dependency>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
@@ -418,13 +410,6 @@
|
||||
<version>${jackson-dataformat-xml.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Apache Commons Compress - 用于POI处理ZIP格式,解决导出Excel时的NoSuchMethodError -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-compress</artifactId>
|
||||
<version>${commons-compress.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
||||
@@ -110,12 +110,6 @@
|
||||
<artifactId>ruoyi-aiflow</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- MCP模块 -->
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-mcp</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>de.codecentric</groupId>
|
||||
<artifactId>spring-boot-admin-starter-client</artifactId>
|
||||
|
||||
@@ -58,9 +58,16 @@ spring:
|
||||
driverClassName: com.mysql.cj.jdbc.Driver
|
||||
# jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
|
||||
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
|
||||
url: jdbc:mysql://127.0.0.1:3306/ruoyi-ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
|
||||
url: jdbc:mysql://127.0.0.1:3306/ruoyi_ai_agent?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
|
||||
username: root
|
||||
password: root
|
||||
agent:
|
||||
url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
|
||||
# url: jdbc:mysql://localhost:3306/agent_db
|
||||
username: root
|
||||
password: root
|
||||
driverClassName: com.mysql.cj.jdbc.Driver
|
||||
|
||||
hikari:
|
||||
# 最大连接池数量
|
||||
maxPoolSize: 20
|
||||
@@ -77,17 +84,13 @@ spring:
|
||||
# 多久检查一次连接的活性
|
||||
keepaliveTime: 30000
|
||||
|
||||
agent:
|
||||
mysql:
|
||||
url: jdbc:mysql://localhost:3306/ruoyi-ai-agent?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
|
||||
username: root
|
||||
password: root
|
||||
|
||||
|
||||
--- # 上传文件地址
|
||||
sys:
|
||||
upload:
|
||||
path: D:\\DownLoad
|
||||
|
||||
|
||||
--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
|
||||
spring.data:
|
||||
redis:
|
||||
@@ -265,3 +268,4 @@ justauth:
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=gitea
|
||||
|
||||
AGENT_ALLOWED_TABLES: "abtest_rule,abtest_project,agent_ban_log,agent_ban_logs,agent_install_sub_task,agent_install_sum_task,agent_install_task"
|
||||
@@ -216,8 +216,6 @@ springdoc:
|
||||
packages-to-scan: org.ruoyi.generator
|
||||
- group: 5.工作流模块
|
||||
packages-to-scan: org.ruoyi.workflow
|
||||
- group: 6.MCP模块
|
||||
packages-to-scan: org.ruoyi.mcp
|
||||
|
||||
# 防止XSS攻击
|
||||
xss:
|
||||
@@ -359,14 +357,3 @@ knowledge:
|
||||
cache-enabled: true
|
||||
# 缓存过期时间(分钟)
|
||||
cache-expire-minutes: 60
|
||||
|
||||
--- # MCP 模块配置
|
||||
app:
|
||||
mcp:
|
||||
client:
|
||||
# 请求超时时间(秒)
|
||||
request-timeout: 30
|
||||
# 连接超时时间(秒)
|
||||
connection-timeout: 10
|
||||
# 最大重试次数
|
||||
max-retries: 3
|
||||
|
||||
@@ -62,6 +62,12 @@
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common-tenant</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
<version>${swagger-annotations.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package org.ruoyi.workflow.base;
|
||||
package org.ruoyi.common.chat.base;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.ruoyi.common.chat.entity.User;
|
||||
import org.ruoyi.common.chat.enums.UserStatusEnum;
|
||||
import org.ruoyi.common.core.domain.model.LoginUser;
|
||||
import org.ruoyi.common.core.exception.base.BaseException;
|
||||
import org.ruoyi.common.satoken.utils.LoginHelper;
|
||||
import org.ruoyi.workflow.entity.User;
|
||||
import org.ruoyi.workflow.enums.UserStatusEnum;
|
||||
|
||||
import static org.ruoyi.workflow.enums.ErrorEnum.A_USER_NOT_FOUND;
|
||||
import static org.ruoyi.common.chat.enums.ErrorEnum.A_USER_NOT_FOUND;
|
||||
|
||||
/**
|
||||
* 线程上下文适配器,统一接入 Sa-Token 登录态。
|
||||
@@ -1,13 +1,14 @@
|
||||
package org.ruoyi.domain.bo.chat;
|
||||
package org.ruoyi.common.chat.domain.bo.chat;
|
||||
|
||||
import org.ruoyi.common.core.validate.AddGroup;
|
||||
import org.ruoyi.common.core.validate.EditGroup;
|
||||
import org.ruoyi.domain.entity.chat.ChatMessage;
|
||||
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import jakarta.validation.constraints.*;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatMessage;
|
||||
import org.ruoyi.common.core.validate.AddGroup;
|
||||
import org.ruoyi.common.core.validate.EditGroup;
|
||||
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
|
||||
|
||||
|
||||
/**
|
||||
* 聊天消息业务对象 chat_message
|
||||
@@ -4,7 +4,7 @@ import io.github.linpeilie.annotations.AutoMapper;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.ruoyi.common.chat.domain.entity.chat.ChatModel;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatModel;
|
||||
import org.ruoyi.common.core.validate.EditGroup;
|
||||
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
|
||||
|
||||
|
||||
@@ -21,6 +21,21 @@ public class ChatRequest {
|
||||
@NotEmpty(message = "传入的模型不能为空")
|
||||
private String model;
|
||||
|
||||
/**
|
||||
* 工作流请求体
|
||||
*/
|
||||
private WorkFlowRunner workFlowRunner;
|
||||
|
||||
/**
|
||||
* 人机交互信息体
|
||||
*/
|
||||
private ReSumeRunner reSumeRunner;
|
||||
|
||||
/**
|
||||
* 是否启用工作流
|
||||
*/
|
||||
private Boolean enableWorkFlow;
|
||||
|
||||
/**
|
||||
* 会话id
|
||||
*/
|
||||
@@ -41,6 +56,11 @@ public class ChatRequest {
|
||||
*/
|
||||
private Long uuid;
|
||||
|
||||
/**
|
||||
* 是否为人机交互用户继续输入
|
||||
*/
|
||||
private Boolean isResume;
|
||||
|
||||
/**
|
||||
* 是否启用深度思考
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.ruoyi.common.chat.domain.dto.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 人机交互输入信息
|
||||
*/
|
||||
@Data
|
||||
public class ReSumeRunner {
|
||||
/**
|
||||
* 运行节点UUID
|
||||
*/
|
||||
private String runtimeUuid;
|
||||
|
||||
/**
|
||||
* 人机交互用户输入信息
|
||||
*/
|
||||
private String feedbackContent;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.ruoyi.common.chat.domain.dto.request;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 工作流请求体信息
|
||||
*/
|
||||
@Data
|
||||
public class WorkFlowRunner {
|
||||
private List<ObjectNode> inputs;
|
||||
private String uuid;
|
||||
}
|
||||
@@ -1,18 +1,17 @@
|
||||
package org.ruoyi.domain.vo.chat;
|
||||
package org.ruoyi.common.chat.domain.vo.chat;
|
||||
|
||||
import org.ruoyi.domain.entity.chat.ChatMessage;
|
||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import org.ruoyi.common.excel.annotation.ExcelDictFormat;
|
||||
import org.ruoyi.common.excel.convert.ExcelDictConvert;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatMessage;
|
||||
import org.ruoyi.common.excel.annotation.ExcelDictFormat;
|
||||
import org.ruoyi.common.excel.convert.ExcelDictConvert;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 聊天消息视图对象 chat_message
|
||||
*
|
||||
@@ -5,7 +5,7 @@ import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import org.ruoyi.common.chat.domain.entity.chat.ChatModel;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatModel;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.ruoyi.workflow.entity;
|
||||
package org.ruoyi.common.chat.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
@@ -1,11 +1,11 @@
|
||||
package org.ruoyi.workflow.entity;
|
||||
package org.ruoyi.common.chat.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.ruoyi.workflow.enums.UserStatusEnum;
|
||||
import org.ruoyi.common.chat.enums.UserStatusEnum;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.ruoyi.common.chat.domain.entity.chat;
|
||||
package org.ruoyi.common.chat.entity.chat;
|
||||
|
||||
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
@@ -1,9 +1,10 @@
|
||||
package org.ruoyi.domain.entity.chat;
|
||||
package org.ruoyi.common.chat.entity.chat;
|
||||
|
||||
import org.ruoyi.common.tenant.core.TenantEntity;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.ruoyi.common.tenant.core.TenantEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.ruoyi.common.chat.domain.entity.chat;
|
||||
package org.ruoyi.common.chat.entity.chat;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.ruoyi.common.chat.entity.image;
|
||||
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||
|
||||
/**
|
||||
* 文生图对话上下文对象
|
||||
*
|
||||
* @author zengxb
|
||||
* @date 2026-02-14
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Builder
|
||||
public class ImageContext {
|
||||
|
||||
/**
|
||||
* 模型管理视图对象
|
||||
*/
|
||||
@NotNull(message = "模型管理视图对象不能为空")
|
||||
private ChatModelVo chatModelVo;
|
||||
|
||||
/**
|
||||
* 提示词
|
||||
*/
|
||||
@NotNull(message = "提示词不能为空")
|
||||
private String prompt;
|
||||
|
||||
/**
|
||||
* 图片尺寸大小
|
||||
*/
|
||||
private String size;
|
||||
|
||||
/**
|
||||
* 随机数种子
|
||||
*/
|
||||
@Min(value = 0, message = "随机数种子不能小于0")
|
||||
@Max(value = 2147483647, message = "随机数种子不能大于2147483647")
|
||||
private Integer seed;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.ruoyi.workflow.enums;
|
||||
package org.ruoyi.common.chat.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IEnum;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.ruoyi.workflow.enums;
|
||||
package org.ruoyi.common.chat.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.ruoyi.enums;
|
||||
package org.ruoyi.common.chat.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
@@ -18,6 +18,7 @@ public enum RoleType {
|
||||
ASSISTANT("assistant"),
|
||||
FUNCTION("function"),
|
||||
TOOL("tool"),
|
||||
WORKFLOW("workFlow")
|
||||
;
|
||||
|
||||
private final String name;
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.ruoyi.workflow.enums;
|
||||
package org.ruoyi.common.chat.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.ruoyi.common.chat.factory;
|
||||
|
||||
import org.ruoyi.common.chat.Service.IChatService;
|
||||
import org.ruoyi.common.chat.service.chat.IChatService;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.ruoyi.common.chat.factory;
|
||||
|
||||
import org.ruoyi.common.chat.service.image.IImageGenerationService;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 文生图服务工厂类
|
||||
*
|
||||
* @author zengxb
|
||||
* @date 2026-02-14
|
||||
*/
|
||||
@Component
|
||||
public class ImageServiceFactory implements ApplicationContextAware {
|
||||
|
||||
private final Map<String, IImageGenerationService> imageSerivceMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
// 初始化时收集所有IImageGenerationService的实现
|
||||
Map<String, IImageGenerationService> serviceMap = applicationContext.getBeansOfType(IImageGenerationService.class);
|
||||
for (IImageGenerationService service : serviceMap.values()) {
|
||||
if (service != null ) {
|
||||
imageSerivceMap.put(service.getProviderName(), service);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取原始服务(不包装代理)
|
||||
*/
|
||||
public IImageGenerationService getOriginalService(String category) {
|
||||
IImageGenerationService service = imageSerivceMap.get(category);
|
||||
if (service == null) {
|
||||
throw new IllegalArgumentException("不支持的模型类别: " + category);
|
||||
}
|
||||
return service;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.ruoyi.common.chat.Service;
|
||||
package org.ruoyi.common.chat.service.chat;
|
||||
|
||||
import org.ruoyi.common.chat.domain.bo.chat.ChatModelBo;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.ruoyi.common.chat.Service;
|
||||
package org.ruoyi.common.chat.service.chat;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import org.ruoyi.common.chat.domain.entity.chat.ChatContext;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatContext;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
/**
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.ruoyi.common.chat.service.chatMessage;
|
||||
|
||||
import org.ruoyi.common.chat.domain.bo.chat.ChatMessageBo;
|
||||
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* 聊天信息抽象基类 - 保存聊天信息
|
||||
*
|
||||
* @author Zengxb
|
||||
* @date 2026-02-24
|
||||
*/
|
||||
public abstract class AbstractChatMessageService {
|
||||
|
||||
/**
|
||||
* 创建日志对象
|
||||
*/
|
||||
Logger log = LoggerFactory.getLogger(AbstractChatMessageService.class);
|
||||
|
||||
@Autowired
|
||||
private IChatMessageService chatMessageService;
|
||||
|
||||
/**
|
||||
* 保存聊天信息
|
||||
*/
|
||||
public void saveChatMessage(ChatRequest chatRequest, Long userId, String content, String role, ChatModelVo chatModelVo){
|
||||
try {
|
||||
// 验证必要的上下文信息
|
||||
if (chatRequest == null || userId == null) {
|
||||
log.warn("缺少必要的聊天上下文信息,无法保存消息");
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建ChatMessageBo对象
|
||||
ChatMessageBo messageBO = new ChatMessageBo();
|
||||
messageBO.setUserId(userId);
|
||||
messageBO.setSessionId(chatRequest.getSessionId());
|
||||
messageBO.setContent(content);
|
||||
messageBO.setRole(role);
|
||||
messageBO.setModelName(chatRequest.getModel());
|
||||
messageBO.setBillingType(chatModelVo.getModelType());
|
||||
messageBO.setRemark(null);
|
||||
|
||||
chatMessageService.insertByBo(messageBO);
|
||||
} catch (Exception e) {
|
||||
log.error("保存{}聊天消息时出错: {}", getProviderName(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务提供商名称
|
||||
*/
|
||||
protected String getProviderName(){
|
||||
return "默认工作流大模型";
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package org.ruoyi.service.chat;
|
||||
package org.ruoyi.common.chat.service.chatMessage;
|
||||
|
||||
import org.ruoyi.common.chat.domain.bo.chat.ChatMessageBo;
|
||||
import org.ruoyi.common.chat.domain.dto.ChatMessageDTO;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatMessageVo;
|
||||
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
||||
import org.ruoyi.domain.bo.chat.ChatMessageBo;
|
||||
import org.ruoyi.domain.vo.chat.ChatMessageVo;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.ruoyi.common.chat.service.image;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import org.ruoyi.common.chat.entity.image.ImageContext;
|
||||
|
||||
/**
|
||||
* 公共文生图接口
|
||||
*/
|
||||
public interface IImageGenerationService {
|
||||
|
||||
/**
|
||||
* 根据文字生成图片
|
||||
*/
|
||||
String generateImage(@Valid ImageContext imageContext);
|
||||
|
||||
/**
|
||||
* 获取服务提供商名称
|
||||
*/
|
||||
String getProviderName();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.ruoyi.common.chat.service.workFlow;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.ruoyi.common.chat.entity.User;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 工作流启动Service接口
|
||||
*
|
||||
* @author Zengxb
|
||||
* @date 2026-02-24
|
||||
*/
|
||||
public interface IWorkFlowStarterService {
|
||||
|
||||
/**
|
||||
* 启动工作流
|
||||
* @param user 用户
|
||||
* @param workflowUuid 工作流UUID
|
||||
* @param userInputs 用户输入信息
|
||||
* @return 流式输出结果
|
||||
*/
|
||||
SseEmitter streaming(User user, String workflowUuid, List<ObjectNode> userInputs, Long sessionId);
|
||||
|
||||
/**
|
||||
* 恢复工作流
|
||||
* @param runtimeUuid 运行时UUID
|
||||
* @param userInput 用户输入
|
||||
* @param sseEmitter SSE连接对象
|
||||
*/
|
||||
void resumeFlow(String runtimeUuid, String userInput, SseEmitter sseEmitter);
|
||||
}
|
||||
@@ -25,12 +25,6 @@
|
||||
<groupId>cn.idev.excel</groupId>
|
||||
<artifactId>fastexcel</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Apache Commons Compress - 用于POI处理ZIP格式,解决Excel导出时的依赖冲突 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-compress</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -65,7 +65,7 @@ public class SseEmitterManager {
|
||||
emitter.onCompletion(() -> {
|
||||
SseEmitter remove = emitters.remove(token);
|
||||
if (remove != null) {
|
||||
remove.complete();
|
||||
// remove.complete();
|
||||
}
|
||||
});
|
||||
emitter.onTimeout(() -> {
|
||||
|
||||
@@ -6,6 +6,9 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.common.core.utils.SpringUtils;
|
||||
import org.ruoyi.common.sse.core.SseEmitterManager;
|
||||
import org.ruoyi.common.sse.dto.SseMessageDto;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* SSE工具类
|
||||
|
||||
@@ -121,23 +121,16 @@ public class GlobalExceptionHandler {
|
||||
|
||||
/**
|
||||
* 拦截未知的运行时异常
|
||||
* 注意:对于文件下载/导出等场景,IOException 可能是正常流程的一部分,
|
||||
* 需要排除 export/download 等路径,避免干扰文件导出
|
||||
*/
|
||||
@ResponseStatus(org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
@ExceptionHandler(IOException.class)
|
||||
public R<Void> handleIoException(IOException e, HttpServletRequest request) {
|
||||
public void handleIoException(IOException e, HttpServletRequest request) {
|
||||
String requestURI = request.getRequestURI();
|
||||
if (requestURI.contains("sse")) {
|
||||
// sse 经常性连接中断 例如关闭浏览器 直接屏蔽
|
||||
return null;
|
||||
}
|
||||
// 排除文件下载/导出相关的 IOException,让异常正常传播以便上层处理
|
||||
if (requestURI.contains("/export") || requestURI.contains("/download")) {
|
||||
// 重新抛出,让调用方处理
|
||||
throw new RuntimeException("文件导出/下载IO异常: " + e.getMessage(), e);
|
||||
return;
|
||||
}
|
||||
log.error("请求地址'{}',连接中断", requestURI, e);
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,13 +146,6 @@ public class GlobalExceptionHandler {
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public R<Void> handleRuntimeException(RuntimeException e, HttpServletRequest request) {
|
||||
String requestURI = request.getRequestURI();
|
||||
// 对于文件导出相关异常,不进行封装处理,让原始异常信息传播
|
||||
Throwable cause = e.getCause();
|
||||
if (requestURI.contains("/export") || requestURI.contains("/download")) {
|
||||
log.error("请求地址'{}',文件导出/下载异常.", requestURI, e);
|
||||
// 对于文件导出,直接返回异常信息,不进行额外封装
|
||||
return R.fail(cause != null ? cause.getMessage() : e.getMessage());
|
||||
}
|
||||
log.error("请求地址'{}',发生未知异常.", requestURI, e);
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
<module>ruoyi-demo</module>
|
||||
<module>ruoyi-generator</module>
|
||||
<module>ruoyi-job</module>
|
||||
<module>ruoyi-mcp</module>
|
||||
<module>ruoyi-system</module>
|
||||
<module>ruoyi-wechat</module>
|
||||
<module>ruoyi-workflow</module>
|
||||
|
||||
@@ -81,12 +81,6 @@
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
<version>${swagger-annotations.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.api-client</groupId>
|
||||
<artifactId>google-api-client</artifactId>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.ruoyi.workflow.base;
|
||||
|
||||
import lombok.Data;
|
||||
import org.ruoyi.workflow.enums.ErrorEnum;
|
||||
import org.ruoyi.common.chat.enums.ErrorEnum;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import org.ruoyi.common.chat.base.ThreadContext;
|
||||
import org.ruoyi.common.core.domain.R;
|
||||
import org.ruoyi.workflow.base.ThreadContext;
|
||||
import org.ruoyi.workflow.dto.workflow.*;
|
||||
import org.ruoyi.workflow.entity.WorkflowComponent;
|
||||
import org.ruoyi.workflow.service.WorkflowComponentService;
|
||||
@@ -72,7 +72,7 @@ public class WorkflowController {
|
||||
@Operation(summary = "流式响应")
|
||||
@PostMapping(value = "/run", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public SseEmitter sseAsk(@RequestBody WorkflowRunReq runReq) {
|
||||
return workflowStarter.streaming(ThreadContext.getCurrentUser(), runReq.getUuid(), runReq.getInputs());
|
||||
return workflowStarter.streaming(ThreadContext.getCurrentUser(), runReq.getUuid(), runReq.getInputs(),runReq.getSessionId());
|
||||
}
|
||||
|
||||
@GetMapping("/mine/search")
|
||||
|
||||
@@ -30,7 +30,7 @@ public class WorkflowRuntimeController {
|
||||
@Operation(summary = "接收用户输入以继续执行剩余流程")
|
||||
@PostMapping(value = "/resume/{runtimeUuid}")
|
||||
public R resume(@PathVariable String runtimeUuid, @RequestBody WorkflowResumeReq resumeReq) {
|
||||
workflowStarter.resumeFlow(runtimeUuid, resumeReq.getFeedbackContent());
|
||||
workflowStarter.resumeFlow(runtimeUuid, resumeReq.getFeedbackContent(), resumeReq.getSseEmitter());
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package org.ruoyi.workflow.dto.workflow;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
@Data
|
||||
public class WorkflowResumeReq {
|
||||
private String feedbackContent;
|
||||
private SseEmitter sseEmitter;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import java.util.List;
|
||||
public class WorkflowRunReq {
|
||||
private List<ObjectNode> inputs;
|
||||
private String uuid;
|
||||
private Long sessionId;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.ruoyi.common.chat.entity.BaseEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.ruoyi.common.chat.entity.BaseEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.ruoyi.common.chat.entity.BaseEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.ruoyi.common.chat.entity.BaseEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.ruoyi.common.chat.entity.BaseEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.ruoyi.common.chat.entity.BaseEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.ruoyi.workflow.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.ruoyi.common.chat.enums.BaseEnum;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.ruoyi.workflow.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.ruoyi.common.chat.enums.BaseEnum;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ import com.google.common.cache.CacheBuilder;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.ruoyi.common.chat.entity.User;
|
||||
import org.ruoyi.workflow.cosntant.AdiConstant;
|
||||
import org.ruoyi.workflow.cosntant.RedisKeyConstant;
|
||||
import org.ruoyi.workflow.entity.User;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
@@ -7,11 +7,11 @@ import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.ruoyi.common.chat.enums.ErrorEnum;
|
||||
import org.ruoyi.common.core.exception.base.BaseException;
|
||||
import org.ruoyi.workflow.dto.workflow.WfComponentReq;
|
||||
import org.ruoyi.workflow.dto.workflow.WfComponentSearchReq;
|
||||
import org.ruoyi.workflow.entity.WorkflowComponent;
|
||||
import org.ruoyi.workflow.enums.ErrorEnum;
|
||||
import org.ruoyi.workflow.mapper.WorkflowComponentMapper;
|
||||
import org.ruoyi.workflow.util.PrivilegeUtil;
|
||||
import org.ruoyi.workflow.util.UuidUtil;
|
||||
@@ -26,7 +26,7 @@ import java.util.List;
|
||||
|
||||
import static org.ruoyi.workflow.cosntant.RedisKeyConstant.WORKFLOW_COMPONENTS;
|
||||
import static org.ruoyi.workflow.cosntant.RedisKeyConstant.WORKFLOW_COMPONENT_START_KEY;
|
||||
import static org.ruoyi.workflow.enums.ErrorEnum.C_WF_COMPONENT_DELETED_FAIL_BY_USED;
|
||||
import static org.ruoyi.common.chat.enums.ErrorEnum.C_WF_COMPONENT_DELETED_FAIL_BY_USED;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
|
||||
@@ -5,10 +5,10 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.common.chat.enums.ErrorEnum;
|
||||
import org.ruoyi.common.core.exception.base.BaseException;
|
||||
import org.ruoyi.workflow.dto.workflow.WfEdgeReq;
|
||||
import org.ruoyi.workflow.entity.WorkflowEdge;
|
||||
import org.ruoyi.workflow.enums.ErrorEnum;
|
||||
import org.ruoyi.workflow.mapper.WorkflowEdgeMapper;
|
||||
import org.ruoyi.workflow.util.MPPageUtil;
|
||||
import org.ruoyi.workflow.util.UuidUtil;
|
||||
|
||||
@@ -6,12 +6,12 @@ import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.common.chat.enums.ErrorEnum;
|
||||
import org.ruoyi.common.core.exception.base.BaseException;
|
||||
import org.ruoyi.workflow.dto.workflow.WfNodeDto;
|
||||
import org.ruoyi.workflow.entity.Workflow;
|
||||
import org.ruoyi.workflow.entity.WorkflowComponent;
|
||||
import org.ruoyi.workflow.entity.WorkflowNode;
|
||||
import org.ruoyi.workflow.enums.ErrorEnum;
|
||||
import org.ruoyi.workflow.enums.WfIODataTypeEnum;
|
||||
import org.ruoyi.workflow.mapper.WorkflowNodeMapper;
|
||||
import org.ruoyi.workflow.util.JsonUtil;
|
||||
|
||||
@@ -4,9 +4,9 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.workflow.base.ThreadContext;
|
||||
import org.ruoyi.common.chat.base.ThreadContext;
|
||||
import org.ruoyi.common.chat.entity.User;
|
||||
import org.ruoyi.workflow.dto.workflow.WfRuntimeNodeDto;
|
||||
import org.ruoyi.workflow.entity.User;
|
||||
import org.ruoyi.workflow.entity.WorkflowRuntimeNode;
|
||||
import org.ruoyi.workflow.mapper.WorkflowRuntimeNodeMapper;
|
||||
import org.ruoyi.workflow.util.JsonUtil;
|
||||
|
||||
@@ -7,13 +7,13 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.ruoyi.workflow.base.ThreadContext;
|
||||
import org.ruoyi.common.chat.base.ThreadContext;
|
||||
import org.ruoyi.common.chat.entity.User;
|
||||
import org.ruoyi.common.chat.enums.ErrorEnum;
|
||||
import org.ruoyi.workflow.dto.workflow.WfRuntimeNodeDto;
|
||||
import org.ruoyi.workflow.dto.workflow.WfRuntimeResp;
|
||||
import org.ruoyi.workflow.entity.User;
|
||||
import org.ruoyi.workflow.entity.Workflow;
|
||||
import org.ruoyi.workflow.entity.WorkflowRuntime;
|
||||
import org.ruoyi.workflow.enums.ErrorEnum;
|
||||
import org.ruoyi.workflow.mapper.WorkflowRunMapper;
|
||||
import org.ruoyi.workflow.util.JsonUtil;
|
||||
import org.ruoyi.workflow.util.MPPageUtil;
|
||||
|
||||
@@ -6,15 +6,15 @@ import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.ruoyi.common.chat.base.ThreadContext;
|
||||
import org.ruoyi.common.chat.entity.User;
|
||||
import org.ruoyi.common.chat.enums.ErrorEnum;
|
||||
import org.ruoyi.common.core.exception.base.BaseException;
|
||||
import org.ruoyi.workflow.base.ThreadContext;
|
||||
import org.ruoyi.workflow.dto.workflow.WfEdgeReq;
|
||||
import org.ruoyi.workflow.dto.workflow.WfNodeDto;
|
||||
import org.ruoyi.workflow.dto.workflow.WorkflowResp;
|
||||
import org.ruoyi.workflow.dto.workflow.WorkflowUpdateReq;
|
||||
import org.ruoyi.workflow.entity.User;
|
||||
import org.ruoyi.workflow.entity.Workflow;
|
||||
import org.ruoyi.workflow.enums.ErrorEnum;
|
||||
import org.ruoyi.workflow.mapper.WorkflowMapper;
|
||||
import org.ruoyi.workflow.util.MPPageUtil;
|
||||
import org.ruoyi.workflow.util.PrivilegeUtil;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package org.ruoyi.workflow.util;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
|
||||
import org.ruoyi.common.chat.base.ThreadContext;
|
||||
import org.ruoyi.common.chat.enums.ErrorEnum;
|
||||
import org.ruoyi.common.core.exception.base.BaseException;
|
||||
import org.ruoyi.workflow.base.ThreadContext;
|
||||
import org.ruoyi.workflow.enums.ErrorEnum;
|
||||
|
||||
import static org.ruoyi.workflow.cosntant.AdiConstant.*;
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package org.ruoyi.workflow.util;
|
||||
|
||||
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||
import org.ruoyi.common.chat.enums.RoleType;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.common.core.exception.ServiceException;
|
||||
import org.ruoyi.common.core.service.ConfigService;
|
||||
import org.ruoyi.common.core.utils.SpringUtils;
|
||||
import org.ruoyi.common.core.utils.StringUtils;
|
||||
import org.ruoyi.workflow.entity.WorkflowNode;
|
||||
import org.ruoyi.workflow.helper.SSEEmitterHelper;
|
||||
import org.ruoyi.workflow.workflow.WfState;
|
||||
import org.ruoyi.workflow.workflow.WorkflowUtil;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
/**
|
||||
* 工作流消息工具类
|
||||
*
|
||||
* @author Zengxb
|
||||
* @date 2026-02-26
|
||||
*/
|
||||
@Slf4j
|
||||
public class WorkflowMessageUtil {
|
||||
|
||||
|
||||
/**
|
||||
* 通知并存储消息(对话使用)
|
||||
* @param wfState 工作流实例状态
|
||||
* @param sseEmitter SSE连接对象
|
||||
* @param node 工作流节点
|
||||
* @param message 消息
|
||||
*/
|
||||
public static void notifyAndStoreMessage(WfState wfState, SseEmitter sseEmitter, WorkflowNode node, String message){
|
||||
saveWorkflowMessage(wfState, message);
|
||||
sendEmitterMessage(sseEmitter, node, message);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取节点的响应模板
|
||||
* @param configKey 参数Key
|
||||
* @return 返回模板样式
|
||||
*/
|
||||
public static String getNodeMessageTemplate(String configKey){
|
||||
ConfigService configService = SpringUtil.getBean(ConfigService.class);
|
||||
String configValue = configService.getConfigValue(configKey);
|
||||
if (StringUtils.isEmpty(configValue)) {
|
||||
throw new ServiceException("请先配置该节点的响应模板");
|
||||
}
|
||||
return configValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存工作流消息公共方法(对话使用)
|
||||
* @param wfState 工作流实例状态
|
||||
* @param message 消息
|
||||
*/
|
||||
public static void saveWorkflowMessage(WfState wfState, String message) {
|
||||
Long sessionId = wfState.getSessionId();
|
||||
Long userId = wfState.getUserId();
|
||||
|
||||
if (sessionId != null && userId != null) {
|
||||
ChatRequest chatRequest = new ChatRequest();
|
||||
chatRequest.setSessionId(sessionId);
|
||||
WorkflowUtil workflowUtil = SpringUtils.getBean(WorkflowUtil.class);
|
||||
workflowUtil.saveChatMessage(chatRequest, userId, message, RoleType.WORKFLOW.getName(), new ChatModelVo());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送SSE消息
|
||||
* @param sseEmitter 连接对象
|
||||
* @param node 工作流定义
|
||||
* @param message 消息
|
||||
*/
|
||||
public static void sendEmitterMessage(SseEmitter sseEmitter, WorkflowNode node, String message) {
|
||||
String nodeUuid = node.getUuid();
|
||||
SSEEmitterHelper.parseAndSendPartialMsg(sseEmitter,"[NODE_CHUNK_" + nodeUuid + "]", message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,9 +4,10 @@ import org.ruoyi.workflow.entity.WorkflowComponent;
|
||||
import org.ruoyi.workflow.entity.WorkflowNode;
|
||||
import org.ruoyi.workflow.workflow.node.AbstractWfNode;
|
||||
import org.ruoyi.workflow.workflow.node.EndNode;
|
||||
import org.ruoyi.workflow.workflow.node.HumanFeedbackNode;
|
||||
import org.ruoyi.workflow.workflow.node.humanFeedBack.HumanFeedbackNode;
|
||||
import org.ruoyi.workflow.workflow.node.answer.LLMAnswerNode;
|
||||
import org.ruoyi.workflow.workflow.node.httpRequest.HttpRequestNode;
|
||||
import org.ruoyi.workflow.workflow.node.image.ImageNode;
|
||||
import org.ruoyi.workflow.workflow.node.keywordExtractor.KeywordExtractorNode;
|
||||
import org.ruoyi.workflow.workflow.node.knowledgeRetrieval.KnowledgeRetrievalNode;
|
||||
import org.ruoyi.workflow.workflow.node.mailSend.MailSendNode;
|
||||
@@ -21,6 +22,7 @@ public class WfNodeFactory {
|
||||
case START -> wfNode = new StartNode(wfComponent, nodeDefinition, wfState, nodeState);
|
||||
case LLM_ANSWER -> wfNode = new LLMAnswerNode(wfComponent, nodeDefinition, wfState, nodeState);
|
||||
case KEYWORD_EXTRACTOR -> wfNode = new KeywordExtractorNode(wfComponent, nodeDefinition, wfState, nodeState);
|
||||
case TONGYI_WANX -> wfNode = new ImageNode(wfComponent, nodeDefinition, wfState, nodeState);
|
||||
case KNOWLEDGE_RETRIEVER -> wfNode = new KnowledgeRetrievalNode(wfComponent, nodeDefinition, wfState, nodeState);
|
||||
case END -> wfNode = new EndNode(wfComponent, nodeDefinition, wfState, nodeState);
|
||||
case MAIL_SEND -> wfNode = new MailSendNode(wfComponent, nodeDefinition, wfState, nodeState);
|
||||
|
||||
@@ -4,8 +4,8 @@ import cn.hutool.core.collection.CollUtil;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.ruoyi.common.chat.enums.ErrorEnum;
|
||||
import org.ruoyi.common.core.exception.base.BaseException;
|
||||
import org.ruoyi.workflow.enums.ErrorEnum;
|
||||
import org.ruoyi.workflow.enums.WfIODataTypeEnum;
|
||||
import org.ruoyi.workflow.util.JsonUtil;
|
||||
import org.ruoyi.workflow.workflow.data.NodeIOData;
|
||||
|
||||
@@ -4,8 +4,8 @@ import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.bsc.langgraph4j.langchain4j.generators.StreamingChatGenerator;
|
||||
import org.bsc.langgraph4j.state.AgentState;
|
||||
import org.ruoyi.common.chat.entity.User;
|
||||
import org.ruoyi.workflow.dto.workflow.WfRuntimeNodeDto;
|
||||
import org.ruoyi.workflow.entity.User;
|
||||
import org.ruoyi.workflow.entity.WorkflowNode;
|
||||
import org.ruoyi.workflow.workflow.data.NodeIOData;
|
||||
import org.ruoyi.workflow.workflow.node.AbstractWfNode;
|
||||
@@ -28,6 +28,7 @@ public class WfState {
|
||||
private Long userId;
|
||||
private String tokenValue;
|
||||
private SseEmitter sseEmitter;
|
||||
private Long sessionId;
|
||||
|
||||
//Source node uuid => target node uuid list
|
||||
private Map<String, List<String>> edges = new HashMap<>();
|
||||
@@ -59,13 +60,14 @@ public class WfState {
|
||||
*/
|
||||
private Set<String> interruptNodes = new HashSet<>();
|
||||
|
||||
public WfState(User user, List<NodeIOData> input, String uuid, Long userId, String tokenValue, SseEmitter sseEmitter) {
|
||||
public WfState(User user, List<NodeIOData> input, String uuid, Long userId, String tokenValue, SseEmitter sseEmitter, Long sessionId) {
|
||||
this.input = input;
|
||||
this.user = user;
|
||||
this.uuid = uuid;
|
||||
this.userId = userId;
|
||||
this.tokenValue = tokenValue;
|
||||
this.sseEmitter = sseEmitter;
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,8 @@ package org.ruoyi.workflow.workflow;
|
||||
import cn.hutool.core.collection.CollStreamUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
@@ -13,20 +15,23 @@ import org.bsc.langgraph4j.langchain4j.generators.StreamingChatGenerator;
|
||||
import org.bsc.langgraph4j.state.AgentState;
|
||||
import org.bsc.langgraph4j.state.StateSnapshot;
|
||||
import org.bsc.langgraph4j.streaming.StreamingOutput;
|
||||
import org.ruoyi.common.chat.entity.User;
|
||||
import org.ruoyi.common.chat.enums.ErrorEnum;
|
||||
import org.ruoyi.common.core.exception.base.BaseException;
|
||||
import org.ruoyi.workflow.base.NodeInputConfigTypeHandler;
|
||||
import org.ruoyi.workflow.dto.workflow.WfRuntimeNodeDto;
|
||||
import org.ruoyi.workflow.dto.workflow.WfRuntimeResp;
|
||||
import org.ruoyi.workflow.entity.*;
|
||||
import org.ruoyi.workflow.enums.ErrorEnum;
|
||||
import org.ruoyi.workflow.helper.SSEEmitterHelper;
|
||||
import org.ruoyi.workflow.service.WorkflowRuntimeNodeService;
|
||||
import org.ruoyi.workflow.service.WorkflowRuntimeService;
|
||||
import org.ruoyi.workflow.util.JsonUtil;
|
||||
import org.ruoyi.workflow.util.WorkflowMessageUtil;
|
||||
import org.ruoyi.workflow.workflow.data.NodeIOData;
|
||||
import org.ruoyi.workflow.workflow.def.WfNodeIO;
|
||||
import org.ruoyi.workflow.workflow.def.WfNodeParamRef;
|
||||
import org.ruoyi.workflow.workflow.node.AbstractWfNode;
|
||||
import org.ruoyi.workflow.workflow.node.enmus.NodeMessageTemplateEnum;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.util.*;
|
||||
@@ -34,7 +39,7 @@ import java.util.function.Function;
|
||||
|
||||
import static org.bsc.langgraph4j.StateGraph.END;
|
||||
import static org.ruoyi.workflow.cosntant.AdiConstant.WorkflowConstant.*;
|
||||
import static org.ruoyi.workflow.enums.ErrorEnum.*;
|
||||
import static org.ruoyi.common.chat.enums.ErrorEnum.*;
|
||||
|
||||
@Slf4j
|
||||
public class WorkflowEngine {
|
||||
@@ -45,9 +50,12 @@ public class WorkflowEngine {
|
||||
private final SSEEmitterHelper sseEmitterHelper;
|
||||
private final WorkflowRuntimeService workflowRuntimeService;
|
||||
private final WorkflowRuntimeNodeService workflowRuntimeNodeService;
|
||||
@Getter
|
||||
private CompiledGraph<WfNodeState> app;
|
||||
@Setter
|
||||
private SseEmitter sseEmitter;
|
||||
private User user;
|
||||
@Getter
|
||||
private WfState wfState;
|
||||
private WfRuntimeResp wfRuntimeResp;
|
||||
|
||||
@@ -68,7 +76,7 @@ public class WorkflowEngine {
|
||||
this.workflowRuntimeNodeService = workflowRuntimeNodeService;
|
||||
}
|
||||
|
||||
public void run(User user, List<ObjectNode> userInputs, SseEmitter sseEmitter, Long userId, String tokenValue) {
|
||||
public void run(User user, List<ObjectNode> userInputs, SseEmitter sseEmitter, Long userId, String tokenValue, Long sessionId) {
|
||||
this.user = user;
|
||||
this.sseEmitter = sseEmitter;
|
||||
log.info("WorkflowEngine run,userId:{},workflowUuid:{},userInputs:{}", user.getId(), workflow.getUuid(), userInputs);
|
||||
@@ -86,7 +94,7 @@ public class WorkflowEngine {
|
||||
Pair<WorkflowNode, Set<WorkflowNode>> startAndEnds = findStartAndEndNode();
|
||||
WorkflowNode startNode = startAndEnds.getLeft();
|
||||
List<NodeIOData> wfInputs = getAndCheckUserInput(userInputs, startNode);
|
||||
this.wfState = new WfState(user, wfInputs, runtimeUuid,userId, tokenValue, sseEmitter);
|
||||
this.wfState = new WfState(user, wfInputs, runtimeUuid,userId, tokenValue, sseEmitter, sessionId);
|
||||
workflowRuntimeService.updateInput(this.wfRuntimeResp.getId(), wfState);
|
||||
|
||||
|
||||
@@ -119,16 +127,31 @@ public class WorkflowEngine {
|
||||
String nextNode = stateSnapshot.config().nextNode().orElse("");
|
||||
//还有下个节点,表示进入中断状态,等待用户输入后继续执<E7BBAD>?
|
||||
if (StringUtils.isNotBlank(nextNode) && !nextNode.equalsIgnoreCase(END)) {
|
||||
String intTip = WorkflowUtil.getHumanFeedbackTip(nextNode, wfNodes);
|
||||
// 获取提示模板
|
||||
String nodeMessageTemplate = WorkflowMessageUtil.getNodeMessageTemplate(NodeMessageTemplateEnum.HUMAN_FEED_BACK.getValue());
|
||||
// 获取人机交互提示信息
|
||||
String intTip = nodeMessageTemplate + WorkflowUtil.getHumanFeedbackTip(nextNode, wfNodes);
|
||||
//将等待输入信息[事件与提示词]发送到到客户端
|
||||
SSEEmitterHelper.parseAndSendPartialMsg(sseEmitter, "[NODE_WAIT_FEEDBACK_BY_" + nextNode + "]", intTip);
|
||||
// 保存提示信息到Chat信息记录中(对话使用)
|
||||
WorkflowMessageUtil.saveWorkflowMessage(wfState, intTip);
|
||||
InterruptedFlow.RUNTIME_TO_GRAPH.put(wfState.getUuid(), this);
|
||||
//更新状<E696B0>?
|
||||
wfState.setProcessStatus(WORKFLOW_PROCESS_STATUS_WAITING_INPUT);
|
||||
workflowRuntimeService.updateOutput(wfRuntimeResp.getId(), wfState);
|
||||
} else {
|
||||
WorkflowRuntime updatedRuntime = workflowRuntimeService.updateOutput(wfRuntimeResp.getId(), wfState);
|
||||
// 保存成功会话信息
|
||||
wfNodes.stream().filter(item -> stateSnapshot.node().equals(item.getUuid()))
|
||||
.findFirst().ifPresent(wfNode -> {
|
||||
// 获取节点模板提示词信息
|
||||
String nodeMessageTemplate = WorkflowMessageUtil.getNodeMessageTemplate(NodeMessageTemplateEnum.END.getValue());
|
||||
// 发送SSE消息驱动事件和保存会话
|
||||
WorkflowMessageUtil.notifyAndStoreMessage(wfState, sseEmitter, wfNode, nodeMessageTemplate);
|
||||
});
|
||||
// 发送结束消息
|
||||
sseEmitterHelper.sendComplete(user.getId(), sseEmitter, updatedRuntime.getOutput());
|
||||
// 发送驱动消息事件
|
||||
InterruptedFlow.RUNTIME_TO_GRAPH.remove(wfState.getUuid());
|
||||
}
|
||||
}
|
||||
@@ -155,10 +178,14 @@ public class WorkflowEngine {
|
||||
|
||||
private void errorWhenExe(Exception e) {
|
||||
log.error("error", e);
|
||||
String nodeMessageTemplate = WorkflowMessageUtil.getNodeMessageTemplate(NodeMessageTemplateEnum.EXCEPTION.getValue());
|
||||
String errorMsg = e.getMessage();
|
||||
if (errorMsg.contains("parallel node doesn't support conditional branch")) {
|
||||
errorMsg = "并行节点中不能包含条件分<EFBFBD>?";
|
||||
}
|
||||
errorMsg = nodeMessageTemplate + errorMsg;
|
||||
// 保存会话信息且发送驱动消息事件
|
||||
WorkflowMessageUtil.saveWorkflowMessage(wfState, errorMsg);
|
||||
sseEmitterHelper.sendErrorAndComplete(user.getId(), sseEmitter, errorMsg);
|
||||
workflowRuntimeService.updateStatus(wfRuntimeResp.getId(), WORKFLOW_PROCESS_STATUS_FAIL, errorMsg);
|
||||
}
|
||||
@@ -241,6 +268,7 @@ public class WorkflowEngine {
|
||||
Map<String, String> strMap = new HashMap<>();
|
||||
strMap.put("ck", chunk);
|
||||
// SSEEmitterHelper.parseAndSendPartialMsg(sseEmitter, "[NODE_CHUNK_" + node + "]", strMap.toString());
|
||||
|
||||
SSEEmitterHelper.parseAndSendPartialMsg(sseEmitter, "[NODE_CHUNK_" + node + "]", chunk);
|
||||
} else {
|
||||
AbstractWfNode abstractWfNode = wfState.getCompletedNodes().stream()
|
||||
@@ -349,8 +377,4 @@ public class WorkflowEngine {
|
||||
return Pair.of(startNode, endNodes);
|
||||
}
|
||||
|
||||
|
||||
public CompiledGraph<WfNodeState> getApp() {
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.bsc.langgraph4j.GraphStateException;
|
||||
import org.bsc.langgraph4j.StateGraph;
|
||||
import org.bsc.langgraph4j.serializer.std.ObjectStreamStateSerializer;
|
||||
import org.ruoyi.common.chat.enums.ErrorEnum;
|
||||
import org.ruoyi.common.core.exception.base.BaseException;
|
||||
import org.ruoyi.workflow.entity.WorkflowComponent;
|
||||
import org.ruoyi.workflow.entity.WorkflowEdge;
|
||||
import org.ruoyi.workflow.entity.WorkflowNode;
|
||||
import org.ruoyi.workflow.enums.ErrorEnum;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
@@ -4,6 +4,8 @@ import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.common.chat.entity.User;
|
||||
import org.ruoyi.common.chat.service.workFlow.IWorkFlowStarterService;
|
||||
import org.ruoyi.common.core.exception.base.BaseException;
|
||||
import org.ruoyi.common.satoken.utils.LoginHelper;
|
||||
import org.ruoyi.common.sse.core.SseEmitterManager;
|
||||
@@ -12,16 +14,16 @@ import org.ruoyi.workflow.helper.SSEEmitterHelper;
|
||||
import org.ruoyi.workflow.service.*;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.ruoyi.workflow.enums.ErrorEnum.*;
|
||||
import static org.ruoyi.common.chat.enums.ErrorEnum.*;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class WorkflowStarter {
|
||||
@Service
|
||||
public class WorkflowStarter implements IWorkFlowStarterService {
|
||||
|
||||
@Lazy
|
||||
@Resource
|
||||
@@ -51,8 +53,7 @@ public class WorkflowStarter {
|
||||
@Resource
|
||||
private SseEmitterManager sseEmitterManager;
|
||||
|
||||
|
||||
public SseEmitter streaming(User user, String workflowUuid, List<ObjectNode> userInputs) {
|
||||
public SseEmitter streaming(User user, String workflowUuid, List<ObjectNode> userInputs, Long sessionId) {
|
||||
// 获取用户ID
|
||||
Long userId = LoginHelper.getUserId();
|
||||
// 获取登录Token
|
||||
@@ -70,12 +71,12 @@ public class WorkflowStarter {
|
||||
sseEmitterHelper.sendErrorAndComplete(user.getId(), sseEmitter, A_WF_DISABLED.getInfo());
|
||||
return sseEmitter;
|
||||
}
|
||||
self.asyncRun(user, workflow, userInputs, sseEmitter, userId, tokenValue);
|
||||
self.asyncRun(user, workflow, userInputs, sseEmitter, userId, tokenValue, sessionId);
|
||||
return sseEmitter;
|
||||
}
|
||||
|
||||
@Async
|
||||
public void asyncRun(User user, Workflow workflow, List<ObjectNode> userInputs, SseEmitter sseEmitter, Long userId, String tokenValue) {
|
||||
public void asyncRun(User user, Workflow workflow, List<ObjectNode> userInputs, SseEmitter sseEmitter, Long userId, String tokenValue, Long sessionId) {
|
||||
log.info("WorkflowEngine run,userId:{},workflowUuid:{},userInputs:{}", user.getId(), workflow.getUuid(), userInputs);
|
||||
List<WorkflowComponent> components = workflowComponentService.getAllEnable();
|
||||
List<WorkflowNode> nodes = workflowNodeService.lambdaQuery()
|
||||
@@ -89,17 +90,22 @@ public class WorkflowStarter {
|
||||
WorkflowEngine workflowEngine = new WorkflowEngine(workflow,
|
||||
sseEmitterHelper, components, nodes, edges,
|
||||
workflowRuntimeService, workflowRuntimeNodeService);
|
||||
workflowEngine.run(user, userInputs, sseEmitter, userId, tokenValue);
|
||||
workflowEngine.run(user, userInputs, sseEmitter, userId, tokenValue, sessionId);
|
||||
}
|
||||
|
||||
@Async
|
||||
public void resumeFlow(String runtimeUuid, String userInput) {
|
||||
public void resumeFlow(String runtimeUuid, String userInput, SseEmitter sseEmitter) {
|
||||
WorkflowEngine workflowEngine = InterruptedFlow.RUNTIME_TO_GRAPH.get(runtimeUuid);
|
||||
if (null == workflowEngine) {
|
||||
log.error("工作流恢复执行时失败,runtime:{}", runtimeUuid);
|
||||
throw new BaseException(A_WF_RESUME_FAIL.getInfo());
|
||||
}
|
||||
// 如果SSE连接对象不为空传入该对象(Chat调用工作流对话使用)
|
||||
if (null != sseEmitter){
|
||||
workflowEngine.setSseEmitter(sseEmitter);
|
||||
// 为了让每个节点都可以发送模板消息 保持SSE对象一致(以防出现向已关闭的SSE对象发送消息)
|
||||
workflowEngine.getWfState().setSseEmitter(sseEmitter);
|
||||
}
|
||||
workflowEngine.resume(userInput);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,12 +11,17 @@ import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bsc.langgraph4j.langchain4j.generators.StreamingChatGenerator;
|
||||
import org.bsc.langgraph4j.state.AgentState;
|
||||
import org.ruoyi.common.chat.Service.IChatModelService;
|
||||
import org.ruoyi.common.chat.Service.IChatService;
|
||||
import org.ruoyi.common.chat.enums.RoleType;
|
||||
import org.ruoyi.common.chat.service.chat.IChatModelService;
|
||||
import org.ruoyi.common.chat.service.chat.IChatService;
|
||||
import org.ruoyi.common.chat.service.chatMessage.AbstractChatMessageService;
|
||||
import org.ruoyi.common.chat.service.image.IImageGenerationService;
|
||||
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
|
||||
import org.ruoyi.common.chat.domain.entity.chat.ChatContext;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatContext;
|
||||
import org.ruoyi.common.chat.entity.image.ImageContext;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||
import org.ruoyi.common.chat.factory.ChatServiceFactory;
|
||||
import org.ruoyi.common.chat.factory.ImageServiceFactory;
|
||||
import org.ruoyi.workflow.base.NodeInputConfigTypeHandler;
|
||||
import org.ruoyi.workflow.entity.WorkflowNode;
|
||||
import org.ruoyi.workflow.enums.WfIODataTypeEnum;
|
||||
@@ -25,6 +30,7 @@ import org.ruoyi.workflow.workflow.data.NodeIOData;
|
||||
import org.ruoyi.workflow.workflow.data.NodeIODataContent;
|
||||
import org.ruoyi.workflow.workflow.def.WfNodeParamRef;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.util.*;
|
||||
@@ -32,12 +38,15 @@ import java.util.*;
|
||||
import static org.ruoyi.workflow.cosntant.AdiConstant.WorkflowConstant.DEFAULT_OUTPUT_PARAM_NAME;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class WorkflowUtil {
|
||||
@Service
|
||||
public class WorkflowUtil extends AbstractChatMessageService {
|
||||
|
||||
@Resource
|
||||
private ChatServiceFactory chatServiceFactory;
|
||||
|
||||
@Resource
|
||||
private ImageServiceFactory imageServiceFactory;
|
||||
|
||||
@Resource
|
||||
private IChatModelService chatModelService;
|
||||
|
||||
@@ -103,7 +112,7 @@ public class WorkflowUtil {
|
||||
}
|
||||
|
||||
public void streamingInvokeLLM(WfState wfState, WfNodeState state, WorkflowNode node, String modelName,
|
||||
List<SystemMessage> systemMessage) {
|
||||
List<SystemMessage> systemMessage, String nodeMessageTemplate) {
|
||||
log.info("stream invoke, modelName: {}", modelName);
|
||||
|
||||
// 根据模型名称查询模型信息
|
||||
@@ -112,16 +121,45 @@ public class WorkflowUtil {
|
||||
throw new IllegalArgumentException("模型不存在: " + modelName);
|
||||
}
|
||||
|
||||
// 根据模型名称找到模型实体
|
||||
String modelVoCategory = chatModelVo.getCategory();
|
||||
// 路由服务提供商
|
||||
String category = chatModelVo.getProviderCode();
|
||||
// 根据 category 获取对应的 ChatService(不使用计费代理,工作流场景单独计费)
|
||||
IChatService chatService = chatServiceFactory.getOriginalService(modelVoCategory);
|
||||
IChatService chatService = chatServiceFactory.getOriginalService(category);
|
||||
|
||||
// 获取用户信息和Token以及SSe连接对象(对话接口需要使用)
|
||||
Long sessionId = wfState.getSessionId();
|
||||
Long userId = wfState.getUserId();
|
||||
String tokenValue = wfState.getTokenValue();
|
||||
SseEmitter sseEmitter = wfState.getSseEmitter();
|
||||
|
||||
// 构建 ruoyi-ai 的 ChatRequest
|
||||
List<ChatMessage> chatMessages = new ArrayList<>();
|
||||
addUserMessage(node, state.getInputs(), chatMessages);
|
||||
chatMessages.addAll(systemMessage);
|
||||
|
||||
// 定义模型调用对象
|
||||
ChatRequest chatRequest = new ChatRequest();
|
||||
// 目前工作流深度思考成员变量只能写死
|
||||
chatRequest.setSessionId(sessionId);
|
||||
chatRequest.setEnableThinking(false);
|
||||
chatRequest.setModel(modelName);
|
||||
chatRequest.setChatMessages(chatMessages);
|
||||
|
||||
// 构建流式生成器
|
||||
StreamingChatGenerator<AgentState> streamingGenerator = StreamingChatGenerator.builder()
|
||||
.mapResult(response -> {
|
||||
String responseTxt = response.aiMessage().text();
|
||||
log.info("llm response:{}", responseTxt);
|
||||
|
||||
// 会话ID不为空时插入数据库
|
||||
if (sessionId != null){
|
||||
// 获取模板消息拼接信息体
|
||||
String message = nodeMessageTemplate + responseTxt;
|
||||
// 保存助手回复消息
|
||||
saveChatMessage(chatRequest, userId, message, RoleType.ASSISTANT.getName(), chatModelVo);
|
||||
log.info("{}消息结束,已保存到数据库", getProviderName());
|
||||
}
|
||||
|
||||
// 传递所有输入数据 + 添加 LLM 输出
|
||||
wfState.getNodeStateByNodeUuid(node.getUuid()).ifPresent(item -> {
|
||||
List<NodeIOData> outputs = new ArrayList<>(item.getInputs());
|
||||
@@ -136,24 +174,9 @@ public class WorkflowUtil {
|
||||
.startingState(state)
|
||||
.build();
|
||||
|
||||
// 获取用户信息和Token以及SSe连接对象(对话接口需要使用)
|
||||
Long userId = wfState.getUserId();
|
||||
String tokenValue = wfState.getTokenValue();
|
||||
SseEmitter sseEmitter = wfState.getSseEmitter();
|
||||
// 构建流式回调响应器
|
||||
StreamingChatResponseHandler handler = streamingGenerator.handler();
|
||||
|
||||
// 构建 ruoyi-ai 的 ChatRequest
|
||||
List<ChatMessage> chatMessages = new ArrayList<>();
|
||||
addUserMessage(node, state.getInputs(), chatMessages);
|
||||
chatMessages.addAll(systemMessage);
|
||||
|
||||
// 定义模型调用对象
|
||||
ChatRequest chatRequest = new ChatRequest();
|
||||
// 目前工作流深度思考成员变量只能写死
|
||||
chatRequest.setEnableThinking(false);
|
||||
chatRequest.setModel(modelName);
|
||||
chatRequest.setChatMessages(chatMessages);
|
||||
|
||||
//构建聊天对话上下文参数
|
||||
ChatContext chatContext = ChatContext.builder()
|
||||
.chatModelVo(chatModelVo)
|
||||
@@ -213,4 +236,29 @@ public class WorkflowUtil {
|
||||
.filter(item -> nameSet.contains(item.getName()))
|
||||
.map(item -> getMessage("user", item.getContent().getValue().toString())).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用LLM 根据文字生成图片
|
||||
*/
|
||||
public String buildTextToImage(String modelName, String prompt, String size, Integer seed){
|
||||
log.info("Generate image invoke, modelName: {}", modelName);
|
||||
// 根据模型名称查询模型信息
|
||||
ChatModelVo chatModelVo = chatModelService.selectModelByName(modelName);
|
||||
if (chatModelVo == null) {
|
||||
throw new IllegalArgumentException("模型不存在: " + modelName);
|
||||
}
|
||||
// 根据模型名称找到模型实体
|
||||
String category = chatModelVo.getProviderCode();
|
||||
// 根据 category 获取对应的 IImageGenerationService(不使用计费代理,工作流场景单独计费)
|
||||
IImageGenerationService imageService = imageServiceFactory.getOriginalService(category);
|
||||
// 构建文生图上下文对象
|
||||
ImageContext imageContext = ImageContext.builder()
|
||||
.chatModelVo(chatModelVo)
|
||||
.prompt(prompt)
|
||||
.size(size)
|
||||
.seed(seed)
|
||||
.build();
|
||||
// 调用LLM 生成图片
|
||||
return imageService.generateImage(imageContext);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,8 @@ import org.ruoyi.workflow.entity.WorkflowNode;
|
||||
import org.ruoyi.workflow.enums.WfIODataTypeEnum;
|
||||
import org.ruoyi.workflow.util.JsonUtil;
|
||||
import org.ruoyi.workflow.util.SpringUtil;
|
||||
import org.ruoyi.workflow.workflow.NodeProcessResult;
|
||||
import org.ruoyi.workflow.workflow.WfNodeInputConfig;
|
||||
import org.ruoyi.workflow.workflow.WfNodeState;
|
||||
import org.ruoyi.workflow.workflow.WfState;
|
||||
import org.ruoyi.workflow.util.WorkflowMessageUtil;
|
||||
import org.ruoyi.workflow.workflow.*;
|
||||
import org.ruoyi.workflow.workflow.data.NodeIOData;
|
||||
import org.ruoyi.workflow.workflow.def.WfNodeIO;
|
||||
import org.ruoyi.workflow.workflow.def.WfNodeParamRef;
|
||||
@@ -30,8 +28,8 @@ import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.ruoyi.workflow.cosntant.AdiConstant.WorkflowConstant.*;
|
||||
import static org.ruoyi.workflow.enums.ErrorEnum.A_WF_NODE_CONFIG_ERROR;
|
||||
import static org.ruoyi.workflow.enums.ErrorEnum.A_WF_NODE_CONFIG_NOT_FOUND;
|
||||
import static org.ruoyi.common.chat.enums.ErrorEnum.A_WF_NODE_CONFIG_ERROR;
|
||||
import static org.ruoyi.common.chat.enums.ErrorEnum.A_WF_NODE_CONFIG_NOT_FOUND;
|
||||
|
||||
/**
|
||||
* 节点实例-运行时
|
||||
@@ -224,4 +222,19 @@ public abstract class AbstractWfNode {
|
||||
return nodeConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话消息保存方法
|
||||
*/
|
||||
public void notifyAndStoreMessage(WfState wfState, String message) {
|
||||
WorkflowMessageUtil.notifyAndStoreMessage(wfState, wfState.getSseEmitter(), node, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取节点的响应模板
|
||||
* @param configKey 参数Key
|
||||
* @return 返回模板样式
|
||||
*/
|
||||
public String getNodeMessageTemplate(String configKey){
|
||||
return WorkflowMessageUtil.getNodeMessageTemplate(configKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,13 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.ruoyi.workflow.entity.WorkflowComponent;
|
||||
import org.ruoyi.workflow.entity.WorkflowNode;
|
||||
import org.ruoyi.workflow.util.SpringUtil;
|
||||
import org.ruoyi.workflow.util.WorkflowMessageUtil;
|
||||
import org.ruoyi.workflow.workflow.NodeProcessResult;
|
||||
import org.ruoyi.workflow.workflow.WfNodeState;
|
||||
import org.ruoyi.workflow.workflow.WfState;
|
||||
import org.ruoyi.workflow.workflow.WorkflowUtil;
|
||||
import org.ruoyi.workflow.workflow.node.AbstractWfNode;
|
||||
import org.ruoyi.workflow.workflow.node.enmus.NodeMessageTemplateEnum;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -46,7 +48,11 @@ public class LLMAnswerNode extends AbstractWfNode {
|
||||
String modelName = nodeConfigObj.getModelName();
|
||||
// 转换系统信息结构
|
||||
List<SystemMessage> systemMessage = List.of(new SystemMessage(prompt));
|
||||
workflowUtil.streamingInvokeLLM(wfState, state, node, modelName, systemMessage);
|
||||
// 获取节点模板提示词信息
|
||||
String nodeMessageTemplate = WorkflowMessageUtil.getNodeMessageTemplate(NodeMessageTemplateEnum.LLM_RESPONSE.getValue());
|
||||
// 发送SSE驱动事件消息
|
||||
WorkflowMessageUtil.sendEmitterMessage(wfState.getSseEmitter(), node, nodeMessageTemplate);
|
||||
workflowUtil.streamingInvokeLLM(wfState, state, node, modelName, systemMessage, nodeMessageTemplate);
|
||||
return new NodeProcessResult();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,6 @@ public class LLMAnswerNodeConfig {
|
||||
@NotBlank
|
||||
private String prompt;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
// @NotBlank
|
||||
private String category;
|
||||
|
||||
@NotNull
|
||||
@JsonProperty("model_name")
|
||||
private String modelName;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.ruoyi.workflow.workflow.node.enmus;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 节点消息模板ConfigKey枚举
|
||||
*/
|
||||
@Getter
|
||||
public enum NodeMessageTemplateEnum {
|
||||
HTTP_REQUEST("node.httpRequest.template"),
|
||||
MAIL_SEND("node.mailsend.template"),
|
||||
IMAGE("node.image.template"),
|
||||
HUMAN_FEED_BACK("node.humanFeedback.template"),
|
||||
SWITCH("node.switch.template"),
|
||||
LLM_RESPONSE("node.llmAnswer.template"),
|
||||
KEYWORD_EXTRACTOR("node.keywordExtractor.template"),
|
||||
EXCEPTION("node.exception.template"),
|
||||
END("node.end.template");
|
||||
|
||||
private final String value;
|
||||
|
||||
NodeMessageTemplateEnum(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import org.ruoyi.workflow.workflow.WfNodeState;
|
||||
import org.ruoyi.workflow.workflow.WfState;
|
||||
import org.ruoyi.workflow.workflow.data.NodeIOData;
|
||||
import org.ruoyi.workflow.workflow.node.AbstractWfNode;
|
||||
import org.ruoyi.workflow.workflow.node.enmus.NodeMessageTemplateEnum;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
@@ -32,6 +33,8 @@ public class HttpRequestNode extends AbstractWfNode {
|
||||
|
||||
@Override
|
||||
public NodeProcessResult onProcess() {
|
||||
// 获取节点模板提示词信息
|
||||
String nodeMessageTemplate = getNodeMessageTemplate(NodeMessageTemplateEnum.HTTP_REQUEST.getValue());
|
||||
try {
|
||||
HttpRequestNodeConfig config = checkAndGetConfig(HttpRequestNodeConfig.class);
|
||||
List<NodeIOData> inputs = state.getInputs();
|
||||
@@ -63,6 +66,9 @@ public class HttpRequestNode extends AbstractWfNode {
|
||||
List<NodeIOData> outputs = new ArrayList<>();
|
||||
outputs.add(NodeIOData.createByText("output", "HTTP响应", response));
|
||||
|
||||
// 保存成功会话信息且发送驱动消息事件
|
||||
String message = nodeMessageTemplate + response;
|
||||
notifyAndStoreMessage(wfState, message);
|
||||
return NodeProcessResult.builder().content(outputs).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -73,6 +79,9 @@ public class HttpRequestNode extends AbstractWfNode {
|
||||
errorOutputs.add(NodeIOData.createByText("output", "错误", ""));
|
||||
errorOutputs.add(NodeIOData.createByText("error", "HTTP请求错误", e.getMessage()));
|
||||
|
||||
// 保存失败会话信息且发送驱动消息事件
|
||||
String message = nodeMessageTemplate + e.getMessage();
|
||||
notifyAndStoreMessage(wfState, message);
|
||||
return NodeProcessResult.builder().content(errorOutputs).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.ruoyi.workflow.workflow.node;
|
||||
package org.ruoyi.workflow.workflow.node.humanFeedBack;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -7,7 +7,9 @@ import org.ruoyi.workflow.entity.WorkflowNode;
|
||||
import org.ruoyi.workflow.workflow.NodeProcessResult;
|
||||
import org.ruoyi.workflow.workflow.WfNodeState;
|
||||
import org.ruoyi.workflow.workflow.WfState;
|
||||
import org.ruoyi.workflow.workflow.WorkflowUtil;
|
||||
import org.ruoyi.workflow.workflow.data.NodeIOData;
|
||||
import org.ruoyi.workflow.workflow.node.AbstractWfNode;
|
||||
|
||||
import static org.ruoyi.workflow.cosntant.AdiConstant.WorkflowConstant.*;
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package org.ruoyi.workflow.workflow.node.image;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.ruoyi.workflow.entity.WorkflowComponent;
|
||||
import org.ruoyi.workflow.entity.WorkflowNode;
|
||||
import org.ruoyi.workflow.util.SpringUtil;
|
||||
import org.ruoyi.workflow.workflow.NodeProcessResult;
|
||||
import org.ruoyi.workflow.workflow.WfNodeState;
|
||||
import org.ruoyi.workflow.workflow.WfState;
|
||||
import org.ruoyi.workflow.workflow.WorkflowUtil;
|
||||
import org.ruoyi.workflow.workflow.data.NodeIOData;
|
||||
import org.ruoyi.workflow.workflow.node.AbstractWfNode;
|
||||
import org.ruoyi.workflow.workflow.node.enmus.NodeMessageTemplateEnum;
|
||||
|
||||
import static org.ruoyi.workflow.cosntant.AdiConstant.WorkflowConstant.NODE_PROCESS_STATUS_SUCCESS;
|
||||
|
||||
/**
|
||||
* 【节点】文生图 <br/>
|
||||
* 节点内容固定格式:ImageNodeConfig
|
||||
*/
|
||||
@Slf4j
|
||||
public class ImageNode extends AbstractWfNode {
|
||||
|
||||
public ImageNode(WorkflowComponent wfComponent, WorkflowNode nodeDef, WfState wfState, WfNodeState nodeState) {
|
||||
super(wfComponent, nodeDef, wfState, nodeState);
|
||||
}
|
||||
|
||||
/**
|
||||
* nodeConfig格式:
|
||||
* {"prompt": "{input}","model_name":"wan2.5-t2i-preview","size":"1024*1024"}
|
||||
*
|
||||
* @return 图片地址URL
|
||||
*/
|
||||
@Override
|
||||
public NodeProcessResult onProcess() {
|
||||
ImageNodeConfig nodeConfigObj = checkAndGetConfig(ImageNodeConfig.class);
|
||||
String inputText = getFirstInputText();
|
||||
log.info("Image node config:{}", nodeConfigObj);
|
||||
String prompt = inputText;
|
||||
if (StringUtils.isNotBlank(nodeConfigObj.getPrompt())) {
|
||||
prompt = WorkflowUtil.renderTemplate(nodeConfigObj.getPrompt(), state.getInputs());
|
||||
}
|
||||
log.info("Image prompt:{}", prompt);
|
||||
// 获取工作流实例
|
||||
WorkflowUtil workflowUtil = SpringUtil.getBean(WorkflowUtil.class);
|
||||
// 获取模型名称
|
||||
String modelName = nodeConfigObj.getModelName();
|
||||
// 获取图片大小
|
||||
String size = nodeConfigObj.getSize();
|
||||
// 获取随机数种子
|
||||
Integer seed = nodeConfigObj.getSeed();
|
||||
// 调用LLM生成图片(后续可以将图片保存到OSS中)
|
||||
String imageUrl = workflowUtil.buildTextToImage(modelName, prompt, size, seed);
|
||||
// 获取节点模板提示词信息
|
||||
String nodeMessageTemplate = getNodeMessageTemplate(NodeMessageTemplateEnum.IMAGE.getValue());
|
||||
// 保存成功信息且发送驱动消息事件
|
||||
String message = nodeMessageTemplate + imageUrl;
|
||||
notifyAndStoreMessage(wfState, message);
|
||||
// 创建节点参数对象
|
||||
NodeIOData nodeIOData = NodeIOData.createByText("output", "image", imageUrl);
|
||||
// 添加到输出列表以便给后续节点使用
|
||||
state.getOutputs().add(nodeIOData);
|
||||
// 设置为成功状态
|
||||
state.setProcessStatus(NODE_PROCESS_STATUS_SUCCESS);
|
||||
return new NodeProcessResult();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.ruoyi.workflow.workflow.node.image;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@EqualsAndHashCode
|
||||
@Data
|
||||
public class ImageNodeConfig {
|
||||
|
||||
/**
|
||||
* 提示词
|
||||
*/
|
||||
private String prompt;
|
||||
|
||||
/**
|
||||
* 模型名称
|
||||
*/
|
||||
@NotNull
|
||||
@JsonProperty("model_name")
|
||||
private String modelName;
|
||||
|
||||
/**
|
||||
* 图片尺寸大小
|
||||
*/
|
||||
private String size;
|
||||
|
||||
/**
|
||||
* 随机数种子
|
||||
*/
|
||||
@Min(value = 0, message = "随机数种子不能小于0")
|
||||
@Max(value = 2147483647, message = "随机数种子不能大于2147483647")
|
||||
private Integer seed;
|
||||
}
|
||||
@@ -6,12 +6,14 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.ruoyi.workflow.entity.WorkflowComponent;
|
||||
import org.ruoyi.workflow.entity.WorkflowNode;
|
||||
import org.ruoyi.workflow.util.SpringUtil;
|
||||
import org.ruoyi.workflow.util.WorkflowMessageUtil;
|
||||
import org.ruoyi.workflow.workflow.NodeProcessResult;
|
||||
import org.ruoyi.workflow.workflow.WfNodeState;
|
||||
import org.ruoyi.workflow.workflow.WfState;
|
||||
import org.ruoyi.workflow.workflow.WorkflowUtil;
|
||||
import org.ruoyi.workflow.workflow.data.NodeIOData;
|
||||
import org.ruoyi.workflow.workflow.node.AbstractWfNode;
|
||||
import org.ruoyi.workflow.workflow.node.enmus.NodeMessageTemplateEnum;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -66,8 +68,12 @@ public class KeywordExtractorNode extends AbstractWfNode {
|
||||
WorkflowUtil workflowUtil = SpringUtil.getBean(WorkflowUtil.class);
|
||||
String modelName = config.getModelName();
|
||||
List<SystemMessage> systemMessage = List.of(new SystemMessage(prompt));
|
||||
// 获取节点模板提示词信息
|
||||
String nodeMessageTemplate = WorkflowMessageUtil.getNodeMessageTemplate(NodeMessageTemplateEnum.KEYWORD_EXTRACTOR.getValue());
|
||||
// 发送SSE事件消息
|
||||
WorkflowMessageUtil.sendEmitterMessage(wfState.getSseEmitter(), node, nodeMessageTemplate);
|
||||
// 使用流式调用
|
||||
workflowUtil.streamingInvokeLLM(wfState, state, node, modelName, systemMessage);
|
||||
workflowUtil.streamingInvokeLLM(wfState, state, node, modelName, systemMessage, nodeMessageTemplate);
|
||||
return new NodeProcessResult();
|
||||
}
|
||||
|
||||
|
||||
@@ -161,7 +161,8 @@ public class KnowledgeRetrievalNode extends AbstractWfNode {
|
||||
tempState,
|
||||
tempNode,
|
||||
modelName,
|
||||
systemMessage
|
||||
systemMessage,
|
||||
""
|
||||
);
|
||||
|
||||
// 等待LLM响应完成(最多等待30秒)
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package org.ruoyi.workflow.workflow.node.mailSend;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alibaba.fastjson.JSONValidator;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -11,6 +14,7 @@ import org.ruoyi.workflow.workflow.WfState;
|
||||
import org.ruoyi.workflow.workflow.WorkflowUtil;
|
||||
import org.ruoyi.workflow.workflow.data.NodeIOData;
|
||||
import org.ruoyi.workflow.workflow.node.AbstractWfNode;
|
||||
import org.ruoyi.workflow.workflow.node.enmus.NodeMessageTemplateEnum;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.mail.javamail.JavaMailSenderImpl;
|
||||
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||
@@ -28,9 +32,20 @@ public class MailSendNode extends AbstractWfNode {
|
||||
|
||||
@Override
|
||||
public NodeProcessResult onProcess() {
|
||||
// 获取节点模板提示词信息
|
||||
String nodeMessageTemplate = getNodeMessageTemplate(NodeMessageTemplateEnum.MAIL_SEND.getValue());
|
||||
try {
|
||||
MailSendNodeConfig config = checkAndGetConfig(MailSendNodeConfig.class);
|
||||
List<NodeIOData> inputs = state.getInputs();
|
||||
// 获取输入信息
|
||||
String input = getDataFromInput(inputs);
|
||||
// 判断是否为JSON格式(LLM输出转换 由LLM生成格式)
|
||||
if (StringUtils.isNotBlank(input) && isJson(input)) {
|
||||
JSONObject inputJson = JSON.parseObject(input);
|
||||
JSONObject configJson = (JSONObject) JSON.toJSON(config);
|
||||
configJson.putAll(inputJson);
|
||||
config = configJson.toJavaObject(MailSendNodeConfig.class);
|
||||
}
|
||||
|
||||
// 安全获取模板(使用 defaultString 避免 null)
|
||||
String subjectTemplate = StringUtils.defaultString(config.getSubject());
|
||||
@@ -49,15 +64,7 @@ public class MailSendNode extends AbstractWfNode {
|
||||
content = WorkflowUtil.renderTemplate(contentTemplate, inputs);
|
||||
} else {
|
||||
// 优先使用 output,如果没有则使用 input
|
||||
content = inputs.stream()
|
||||
.filter(item -> "output".equals(item.getName()))
|
||||
.map(NodeIOData::valueToString)
|
||||
.findFirst()
|
||||
.orElseGet(() -> inputs.stream()
|
||||
.filter(item -> "input".equals(item.getName()))
|
||||
.map(NodeIOData::valueToString)
|
||||
.findFirst()
|
||||
.orElse(""));
|
||||
content = getDataFromInput(inputs);
|
||||
}
|
||||
|
||||
// 将换行符转换为 HTML 换行
|
||||
@@ -84,9 +91,9 @@ public class MailSendNode extends AbstractWfNode {
|
||||
|
||||
// 设置收件人
|
||||
String[] toArray = Arrays.stream(toMails.split(","))
|
||||
.map(String::trim)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.toArray(String[]::new);
|
||||
.map(String::trim)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.toArray(String[]::new);
|
||||
if (toArray.length == 0) {
|
||||
throw new IllegalArgumentException("收件人邮箱列表为空");
|
||||
}
|
||||
@@ -95,9 +102,9 @@ public class MailSendNode extends AbstractWfNode {
|
||||
// 设置抄送(如有)
|
||||
if (StringUtils.isNotBlank(ccMails)) {
|
||||
String[] ccArray = Arrays.stream(ccMails.split(","))
|
||||
.map(String::trim)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.toArray(String[]::new);
|
||||
.map(String::trim)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.toArray(String[]::new);
|
||||
if (ccArray.length > 0) {
|
||||
helper.setCc(ccArray);
|
||||
}
|
||||
@@ -111,25 +118,29 @@ public class MailSendNode extends AbstractWfNode {
|
||||
mailSender.send(message);
|
||||
log.info("Email sent successfully to: {}", toMails);
|
||||
|
||||
// 保存成功会话信息且发送驱动消息事件
|
||||
String resultMessage = nodeMessageTemplate + "发送邮箱成功";
|
||||
notifyAndStoreMessage(wfState, resultMessage);
|
||||
|
||||
// 构造输出:统一输出为 output 参数
|
||||
List<NodeIOData> outputs = new java.util.ArrayList<>();
|
||||
|
||||
// 优先使用 output,如果没有则使用 input(但重命名为 output)
|
||||
inputs.stream()
|
||||
.filter(item -> "output".equals(item.getName()))
|
||||
.findFirst()
|
||||
.ifPresentOrElse(
|
||||
outputs::add,
|
||||
() -> inputs.stream()
|
||||
.filter(item -> "input".equals(item.getName()))
|
||||
.findFirst()
|
||||
.ifPresent(inputParam -> {
|
||||
String title = inputParam.getContent() != null && inputParam.getContent().getTitle() != null
|
||||
? inputParam.getContent().getTitle() : "";
|
||||
NodeIOData outputParam = NodeIOData.createByText("output", title, inputParam.valueToString());
|
||||
outputs.add(outputParam);
|
||||
})
|
||||
);
|
||||
.filter(item -> "output".equals(item.getName()))
|
||||
.findFirst()
|
||||
.ifPresentOrElse(
|
||||
outputs::add,
|
||||
() -> inputs.stream()
|
||||
.filter(item -> "input".equals(item.getName()))
|
||||
.findFirst()
|
||||
.ifPresent(inputParam -> {
|
||||
String title = inputParam.getContent() != null && inputParam.getContent().getTitle() != null
|
||||
? inputParam.getContent().getTitle() : "";
|
||||
NodeIOData outputParam = NodeIOData.createByText("output", title, resultMessage);
|
||||
outputs.add(outputParam);
|
||||
})
|
||||
);
|
||||
|
||||
return NodeProcessResult.builder().content(outputs).build();
|
||||
|
||||
@@ -138,23 +149,27 @@ public class MailSendNode extends AbstractWfNode {
|
||||
// 异常时也统一输出为 output 参数,添加错误信息
|
||||
List<NodeIOData> errorOutputs = new java.util.ArrayList<>();
|
||||
|
||||
state.getInputs().stream()
|
||||
.filter(item -> "output".equals(item.getName()))
|
||||
.findFirst()
|
||||
.ifPresentOrElse(
|
||||
errorOutputs::add,
|
||||
() -> state.getInputs().stream()
|
||||
.filter(item -> "input".equals(item.getName()))
|
||||
.findFirst()
|
||||
.ifPresent(inputParam -> {
|
||||
String title = inputParam.getContent() != null && inputParam.getContent().getTitle() != null
|
||||
? inputParam.getContent().getTitle() : "";
|
||||
NodeIOData outputParam = NodeIOData.createByText("output", title, inputParam.valueToString());
|
||||
errorOutputs.add(outputParam);
|
||||
})
|
||||
);
|
||||
// 保存失败会话信息且发送驱动消息事件
|
||||
String resultMessage = nodeMessageTemplate + "发送邮箱失败: " + e.getMessage();
|
||||
notifyAndStoreMessage(wfState, resultMessage);
|
||||
|
||||
errorOutputs.add(NodeIOData.createByText("error", "mail", e.getMessage()));
|
||||
state.getInputs().stream()
|
||||
.filter(item -> "output".equals(item.getName()))
|
||||
.findFirst()
|
||||
.ifPresentOrElse(
|
||||
errorOutputs::add,
|
||||
() -> state.getInputs().stream()
|
||||
.filter(item -> "input".equals(item.getName()))
|
||||
.findFirst()
|
||||
.ifPresent(inputParam -> {
|
||||
String title = inputParam.getContent() != null && inputParam.getContent().getTitle() != null
|
||||
? inputParam.getContent().getTitle() : "";
|
||||
NodeIOData outputParam = NodeIOData.createByText("output", title, resultMessage);
|
||||
errorOutputs.add(outputParam);
|
||||
})
|
||||
);
|
||||
|
||||
errorOutputs.add(NodeIOData.createByText("error", "mail", resultMessage));
|
||||
return NodeProcessResult.builder().content(errorOutputs).build();
|
||||
}
|
||||
}
|
||||
@@ -174,4 +189,40 @@ public class MailSendNode extends AbstractWfNode {
|
||||
|
||||
return sender;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取信息
|
||||
* @param inputs 用户输入
|
||||
* @return 返回输入信息
|
||||
*/
|
||||
public String getDataFromInput(List<NodeIOData> inputs) {
|
||||
return inputs.stream()
|
||||
.filter(item -> "output".equals(item.getName()))
|
||||
.map(NodeIOData::valueToString)
|
||||
.findFirst()
|
||||
.orElseGet(() -> inputs.stream()
|
||||
.filter(item -> "input".equals(item.getName()))
|
||||
.map(NodeIOData::valueToString)
|
||||
.findFirst()
|
||||
.orElse(""));
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断字符串是否为合法的 JSON 格式
|
||||
*
|
||||
* @param str 待检测的字符串
|
||||
* @return true 表示是合法 JSON (包括 JSONObject, JSONArray, 或基本类型值)
|
||||
*/
|
||||
public static boolean isJson(String str) {
|
||||
if (str == null || str.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
// 使用 try-with-resources 正确处理 JSONValidator 资源关闭
|
||||
try (JSONValidator validator = JSONValidator.from(str.trim())) {
|
||||
return validator.getType() == JSONValidator.Type.Object;
|
||||
} catch (Exception e) {
|
||||
log.warn("JSON格式校验失败: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ import org.ruoyi.workflow.workflow.node.AbstractWfNode;
|
||||
import java.util.List;
|
||||
|
||||
import static org.ruoyi.workflow.cosntant.AdiConstant.WorkflowConstant.DEFAULT_OUTPUT_PARAM_NAME;
|
||||
import static org.ruoyi.workflow.enums.ErrorEnum.A_WF_NODE_CONFIG_ERROR;
|
||||
import static org.ruoyi.workflow.enums.ErrorEnum.A_WF_NODE_CONFIG_NOT_FOUND;
|
||||
import static org.ruoyi.common.chat.enums.ErrorEnum.A_WF_NODE_CONFIG_ERROR;
|
||||
import static org.ruoyi.common.chat.enums.ErrorEnum.A_WF_NODE_CONFIG_NOT_FOUND;
|
||||
|
||||
@Slf4j
|
||||
public class StartNode extends AbstractWfNode {
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.ruoyi.workflow.workflow.WfNodeState;
|
||||
import org.ruoyi.workflow.workflow.WfState;
|
||||
import org.ruoyi.workflow.workflow.data.NodeIOData;
|
||||
import org.ruoyi.workflow.workflow.node.AbstractWfNode;
|
||||
import org.ruoyi.workflow.workflow.node.enmus.NodeMessageTemplateEnum;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
@@ -43,6 +44,9 @@ public class SwitcherNode extends AbstractWfNode {
|
||||
log.info("条件分支节点处理中,分支数量: {}",
|
||||
config.getCases() != null ? config.getCases().size() : 0);
|
||||
|
||||
// 获取提示模板
|
||||
String nodeMessageTemplate = getNodeMessageTemplate(NodeMessageTemplateEnum.SWITCH.getValue());
|
||||
|
||||
// 按顺序评估每个分支
|
||||
if (config.getCases() != null) {
|
||||
for (int i = 0; i < config.getCases().size(); i++) {
|
||||
@@ -52,13 +56,17 @@ public class SwitcherNode extends AbstractWfNode {
|
||||
|
||||
if (evaluateCase(switcherCase, inputs)) {
|
||||
// 检查目标节点UUID是否为空
|
||||
if (StringUtils.isBlank(switcherCase.getTargetNodeUuid())) {
|
||||
String targetNodeUuid = switcherCase.getTargetNodeUuid();
|
||||
if (StringUtils.isBlank(targetNodeUuid)) {
|
||||
log.warn("分支 {} 匹配但目标节点UUID为空,跳过到下一个分支", i + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 根据目标节点UUID获取对应节点名称
|
||||
findNodeAndNotify(targetNodeUuid, nodeMessageTemplate);
|
||||
|
||||
log.info("分支 {} 匹配,跳转到节点: {}",
|
||||
i + 1, switcherCase.getTargetNodeUuid());
|
||||
i + 1, targetNodeUuid);
|
||||
|
||||
// 构造输出:只保留 output 和其他非 input 参数 + 添加分支匹配信息
|
||||
List<NodeIOData> outputs = new java.util.ArrayList<>();
|
||||
@@ -85,12 +93,12 @@ public class SwitcherNode extends AbstractWfNode {
|
||||
|
||||
outputs.add(NodeIOData.createByText("matched_case", "switcher", String.valueOf(i + 1)));
|
||||
outputs.add(NodeIOData.createByText("case_uuid", "switcher", switcherCase.getUuid()));
|
||||
outputs.add(NodeIOData.createByText("target_node", "switcher", switcherCase.getTargetNodeUuid()));
|
||||
outputs.add(NodeIOData.createByText("target_node", "switcher", targetNodeUuid));
|
||||
|
||||
// WorkflowEngine 会自动将 nextNodeUuid 放入 resultMap 的 "next" 键中
|
||||
return NodeProcessResult.builder()
|
||||
.content(outputs)
|
||||
.nextNodeUuid(switcherCase.getTargetNodeUuid())
|
||||
.nextNodeUuid(targetNodeUuid)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -99,6 +107,9 @@ public class SwitcherNode extends AbstractWfNode {
|
||||
// 所有分支都不满足,使用默认分支
|
||||
log.info("没有分支匹配,使用默认分支: {}", config.getDefaultTargetNodeUuid());
|
||||
|
||||
// 根据默认目标节点UUID获取对应节点名称
|
||||
findNodeAndNotify(config.getDefaultTargetNodeUuid(), nodeMessageTemplate);
|
||||
|
||||
if (StringUtils.isBlank(config.getDefaultTargetNodeUuid())) {
|
||||
log.warn("默认目标节点UUID为空,工作流可能在此停止");
|
||||
}
|
||||
@@ -154,6 +165,21 @@ public class SwitcherNode extends AbstractWfNode {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据节点ID查询对应节点
|
||||
* @param targetNodeUuid 节点UUID
|
||||
* @param nodeMessageTemplate 节点消息模板
|
||||
*/
|
||||
private void findNodeAndNotify(String targetNodeUuid, String nodeMessageTemplate) {
|
||||
// 根据目标节点UUID获取对应节点名称
|
||||
WorkflowNode workflowNode = workflowNodeService.lambdaQuery().eq(WorkflowNode::getUuid, targetNodeUuid).one();
|
||||
if (null != workflowNode){
|
||||
// 获取节点名称
|
||||
String message = nodeMessageTemplate + workflowNode.getTitle();
|
||||
notifyAndStoreMessage(wfState, message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 评估单个分支的条件
|
||||
*
|
||||
|
||||
@@ -19,11 +19,6 @@
|
||||
<artifactId>ruoyi-common-chat</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-mcp</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common-sensitive</artifactId>
|
||||
@@ -53,6 +48,12 @@
|
||||
<version>${langchain4j.community.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-community-zhipu-ai</artifactId>
|
||||
<version>${langchain4j.community.zhipu.ai.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>dev.langchain4j</groupId>-->
|
||||
<!-- <artifactId>langchain4j-community-zhipu-ai</artifactId>-->
|
||||
|
||||
@@ -7,35 +7,40 @@ import dev.langchain4j.service.V;
|
||||
|
||||
public interface McpAgent {
|
||||
/**
|
||||
* 系统提示词:通用工具调用智能体
|
||||
* 不限定具体工具类型,让 LangChain4j 自动传递工具描述给 LLM
|
||||
* 系统提示词:定义智能体身份、核心职责、强制遵守的规则
|
||||
* 适配SSE流式特性,明确工具全来自远端MCP服务,仅做代理调用和结果整理
|
||||
*/
|
||||
@SystemMessage("""
|
||||
你是一个AI助手,可以通过调用各种工具来帮助用户完成不同的任务。
|
||||
你是专业的MCP服务工具代理智能体,核心能力是通过HTTP SSE流式传输协议,调用本地http://localhost:8085/sse地址上MCP服务端注册的所有工具。
|
||||
你的核心工作职责:
|
||||
1. 准确理解用户的自然语言请求,判断需要调用MCP服务端的哪一个/哪些工具;
|
||||
2. 通过绑定的工具提供者,向MCP服务端发起工具调用请求,传递完整的工具执行参数;
|
||||
3. 实时接收MCP服务端通过SSE流式返回的工具执行结果,保证结果片段的完整性;
|
||||
4. 将流式结果按原始顺序整理为清晰、易懂的自然语言答案,返回给用户。
|
||||
|
||||
【工具使用规则】
|
||||
1. 根据用户的请求,判断需要使用哪些工具
|
||||
2. 仔细阅读每个工具的描述,确保理解工具的功能和参数要求
|
||||
3. 使用正确的参数调用工具
|
||||
4. 如果工具执行失败,向用户友好地说明错误原因,并尝试提供替代方案
|
||||
5. 对于复杂任务,可以分步骤使用多个工具完成
|
||||
6. 将工具执行结果以清晰易懂的方式呈现给用户
|
||||
|
||||
【响应格式】
|
||||
- 直接回答用户的问题
|
||||
- 如果使用了工具,说明使用了什么工具以及结果
|
||||
- 如果遇到错误,提供友好的错误信息和解决建议
|
||||
【强制遵守的核心规则 - 无例外】
|
||||
1. 所有工具调用必须通过远端MCP服务执行,严禁尝试本地执行任何业务逻辑;
|
||||
2. 处理SSE流式结果时,严格保留结果片段的返回顺序,不得打乱或遗漏;
|
||||
3. 若MCP服务返回错误(如工具未找到、参数错误、执行失败),直接将错误信息友好反馈给用户,无需额外推理;
|
||||
4. 工具执行结果若为结构化数据(如JSON、表格),需格式化后返回,提升可读性。
|
||||
""")
|
||||
|
||||
/**
|
||||
* 用户消息模板:{{query}}为参数占位符,与方法入参的@V("query")绑定
|
||||
*/
|
||||
@UserMessage("""
|
||||
请通过调用MCP服务端的工具,处理用户的以下请求:
|
||||
{{query}}
|
||||
""")
|
||||
|
||||
@Agent("通用工具调用智能体")
|
||||
/**
|
||||
* 智能体对外调用入口
|
||||
* @param query 用户的自然语言请求
|
||||
* @return 处理结果
|
||||
* 智能体标识:用于日志打印、监控追踪、多智能体协作时的身份识别
|
||||
*/
|
||||
@Agent("MCP服务SSE流式代理智能体-连接本地8085端口")
|
||||
/**
|
||||
* 智能体对外调用入口方法
|
||||
* @param query 用户的自然语言请求(如:生成订单数据柱状图、查询今日天气)
|
||||
* @V("query") 将方法入参值绑定到@UserMessage的{{query}}占位符中
|
||||
* @return 整理后的MCP工具执行结果(流式结果会自动拼接为完整字符串)
|
||||
*/
|
||||
String callMcpTool(@V("query") String query);
|
||||
}
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
package org.ruoyi.agent.config;
|
||||
// package org.ruoyi.agent.config;
|
||||
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import javax.sql.DataSource;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
// import com.zaxxer.hikari.HikariConfig;
|
||||
// import com.zaxxer.hikari.HikariDataSource;
|
||||
// import javax.sql.DataSource;
|
||||
// import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
// import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
// import org.springframework.context.annotation.Bean;
|
||||
// import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Agent MySQL 数据源配置
|
||||
* 为 Agent 配置独立的 MySQL 数据库连接池(HikariCP)
|
||||
*
|
||||
* 仅在 agent.mysql.enabled=true 时启用
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(AgentMysqlProperties.class)
|
||||
@ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true")
|
||||
public class AgentMysqlConfig {
|
||||
// /**
|
||||
// * Agent MySQL 数据源配置
|
||||
// * 为 Agent 配置独立的 MySQL 数据库连接池(HikariCP)
|
||||
// *
|
||||
// * 仅在 agent.mysql.enabled=true 时启用
|
||||
// */
|
||||
// @Configuration
|
||||
// @EnableConfigurationProperties(AgentMysqlProperties.class)
|
||||
// @ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true")
|
||||
// public class AgentMysqlConfig {
|
||||
|
||||
/**
|
||||
* 创建 Agent 专用的数据源
|
||||
* 与项目主数据源隔离,独立管理
|
||||
*
|
||||
* @param properties Agent MySQL 配置属性
|
||||
* @return HikariCP 数据源
|
||||
*/
|
||||
@Bean("agentDataSource")
|
||||
public DataSource agentDataSource(AgentMysqlProperties properties) {
|
||||
HikariConfig config = new HikariConfig();
|
||||
config.setJdbcUrl(properties.getUrl());
|
||||
config.setUsername(properties.getUsername());
|
||||
config.setPassword(properties.getPassword());
|
||||
config.setDriverClassName("com.mysql.cj.jdbc.Driver");
|
||||
config.setMaximumPoolSize(properties.getMaxPoolSize());
|
||||
config.setMinimumIdle(properties.getMinIdle());
|
||||
config.setConnectionTimeout(30000);
|
||||
config.setIdleTimeout(600000);
|
||||
config.setMaxLifetime(1800000);
|
||||
// /**
|
||||
// * 创建 Agent 专用的数据源
|
||||
// * 与项目主数据源隔离,独立管理
|
||||
// *
|
||||
// * @param properties Agent MySQL 配置属性
|
||||
// * @return HikariCP 数据源
|
||||
// */
|
||||
// @Bean("agentDataSource")
|
||||
// public DataSource agentDataSource(AgentMysqlProperties properties) {
|
||||
// HikariConfig config = new HikariConfig();
|
||||
// config.setJdbcUrl(properties.getUrl());
|
||||
// config.setUsername(properties.getUsername());
|
||||
// config.setPassword(properties.getPassword());
|
||||
// config.setDriverClassName("com.mysql.cj.jdbc.Driver");
|
||||
// config.setMaximumPoolSize(properties.getMaxPoolSize());
|
||||
// config.setMinimumIdle(properties.getMinIdle());
|
||||
// config.setConnectionTimeout(30000);
|
||||
// config.setIdleTimeout(600000);
|
||||
// config.setMaxLifetime(1800000);
|
||||
|
||||
return new HikariDataSource(config);
|
||||
}
|
||||
}
|
||||
// return new HikariDataSource(config);
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -16,7 +16,8 @@ public class TableStructure {
|
||||
* 表名
|
||||
*/
|
||||
private String tableName;
|
||||
|
||||
|
||||
private String tableType; // 添加此字段:BASE TABLE 或 VIEW
|
||||
/**
|
||||
* 表注释/说明
|
||||
*/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
package org.ruoyi.agent.manager;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 架构初始化器
|
||||
* 在应用启动完成后自动初始化表结构缓存
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true")
|
||||
// @ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true")
|
||||
public class TableSchemaInitializer {
|
||||
|
||||
@Autowired(required = false)
|
||||
|
||||
@@ -1,16 +1,30 @@
|
||||
package org.ruoyi.agent.manager;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.ruoyi.agent.domain.ColumnInfo;
|
||||
import org.ruoyi.agent.domain.TableStructure;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.baomidou.dynamic.datasource.annotation.DS;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 表结构管理器
|
||||
@@ -24,12 +38,15 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true")
|
||||
@DS("agent")
|
||||
public class TableSchemaManager {
|
||||
|
||||
|
||||
@Autowired(required = false)
|
||||
private DataSource agentDataSource;
|
||||
|
||||
@Value("${AGENT_ALLOWED_TABLES}")
|
||||
private String allowedTables;
|
||||
|
||||
/**
|
||||
* 表结构缓存 (表名 -> 表结构)
|
||||
* 使用 ConcurrentHashMap 支持高并发访问
|
||||
@@ -55,12 +72,12 @@ public class TableSchemaManager {
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
log.info("Initializing database schema cache...");
|
||||
loadAllowedTableSchemas();
|
||||
initialized = true;
|
||||
log.info("Schema cache initialized with {} tables", schemaCache.size());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to initialize schema cache", e);
|
||||
}
|
||||
@@ -103,6 +120,7 @@ public class TableSchemaManager {
|
||||
try (ResultSet tableRs = metaData.getTables(conn.getCatalog(), null, tableName, new String[]{"TABLE"})) {
|
||||
if (tableRs.next()) {
|
||||
table.setTableComment(tableRs.getString("REMARKS"));
|
||||
table.setTableType(tableRs.getString("TABLE_TYPE"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +201,6 @@ public class TableSchemaManager {
|
||||
* 获取所有允许的表名
|
||||
*/
|
||||
public List<String> getAllowedTableNames() {
|
||||
String allowedTables = System.getenv("AGENT_ALLOWED_TABLES");
|
||||
if (allowedTables == null || allowedTables.trim().isEmpty()) {
|
||||
log.warn("AGENT_ALLOWED_TABLES not configured");
|
||||
return new ArrayList<>();
|
||||
@@ -224,7 +241,7 @@ public class TableSchemaManager {
|
||||
* 检查表是否在允许列表中
|
||||
*/
|
||||
private boolean isTableAllowed(String tableName) {
|
||||
String allowedTables = System.getenv("AGENT_ALLOWED_TABLES");
|
||||
// String allowedTables = System.getenv("AGENT_ALLOWED_TABLES");
|
||||
if (allowedTables == null || allowedTables.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
package org.ruoyi.agent.tool;
|
||||
|
||||
import dev.langchain4j.agent.tool.Tool;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
@@ -16,17 +9,26 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
|
||||
|
||||
import dev.langchain4j.agent.tool.Tool;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 执行 SQL 查询的 Tool
|
||||
* 执行指定的 SELECT SQL 查询并返回结果
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true")
|
||||
public class ExecuteSqlQueryTool {
|
||||
|
||||
@Autowired(required = false)
|
||||
private DataSource agentDataSource;
|
||||
private DataSource dataSource;
|
||||
|
||||
/**
|
||||
* 执行 SELECT SQL 查询
|
||||
@@ -37,6 +39,8 @@ public class ExecuteSqlQueryTool {
|
||||
*/
|
||||
@Tool("Execute a SELECT SQL query and return the results. Example: SELECT * FROM sys_user")
|
||||
public String executeSql(String sql) {
|
||||
// 2. 手动推入数据源上下文
|
||||
DynamicDataSourceContextHolder.push("agent");
|
||||
if (sql == null || sql.trim().isEmpty()) {
|
||||
return "Error: SQL query cannot be empty";
|
||||
}
|
||||
@@ -48,11 +52,11 @@ public class ExecuteSqlQueryTool {
|
||||
}
|
||||
|
||||
try {
|
||||
if (agentDataSource == null) {
|
||||
if (dataSource == null) {
|
||||
return "Error: Database datasource not configured";
|
||||
}
|
||||
|
||||
try (Connection connection = agentDataSource.getConnection()) {
|
||||
try (Connection connection = dataSource.getConnection()) {
|
||||
try (PreparedStatement preparedStatement = connection.prepareStatement(sql);
|
||||
ResultSet resultSet = preparedStatement.executeQuery()) {
|
||||
|
||||
@@ -82,7 +86,12 @@ public class ExecuteSqlQueryTool {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error executing SQL: {}", sql, e);
|
||||
// 3. 必须在 finally 中清除上下文,防止污染其他请求
|
||||
DynamicDataSourceContextHolder.clear();
|
||||
return "Error: " + e.getMessage();
|
||||
} finally {
|
||||
// 3. 必须在 finally 中清除上下文,防止污染其他请求
|
||||
DynamicDataSourceContextHolder.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
package org.ruoyi.agent.tool;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.ruoyi.agent.domain.TableStructure;
|
||||
import org.ruoyi.agent.manager.TableSchemaManager;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import dev.langchain4j.agent.tool.Tool;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 查询数据库所有表的 Tool
|
||||
@@ -19,12 +16,11 @@ import java.util.List;
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true")
|
||||
public class QueryAllTablesTool {
|
||||
|
||||
@Autowired(required = false)
|
||||
private DataSource agentDataSource;
|
||||
|
||||
|
||||
@Autowired
|
||||
private TableSchemaManager tableSchemaManager; // 注入管理器
|
||||
/**
|
||||
* 查询数据库中所有表
|
||||
* 返回数据库中存在的所有表的列表
|
||||
@@ -34,44 +30,36 @@ public class QueryAllTablesTool {
|
||||
@Tool("Query all tables in the database and return table names and basic information")
|
||||
public String queryAllTables() {
|
||||
try {
|
||||
if (agentDataSource == null) {
|
||||
return "Error: Database datasource not configured";
|
||||
}
|
||||
|
||||
try (Connection connection = agentDataSource.getConnection()) {
|
||||
DatabaseMetaData databaseMetaData = connection.getMetaData();
|
||||
ResultSet resultSet = databaseMetaData.getTables(null, null, null, new String[]{"TABLE"});
|
||||
|
||||
List<String> tableNames = new ArrayList<>();
|
||||
List<String> tableDetails = new ArrayList<>();
|
||||
|
||||
while (resultSet.next()) {
|
||||
String tableName = resultSet.getString("TABLE_NAME");
|
||||
String tableComment = resultSet.getString("REMARKS");
|
||||
String tableType = resultSet.getString("TABLE_TYPE");
|
||||
|
||||
tableNames.add(tableName);
|
||||
tableDetails.add(String.format("- %s (%s) - %s",
|
||||
tableName, tableType, tableComment != null ? tableComment : "No comment"));
|
||||
// 1. 从管理器获取所有允许的表结构信息(内部已包含初始化/缓存逻辑)
|
||||
List<TableStructure> tableSchemas = tableSchemaManager.getAllowedTableSchemas();
|
||||
|
||||
if (tableSchemas == null || tableSchemas.isEmpty()) {
|
||||
return "No tables found in database or cache is empty.";
|
||||
}
|
||||
resultSet.close();
|
||||
|
||||
if (tableNames.isEmpty()) {
|
||||
return "No tables found in database";
|
||||
}
|
||||
|
||||
|
||||
// 2. 格式化结果
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append("Found ").append(tableNames.size()).append(" tables:\n");
|
||||
for (String detail : tableDetails) {
|
||||
result.append(detail).append("\n");
|
||||
result.append("Found ").append(tableSchemas.size()).append(" tables in cache:\n");
|
||||
|
||||
for (TableStructure schema : tableSchemas) {
|
||||
String tableName = schema.getTableName();
|
||||
String tableType = schema.getTableType() != null ? schema.getTableType() : "TABLE";
|
||||
String tableComment = schema.getTableComment();
|
||||
|
||||
result.append(String.format("- %s (%s) - %s\n",
|
||||
tableName,
|
||||
tableType,
|
||||
tableComment != null ? tableComment : "No comment"));
|
||||
}
|
||||
|
||||
log.info("Successfully queried {} tables", tableNames.size());
|
||||
|
||||
log.info("Successfully retrieved {} tables from schema cache", tableSchemas.size());
|
||||
return result.toString();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error retrieving tables from cache", e);
|
||||
return "Error: " + e.getMessage();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error querying all tables", e);
|
||||
return "Error: " + e.getMessage();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,83 +1,57 @@
|
||||
package org.ruoyi.agent.tool;
|
||||
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import dev.langchain4j.agent.tool.Tool;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
|
||||
/**
|
||||
* 查询表建表详情的 Tool
|
||||
* 根据表名查询该表的建表 SQL 语句
|
||||
*/
|
||||
@Slf4j
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
|
||||
|
||||
import dev.langchain4j.agent.tool.Tool;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Component
|
||||
@ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true")
|
||||
@Slf4j
|
||||
public class QueryTableSchemaTool {
|
||||
|
||||
@Autowired(required = false)
|
||||
private DataSource agentDataSource;
|
||||
private DataSource dataSource;
|
||||
|
||||
/**
|
||||
* 根据表名查询建表详情
|
||||
* 返回指定表的 CREATE TABLE 语句
|
||||
*
|
||||
* @param tableName 表名
|
||||
* @return 包含建表 SQL 的结果
|
||||
*/
|
||||
@Tool("Query the CREATE TABLE statement (DDL) for a specific table by table name")
|
||||
public String queryTableSchema(String tableName) {
|
||||
// 2. 手动推入数据源上下文
|
||||
DynamicDataSourceContextHolder.push("agent");
|
||||
if (tableName == null || tableName.trim().isEmpty()) {
|
||||
return "Error: Table name cannot be empty";
|
||||
}
|
||||
|
||||
// 验证表名有效性,防止 SQL 注入
|
||||
if (!isValidIdentifier(tableName)) {
|
||||
if (!tableName.matches("^[a-zA-Z0-9_]+$")) {
|
||||
return "Error: Invalid table name format";
|
||||
}
|
||||
|
||||
try {
|
||||
if (agentDataSource == null) {
|
||||
return "Error: Database datasource not configured";
|
||||
String sql = "SHOW CREATE TABLE `" + tableName + "`";
|
||||
|
||||
try (Connection connection = dataSource.getConnection();
|
||||
PreparedStatement ps = connection.prepareStatement(sql);
|
||||
ResultSet rs = ps.executeQuery()) {
|
||||
|
||||
if (rs.next()) {
|
||||
return rs.getString("Create Table");
|
||||
}
|
||||
try (Connection connection = agentDataSource.getConnection()) {
|
||||
String sql = "SHOW CREATE TABLE " + tableName;
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(sql);
|
||||
ResultSet resultSet = preparedStatement.executeQuery();
|
||||
return "Table not found: " + tableName;
|
||||
|
||||
if (resultSet.next()) {
|
||||
String createTableSql = resultSet.getString("Create Table");
|
||||
resultSet.close();
|
||||
preparedStatement.close();
|
||||
|
||||
log.info("Successfully queried schema for table: {}", tableName);
|
||||
return "CREATE TABLE DDL for " + tableName + ":\n\n" + createTableSql;
|
||||
}
|
||||
|
||||
resultSet.close();
|
||||
preparedStatement.close();
|
||||
return "Error: Table not found or not accessible: " + tableName;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error querying table schema for table: {}", tableName, e);
|
||||
// 3. 必须在 finally 中清除上下文,防止污染其他请求
|
||||
DynamicDataSourceContextHolder.clear();
|
||||
log.error("Error querying table schema: {}", tableName, e);
|
||||
return "Error: " + e.getMessage();
|
||||
} finally {
|
||||
// 3. 必须在 finally 中清除上下文,防止污染其他请求
|
||||
DynamicDataSourceContextHolder.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证是否为有效的 SQL 标识符
|
||||
*/
|
||||
private boolean isValidIdentifier(String identifier) {
|
||||
if (identifier == null || identifier.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return identifier.matches("^[a-zA-Z0-9_\\.]+$");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@ import lombok.RequiredArgsConstructor;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.*;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import org.ruoyi.service.chat.IChatMessageService;
|
||||
import org.ruoyi.common.chat.domain.bo.chat.ChatMessageBo;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatMessageVo;
|
||||
import org.ruoyi.common.chat.service.chatMessage.IChatMessageService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.ruoyi.common.idempotent.annotation.RepeatSubmit;
|
||||
@@ -18,8 +20,6 @@ import org.ruoyi.common.core.validate.AddGroup;
|
||||
import org.ruoyi.common.core.validate.EditGroup;
|
||||
import org.ruoyi.common.log.enums.BusinessType;
|
||||
import org.ruoyi.common.excel.utils.ExcelUtil;
|
||||
import org.ruoyi.domain.vo.chat.ChatMessageVo;
|
||||
import org.ruoyi.domain.bo.chat.ChatMessageBo;
|
||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,7 +6,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.*;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import org.ruoyi.common.chat.Service.IChatModelService;
|
||||
import org.ruoyi.common.chat.service.chat.IChatModelService;
|
||||
import org.ruoyi.common.chat.domain.bo.chat.ChatModelBo;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||
import org.ruoyi.enums.ModelType;
|
||||
|
||||
@@ -10,6 +10,8 @@ import org.ruoyi.domain.entity.knowledge.KnowledgeGraphInstance;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,6 +10,8 @@ import org.ruoyi.domain.entity.knowledge.KnowledgeGraphSegment;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,7 +14,6 @@ public enum ChatModeType {
|
||||
ZHI_PU("zhipu", "智谱清言"),
|
||||
DEEP_SEEK("deepseek", "深度求索"),
|
||||
QIAN_WEN("qianwen", "通义千问"),
|
||||
PPIO("ppio", "PPIO派欧云"),
|
||||
OPEN_AI("openai", "openai");
|
||||
private final String code;
|
||||
private final String description;
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.ruoyi.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 文生图模型分类
|
||||
*
|
||||
* @author Zengxb
|
||||
* @date 2026-02-14
|
||||
*/
|
||||
@Getter
|
||||
public enum ImageModeType {
|
||||
|
||||
TONGYI_WANX("Tongyiwanx", "万相");
|
||||
|
||||
private final String code;
|
||||
private final String description;
|
||||
|
||||
ImageModeType(String code, String description) {
|
||||
this.code = code;
|
||||
this.description = description;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package org.ruoyi.factory;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.common.chat.Service.IChatModelService;
|
||||
import org.ruoyi.common.chat.service.chat.IChatModelService;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||
import org.ruoyi.service.embed.BaseEmbedModelService;
|
||||
import org.ruoyi.service.embed.MultiModalEmbedModelService;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.ruoyi.mapper.chat;
|
||||
|
||||
import org.ruoyi.domain.entity.chat.ChatMessage;
|
||||
import org.ruoyi.domain.vo.chat.ChatMessageVo;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatMessageVo;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatMessage;
|
||||
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.ruoyi.mapper.chat;
|
||||
|
||||
import org.ruoyi.common.chat.domain.entity.chat.ChatModel;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatModel;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.ruoyi.service.chat.impl;
|
||||
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.ChatMessage;
|
||||
import dev.langchain4j.data.message.UserMessage;
|
||||
import dev.langchain4j.mcp.McpToolProvider;
|
||||
@@ -24,17 +25,20 @@ import org.ruoyi.agent.WebSearchAgent;
|
||||
import org.ruoyi.agent.tool.ExecuteSqlQueryTool;
|
||||
import org.ruoyi.agent.tool.QueryAllTablesTool;
|
||||
import org.ruoyi.agent.tool.QueryTableSchemaTool;
|
||||
import org.ruoyi.common.chat.Service.IChatService;
|
||||
import org.ruoyi.common.chat.base.ThreadContext;
|
||||
import org.ruoyi.common.chat.domain.dto.request.ReSumeRunner;
|
||||
import org.ruoyi.common.chat.domain.dto.request.WorkFlowRunner;
|
||||
import org.ruoyi.common.chat.enums.RoleType;
|
||||
import org.ruoyi.common.chat.service.chat.IChatService;
|
||||
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
|
||||
import org.ruoyi.common.chat.domain.entity.chat.ChatContext;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatContext;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||
import org.ruoyi.common.chat.service.chatMessage.AbstractChatMessageService;
|
||||
import org.ruoyi.common.chat.service.workFlow.IWorkFlowStarterService;
|
||||
import org.ruoyi.common.core.utils.ObjectUtils;
|
||||
import org.ruoyi.common.core.utils.SpringUtils;
|
||||
import org.ruoyi.common.core.utils.StringUtils;
|
||||
import org.ruoyi.common.sse.utils.SseMessageUtils;
|
||||
import org.ruoyi.domain.bo.chat.ChatMessageBo;
|
||||
import org.ruoyi.enums.RoleType;
|
||||
import org.ruoyi.service.chat.IChatMessageService;
|
||||
import org.ruoyi.service.chat.impl.memory.PersistentChatMemoryStore;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
@@ -58,7 +62,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
*/
|
||||
@Slf4j
|
||||
@Validated
|
||||
public abstract class AbstractStreamingChatService implements IChatService {
|
||||
public abstract class AbstractStreamingChatService extends AbstractChatMessageService implements IChatService {
|
||||
|
||||
/**
|
||||
* 默认保留的消息窗口大小(用于长期记忆)
|
||||
@@ -76,6 +80,11 @@ public abstract class AbstractStreamingChatService implements IChatService {
|
||||
*/
|
||||
private static final Map<Object, MessageWindowChatMemory> memoryCache = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 获取工作流启用Bean对象
|
||||
*/
|
||||
private static final IWorkFlowStarterService starterService = SpringUtils.getBean(IWorkFlowStarterService.class);
|
||||
|
||||
/**
|
||||
* 定义聊天流程骨架
|
||||
*/
|
||||
@@ -108,9 +117,28 @@ public abstract class AbstractStreamingChatService implements IChatService {
|
||||
|
||||
// 保存用户消息
|
||||
saveChatMessage(chatRequest, userId, content, RoleType.USER.getName(), chatModelVo);
|
||||
|
||||
// 判断用户是否重新输入
|
||||
boolean isResume = chatRequest.getIsResume() != null && chatRequest.getIsResume();
|
||||
if (isResume){
|
||||
ReSumeRunner reSumeRunner = chatRequest.getReSumeRunner();
|
||||
if (ObjectUtils.isNotEmpty(reSumeRunner)){
|
||||
starterService.resumeFlow(reSumeRunner.getRuntimeUuid(), reSumeRunner.getFeedbackContent(), emitter);
|
||||
return emitter;
|
||||
}
|
||||
}
|
||||
|
||||
// 判断用户是否开启工作流
|
||||
boolean enableWorkFlow = chatRequest.getEnableWorkFlow() != null && chatRequest.getEnableWorkFlow();
|
||||
if (enableWorkFlow) {
|
||||
WorkFlowRunner runner = chatRequest.getWorkFlowRunner();
|
||||
if (ObjectUtils.isNotEmpty(runner)){
|
||||
return starterService.streaming(ThreadContext.getCurrentUser(), runner.getUuid(), runner.getInputs(), chatRequest.getSessionId());
|
||||
}
|
||||
}
|
||||
|
||||
// 使用长期记忆增强的消息列表
|
||||
List<ChatMessage> messagesWithMemory = buildMessagesWithMemory(chatRequest);
|
||||
|
||||
if (chatRequest.getEnableThinking()) {
|
||||
String msg = doAgent(content, chatModelVo);
|
||||
SseMessageUtils.sendMessage(userId, msg);
|
||||
@@ -119,13 +147,10 @@ public abstract class AbstractStreamingChatService implements IChatService {
|
||||
saveChatMessage(chatRequest, userId, msg, RoleType.ASSISTANT.getName(), chatModelVo);
|
||||
} else {
|
||||
// 创建包含内存管理的响应处理器
|
||||
if (ObjectUtils.isEmpty(handler)) {
|
||||
handler = createResponseHandler(chatRequest, userId, tokenValue, chatModelVo);
|
||||
}
|
||||
handler = ObjectUtils.isEmpty(handler) ? createResponseHandler(chatRequest, userId, tokenValue, chatModelVo) : handler;
|
||||
// 调用具体实现的聊天方法
|
||||
doChat(chatModelVo, chatRequest, messagesWithMemory, handler);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
SseMessageUtils.sendMessage(userId, "对话出错:" + e.getMessage());
|
||||
SseMessageUtils.completeConnection(userId, tokenValue);
|
||||
@@ -144,6 +169,12 @@ public abstract class AbstractStreamingChatService implements IChatService {
|
||||
*/
|
||||
protected List<ChatMessage> buildMessagesWithMemory(ChatRequest chatRequest) {
|
||||
List<ChatMessage> messages = new ArrayList<>();
|
||||
// 工作流对话消息
|
||||
List<ChatMessage> chatMessages = chatRequest.getChatMessages();
|
||||
if (!CollectionUtils.isEmpty(chatMessages)){
|
||||
messages.addAll(chatMessages);
|
||||
}
|
||||
// 开启长期记忆
|
||||
if (enablePersistentMemory && chatRequest.getSessionId() != null) {
|
||||
MessageWindowChatMemory memory = createChatMemory(chatRequest.getSessionId());
|
||||
if (memory != null) {
|
||||
@@ -155,11 +186,6 @@ public abstract class AbstractStreamingChatService implements IChatService {
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
// 工作流方式
|
||||
List<ChatMessage> chatMessages = chatRequest.getChatMessages();
|
||||
if (!CollectionUtils.isEmpty(chatMessages)){
|
||||
messages.addAll(chatMessages);
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
@@ -276,40 +302,6 @@ public abstract class AbstractStreamingChatService implements IChatService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存聊天消息到数据库
|
||||
*
|
||||
* @param chatRequest 聊天请求
|
||||
* @param userId 用户ID
|
||||
* @param content 消息内容
|
||||
* @param role 消息角色
|
||||
* @param chatModelVo 模型配置
|
||||
*/
|
||||
private void saveChatMessage(ChatRequest chatRequest, Long userId, String content, String role, ChatModelVo chatModelVo) {
|
||||
try {
|
||||
// 验证必要的上下文信息
|
||||
if (chatRequest == null || userId == null) {
|
||||
log.warn("缺少必要的聊天上下文信息,无法保存消息");
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建ChatMessageBo对象
|
||||
ChatMessageBo messageBO = new ChatMessageBo();
|
||||
messageBO.setUserId(userId);
|
||||
messageBO.setSessionId(chatRequest.getSessionId());
|
||||
messageBO.setContent(content);
|
||||
messageBO.setRole(role);
|
||||
messageBO.setModelName(chatRequest.getModel());
|
||||
messageBO.setBillingType(chatModelVo.getModelType());
|
||||
messageBO.setRemark(null);
|
||||
|
||||
IChatMessageService chatMessageService = SpringUtils.getBean(IChatMessageService.class);
|
||||
chatMessageService.insertByBo(messageBO);
|
||||
} catch (Exception e) {
|
||||
log.error("保存{}聊天消息时出错: {}", getProviderName(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建具体厂商的 StreamingChatModel
|
||||
* 子类必须实现此方法,返回对应厂商的模型实例
|
||||
@@ -325,26 +317,26 @@ public abstract class AbstractStreamingChatService implements IChatService {
|
||||
protected String doAgent(String userMessage, ChatModelVo chatModelVo) {
|
||||
// 步骤1: 配置MCP传输层 - 连接到bing-cn-mcp服务器
|
||||
// 该服务提供两个工具: bing_search (必应搜索) 和 crawl_webpage (网页抓取)
|
||||
McpTransport transport = new StdioMcpTransport.Builder()
|
||||
.command(List.of("C:\\Program Files\\nodejs\\npx.cmd", "-y",
|
||||
"bing-cn-mcp"
|
||||
))
|
||||
.logEvents(true)
|
||||
.build();
|
||||
// McpTransport transport = new StdioMcpTransport.Builder()
|
||||
// .command(List.of("C:\\Program Files\\nodejs\\npx.cmd", "-y",
|
||||
// "bing-cn-mcp"
|
||||
// ))
|
||||
// .logEvents(true)
|
||||
// .build();
|
||||
|
||||
// 步骤2: 创建MCP客户端
|
||||
McpClient mcpClient = new DefaultMcpClient.Builder()
|
||||
.transport(transport)
|
||||
.build();
|
||||
// // 步骤2: 创建MCP客户端
|
||||
// McpClient mcpClient = new DefaultMcpClient.Builder()
|
||||
// .transport(transport)
|
||||
// .build();
|
||||
|
||||
// 步骤3: 配置工具提供者
|
||||
ToolProvider toolProvider = McpToolProvider.builder()
|
||||
.mcpClients(List.of(mcpClient))
|
||||
.build();
|
||||
// // 步骤3: 配置工具提供者
|
||||
// ToolProvider toolProvider = McpToolProvider.builder()
|
||||
// .mcpClients(List.of(mcpClient))
|
||||
// .build();
|
||||
|
||||
|
||||
McpTransport transport1 = new StdioMcpTransport.Builder()
|
||||
.command(List.of("C:\\Program Files\\nodejs\\npx.cmd", "-y",
|
||||
.command(List.of("npx", "-y",
|
||||
"mcp-echarts"
|
||||
))
|
||||
.logEvents(true)
|
||||
@@ -361,40 +353,52 @@ public abstract class AbstractStreamingChatService implements IChatService {
|
||||
.build();
|
||||
|
||||
// 步骤4: 配置OpenAI模型
|
||||
OpenAiChatModel PLANNER_MODEL = OpenAiChatModel.builder()
|
||||
.baseUrl(chatModelVo.getApiHost())
|
||||
// OpenAiChatModel PLANNER_MODEL = OpenAiChatModel.builder()
|
||||
// .baseUrl(chatModelVo.getApiHost())
|
||||
// .apiKey(chatModelVo.getApiKey())
|
||||
// .modelName(chatModelVo.getModelName())
|
||||
// .build();
|
||||
|
||||
|
||||
QwenChatModel qwenChatModel = QwenChatModel.builder()
|
||||
// .baseUrl(chatModelVo.getApiHost())
|
||||
.apiKey(chatModelVo.getApiKey())
|
||||
.modelName(chatModelVo.getModelName())
|
||||
.build();
|
||||
|
||||
.build();
|
||||
|
||||
SqlAgent sqlAgent = AgenticServices.agentBuilder(SqlAgent.class)
|
||||
.chatModel(PLANNER_MODEL)
|
||||
.chatModel(
|
||||
qwenChatModel)
|
||||
.tools(
|
||||
new QueryAllTablesTool(),
|
||||
new QueryTableSchemaTool(),
|
||||
new ExecuteSqlQueryTool()
|
||||
SpringUtils.getBean(QueryAllTablesTool.class), // 必须通过 getBean 获取
|
||||
SpringUtils.getBean(QueryTableSchemaTool.class),
|
||||
SpringUtils.getBean(ExecuteSqlQueryTool.class)
|
||||
)
|
||||
.build();
|
||||
|
||||
WebSearchAgent searchAgent = AgenticServices.agentBuilder(WebSearchAgent.class)
|
||||
.chatModel(PLANNER_MODEL)
|
||||
.toolProvider(toolProvider)
|
||||
.build();
|
||||
// WebSearchAgent searchAgent = AgenticServices.agentBuilder(WebSearchAgent.class)
|
||||
// .chatModel(PLANNER_MODEL)
|
||||
// .toolProvider(toolProvider)
|
||||
// .build();
|
||||
|
||||
ChartGenerationAgent chartGenerationAgent = AgenticServices.agentBuilder(ChartGenerationAgent.class)
|
||||
.chatModel(PLANNER_MODEL)
|
||||
.chatModel(
|
||||
qwenChatModel)
|
||||
.toolProvider(toolProvider1)
|
||||
.build();
|
||||
|
||||
String res = sqlAgent.getData(userMessage);
|
||||
String res1 = chartGenerationAgent.generateChart(res);
|
||||
System.out.println(res1);
|
||||
System.out.println(res);
|
||||
SupervisorAgent supervisor = AgenticServices
|
||||
.supervisorBuilder()
|
||||
.chatModel(PLANNER_MODEL)
|
||||
.chatModel(qwenChatModel)
|
||||
.subAgents(sqlAgent, chartGenerationAgent)
|
||||
.responseStrategy(SupervisorResponseStrategy.LAST)
|
||||
.build();
|
||||
|
||||
String invoke = supervisor.invoke(userMessage);
|
||||
System.out.println(invoke);
|
||||
return invoke;
|
||||
return res1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package org.ruoyi.service.chat.impl;
|
||||
|
||||
import org.ruoyi.common.chat.domain.bo.chat.ChatMessageBo;
|
||||
import org.ruoyi.common.chat.domain.dto.ChatMessageDTO;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatMessageVo;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatMessage;
|
||||
import org.ruoyi.common.chat.service.chatMessage.IChatMessageService;
|
||||
import org.ruoyi.common.core.utils.MapstructUtils;
|
||||
import org.ruoyi.common.core.utils.StringUtils;
|
||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
||||
@@ -10,11 +14,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.service.chat.IChatMessageService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.ruoyi.domain.bo.chat.ChatMessageBo;
|
||||
import org.ruoyi.domain.vo.chat.ChatMessageVo;
|
||||
import org.ruoyi.domain.entity.chat.ChatMessage;
|
||||
import org.ruoyi.mapper.chat.ChatMessageMapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user