Commit a8d7da329f35bbd410fa3ea793cea8482467fe9f

Authored by 芋道源码
Committed by Gitee
2 parents 2a9dffb6 dd0f8a71

!1256 Spring AI 1.0.0 M6 适配:增加知识库、工具调用(function calling)、工作流、豆包/混元/硅基流动等模型的接入

Merge pull request !1256 from 芋道源码/feature/ai
Showing 180 changed files with 4714 additions and 7044 deletions

Too many changes.

To preserve performance only 100 of 180 files are displayed.

.image/common/ai-feature.png

20.7 KB | W: | H:

24.9 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
sql/mysql/ruoyi-vue-pro.sql
... ... @@ -11,7 +11,7 @@
11 11 Target Server Version : 80200 (8.2.0)
12 12 File Encoding : 65001
13 13  
14   - Date: 31/12/2024 09:16:18
  14 + Date: 14/03/2025 22:52:31
15 15 */
16 16  
17 17 SET NAMES utf8mb4;
... ... @@ -91,7 +91,7 @@ CREATE TABLE `infra_api_error_log` (
91 91 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
92 92 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
93 93 PRIMARY KEY (`id`) USING BTREE
94   -) ENGINE = InnoDB AUTO_INCREMENT = 21226 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
  94 +) ENGINE = InnoDB AUTO_INCREMENT = 21417 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
95 95  
96 96 -- ----------------------------
97 97 -- Records of infra_api_error_log
... ... @@ -128,7 +128,7 @@ CREATE TABLE `infra_codegen_column` (
128 128 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
129 129 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
130 130 PRIMARY KEY (`id`) USING BTREE
131   -) ENGINE = InnoDB AUTO_INCREMENT = 2483 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表字段定义';
  131 +) ENGINE = InnoDB AUTO_INCREMENT = 2538 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表字段定义';
132 132  
133 133 -- ----------------------------
134 134 -- Records of infra_codegen_column
... ... @@ -166,7 +166,7 @@ CREATE TABLE `infra_codegen_table` (
166 166 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
167 167 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
168 168 PRIMARY KEY (`id`) USING BTREE
169   -) ENGINE = InnoDB AUTO_INCREMENT = 187 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表定义';
  169 +) ENGINE = InnoDB AUTO_INCREMENT = 191 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表定义';
170 170  
171 171 -- ----------------------------
172 172 -- Records of infra_codegen_table
... ... @@ -250,7 +250,7 @@ CREATE TABLE `infra_file` (
250 250 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
251 251 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
252 252 PRIMARY KEY (`id`) USING BTREE
253   -) ENGINE = InnoDB AUTO_INCREMENT = 1577 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
  253 +) ENGINE = InnoDB AUTO_INCREMENT = 1655 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
254 254  
255 255 -- ----------------------------
256 256 -- Records of infra_file
... ... @@ -444,7 +444,7 @@ CREATE TABLE `system_dict_data` (
444 444 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
445 445 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
446 446 PRIMARY KEY (`id`) USING BTREE
447   -) ENGINE = InnoDB AUTO_INCREMENT = 1683 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典数据表';
  447 +) ENGINE = InnoDB AUTO_INCREMENT = 1694 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典数据表';
448 448  
449 449 -- ----------------------------
450 450 -- Records of system_dict_data
... ... @@ -831,7 +831,7 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st
831 831 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1551, 2, '描述模式', '2', 'ai_generate_mode', 0, '', '', '', '1', '2024-06-27 22:46:37', '1', '2024-06-28 01:22:24', b'0');
832 832 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1552, 8, 'Suno', 'Suno', 'ai_platform', 0, '', '', '', '1', '2024-06-29 09:13:36', '1', '2024-06-29 09:13:41', b'0');
833 833 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1553, 9, 'DeepSeek', 'DeepSeek', 'ai_platform', 0, '', '', '', '1', '2024-07-06 12:04:30', '1', '2024-07-06 12:05:20', b'0');
834   -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1554, 10, '智谱', 'ZhiPu', 'ai_platform', 0, '', '', '', '1', '2024-07-06 18:00:35', '1', '2024-07-06 18:00:35', b'0');
  834 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1554, 13, '智谱', 'ZhiPu', 'ai_platform', 0, '', '', '', '1', '2024-07-06 18:00:35', '1', '2025-02-24 20:18:41', b'0');
835 835 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1555, 4, '长', '4', 'ai_write_length', 0, '', '', '', '1', '2024-07-07 15:49:03', '1', '2024-07-07 15:49:03', b'0');
836 836 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1556, 5, '段落', '5', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:49:54', '1', '2024-07-07 15:49:54', b'0');
837 837 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1557, 6, '文章', '6', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:50:05', '1', '2024-07-07 15:50:05', b'0');
... ... @@ -888,13 +888,23 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st
888 888 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1673, 2, 'OPC UA', '2', 'iot_protocol_type', 0, '', '', '', '1', '2024-09-06 22:26:31', '1', '2024-09-06 22:26:31', b'0');
889 889 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1674, 3, 'ZigBee', '3', 'iot_protocol_type', 0, '', '', '', '1', '2024-09-06 22:26:39', '1', '2024-09-06 22:26:39', b'0');
890 890 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1675, 4, 'BLE', '4', 'iot_protocol_type', 0, '', '', '', '1', '2024-09-06 22:26:48', '1', '2024-09-06 22:26:48', b'0');
891   -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1676, 0, '未激活', '0', 'iot_device_status', 0, '', '', '', '1', '2024-09-21 08:13:34', '1', '2024-09-21 08:13:34', b'0');
892   -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1677, 1, '在线', '1', 'iot_device_status', 0, '', '', '', '1', '2024-09-21 08:13:48', '1', '2024-09-21 08:13:48', b'0');
893   -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1678, 2, '离线', '2', 'iot_device_status', 0, '', '', '', '1', '2024-09-21 08:13:59', '1', '2024-09-21 08:13:59', b'0');
894   -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1679, 3, '已禁用', '3', 'iot_device_status', 0, '', '', '', '1', '2024-09-21 08:14:13', '1', '2024-09-21 08:14:13', b'0');
  891 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1676, 0, '未激活', '0', 'iot_device_state', 0, '', '', '', '1', '2024-09-21 08:13:34', '1', '2025-01-28 16:09:27', b'0');
  892 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1677, 1, '在线', '1', 'iot_device_state', 0, '', '', '', '1', '2024-09-21 08:13:48', '1', '2025-01-28 16:09:26', b'0');
  893 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1678, 2, '离线', '2', 'iot_device_state', 0, '', '', '', '1', '2024-09-21 08:13:59', '1', '2025-01-28 16:09:25', b'0');
895 894 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1680, 1, '属性', '1', 'iot_product_function_type', 0, '', '', '', '1', '2024-09-29 20:03:01', '1', '2024-09-29 20:09:41', b'0');
896 895 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1681, 2, '服务', '2', 'iot_product_function_type', 0, '', '', '', '1', '2024-09-29 20:03:11', '1', '2024-09-29 20:08:23', b'0');
897 896 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1682, 3, '事件', '3', 'iot_product_function_type', 0, '', '', '', '1', '2024-09-29 20:03:20', '1', '2024-09-29 20:08:20', b'0');
  897 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1683, 10, '字节豆包', 'DouBao', 'ai_platform', 0, '', '', '', '1', '2025-02-23 19:51:40', '1', '2025-02-23 19:52:02', b'0');
  898 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1684, 11, '腾讯混元', 'HunYuan', 'ai_platform', 0, '', '', '', '1', '2025-02-23 20:58:04', '1', '2025-02-23 20:58:04', b'0');
  899 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1685, 12, '硅基流动', 'SiliconFlow', 'ai_platform', 0, '', '', '', '1', '2025-02-24 20:19:09', '1', '2025-02-24 20:19:09', b'0');
  900 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1686, 1, '聊天', '1', 'ai_model_type', 0, '', '', '', '1', '2025-03-03 12:26:34', '1', '2025-03-03 12:26:34', b'0');
  901 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1687, 2, '图像', '2', 'ai_model_type', 0, '', '', '', '1', '2025-03-03 12:27:23', '1', '2025-03-03 12:27:23', b'0');
  902 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1688, 3, '音频', '3', 'ai_model_type', 0, '', '', '', '1', '2025-03-03 12:27:51', '1', '2025-03-03 12:27:51', b'0');
  903 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1689, 4, '视频', '4', 'ai_model_type', 0, '', '', '', '1', '2025-03-03 12:28:03', '1', '2025-03-03 12:28:03', b'0');
  904 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1690, 5, '向量', '5', 'ai_model_type', 0, '', '', '', '1', '2025-03-03 12:28:15', '1', '2025-03-03 12:28:15', b'0');
  905 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1691, 6, '重排', '6', 'ai_model_type', 0, '', '', '', '1', '2025-03-03 12:28:26', '1', '2025-03-03 12:28:26', b'0');
  906 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1692, 14, 'MiniMax', 'MiniMax', 'ai_platform', 0, '', '', '', '1', '2025-03-11 20:04:51', '1', '2025-03-11 20:04:51', b'0');
  907 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1693, 15, '月之暗灭', 'Moonshot', 'ai_platform', 0, '', '', '', '1', '2025-03-11 20:05:08', '1', '2025-03-11 20:05:08', b'0');
898 908 COMMIT;
899 909  
900 910 -- ----------------------------
... ... @@ -914,7 +924,7 @@ CREATE TABLE `system_dict_type` (
914 924 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
915 925 `deleted_time` datetime NULL DEFAULT NULL COMMENT '删除时间',
916 926 PRIMARY KEY (`id`) USING BTREE
917   -) ENGINE = InnoDB AUTO_INCREMENT = 640 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表';
  927 +) ENGINE = InnoDB AUTO_INCREMENT = 641 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表';
918 928  
919 929 -- ----------------------------
920 930 -- Records of system_dict_type
... ... @@ -1014,13 +1024,14 @@ INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creat
1014 1024 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (628, 'AI 写作类型', 'ai_write_type', 0, '', '1', '2024-07-10 21:25:29', '1', '2024-07-10 21:25:29', b'0', '1970-01-01 00:00:00');
1015 1025 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (629, 'BPM 流程模型类型', 'bpm_model_type', 0, '', '1', '2024-08-26 15:21:43', '1', '2024-08-26 15:21:43', b'0', '1970-01-01 00:00:00');
1016 1026 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (630, 'IOT 接入网关协议', 'iot_protocol_type', 0, '', '1', '2024-09-06 22:20:17', '1', '2024-09-06 22:20:17', b'0', '1970-01-01 00:00:00');
1017   -INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (631, 'IOT 设备状态', 'iot_device_status', 0, '', '1', '2024-09-21 08:12:55', '1', '2024-09-21 08:12:55', b'0', '1970-01-01 00:00:00');
  1027 +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (631, 'IOT 设备状态', 'iot_device_state', 0, '', '1', '2024-09-21 08:12:55', '1', '2025-01-28 16:09:42', b'0', '1970-01-01 00:00:00');
1018 1028 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (632, 'IOT 物模型功能类型', 'iot_product_function_type', 0, '', '1', '2024-09-29 20:02:36', '1', '2024-09-29 20:09:26', b'0', '1970-01-01 00:00:00');
1019 1029 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (634, 'IOT 数据格式', 'iot_data_format', 0, '', '1', '2024-08-10 11:52:58', '1', '2024-09-06 14:30:14', b'0', '1970-01-01 00:00:00');
1020 1030 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (635, 'IOT 产品设备类型', 'iot_product_device_type', 0, '', '1', '2024-08-10 11:54:30', '1', '2024-08-10 04:06:56', b'0', '1970-01-01 00:00:00');
1021 1031 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (637, 'IOT 产品状态', 'iot_product_status', 0, '', '1', '2024-08-10 12:06:09', '1', '2024-08-10 12:06:09', b'0', '1970-01-01 00:00:00');
1022 1032 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (638, 'IOT 数据校验级别', 'iot_validate_type', 0, '', '1', '2024-09-06 20:05:13', '1', '2024-09-06 20:05:13', b'0', '1970-01-01 00:00:00');
1023 1033 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (639, 'IOT 联网方式', 'iot_net_type', 0, '', '1', '2024-09-06 22:04:13', '1', '2024-09-06 22:04:13', b'0', '1970-01-01 00:00:00');
  1034 +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (640, 'AI 模型类型', 'ai_model_type', 0, '', '1', '2025-03-03 12:24:07', '1', '2025-03-03 12:24:07', b'0', '1970-01-01 00:00:00');
1024 1035 COMMIT;
1025 1036  
1026 1037 -- ----------------------------
... ... @@ -1044,7 +1055,7 @@ CREATE TABLE `system_login_log` (
1044 1055 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
1045 1056 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
1046 1057 PRIMARY KEY (`id`) USING BTREE
1047   -) ENGINE = InnoDB AUTO_INCREMENT = 3415 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
  1058 +) ENGINE = InnoDB AUTO_INCREMENT = 3442 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
1048 1059  
1049 1060 -- ----------------------------
1050 1061 -- Records of system_login_log
... ... @@ -1175,7 +1186,7 @@ CREATE TABLE `system_menu` (
1175 1186 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
1176 1187 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
1177 1188 PRIMARY KEY (`id`) USING BTREE
1178   -) ENGINE = InnoDB AUTO_INCREMENT = 2913 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '菜单权限表';
  1189 +) ENGINE = InnoDB AUTO_INCREMENT = 2925 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '菜单权限表';
1179 1190  
1180 1191 -- ----------------------------
1181 1192 -- Records of system_menu
... ... @@ -1373,7 +1384,7 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
1373 1384 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1241, '文件配置删除', 'infra:file-config:delete', 3, 4, 1237, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-03-15 14:35:28', '', '2022-04-20 17:03:10', b'0');
1374 1385 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1242, '文件配置导出', 'infra:file-config:export', 3, 5, 1237, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-03-15 14:35:28', '', '2022-04-20 17:03:10', b'0');
1375 1386 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1243, '文件管理', '', 2, 6, 2, 'file', 'ep:files', NULL, '', 0, b'1', b'1', b'1', '1', '2022-03-16 23:47:40', '1', '2024-04-23 00:02:11', b'0');
1376   -INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1254, '作者动态', '', 1, 0, 0, 'https://www.iocoder.cn', 'ep:avatar', NULL, NULL, 0, b'1', b'1', b'1', '1', '2022-04-23 01:03:15', '1', '2024-09-06 09:19:42', b'0');
  1387 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1254, '作者动态', '', 1, 0, 0, 'https://www.iocoder.cn', 'ep:avatar', NULL, NULL, 0, b'1', b'1', b'1', '1', '2022-04-23 01:03:15', '104', '2025-01-04 10:59:37', b'0');
1377 1388 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1255, '数据源配置', '', 2, 1, 2, 'data-source-config', 'ep:data-analysis', 'infra/dataSourceConfig/index', 'InfraDataSourceConfig', 0, b'1', b'1', b'1', '', '2022-04-27 14:37:32', '1', '2024-02-29 08:51:25', b'0');
1378 1389 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1256, '数据源配置查询', 'infra:data-source-config:query', 3, 1, 1255, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-04-27 14:37:32', '', '2022-04-27 14:37:32', b'0');
1379 1390 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1257, '数据源配置创建', 'infra:data-source-config:create', 3, 2, 1255, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-04-27 14:37:32', '', '2022-04-27 14:37:32', b'0');
... ... @@ -1978,11 +1989,11 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
1978 1989 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2763, 'API 密钥创建', 'ai:api-key:create', 3, 2, 2761, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-05-09 14:52:56', '1', '2024-05-13 20:36:26', b'0');
1979 1990 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2764, 'API 密钥更新', 'ai:api-key:update', 3, 3, 2761, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-05-09 14:52:56', '1', '2024-05-13 20:36:42', b'0');
1980 1991 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2765, 'API 密钥删除', 'ai:api-key:delete', 3, 4, 2761, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-05-09 14:52:56', '1', '2024-05-13 20:36:48', b'0');
1981   -INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2767, '聊天模型', '', 2, 0, 2760, 'chat-model', 'fa-solid:abacus', 'ai/model/chatModel/index.vue', 'AiChatModel', 0, b'1', b'1', b'1', '', '2024-05-10 14:42:48', '1', '2024-05-10 22:44:16', b'0');
1982   -INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2768, '聊天模型查询', 'ai:chat-model:query', 3, 1, 2767, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-05-10 14:42:48', '1', '2024-05-13 20:37:02', b'0');
1983   -INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2769, '聊天模型创建', 'ai:chat-model:create', 3, 2, 2767, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-05-10 14:42:48', '1', '2024-05-13 20:37:12', b'0');
1984   -INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2770, '聊天模型更新', 'ai:chat-model:update', 3, 3, 2767, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-05-10 14:42:48', '1', '2024-05-13 20:37:18', b'0');
1985   -INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2771, '聊天模型删除', 'ai:chat-model:delete', 3, 4, 2767, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-05-10 14:42:48', '1', '2024-05-13 20:37:23', b'0');
  1992 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2767, '模型配置', '', 2, 0, 2760, 'model', 'fa-solid:abacus', 'ai/model/model/index.vue', 'AiModel', 0, b'1', b'1', b'1', '', '2024-05-10 14:42:48', '1', '2025-03-03 09:57:41', b'0');
  1993 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2768, '聊天模型查询', 'ai:model:query', 3, 1, 2767, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-05-10 14:42:48', '1', '2025-03-03 09:19:46', b'0');
  1994 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2769, '聊天模型创建', 'ai:model:create', 3, 2, 2767, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-05-10 14:42:48', '1', '2025-03-03 09:20:10', b'0');
  1995 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2770, '聊天模型更新', 'ai:model:update', 3, 3, 2767, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-05-10 14:42:48', '1', '2025-03-03 09:20:14', b'0');
  1996 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2771, '聊天模型删除', 'ai:model:delete', 3, 4, 2767, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-05-10 14:42:48', '1', '2025-03-03 09:20:27', b'0');
1986 1997 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2773, '聊天角色', '', 2, 0, 2760, 'chat-role', 'fa:user-secret', 'ai/model/chatRole/index.vue', 'AiChatRole', 0, b'1', b'1', b'1', '', '2024-05-13 12:39:28', '1', '2024-05-13 20:41:45', b'0');
1987 1998 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2774, '聊天角色查询', 'ai:chat-role:query', 3, 1, 2773, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-05-13 12:39:28', '', '2024-05-13 12:39:28', b'0');
1988 1999 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2775, '聊天角色创建', 'ai:chat-role:create', 3, 2, 2773, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-05-13 12:39:28', '', '2024-05-13 12:39:28', b'0');
... ... @@ -2008,7 +2019,7 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
2008 2019 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2795, 'AI 写作删除', 'ai:write:delete', 3, 4, 2793, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-07-10 13:24:34', '', '2024-07-10 13:24:34', b'0');
2009 2020 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2796, 'AI 音乐', '', 2, 4, 2758, 'music', 'fa:music', 'ai/music/index/index.vue', 'AiMusic', 0, b'1', b'1', b'1', '1', '2024-07-17 09:21:12', '1', '2024-07-29 21:11:52', b'0');
2010 2021 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2797, '客服中心', '', 2, 100, 2362, 'kefu', 'fa-solid:user-alt', 'mall/promotion/kefu/index', 'KeFu', 0, b'1', b'1', b'1', '1', '2024-07-17 23:49:05', '1', '2024-07-17 23:49:16', b'0');
2011   -INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2798, 'AI 思维导图', '', 2, 5, 2758, 'mind-map', 'fa:sitemap', 'ai/mindmap/index/index.vue', 'AiMindMap', 0, b'1', b'1', b'1', '1', '2024-07-29 21:31:59', '1', '2024-07-29 21:33:20', b'0');
  2022 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2798, 'AI 思维导图', '', 2, 6, 2758, 'mind-map', 'fa:sitemap', 'ai/mindmap/index/index.vue', 'AiMindMap', 0, b'1', b'1', b'1', '1', '2024-07-29 21:31:59', '1', '2025-03-02 18:57:31', b'0');
2012 2023 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2799, '导图管理', '', 2, 14, 2760, 'mind-map', 'fa:map', 'ai/mindmap/manager/index', 'AiMindMapManager', 0, b'1', b'1', b'1', '', '2024-08-10 09:15:09', '1', '2024-08-10 17:24:28', b'0');
2013 2024 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2800, '思维导图查询', 'ai:mind-map:query', 3, 1, 2799, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-08-10 09:15:09', '', '2024-08-10 09:15:09', b'0');
2014 2025 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2801, '思维导图删除', 'ai:mind-map:delete', 3, 4, 2799, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-08-10 09:15:09', '', '2024-08-10 09:15:09', b'0');
... ... @@ -2045,6 +2056,18 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
2045 2056 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2910, 'IoT 产品物模型删除', 'iot:think-model-function:delete', 3, 4, 2906, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-09-25 22:12:09', '', '2024-09-25 22:12:09', b'0');
2046 2057 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2911, 'IoT 产品物模型导出', 'iot:think-model-function:export', 3, 5, 2906, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-09-25 22:12:09', '', '2024-09-25 22:12:09', b'0');
2047 2058 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2912, '创建推广员', 'trade:brokerage-user:create', 3, 7, 2346, '', '', '', '', 0, b'1', b'1', b'1', '1', '2024-12-01 14:32:39', '1', '2024-12-01 14:32:39', b'0');
  2059 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2913, '流程清理', 'bpm:model:clean', 3, 7, 1193, '', '', '', '', 0, b'1', b'1', b'1', '1', '2025-01-17 19:32:06', '1', '2025-01-17 19:32:06', b'0');
  2060 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2914, '积分商城活动关闭', 'promotion:point-activity:close', 3, 6, 2808, '', '', '', '', 0, b'1', b'1', b'1', '1', '2025-01-23 20:23:34', '1', '2025-01-23 20:23:34', b'0');
  2061 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2915, 'AI 知识库', '', 2, 5, 2758, 'knowledge', 'ep:notebook', 'ai/knowledge/knowledge/index', 'AiKnowledge', 0, b'1', b'1', b'1', '', '2025-02-28 07:04:21', '1', '2025-03-02 18:58:37', b'0');
  2062 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2916, 'AI 知识库查询', 'ai:knowledge:query', 3, 1, 2915, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2025-02-28 07:04:21', '', '2025-02-28 07:04:21', b'0');
  2063 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2917, 'AI 知识库创建', 'ai:knowledge:create', 3, 2, 2915, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2025-02-28 07:04:21', '', '2025-02-28 07:04:21', b'0');
  2064 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2918, 'AI 知识库更新', 'ai:knowledge:update', 3, 3, 2915, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2025-02-28 07:04:21', '', '2025-02-28 07:04:21', b'0');
  2065 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2919, 'AI 知识库删除', 'ai:knowledge:delete', 3, 4, 2915, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2025-02-28 07:04:21', '', '2025-02-28 07:04:21', b'0');
  2066 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2920, '工具管理', '', 2, 0, 2760, 'tool', 'fa-solid:tools', 'ai/model/tool/index.vue', 'AiTool', 0, b'1', b'1', b'1', '', '2025-03-14 11:19:29', '1', '2025-03-14 19:20:18', b'0');
  2067 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2921, '工具查询', 'ai:tool:query', 3, 1, 2920, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2025-03-14 11:19:29', '', '2025-03-14 11:19:29', b'0');
  2068 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2922, '工具创建', 'ai:tool:create', 3, 2, 2920, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2025-03-14 11:19:29', '', '2025-03-14 11:19:29', b'0');
  2069 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2923, '工具更新', 'ai:tool:update', 3, 3, 2920, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2025-03-14 11:19:29', '', '2025-03-14 11:19:29', b'0');
  2070 +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2924, '工具删除', 'ai:tool:delete', 3, 4, 2920, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2025-03-14 11:19:29', '', '2025-03-14 11:19:29', b'0');
2048 2071 COMMIT;
2049 2072  
2050 2073 -- ----------------------------
... ... @@ -2166,7 +2189,7 @@ CREATE TABLE `system_oauth2_access_token` (
2166 2189 PRIMARY KEY (`id`) USING BTREE,
2167 2190 INDEX `idx_access_token`(`access_token` ASC) USING BTREE,
2168 2191 INDEX `idx_refresh_token`(`refresh_token` ASC) USING BTREE
2169   -) ENGINE = InnoDB AUTO_INCREMENT = 12055 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
  2192 +) ENGINE = InnoDB AUTO_INCREMENT = 13666 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
2170 2193  
2171 2194 -- ----------------------------
2172 2195 -- Records of system_oauth2_access_token
... ... @@ -2288,7 +2311,7 @@ CREATE TABLE `system_oauth2_refresh_token` (
2288 2311 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
2289 2312 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
2290 2313 PRIMARY KEY (`id`) USING BTREE
2291   -) ENGINE = InnoDB AUTO_INCREMENT = 1711 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌';
  2314 +) ENGINE = InnoDB AUTO_INCREMENT = 1732 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌';
2292 2315  
2293 2316 -- ----------------------------
2294 2317 -- Records of system_oauth2_refresh_token
... ... @@ -2322,7 +2345,7 @@ CREATE TABLE `system_operate_log` (
2322 2345 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
2323 2346 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
2324 2347 PRIMARY KEY (`id`) USING BTREE
2325   -) ENGINE = InnoDB AUTO_INCREMENT = 9064 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录 V2 版本';
  2348 +) ENGINE = InnoDB AUTO_INCREMENT = 9065 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录 V2 版本';
2326 2349  
2327 2350 -- ----------------------------
2328 2351 -- Records of system_operate_log
... ... @@ -2783,9 +2806,7 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t
2783 2806 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2139, 2, 1011, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
2784 2807 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2140, 2, 1012, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
2785 2808 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2141, 2, 1013, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
2786   -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2142, 2, 1014, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
2787 2809 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2143, 2, 1015, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
2788   -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2144, 2, 1016, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
2789 2810 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2145, 2, 1017, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
2790 2811 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2146, 2, 1018, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
2791 2812 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2147, 2, 1019, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
... ... @@ -3305,7 +3326,7 @@ CREATE TABLE `system_sms_code` (
3305 3326 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
3306 3327 PRIMARY KEY (`id`) USING BTREE,
3307 3328 INDEX `idx_mobile`(`mobile` ASC) USING BTREE COMMENT '手机号'
3308   -) ENGINE = InnoDB AUTO_INCREMENT = 645 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '手机验证码';
  3329 +) ENGINE = InnoDB AUTO_INCREMENT = 646 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '手机验证码';
3309 3330  
3310 3331 -- ----------------------------
3311 3332 -- Records of system_sms_code
... ... @@ -3346,7 +3367,7 @@ CREATE TABLE `system_sms_log` (
3346 3367 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
3347 3368 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
3348 3369 PRIMARY KEY (`id`) USING BTREE
3349   -) ENGINE = InnoDB AUTO_INCREMENT = 1241 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志';
  3370 +) ENGINE = InnoDB AUTO_INCREMENT = 1255 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志';
3350 3371  
3351 3372 -- ----------------------------
3352 3373 -- Records of system_sms_log
... ... @@ -3588,7 +3609,7 @@ CREATE TABLE `system_user_role` (
3588 3609 `deleted` bit(1) NULL DEFAULT b'0' COMMENT '是否删除',
3589 3610 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
3590 3611 PRIMARY KEY (`id`) USING BTREE
3591   -) ENGINE = InnoDB AUTO_INCREMENT = 47 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户和角色关联表';
  3612 +) ENGINE = InnoDB AUTO_INCREMENT = 48 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户和角色关联表';
3592 3613  
3593 3614 -- ----------------------------
3594 3615 -- Records of system_user_role
... ... @@ -3604,12 +3625,12 @@ INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_t
3604 3625 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (15, 111, 110, '110', '2022-02-23 13:14:38', '110', '2022-02-23 13:14:38', b'0', 121);
3605 3626 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (16, 113, 111, '1', '2022-03-07 21:37:58', '1', '2022-03-07 21:37:58', b'0', 122);
3606 3627 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (18, 1, 2, '1', '2022-05-12 20:39:29', '1', '2022-05-12 20:39:29', b'0', 1);
3607   -INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (20, 104, 101, '1', '2022-05-28 15:43:57', '1', '2022-05-28 15:43:57', b'0', 1);
3608 3628 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (22, 115, 2, '1', '2022-07-21 22:08:30', '1', '2022-07-21 22:08:30', b'0', 1);
3609 3629 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (35, 112, 1, '1', '2024-03-15 20:00:24', '1', '2024-03-15 20:00:24', b'0', 1);
3610 3630 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (36, 118, 1, '1', '2024-03-17 09:12:08', '1', '2024-03-17 09:12:08', b'0', 1);
3611 3631 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (38, 114, 101, '1', '2024-03-24 22:23:03', '1', '2024-03-24 22:23:03', b'0', 1);
3612 3632 INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (46, 117, 1, '1', '2024-10-02 10:16:11', '1', '2024-10-02 10:16:11', b'0', 1);
  3633 +INSERT INTO `system_user_role` (`id`, `user_id`, `role_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (47, 104, 2, '1', '2025-01-04 10:40:33', '1', '2025-01-04 10:40:33', b'0', 1);
3613 3634 COMMIT;
3614 3635  
3615 3636 -- ----------------------------
... ... @@ -3644,10 +3665,10 @@ CREATE TABLE `system_users` (
3644 3665 -- Records of system_users
3645 3666 -- ----------------------------
3646 3667 BEGIN;
3647   -INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '芋道源码', '管理员', 103, '[1,2]', 'aoteman@126.com', '18818260277', 2, 'http://test.yudao.iocoder.cn/bf2002b38950c904243be7c825d3f82e29f25a44526583c3fde2ebdff3a87f75.png', 0, '0:0:0:0:0:0:0:1', '2024-12-28 20:29:58', 'admin', '2021-01-05 17:03:47', NULL, '2024-12-28 20:29:58', b'0', 1);
  3668 +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '芋道源码', '管理员', 103, '[1,2]', 'aoteman@126.com', '18818260277', 2, 'http://test.yudao.iocoder.cn/bf2002b38950c904243be7c825d3f82e29f25a44526583c3fde2ebdff3a87f75.png', 0, '0:0:0:0:0:0:0:1', '2025-03-13 12:44:59', 'admin', '2021-01-05 17:03:47', NULL, '2025-03-13 12:44:59', b'0', 1);
3648 3669 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (100, 'yudao', '$2a$04$IgUse/ibRzAZ3rngCThmtemJeoh15Ux1TQ2hIMe4iwt/K3LcFHEda', '芋道', '不要吓我', 104, '[1]', 'yudao@iocoder.cn', '15601691300', 1, '', 0, '0:0:0:0:0:0:0:1', '2024-11-02 14:00:46', '', '2021-01-07 09:07:17', NULL, '2024-11-02 14:00:46', b'0', 1);
3649 3670 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (103, 'yuanma', '$2a$04$fUBSmjKCPYAUmnMzOb6qE.eZCGPhHi1JmAKclODbfS/O7fHOl2bH6', '源码', NULL, 106, NULL, 'yuanma@iocoder.cn', '15601701300', 0, '', 0, '0:0:0:0:0:0:0:1', '2024-08-11 17:48:12', '', '2021-01-13 23:50:35', NULL, '2024-08-11 17:48:12', b'0', 1);
3650   -INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (104, 'test', '$2a$04$jDFLttgfik0QqJKAbfhMa.2A9xXoZmAIxakdFJUzkX.MgBKT6ddo6', '测试号', NULL, 107, '[1,2]', '111@qq.com', '15601691200', 1, '', 0, '0:0:0:0:0:0:0:1', '2024-09-17 15:05:43', '', '2021-01-21 02:13:53', NULL, '2024-09-17 15:05:43', b'0', 1);
  3671 +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (104, 'test', '$2a$04$BrwaYn303hjA/6TnXqdGoOLhyHOAA0bVrAFu6.1dJKycqKUnIoRz2', '测试号', NULL, 107, '[1,2]', '111@qq.com', '15601691200', 1, '', 0, '0:0:0:0:0:0:0:1', '2025-01-04 10:40:49', '', '2021-01-21 02:13:53', NULL, '2025-01-04 10:40:49', b'0', 1);
3651 3672 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (107, 'admin107', '$2a$10$dYOOBKMO93v/.ReCqzyFg.o67Tqk.bbc2bhrpyBGkIw9aypCtr2pm', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, '', 0, '', NULL, '1', '2022-02-20 22:59:33', '1', '2022-02-27 08:26:51', b'0', 118);
3652 3673 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (108, 'admin108', '$2a$10$y6mfvKoNYL1GXWak8nYwVOH.kCWqjactkzdoIDgiKl93WN3Ejg.Lu', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, '', 0, '', NULL, '1', '2022-02-20 23:00:50', '1', '2022-02-27 08:26:53', b'0', 119);
3653 3674 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (109, 'admin109', '$2a$10$JAqvH0tEc0I7dfDVBI7zyuB4E3j.uH6daIjV53.vUS6PknFkDJkuK', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, '', 0, '', NULL, '1', '2022-02-20 23:11:50', '1', '2022-02-27 08:26:56', b'0', 120);
... ...
yudao-dependencies/pom.xml
... ... @@ -60,7 +60,7 @@
60 60 <transmittable-thread-local.version>2.14.5</transmittable-thread-local.version>
61 61 <commons-net.version>3.11.1</commons-net.version>
62 62 <jsch.version>0.1.55</jsch.version>
63   - <tika-core.version>2.9.2</tika-core.version>
  63 + <tika-core.version>3.1.0</tika-core.version> <!-- TODO 芋艿:升级后,需要看看 jdk8 的兼容性;也要同步 cloud -->
64 64 <ip2region.version>2.7.0</ip2region.version>
65 65 <bizlog-sdk.version>3.0.6</bizlog-sdk.version>
66 66 <netty.version>4.1.116.Final</netty.version>
... ...
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java
... ... @@ -7,6 +7,7 @@ import com.google.common.collect.HashMultimap;
7 7 import com.google.common.collect.Multimap;
8 8 import jakarta.annotation.Resource;
9 9 import jakarta.annotation.security.PermitAll;
  10 +import jakarta.servlet.DispatcherType;
10 11 import org.springframework.boot.autoconfigure.AutoConfiguration;
11 12 import org.springframework.boot.autoconfigure.AutoConfigureOrder;
12 13 import org.springframework.context.ApplicationContext;
... ... @@ -142,7 +143,9 @@ public class YudaoWebSecurityConfigurerAdapter {
142 143 // ②:每个项目的自定义规则
143 144 .authorizeHttpRequests(c -> authorizeRequestsCustomizers.forEach(customizer -> customizer.customize(c)))
144 145 // ③:兜底规则,必须认证
145   - .authorizeHttpRequests(c -> c.anyRequest().authenticated());
  146 + .authorizeHttpRequests(c -> c
  147 + .dispatcherTypeMatchers(DispatcherType.ASYNC).permitAll() // WebFlux 异步请求,无需认证,目的:SSE 场景
  148 + .anyRequest().authenticated());
146 149  
147 150 // 添加 Token Filter
148 151 httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
... ...
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiChatRoleEnum.java
... ... @@ -35,11 +35,7 @@ public enum AiChatRoleEnum {
35 35 ### 微信
36 36 除此之外不要任何解释性语句。
37 37 """),
38   -
39   - AI_KNOWLEDGE_ROLE("知识库助手", """
40   - 给你提供一些数据参考:{info},请回答我的问题。
41   - 请你跟进数据参考与工具返回结果回复用户的请求。
42   - """);
  38 + ;
43 39  
44 40 /**
45 41 * 角色名
... ...
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java
... ... @@ -12,49 +12,53 @@ public interface ErrorCodeConstants {
12 12 // ========== API 密钥 1-040-000-000 ==========
13 13 ErrorCode API_KEY_NOT_EXISTS = new ErrorCode(1_040_000_000, "API 密钥不存在");
14 14 ErrorCode API_KEY_DISABLE = new ErrorCode(1_040_000_001, "API 密钥已禁用!");
15   - ErrorCode API_KEY_MIDJOURNEY_NOT_FOUND = new ErrorCode(1_040_000_900, "Midjourney 模型不存在");
16   - ErrorCode API_KEY_SUNO_NOT_FOUND = new ErrorCode(1_040_000_901, "Suno 模型不存在");
17   - ErrorCode API_KEY_IMAGE_NODE_FOUND = new ErrorCode(1_040_000_902, "平台({}) 图片模型未配置");
18 15  
19   - // ========== API 聊天模型 1-040-001-000 ==========
20   - ErrorCode CHAT_MODEL_NOT_EXISTS = new ErrorCode(1_040_001_000, "模型不存在!");
21   - ErrorCode CHAT_MODEL_DISABLE = new ErrorCode(1_040_001_001, "模型({})已禁用!");
22   - ErrorCode CHAT_MODEL_DEFAULT_NOT_EXISTS = new ErrorCode(1_040_001_002, "操作失败,找不到默认聊天模型");
  16 + // ========== API 模型 1-040-001-000 ==========
  17 + ErrorCode MODEL_NOT_EXISTS = new ErrorCode(1_040_001_000, "模型不存在!");
  18 + ErrorCode MODEL_DISABLE = new ErrorCode(1_040_001_001, "模型({})已禁用!");
  19 + ErrorCode MODEL_DEFAULT_NOT_EXISTS = new ErrorCode(1_040_001_002, "操作失败,找不到默认模型");
  20 + ErrorCode MODEL_USE_TYPE_ERROR = new ErrorCode(1_040_001_003, "操作失败,该模型的模型类型不正确");
23 21  
24   - // ========== API 聊天模型 1-040-002-000 ==========
  22 + // ========== API 聊天角色 1-040-002-000 ==========
25 23 ErrorCode CHAT_ROLE_NOT_EXISTS = new ErrorCode(1_040_002_000, "聊天角色不存在");
26 24 ErrorCode CHAT_ROLE_DISABLE = new ErrorCode(1_040_001_001, "聊天角色({})已禁用!");
27 25  
28 26 // ========== API 聊天会话 1-040-003-000 ==========
29   -
30 27 ErrorCode CHAT_CONVERSATION_NOT_EXISTS = new ErrorCode(1_040_003_000, "对话不存在!");
31 28 ErrorCode CHAT_CONVERSATION_MODEL_ERROR = new ErrorCode(1_040_003_001, "操作失败,该聊天模型的配置不完整");
32 29  
33 30 // ========== API 聊天消息 1-040-004-000 ==========
34   -
35 31 ErrorCode CHAT_MESSAGE_NOT_EXIST = new ErrorCode(1_040_004_000, "消息不存在!");
36 32 ErrorCode CHAT_STREAM_ERROR = new ErrorCode(1_040_004_001, "对话生成异常!");
37 33  
38 34 // ========== API 绘画 1-040-005-000 ==========
39   -
40   - ErrorCode IMAGE_NOT_EXISTS = new ErrorCode(1_022_005_000, "图片不存在!");
41   - ErrorCode IMAGE_MIDJOURNEY_SUBMIT_FAIL = new ErrorCode(1_022_005_001, "Midjourney 提交失败!原因:{}");
42   - ErrorCode IMAGE_CUSTOM_ID_NOT_EXISTS = new ErrorCode(1_022_005_002, "Midjourney 按钮 customId 不存在! {}");
43   - ErrorCode IMAGE_FAIL = new ErrorCode(1_022_005_002, "图片绘画失败! {}");
  35 + ErrorCode IMAGE_NOT_EXISTS = new ErrorCode(1_040_005_000, "图片不存在!");
  36 + ErrorCode IMAGE_MIDJOURNEY_SUBMIT_FAIL = new ErrorCode(1_040_005_001, "Midjourney 提交失败!原因:{}");
  37 + ErrorCode IMAGE_CUSTOM_ID_NOT_EXISTS = new ErrorCode(1_040_005_002, "Midjourney 按钮 customId 不存在! {}");
44 38  
45 39 // ========== API 音乐 1-040-006-000 ==========
46   - ErrorCode MUSIC_NOT_EXISTS = new ErrorCode(1_022_006_000, "音乐不存在!");
  40 + ErrorCode MUSIC_NOT_EXISTS = new ErrorCode(1_040_006_000, "音乐不存在!");
47 41  
48   - // ========== API 写作 1-022-007-000 ==========
49   - ErrorCode WRITE_NOT_EXISTS = new ErrorCode(1_022_007_000, "作文不存在!");
50   - ErrorCode WRITE_STREAM_ERROR = new ErrorCode(1_022_07_001, "写作生成异常!");
  42 + // ========== API 写作 1-040-007-000 ==========
  43 + ErrorCode WRITE_NOT_EXISTS = new ErrorCode(1_040_007_000, "作文不存在!");
  44 + ErrorCode WRITE_STREAM_ERROR = new ErrorCode(1_040_07_001, "写作生成异常!");
51 45  
52 46 // ========== API 思维导图 1-040-008-000 ==========
53 47 ErrorCode MIND_MAP_NOT_EXISTS = new ErrorCode(1_040_008_000, "思维导图不存在!");
54 48  
55   - // ========== API 知识库 1-022-008-000 ==========
56   - ErrorCode KNOWLEDGE_NOT_EXISTS = new ErrorCode(1_022_008_000, "知识库不存在!");
57   - ErrorCode KNOWLEDGE_DOCUMENT_NOT_EXISTS = new ErrorCode(1_022_008_001, "文档不存在!");
58   - ErrorCode KNOWLEDGE_SEGMENT_NOT_EXISTS = new ErrorCode(1_022_008_002, "段落不存在!");
  49 + // ========== API 知识库 1-040-009-000 ==========
  50 + ErrorCode KNOWLEDGE_NOT_EXISTS = new ErrorCode(1_040_009_000, "知识库不存在!");
  51 +
  52 + ErrorCode KNOWLEDGE_DOCUMENT_NOT_EXISTS = new ErrorCode(1_040_009_101, "文档不存在!");
  53 + ErrorCode KNOWLEDGE_DOCUMENT_FILE_EMPTY = new ErrorCode(1_040_009_102, "文档内容为空!");
  54 + ErrorCode KNOWLEDGE_DOCUMENT_FILE_DOWNLOAD_FAIL = new ErrorCode(1_040_009_102, "文件下载失败!");
  55 + ErrorCode KNOWLEDGE_DOCUMENT_FILE_READ_FAIL = new ErrorCode(1_040_009_102, "文档加载失败!");
  56 +
  57 + ErrorCode KNOWLEDGE_SEGMENT_NOT_EXISTS = new ErrorCode(1_040_009_202, "段落不存在!");
  58 + ErrorCode KNOWLEDGE_SEGMENT_CONTENT_TOO_LONG = new ErrorCode(1_040_009_203, "内容 Token 数为 {},超过最大限制 {}");
  59 +
  60 + // ========== AI 工具 1-040-010-000 ==========
  61 + ErrorCode TOOL_NOT_EXISTS = new ErrorCode(1_040_010_000, "工具不存在");
  62 + ErrorCode TOOL_NAME_NOT_EXISTS = new ErrorCode(1_040_010_001, "工具({})找不到 Bean");
59 63  
60 64 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.java
... ... @@ -12,15 +12,18 @@ import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessage
12 12 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO;
13 13 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
14 14 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
  15 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
  16 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
15 17 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
16 18 import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService;
17 19 import cn.iocoder.yudao.module.ai.service.chat.AiChatMessageService;
  20 +import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService;
  21 +import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService;
18 22 import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
19 23 import io.swagger.v3.oas.annotations.Operation;
20 24 import io.swagger.v3.oas.annotations.Parameter;
21 25 import io.swagger.v3.oas.annotations.tags.Tag;
22 26 import jakarta.annotation.Resource;
23   -import jakarta.annotation.security.PermitAll;
24 27 import jakarta.validation.Valid;
25 28 import lombok.extern.slf4j.Slf4j;
26 29 import org.springframework.http.MediaType;
... ... @@ -33,7 +36,7 @@ import java.util.List;
33 36 import java.util.Map;
34 37  
35 38 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
36   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  39 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
37 40 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
38 41  
39 42 @Tag(name = "管理后台 - 聊天消息")
... ... @@ -48,6 +51,10 @@ public class AiChatMessageController {
48 51 private AiChatConversationService chatConversationService;
49 52 @Resource
50 53 private AiChatRoleService chatRoleService;
  54 + @Resource
  55 + private AiKnowledgeSegmentService knowledgeSegmentService;
  56 + @Resource
  57 + private AiKnowledgeDocumentService knowledgeDocumentService;
51 58  
52 59 @Operation(summary = "发送消息(段式)", description = "一次性返回,响应较慢")
53 60 @PostMapping("/send")
... ... @@ -57,7 +64,6 @@ public class AiChatMessageController {
57 64  
58 65 @Operation(summary = "发送消息(流式)", description = "流式返回,响应较快")
59 66 @PostMapping(value = "/send-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
60   - @PermitAll // 解决 SSE 最终响应的时候,会被 Access Denied 拦截的问题
61 67 public Flux<CommonResult<AiChatMessageSendRespVO>> sendChatMessageStream(@Valid @RequestBody AiChatMessageSendReqVO sendReqVO) {
62 68 return chatMessageService.sendChatMessageStream(sendReqVO, getLoginUserId());
63 69 }
... ... @@ -71,8 +77,38 @@ public class AiChatMessageController {
71 77 if (conversation == null || ObjUtil.notEqual(conversation.getUserId(), getLoginUserId())) {
72 78 return success(Collections.emptyList());
73 79 }
  80 + // 1. 获取消息列表
74 81 List<AiChatMessageDO> messageList = chatMessageService.getChatMessageListByConversationId(conversationId);
75   - return success(BeanUtils.toBean(messageList, AiChatMessageRespVO.class));
  82 + if (CollUtil.isEmpty(messageList)) {
  83 + return success(Collections.emptyList());
  84 + }
  85 +
  86 + // 2. 拼接数据,主要是知识库段落信息
  87 + Map<Long, AiKnowledgeSegmentDO> segmentMap = knowledgeSegmentService.getKnowledgeSegmentMap(convertListByFlatMap(messageList,
  88 + message -> CollUtil.isEmpty(message.getSegmentIds()) ? null : message.getSegmentIds().stream()));
  89 + Map<Long, AiKnowledgeDocumentDO> documentMap = knowledgeDocumentService.getKnowledgeDocumentMap(
  90 + convertList(segmentMap.values(), AiKnowledgeSegmentDO::getDocumentId));
  91 + List<AiChatMessageRespVO> messageVOList = BeanUtils.toBean(messageList, AiChatMessageRespVO.class);
  92 + for (int i = 0; i < messageList.size(); i++) {
  93 + AiChatMessageDO message = messageList.get(i);
  94 + if (CollUtil.isEmpty(message.getSegmentIds())) {
  95 + continue;
  96 + }
  97 + // 设置知识库段落信息
  98 + messageVOList.get(i).setSegments(convertList(message.getSegmentIds(), segmentId -> {
  99 + AiKnowledgeSegmentDO segment = segmentMap.get(segmentId);
  100 + if (segment == null) {
  101 + return null;
  102 + }
  103 + AiKnowledgeDocumentDO document = documentMap.get(segment.getDocumentId());
  104 + if (document == null) {
  105 + return null;
  106 + }
  107 + return new AiChatMessageRespVO.KnowledgeSegment().setId(segment.getId()).setContent(segment.getContent())
  108 + .setDocumentId(segment.getDocumentId()).setDocumentName(document.getName());
  109 + }));
  110 + }
  111 + return success(messageVOList);
76 112 }
77 113  
78 114 @Operation(summary = "删除消息")
... ... @@ -105,7 +141,8 @@ public class AiChatMessageController {
105 141 Map<Long, AiChatRoleDO> roleMap = chatRoleService.getChatRoleMap(
106 142 convertSet(pageResult.getList(), AiChatMessageDO::getRoleId));
107 143 return success(BeanUtils.toBean(pageResult, AiChatMessageRespVO.class,
108   - respVO -> MapUtils.findAndThen(roleMap, respVO.getRoleId(), role -> respVO.setRoleName(role.getName()))));
  144 + respVO -> MapUtils.findAndThen(roleMap, respVO.getRoleId(),
  145 + role -> respVO.setRoleName(role.getName()))));
109 146 }
110 147  
111 148 @Operation(summary = "管理员删除消息")
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationRespVO.java
1 1 package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation;
2 2  
3   -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
  3 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
4 4 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
5 5 import com.fhs.core.trans.anno.Trans;
6 6 import com.fhs.core.trans.constant.TransType;
... ... @@ -31,7 +31,7 @@ public class AiChatConversationRespVO implements VO {
31 31 private Long roleId;
32 32  
33 33 @Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
34   - @Trans(type = TransType.SIMPLE, target = AiChatModelDO.class, fields = "name", ref = "modelName")
  34 + @Trans(type = TransType.SIMPLE, target = AiModelDO.class, fields = "name", ref = "modelName")
35 35 private Long modelId;
36 36  
37 37 @Schema(description = "模型标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "ERNIE-Bot-turbo-0922")
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageRespVO.java
... ... @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
4 4 import lombok.Data;
5 5  
6 6 import java.time.LocalDateTime;
  7 +import java.util.List;
7 8  
8 9 @Schema(description = "管理后台 - AI 聊天消息 Response VO")
9 10 @Data
... ... @@ -39,6 +40,12 @@ public class AiChatMessageRespVO {
39 40 @Schema(description = "是否携带上下文", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
40 41 private Boolean useContext;
41 42  
  43 + @Schema(description = "知识库段落编号数组", example = "[1,2,3]")
  44 + private List<Long> segmentIds;
  45 +
  46 + @Schema(description = "知识库段落数组")
  47 + private List<KnowledgeSegment> segments;
  48 +
42 49 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-05-12 12:51")
43 50 private LocalDateTime createTime;
44 51  
... ... @@ -47,4 +54,22 @@ public class AiChatMessageRespVO {
47 54 @Schema(description = "角色名字", example = "小黄")
48 55 private String roleName;
49 56  
  57 + @Schema(description = "知识库段落", example = "Java 开发手册")
  58 + @Data
  59 + public static class KnowledgeSegment {
  60 +
  61 + @Schema(description = "段落编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  62 + private Long id;
  63 +
  64 + @Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
  65 + private String content;
  66 +
  67 + @Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
  68 + private Long documentId;
  69 +
  70 + @Schema(description = "文档名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品使用手册")
  71 + private String documentName;
  72 +
  73 + }
  74 +
50 75 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageSendRespVO.java
... ... @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
4 4 import lombok.Data;
5 5  
6 6 import java.time.LocalDateTime;
  7 +import java.util.List;
7 8  
8 9 @Schema(description = "管理后台 - AI 聊天消息发送 Response VO")
9 10 @Data
... ... @@ -28,6 +29,12 @@ public class AiChatMessageSendRespVO {
28 29 @Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊")
29 30 private String content;
30 31  
  32 + @Schema(description = "知识库段落编号数组", example = "[1,2,3]")
  33 + private List<Long> segmentIds;
  34 +
  35 + @Schema(description = "知识库段落数组")
  36 + private List<AiChatMessageRespVO.KnowledgeSegment> segments;
  37 +
31 38 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
32 39 private LocalDateTime createTime;
33 40  
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageDrawReqVO.java
... ... @@ -14,18 +14,15 @@ import java.util.Map;
14 14 @Data
15 15 public class AiImageDrawReqVO {
16 16  
17   - @Schema(description = "模型平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "OpenAI")
18   - private String platform; // 参见 AiPlatformEnum 枚举
  17 + @Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  18 + @NotNull(message = "模型编号不能为空")
  19 + private Long modelId;
19 20  
20 21 @Schema(description = "提示词", requiredMode = Schema.RequiredMode.REQUIRED, example = "画一个长城")
21 22 @NotEmpty(message = "提示词不能为空")
22 23 @Size(max = 1200, message = "提示词最大 1200")
23 24 private String prompt;
24 25  
25   - @Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "stable-diffusion-v1-6")
26   - @NotEmpty(message = "模型不能为空")
27   - private String model;
28   -
29 26 /**
30 27 * 1. dall-e-2 模型:256x256、512x512、1024x1024
31 28 * 2. dall-e-3 模型:1024x1024, 1792x1024, 或 1024x1792
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/midjourney/AiMidjourneyImagineReqVO.java
... ... @@ -13,9 +13,9 @@ public class AiMidjourneyImagineReqVO {
13 13 @NotEmpty(message = "提示词不能为空!")
14 14 private String prompt;
15 15  
16   - @Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "midjourney")
17   - @NotEmpty(message = "模型不能为空")
18   - private String model; // 参考 MidjourneyApi.ModelEnum
  16 + @Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  17 + @NotNull(message = "模型编号不能为空")
  18 + private Long modelId;
19 19  
20 20 @Schema(description = "图片宽度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
21 21 @NotNull(message = "图片宽度不能为空")
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeController.http 0 → 100644
  1 +### 创建知识库
  2 +POST {{baseUrl}}/ai/knowledge/create
  3 +Content-Type: application/json
  4 +Authorization: {{token}}
  5 +tenant-id: {{adminTenantId}}
  6 +
  7 +{
  8 + "name": "测试标题",
  9 + "description": "测试描述",
  10 + "embeddingModelId": 30,
  11 + "topK": 3,
  12 + "similarityThreshold": 0.5,
  13 + "status": 0
  14 +}
  15 +
  16 +### 更新知识库
  17 +PUT {{baseUrl}}/ai/knowledge/update
  18 +Content-Type: application/json
  19 +Authorization: {{token}}
  20 +tenant-id: {{adminTenantId}}
  21 +
  22 +{
  23 + "id": 1,
  24 + "name": "测试标题(更新)",
  25 + "description": "测试描述",
  26 + "embeddingModelId": 30,
  27 + "topK": 5,
  28 + "similarityThreshold": 0.6,
  29 + "status": 0
  30 +}
  31 +
  32 +### 获取知识库分页
  33 +GET {{baseUrl}}/ai/knowledge/page?pageNo=1&pageSize=10
  34 +Authorization: {{token}}
  35 +tenant-id: {{adminTenantId}}
0 36 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeController.java
1 1 package cn.iocoder.yudao.module.ai.controller.admin.knowledge;
2 2  
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
3 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6   -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeCreateReqVO;
7 7 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO;
8 8 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeRespVO;
9   -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateReqVO;
  9 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeSaveReqVO;
10 10 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
11 11 import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeService;
12 12 import io.swagger.v3.oas.annotations.Operation;
  13 +import io.swagger.v3.oas.annotations.Parameter;
13 14 import io.swagger.v3.oas.annotations.tags.Tag;
14 15 import jakarta.annotation.Resource;
15 16 import jakarta.validation.Valid;
  17 +import org.springframework.security.access.prepost.PreAuthorize;
16 18 import org.springframework.validation.annotation.Validated;
17 19 import org.springframework.web.bind.annotation.*;
18 20  
  21 +import java.util.List;
  22 +
19 23 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
20   -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  24 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
21 25  
22 26 @Tag(name = "管理后台 - AI 知识库")
23 27 @RestController
... ... @@ -30,21 +34,42 @@ public class AiKnowledgeController {
30 34  
31 35 @GetMapping("/page")
32 36 @Operation(summary = "获取知识库分页")
  37 + @PreAuthorize("@ss.hasPermission('ai:knowledge:query')")
33 38 public CommonResult<PageResult<AiKnowledgeRespVO>> getKnowledgePage(@Valid AiKnowledgePageReqVO pageReqVO) {
34   - PageResult<AiKnowledgeDO> pageResult = knowledgeService.getKnowledgePage(getLoginUserId(), pageReqVO);
  39 + PageResult<AiKnowledgeDO> pageResult = knowledgeService.getKnowledgePage(pageReqVO);
35 40 return success(BeanUtils.toBean(pageResult, AiKnowledgeRespVO.class));
36 41 }
37 42  
  43 + @GetMapping("/get")
  44 + @Operation(summary = "获得知识库")
  45 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  46 + @PreAuthorize("@ss.hasPermission('ai:knowledge:query')")
  47 + public CommonResult<AiKnowledgeRespVO> getKnowledge(@RequestParam("id") Long id) {
  48 + AiKnowledgeDO knowledge = knowledgeService.getKnowledge(id);
  49 + return success(BeanUtils.toBean(knowledge, AiKnowledgeRespVO.class));
  50 + }
  51 +
38 52 @PostMapping("/create")
39 53 @Operation(summary = "创建知识库")
40   - public CommonResult<Long> createKnowledge(@RequestBody @Valid AiKnowledgeCreateReqVO createReqVO) {
41   - return success(knowledgeService.createKnowledge(createReqVO, getLoginUserId()));
  54 + @PreAuthorize("@ss.hasPermission('ai:knowledge:create')")
  55 + public CommonResult<Long> createKnowledge(@RequestBody @Valid AiKnowledgeSaveReqVO createReqVO) {
  56 + return success(knowledgeService.createKnowledge(createReqVO));
42 57 }
43 58  
44 59 @PutMapping("/update")
45 60 @Operation(summary = "更新知识库")
46   - public CommonResult<Boolean> updateKnowledge(@RequestBody @Valid AiKnowledgeUpdateReqVO updateReqVO) {
47   - knowledgeService.updateKnowledge(updateReqVO, getLoginUserId());
  61 + @PreAuthorize("@ss.hasPermission('ai:knowledge:update')")
  62 + public CommonResult<Boolean> updateKnowledge(@RequestBody @Valid AiKnowledgeSaveReqVO updateReqVO) {
  63 + knowledgeService.updateKnowledge(updateReqVO);
48 64 return success(true);
49 65 }
  66 +
  67 + @GetMapping("/simple-list")
  68 + @Operation(summary = "获得知识库的精简列表")
  69 + public CommonResult<List<AiKnowledgeRespVO>> getKnowledgeSimpleList() {
  70 + List<AiKnowledgeDO> list = knowledgeService.getKnowledgeSimpleListByStatus(CommonStatusEnum.ENABLE.getStatus());
  71 + return success(convertList(list, knowledge -> new AiKnowledgeRespVO()
  72 + .setId(knowledge.getId()).setName(knowledge.getName())));
  73 + }
  74 +
50 75 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeDocumentController.http 0 → 100644
  1 +### 创建知识文档
  2 +POST {{baseUrl}}/ai/knowledge/document/create
  3 +Content-Type: application/json
  4 +Authorization: Bearer {{token}}
  5 +tenant-id: {{adminTenantId}}
  6 +
  7 +{
  8 + "knowledgeId": 2,
  9 + "name": "测试文档",
  10 + "url": "https://static.iocoder.cn/README.md",
  11 + "segmentMaxTokens": 800
  12 +}
  13 +
  14 +### 批量创建知识文档
  15 +POST {{baseUrl}}/ai/knowledge/document/create-list
  16 +Content-Type: application/json
  17 +Authorization: Bearer {{token}}
  18 +tenant-id: {{adminTenantId}}
  19 +
  20 +{
  21 + "knowledgeId": 1,
  22 + "list": [
  23 + {
  24 + "name": "测试文档1",
  25 + "url": "https://static.iocoder.cn/README.md",
  26 + "segmentMaxTokens": 800
  27 + },
  28 + {
  29 + "name": "测试文档2",
  30 + "url": "https://static.iocoder.cn/README_yudao.md",
  31 + "segmentMaxTokens": 400
  32 + }
  33 + ]
  34 +}
  35 +
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeDocumentController.java
... ... @@ -3,9 +3,7 @@ package cn.iocoder.yudao.module.ai.controller.admin.knowledge;
3 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6   -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO;
7   -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentRespVO;
8   -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateReqVO;
  6 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.*;
9 7 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeDocumentCreateReqVO;
10 8 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
11 9 import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService;
... ... @@ -13,9 +11,12 @@ import io.swagger.v3.oas.annotations.Operation;
13 11 import io.swagger.v3.oas.annotations.tags.Tag;
14 12 import jakarta.annotation.Resource;
15 13 import jakarta.validation.Valid;
  14 +import org.springframework.security.access.prepost.PreAuthorize;
16 15 import org.springframework.validation.annotation.Validated;
17 16 import org.springframework.web.bind.annotation.*;
18 17  
  18 +import java.util.List;
  19 +
19 20 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
20 21  
21 22 @Tag(name = "管理后台 - AI 知识库文档")
... ... @@ -27,25 +28,63 @@ public class AiKnowledgeDocumentController {
27 28 @Resource
28 29 private AiKnowledgeDocumentService documentService;
29 30  
30   - @PostMapping("/create")
31   - @Operation(summary = "新建文档")
32   - public CommonResult<Long> createKnowledgeDocument(@Valid AiKnowledgeDocumentCreateReqVO reqVO) {
33   - Long knowledgeDocumentId = documentService.createKnowledgeDocument(reqVO);
34   - return success(knowledgeDocumentId);
35   - }
36   -
37 31 @GetMapping("/page")
38 32 @Operation(summary = "获取文档分页")
39   - public CommonResult<PageResult<AiKnowledgeDocumentRespVO>> getKnowledgeDocumentPage(@Valid AiKnowledgeDocumentPageReqVO pageReqVO) {
  33 + @PreAuthorize("@ss.hasPermission('ai:knowledge:query')")
  34 + public CommonResult<PageResult<AiKnowledgeDocumentRespVO>> getKnowledgeDocumentPage(
  35 + @Valid AiKnowledgeDocumentPageReqVO pageReqVO) {
40 36 PageResult<AiKnowledgeDocumentDO> pageResult = documentService.getKnowledgeDocumentPage(pageReqVO);
41 37 return success(BeanUtils.toBean(pageResult, AiKnowledgeDocumentRespVO.class));
42 38 }
43 39  
  40 + @GetMapping("/get")
  41 + @Operation(summary = "获取文档详情")
  42 + @PreAuthorize("@ss.hasPermission('ai:knowledge:query')")
  43 + public CommonResult<AiKnowledgeDocumentRespVO> getKnowledgeDocument(@RequestParam("id") Long id) {
  44 + AiKnowledgeDocumentDO document = documentService.getKnowledgeDocument(id);
  45 + return success(BeanUtils.toBean(document, AiKnowledgeDocumentRespVO.class));
  46 + }
  47 +
  48 + @PostMapping("/create")
  49 + @Operation(summary = "新建文档(单个)")
  50 + @PreAuthorize("@ss.hasPermission('ai:knowledge:create')")
  51 + public CommonResult<Long> createKnowledgeDocument(@RequestBody @Valid AiKnowledgeDocumentCreateReqVO reqVO) {
  52 + Long id = documentService.createKnowledgeDocument(reqVO);
  53 + return success(id);
  54 + }
  55 +
  56 + @PostMapping("/create-list")
  57 + @Operation(summary = "新建文档(多个)")
  58 + @PreAuthorize("@ss.hasPermission('ai:knowledge:create')")
  59 + public CommonResult<List<Long>> createKnowledgeDocumentList(
  60 + @RequestBody @Valid AiKnowledgeDocumentCreateListReqVO reqVO) {
  61 + List<Long> ids = documentService.createKnowledgeDocumentList(reqVO);
  62 + return success(ids);
  63 + }
  64 +
44 65 @PutMapping("/update")
45 66 @Operation(summary = "更新文档")
  67 + @PreAuthorize("@ss.hasPermission('ai:knowledge:update')")
46 68 public CommonResult<Boolean> updateKnowledgeDocument(@Valid @RequestBody AiKnowledgeDocumentUpdateReqVO reqVO) {
47 69 documentService.updateKnowledgeDocument(reqVO);
48 70 return success(true);
49 71 }
50 72  
  73 + @PutMapping("/update-status")
  74 + @Operation(summary = "更新文档状态")
  75 + @PreAuthorize("@ss.hasPermission('ai:knowledge:update')")
  76 + public CommonResult<Boolean> updateKnowledgeDocumentStatus(
  77 + @Valid @RequestBody AiKnowledgeDocumentUpdateStatusReqVO reqVO) {
  78 + documentService.updateKnowledgeDocumentStatus(reqVO);
  79 + return success(true);
  80 + }
  81 +
  82 + @DeleteMapping("/delete")
  83 + @Operation(summary = "删除文档")
  84 + @PreAuthorize("@ss.hasPermission('ai:knowledge:delete')")
  85 + public CommonResult<Boolean> deleteKnowledgeDocument(@RequestParam("id") Long id) {
  86 + documentService.deleteKnowledgeDocument(id);
  87 + return success(true);
  88 + }
  89 +
51 90 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeSegmentController.http 0 → 100644
  1 +### 切片内容
  2 +GET {{baseUrl}}/ai/knowledge/segment/split?url=https://static.iocoder.cn/README_yudao.md&segmentMaxTokens=800
  3 +Content-Type: application/json
  4 +Authorization: Bearer {{token}}
  5 +tenant-id: {{adminTenantId}}
  6 +
  7 +### 搜索段落内容
  8 +GET {{baseUrl}}/ai/knowledge/segment/search?knowledgeId=2&content=如何使用这个产品&topK=5&similarityThreshold=0.1
  9 +Content-Type: application/json
  10 +Authorization: Bearer {{token}}
  11 +tenant-id: {{adminTenantId}}
  12 +
  13 +### 获取文档处理列表
  14 +GET {{baseUrl}}/ai/knowledge/segment/get-process-list?documentIds=1,2,3
  15 +Content-Type: application/json
  16 +Authorization: Bearer {{token}}
  17 +tenant-id: {{adminTenantId}}
0 18 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeSegmentController.java
1 1 package cn.iocoder.yudao.module.ai.controller.admin.knowledge;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
5 7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6   -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO;
7   -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentRespVO;
8   -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateReqVO;
9   -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO;
  8 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.*;
  9 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
10 10 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
  11 +import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService;
11 12 import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService;
  13 +import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchReqBO;
  14 +import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchRespBO;
12 15 import io.swagger.v3.oas.annotations.Operation;
  16 +import io.swagger.v3.oas.annotations.Parameter;
  17 +import io.swagger.v3.oas.annotations.Parameters;
13 18 import io.swagger.v3.oas.annotations.tags.Tag;
14 19 import jakarta.annotation.Resource;
15 20 import jakarta.validation.Valid;
  21 +import org.hibernate.validator.constraints.URL;
  22 +import org.springframework.security.access.prepost.PreAuthorize;
16 23 import org.springframework.validation.annotation.Validated;
17 24 import org.springframework.web.bind.annotation.*;
18 25  
  26 +import java.util.Collections;
  27 +import java.util.List;
  28 +import java.util.Map;
  29 +
19 30 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  31 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
20 32  
21 33 @Tag(name = "管理后台 - AI 知识库段落")
22 34 @RestController
... ... @@ -26,26 +38,93 @@ public class AiKnowledgeSegmentController {
26 38  
27 39 @Resource
28 40 private AiKnowledgeSegmentService segmentService;
  41 + @Resource
  42 + private AiKnowledgeDocumentService documentService;
  43 +
  44 + @GetMapping("/get")
  45 + @Operation(summary = "获取段落详情")
  46 + @Parameter(name = "id", description = "段落编号", required = true, example = "1024")
  47 + @PreAuthorize("@ss.hasPermission('ai:knowledge:query')")
  48 + public CommonResult<AiKnowledgeSegmentRespVO> getKnowledgeSegment(@RequestParam("id") Long id) {
  49 + AiKnowledgeSegmentDO segment = segmentService.getKnowledgeSegment(id);
  50 + return success(BeanUtils.toBean(segment, AiKnowledgeSegmentRespVO.class));
  51 + }
29 52  
30 53 @GetMapping("/page")
31 54 @Operation(summary = "获取段落分页")
32   - public CommonResult<PageResult<AiKnowledgeSegmentRespVO>> getKnowledgeSegmentPage(@Valid AiKnowledgeSegmentPageReqVO pageReqVO) {
  55 + @PreAuthorize("@ss.hasPermission('ai:knowledge:query')")
  56 + public CommonResult<PageResult<AiKnowledgeSegmentRespVO>> getKnowledgeSegmentPage(
  57 + @Valid AiKnowledgeSegmentPageReqVO pageReqVO) {
33 58 PageResult<AiKnowledgeSegmentDO> pageResult = segmentService.getKnowledgeSegmentPage(pageReqVO);
34 59 return success(BeanUtils.toBean(pageResult, AiKnowledgeSegmentRespVO.class));
35 60 }
36 61  
  62 + @PostMapping("/create")
  63 + @Operation(summary = "创建段落")
  64 + @PreAuthorize("@ss.hasPermission('ai:knowledge:create')")
  65 + public CommonResult<Long> createKnowledgeSegment(@Valid @RequestBody AiKnowledgeSegmentSaveReqVO createReqVO) {
  66 + return success(segmentService.createKnowledgeSegment(createReqVO));
  67 + }
  68 +
37 69 @PutMapping("/update")
38 70 @Operation(summary = "更新段落内容")
39   - public CommonResult<Boolean> updateKnowledgeSegment(@Valid @RequestBody AiKnowledgeSegmentUpdateReqVO reqVO) {
  71 + @PreAuthorize("@ss.hasPermission('ai:knowledge:update')")
  72 + public CommonResult<Boolean> updateKnowledgeSegment(@Valid @RequestBody AiKnowledgeSegmentSaveReqVO reqVO) {
40 73 segmentService.updateKnowledgeSegment(reqVO);
41 74 return success(true);
42 75 }
43 76  
44 77 @PutMapping("/update-status")
45 78 @Operation(summary = "启禁用段落内容")
46   - public CommonResult<Boolean> updateKnowledgeSegmentStatus(@Valid @RequestBody AiKnowledgeSegmentUpdateStatusReqVO reqVO) {
  79 + @PreAuthorize("@ss.hasPermission('ai:knowledge:update')")
  80 + public CommonResult<Boolean> updateKnowledgeSegmentStatus(
  81 + @Valid @RequestBody AiKnowledgeSegmentUpdateStatusReqVO reqVO) {
47 82 segmentService.updateKnowledgeSegmentStatus(reqVO);
48 83 return success(true);
49 84 }
50 85  
  86 + @GetMapping("/split")
  87 + @Operation(summary = "切片内容")
  88 + @Parameters({
  89 + @Parameter(name = "url", description = "文档 URL", required = true),
  90 + @Parameter(name = "segmentMaxTokens", description = "分段的最大 Token 数", required = true)
  91 + })
  92 + @PreAuthorize("@ss.hasPermission('ai:knowledge:query')")
  93 + public CommonResult<List<AiKnowledgeSegmentRespVO>> splitContent(
  94 + @RequestParam("url") @URL String url,
  95 + @RequestParam(value = "segmentMaxTokens") Integer segmentMaxTokens) {
  96 + List<AiKnowledgeSegmentDO> segments = segmentService.splitContent(url, segmentMaxTokens);
  97 + return success(BeanUtils.toBean(segments, AiKnowledgeSegmentRespVO.class));
  98 + }
  99 +
  100 + @GetMapping("/get-process-list")
  101 + @Operation(summary = "获取文档处理列表")
  102 + @Parameter(name = "documentIds", description = "文档编号列表", required = true, example = "1,2,3")
  103 + @PreAuthorize("@ss.hasPermission('ai:knowledge:query')")
  104 + public CommonResult<List<AiKnowledgeSegmentProcessRespVO>> getKnowledgeSegmentProcessList(
  105 + @RequestParam("documentIds") List<Long> documentIds) {
  106 + List<AiKnowledgeSegmentProcessRespVO> list = segmentService.getKnowledgeSegmentProcessList(documentIds);
  107 + return success(list);
  108 + }
  109 +
  110 + @GetMapping("/search")
  111 + @Operation(summary = "搜索段落内容")
  112 + @PreAuthorize("@ss.hasPermission('ai:knowledge:query')")
  113 + public CommonResult<List<AiKnowledgeSegmentSearchRespVO>> searchKnowledgeSegment(
  114 + @Valid AiKnowledgeSegmentSearchReqVO reqVO) {
  115 + // 1. 搜索段落
  116 + List<AiKnowledgeSegmentSearchRespBO> segments = segmentService
  117 + .searchKnowledgeSegment(BeanUtils.toBean(reqVO, AiKnowledgeSegmentSearchReqBO.class));
  118 + if (CollUtil.isEmpty(segments)) {
  119 + return success(Collections.emptyList());
  120 + }
  121 +
  122 + // 2. 拼接 VO
  123 + Map<Long, AiKnowledgeDocumentDO> documentMap = documentService.getKnowledgeDocumentMap(convertSet(
  124 + segments, AiKnowledgeSegmentSearchRespBO::getDocumentId));
  125 + return success(BeanUtils.toBean(segments, AiKnowledgeSegmentSearchRespVO.class,
  126 + segment -> MapUtils.findAndThen(documentMap, segment.getDocumentId(),
  127 + document -> segment.setDocumentName(document.getName()))));
  128 + }
  129 +
51 130 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentCreateListReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotBlank;
  5 +import jakarta.validation.constraints.NotEmpty;
  6 +import jakarta.validation.constraints.NotNull;
  7 +import lombok.Data;
  8 +import org.hibernate.validator.constraints.URL;
  9 +
  10 +import java.util.List;
  11 +
  12 +@Schema(description = "管理后台 - AI 知识库文档批量创建 Request VO")
  13 +@Data
  14 +public class AiKnowledgeDocumentCreateListReqVO {
  15 +
  16 + @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1204")
  17 + @NotNull(message = "知识库编号不能为空")
  18 + private Long knowledgeId;
  19 +
  20 + @Schema(description = "分段的最大 Token 数", requiredMode = Schema.RequiredMode.REQUIRED, example = "800")
  21 + @NotNull(message = "分段的最大 Token 数不能为空")
  22 + private Integer segmentMaxTokens;
  23 +
  24 + @Schema(description = "文档列表", requiredMode = Schema.RequiredMode.REQUIRED)
  25 + @NotEmpty(message = "文档列表不能为空")
  26 + private List<Document> list;
  27 +
  28 + @Schema(description = "文档")
  29 + @Data
  30 + public static class Document {
  31 +
  32 + @Schema(description = "文档名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "三方登陆")
  33 + @NotBlank(message = "文档名称不能为空")
  34 + private String name;
  35 +
  36 + @Schema(description = "文档 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://doc.iocoder.cn")
  37 + @URL(message = "文档 URL 格式不正确")
  38 + private String url;
  39 +
  40 + }
  41 +
  42 +}
0 43 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentPageReqVO.java
... ... @@ -8,6 +8,9 @@ import lombok.Data;
8 8 @Data
9 9 public class AiKnowledgeDocumentPageReqVO extends PageParam {
10 10  
  11 + @Schema(description = "知识库编号", example = "1")
  12 + private Long knowledgeId;
  13 +
11 14 @Schema(description = "文档名称", example = "Java 开发手册")
12 15 private String name;
13 16  
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentRespVO.java
1 1 package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document;
2 2  
3   -import cn.iocoder.yudao.framework.common.pojo.PageParam;
4 3 import io.swagger.v3.oas.annotations.media.Schema;
5 4 import lombok.Data;
6 5  
7   -@Schema(description = "管理后台 - AI 知识库-文档 Response VO")
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "管理后台 - AI 知识库文档 Response VO")
8 9 @Data
9   -public class AiKnowledgeDocumentRespVO extends PageParam {
  10 +public class AiKnowledgeDocumentRespVO {
10 11  
11   - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
  12 + @Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
12 13 private Long id;
13 14  
14 15 @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
15 16 private Long knowledgeId;
16 17  
17   - @Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
  18 + @Schema(description = "文档名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
18 19 private String name;
19 20  
20   - @Schema(description = "内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 是一门面向对象的语言.....")
  21 + @Schema(description = "文档 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://doc.iocoder.cn")
  22 + private String url;
  23 +
  24 + @Schema(description = "文档内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 是一门面向对象的语言.....")
21 25 private String content;
22 26  
23   - @Schema(description = "文档 url", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://doc.iocoder.cn")
24   - private String url;
  27 + @Schema(description = "文档内容长度", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
  28 + private Integer contentLength;
25 29  
26   - @Schema(description = "token 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  30 + @Schema(description = "文档 Token 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
27 31 private Integer tokens;
28 32  
29   - @Schema(description = "字符数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1008")
30   - private Integer wordCount;
  33 + @Schema(description = "分片最大 Token 数", requiredMode = Schema.RequiredMode.REQUIRED, example = "512")
  34 + private Integer segmentMaxTokens;
31 35  
32   - @Schema(description = "切片状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
33   - private Integer sliceStatus;
  36 + @Schema(description = "召回次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  37 + private Integer retrievalCount;
34 38  
35   - @Schema(description = "文档状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  39 + @Schema(description = "文档状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
36 40 private Integer status;
37 41  
  42 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  43 + private LocalDateTime createTime;
  44 +
38 45 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentUpdateReqVO.java
1 1 package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document;
2 2  
3   -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4   -import cn.iocoder.yudao.framework.common.validation.InEnum;
5 3 import io.swagger.v3.oas.annotations.media.Schema;
6 4 import jakarta.validation.constraints.NotNull;
7 5 import lombok.Data;
8 6  
9   -
10   -@Schema(description = "管理后台 - AI 更新 知识库-文档 Request VO")
  7 +@Schema(description = "管理后台 - AI 知识库文档更新 Request VO")
11 8 @Data
12 9 public class AiKnowledgeDocumentUpdateReqVO {
13 10  
14   -
15 11 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583")
16 12 @NotNull(message = "编号不能为空")
17 13 private Long id;
18 14  
19   - @Schema(description = "是否启用", example = "1")
20   - @InEnum(CommonStatusEnum.class)
21   - private Integer status;
22   -
23 15 @Schema(description = "名称", example = "Java 开发手册")
24 16 private String name;
25 17  
  18 + @Schema(description = "分片最大 Token 数", example = "1000")
  19 + private Integer segmentMaxTokens;
  20 +
26 21 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentUpdateStatusReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import jakarta.validation.constraints.NotNull;
  7 +import lombok.Data;
  8 +
  9 +@Schema(description = "管理后台 - AI 知识库文档更新状态 Request VO")
  10 +@Data
  11 +public class AiKnowledgeDocumentUpdateStatusReqVO {
  12 +
  13 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583")
  14 + @NotNull(message = "编号不能为空")
  15 + private Long id;
  16 +
  17 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  18 + @NotNull(message = "状态不能为空")
  19 + @InEnum(CommonStatusEnum.class)
  20 + private Integer status;
  21 +
  22 +}
0 23 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeDocumentCreateReqVO.java
... ... @@ -23,24 +23,8 @@ public class AiKnowledgeDocumentCreateReqVO {
23 23 @URL(message = "文档 URL 格式不正确")
24 24 private String url;
25 25  
26   - @Schema(description = "每个段落的目标 token 数", requiredMode = Schema.RequiredMode.REQUIRED, example = "800")
27   - @NotNull(message = "每个段落的目标 token 数不能为空")
28   - private Integer defaultSegmentTokens;
29   -
30   - @Schema(description = "每个段落的最小字符数", requiredMode = Schema.RequiredMode.REQUIRED, example = "350")
31   - @NotNull(message = "每个段落的最小字符数不能为空")
32   - private Integer minSegmentWordCount;
33   -
34   - @Schema(description = "丢弃阈值:低于此阈值的段落会被丢弃", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
35   - @NotNull(message = "丢弃阈值不能为空")
36   - private Integer minChunkLengthToEmbed;
37   -
38   - @Schema(description = "最大段落数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000")
39   - @NotNull(message = "最大段落数不能为空")
40   - private Integer maxNumSegments;
41   -
42   - @Schema(description = "分块是否保留分隔符", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
43   - @NotNull(message = "分块是否保留分隔符不能为空")
44   - private Boolean keepSeparator;
  26 + @Schema(description = "分段的最大 Token 数", requiredMode = Schema.RequiredMode.REQUIRED, example = "800")
  27 + @NotNull(message = "分段的最大 Token 数不能为空")
  28 + private Integer segmentMaxTokens;
45 29  
46 30 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgePageReqVO.java
1 1 package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
2 2  
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
3 4 import cn.iocoder.yudao.framework.common.pojo.PageParam;
  5 +import cn.iocoder.yudao.framework.common.validation.InEnum;
4 6 import io.swagger.v3.oas.annotations.media.Schema;
5 7 import lombok.Data;
  8 +import org.springframework.format.annotation.DateTimeFormat;
  9 +
  10 +import java.time.LocalDateTime;
  11 +
  12 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
6 13  
7 14 @Schema(description = "管理后台 - AI 知识库的分页 Request VO")
8 15 @Data
9 16 public class AiKnowledgePageReqVO extends PageParam {
10 17  
11   - @Schema(description = "知识库名称", example = "Java 开发手册")
  18 + @Schema(description = "知识库名称", example = "芋艿")
12 19 private String name;
13 20  
  21 + @Schema(description = "是否启用", example = "1")
  22 + @InEnum(CommonStatusEnum.class)
  23 + private Integer status;
  24 +
  25 + @Schema(description = "创建时间")
  26 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  27 + private LocalDateTime[] createTime;
  28 +
14 29 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeRespVO.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import lombok.Data;
5 5  
  6 +import java.time.LocalDateTime;
6 7  
7 8 @Schema(description = "管理后台 - AI 知识库 Response VO")
8 9 @Data
... ... @@ -17,10 +18,22 @@ public class AiKnowledgeRespVO {
17 18 @Schema(description = "知识库描述", example = "帮助你快速构建系统")
18 19 private String description;
19 20  
20   - @Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "14")
21   - private Long modelId;
  21 + @Schema(description = "向量模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "14")
  22 + private Long embeddingModelId;
22 23  
23   - @Schema(description = "模型标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "qwen-72b-chat")
24   - private String model;
  24 + @Schema(description = "向量模型标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "qwen-72b-chat")
  25 + private String embeddingModel;
  26 +
  27 + @Schema(description = "topK", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
  28 + private Integer topK;
  29 +
  30 + @Schema(description = "相似度阈值", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.7")
  31 + private Double similarityThreshold;
  32 +
  33 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  34 + private Integer status;
  35 +
  36 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  37 + private LocalDateTime createTime;
25 38  
26 39 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeCreateReqVO.java renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeSaveReqVO.java
1 1 package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
2 2  
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
3 5 import io.swagger.v3.oas.annotations.media.Schema;
4 6 import jakarta.validation.constraints.NotBlank;
5 7 import jakarta.validation.constraints.NotNull;
6 8 import lombok.Data;
7 9  
8   -import java.util.List;
9   -
10   -@Schema(description = "管理后台 - AI 知识库创建 Request VO")
  10 +@Schema(description = "管理后台 - AI 知识库新增/修改 Request VO")
11 11 @Data
12   -public class AiKnowledgeCreateReqVO {
  12 +public class AiKnowledgeSaveReqVO {
  13 +
  14 + @Schema(description = "对话编号", example = "1204")
  15 + private Long id;
13 16  
14 17 @Schema(description = "知识库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "ruoyi-vue-pro 用户指南")
15 18 @NotBlank(message = "知识库名称不能为空")
... ... @@ -18,19 +21,21 @@ public class AiKnowledgeCreateReqVO {
18 21 @Schema(description = "知识库描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "存储 ruoyi-vue-pro 操作文档")
19 22 private String description;
20 23  
21   - @Schema(description = "可见权限,只能选择哪些人可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1,2,3]")
22   - private List<Long> visibilityPermissions;
  24 + @Schema(description = "向量模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  25 + @NotNull(message = "向量模型不能为空")
  26 + private Long embeddingModelId;
23 27  
24   - @Schema(description = "嵌入模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
25   - @NotNull(message = "嵌入模型不能为空")
26   - private Long modelId;
  28 + @Schema(description = "topK", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
  29 + @NotNull(message = "topK 不能为空")
  30 + private Integer topK;
27 31  
28 32 @Schema(description = "相似性阈值", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.5")
29 33 @NotNull(message = "相似性阈值不能为空")
30 34 private Double similarityThreshold;
31 35  
32   - @Schema(description = "topK", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
33   - @NotNull(message = "topK 不能为空")
34   - private Integer topK;
  36 + @Schema(description = "是否启用", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  37 + @NotNull(message = "是否启用不能为空")
  38 + @InEnum(CommonStatusEnum.class)
  39 + private Integer status;
35 40  
36 41 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeUpdateReqVO.java deleted
1   -package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
2   -
3   -import io.swagger.v3.oas.annotations.media.Schema;
4   -import jakarta.validation.constraints.NotBlank;
5   -import jakarta.validation.constraints.NotNull;
6   -import lombok.Data;
7   -
8   -import java.util.List;
9   -
10   -@Schema(description = "管理后台 - AI 知识库更新【我的】 Request VO")
11   -@Data
12   -public class AiKnowledgeUpdateReqVO {
13   -
14   - @Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1204")
15   - @NotNull(message = "知识库编号不能为空")
16   - private Long id;
17   -
18   - @Schema(description = "知识库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "")
19   - @NotBlank(message = "知识库名称不能为空")
20   - private String name;
21   -
22   - @Schema(description = "知识库描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "")
23   - private String description;
24   -
25   - @Schema(description = "可见权限,只能选择哪些人可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3")
26   - private List<Long> visibilityPermissions;
27   -
28   - @Schema(description = "嵌入模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
29   - @NotNull(message = "嵌入模型不能为空")
30   - private Long modelId;
31   -
32   -}
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentPageReqVO.java
1 1 package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
2 2  
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
3 4 import cn.iocoder.yudao.framework.common.pojo.PageParam;
  5 +import cn.iocoder.yudao.framework.common.validation.InEnum;
4 6 import io.swagger.v3.oas.annotations.media.Schema;
5 7 import lombok.Data;
6 8  
... ... @@ -8,13 +10,14 @@ import lombok.Data;
8 10 @Data
9 11 public class AiKnowledgeSegmentPageReqVO extends PageParam {
10 12  
11   - @Schema(description = "分段状态", example = "1")
12   - private Integer status;
13   -
14 13 @Schema(description = "文档编号", example = "1")
15 14 private Integer documentId;
16 15  
17 16 @Schema(description = "分段内容关键字", example = "Java 开发")
18   - private String keyword;
  17 + private String content;
  18 +
  19 + @Schema(description = "分段状态", example = "1")
  20 + @InEnum(CommonStatusEnum.class)
  21 + private Integer status;
19 22  
20 23 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentProcessRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "管理后台 - AI 知识库段落向量进度 Response VO")
  7 +@Data
  8 +public class AiKnowledgeSegmentProcessRespVO {
  9 +
  10 + @Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  11 + private Long documentId;
  12 +
  13 + @Schema(description = "总段落数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  14 + private Long count;
  15 +
  16 + @Schema(description = "已向量化段落数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
  17 + private Long embeddingCount;
  18 +
  19 +}
0 20 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentRespVO.java
... ... @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import lombok.Data;
5 5  
6   -@Schema(description = "管理后台 - AI 知识库-文档 Response VO")
  6 +@Schema(description = "管理后台 - AI 知识库文档分片 Response VO")
7 7 @Data
8 8 public class AiKnowledgeSegmentRespVO {
9 9  
... ... @@ -22,13 +22,19 @@ public class AiKnowledgeSegmentRespVO {
22 22 @Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
23 23 private String content;
24 24  
  25 + @Schema(description = "切片内容长度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  26 + private Integer contentLength;
  27 +
25 28 @Schema(description = "token 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
26 29 private Integer tokens;
27 30  
28   - @Schema(description = "字符数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1008")
29   - private Integer wordCount;
  31 + @Schema(description = "召回次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  32 + private Integer retrievalCount;
30 33  
31 34 @Schema(description = "文档状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
32 35 private Integer status;
33 36  
  37 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  38 + private Long createTime;
  39 +
34 40 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentUpdateReqVO.java renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentSaveReqVO.java
1 1 package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
2 2  
3 3 import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotEmpty;
4 5 import lombok.Data;
5 6  
6   -
7   -@Schema(description = "管理后台 - AI 更新 知识库-段落 request VO")
  7 +@Schema(description = "管理后台 - AI 新增/修改知识库段落 request VO")
8 8 @Data
9   -public class AiKnowledgeSegmentUpdateReqVO {
  9 +public class AiKnowledgeSegmentSaveReqVO {
10 10  
11   - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
  11 + @Schema(description = "编号", example = "24790")
12 12 private Long id;
13 13  
  14 + @Schema(description = "知识库文档编号", example = "1024")
  15 + private Long documentId;
  16 +
14 17 @Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
  18 + @NotEmpty(message = "切片内容不能为空")
15 19 private String content;
16 20  
17 21 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentSearchReqVO.java
... ... @@ -3,15 +3,25 @@ package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import lombok.Data;
5 5  
  6 +import jakarta.validation.constraints.NotEmpty;
  7 +import jakarta.validation.constraints.NotNull;
6 8  
7   -@Schema(description = "管理后台 - AI 知识库段落召回 Request VO")
  9 +@Schema(description = "管理后台 - AI 知识库段落搜索 Request VO")
8 10 @Data
9 11 public class AiKnowledgeSegmentSearchReqVO {
10 12  
11   - @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
  13 + @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  14 + @NotNull(message = "知识库编号不能为空")
12 15 private Long knowledgeId;
13 16  
14   - @Schema(description = "内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 学习路线")
  17 + @Schema(description = "内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "如何使用这个产品")
  18 + @NotEmpty(message = "内容不能为空")
15 19 private String content;
16 20  
  21 + @Schema(description = "最大返回数量", example = "5")
  22 + private Integer topK;
  23 +
  24 + @Schema(description = "相似度阈值", example = "0.7")
  25 + private Double similarityThreshold;
  26 +
17 27 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentSearchRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "管理后台 - AI 知识库段落搜索 Response VO")
  7 +@Data
  8 +public class AiKnowledgeSegmentSearchRespVO extends AiKnowledgeSegmentRespVO {
  9 +
  10 + @Schema(description = "文档名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品使用手册")
  11 + private String documentName;
  12 +
  13 + @Schema(description = "相似度分数", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.95")
  14 + private Double score;
  15 +
  16 +}
0 17 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/mindmap/AiMindMapController.java
... ... @@ -12,7 +12,6 @@ import io.swagger.v3.oas.annotations.Operation;
12 12 import io.swagger.v3.oas.annotations.Parameter;
13 13 import io.swagger.v3.oas.annotations.tags.Tag;
14 14 import jakarta.annotation.Resource;
15   -import jakarta.annotation.security.PermitAll;
16 15 import jakarta.validation.Valid;
17 16 import org.springframework.http.MediaType;
18 17 import org.springframework.security.access.prepost.PreAuthorize;
... ... @@ -32,7 +31,6 @@ public class AiMindMapController {
32 31  
33 32 @PostMapping(value = "/generate-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
34 33 @Operation(summary = "导图生成(流式)", description = "流式返回,响应较快")
35   - @PermitAll // 解决 SSE 最终响应的时候,会被 Access Denied 拦截的问题
36 34 public Flux<CommonResult<String>> generateMindMap(@RequestBody @Valid AiMindMapGenerateReqVO generateReqVO) {
37 35 return mindMapService.generateMindMap(generateReqVO, getLoginUserId());
38 36 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/mindmap/vo/AiMindMapPageReqVO.java
... ... @@ -13,8 +13,6 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
13 13  
14 14 @Schema(description = "管理后台 - AI 思维导图分页 Request VO")
15 15 @Data
16   -@EqualsAndHashCode(callSuper = true)
17   -@ToString(callSuper = true)
18 16 public class AiMindMapPageReqVO extends PageParam {
19 17  
20 18 @Schema(description = "用户编号", example = "4325")
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiApiKeyController.java
... ... @@ -6,9 +6,8 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 6 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyPageReqVO;
7 7 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyRespVO;
8 8 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveReqVO;
9   -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelRespVO;
  9 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelRespVO;
10 10 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
11   -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
12 11 import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
13 12 import io.swagger.v3.oas.annotations.Operation;
14 13 import io.swagger.v3.oas.annotations.Parameter;
... ... @@ -76,9 +75,9 @@ public class AiApiKeyController {
76 75  
77 76 @GetMapping("/simple-list")
78 77 @Operation(summary = "获得 API 密钥分页列表")
79   - public CommonResult<List<AiChatModelRespVO>> getApiKeySimpleList() {
  78 + public CommonResult<List<AiModelRespVO>> getApiKeySimpleList() {
80 79 List<AiApiKeyDO> list = apiKeyService.getApiKeyList();
81   - return success(convertList(list, key -> new AiChatModelRespVO().setId(key.getId()).setName(key.getName())));
  80 + return success(convertList(list, key -> new AiModelRespVO().setId(key.getId()).setName(key.getName())));
82 81 }
83 82  
84 83 }
85 84 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiChatModelController.java deleted
1   -package cn.iocoder.yudao.module.ai.controller.admin.model;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
5   -import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6   -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelPageReqVO;
7   -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelRespVO;
8   -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelSaveReqVO;
9   -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
10   -import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
11   -import io.swagger.v3.oas.annotations.Operation;
12   -import io.swagger.v3.oas.annotations.Parameter;
13   -import io.swagger.v3.oas.annotations.tags.Tag;
14   -import jakarta.annotation.Resource;
15   -import jakarta.validation.Valid;
16   -import org.springframework.security.access.prepost.PreAuthorize;
17   -import org.springframework.validation.annotation.Validated;
18   -import org.springframework.web.bind.annotation.*;
19   -
20   -import java.util.List;
21   -
22   -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
23   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
24   -
25   -@Tag(name = "管理后台 - AI 聊天模型")
26   -@RestController
27   -@RequestMapping("/ai/chat-model")
28   -@Validated
29   -public class AiChatModelController {
30   -
31   - @Resource
32   - private AiChatModelService chatModelService;
33   -
34   - @PostMapping("/create")
35   - @Operation(summary = "创建聊天模型")
36   - @PreAuthorize("@ss.hasPermission('ai:chat-model:create')")
37   - public CommonResult<Long> createChatModel(@Valid @RequestBody AiChatModelSaveReqVO createReqVO) {
38   - return success(chatModelService.createChatModel(createReqVO));
39   - }
40   -
41   - @PutMapping("/update")
42   - @Operation(summary = "更新聊天模型")
43   - @PreAuthorize("@ss.hasPermission('ai:chat-model:update')")
44   - public CommonResult<Boolean> updateChatModel(@Valid @RequestBody AiChatModelSaveReqVO updateReqVO) {
45   - chatModelService.updateChatModel(updateReqVO);
46   - return success(true);
47   - }
48   -
49   - @DeleteMapping("/delete")
50   - @Operation(summary = "删除聊天模型")
51   - @Parameter(name = "id", description = "编号", required = true)
52   - @PreAuthorize("@ss.hasPermission('ai:chat-model:delete')")
53   - public CommonResult<Boolean> deleteChatModel(@RequestParam("id") Long id) {
54   - chatModelService.deleteChatModel(id);
55   - return success(true);
56   - }
57   -
58   - @GetMapping("/get")
59   - @Operation(summary = "获得聊天模型")
60   - @Parameter(name = "id", description = "编号", required = true, example = "1024")
61   - @PreAuthorize("@ss.hasPermission('ai:chat-model:query')")
62   - public CommonResult<AiChatModelRespVO> getChatModel(@RequestParam("id") Long id) {
63   - AiChatModelDO chatModel = chatModelService.getChatModel(id);
64   - return success(BeanUtils.toBean(chatModel, AiChatModelRespVO.class));
65   - }
66   -
67   - @GetMapping("/page")
68   - @Operation(summary = "获得聊天模型分页")
69   - @PreAuthorize("@ss.hasPermission('ai:chat-model:query')")
70   - public CommonResult<PageResult<AiChatModelRespVO>> getChatModelPage(@Valid AiChatModelPageReqVO pageReqVO) {
71   - PageResult<AiChatModelDO> pageResult = chatModelService.getChatModelPage(pageReqVO);
72   - return success(BeanUtils.toBean(pageResult, AiChatModelRespVO.class));
73   - }
74   -
75   - @GetMapping("/simple-list")
76   - @Operation(summary = "获得聊天模型列表")
77   - @Parameter(name = "status", description = "状态", required = true, example = "1")
78   - public CommonResult<List<AiChatModelRespVO>> getChatModelSimpleList(@RequestParam("status") Integer status) {
79   - List<AiChatModelDO> list = chatModelService.getChatModelListByStatus(status);
80   - return success(convertList(list, model -> new AiChatModelRespVO().setId(model.getId())
81   - .setName(model.getName()).setModel(model.getModel())));
82   - }
83   -
84   -}
85 0 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiChatRoleController.java
... ... @@ -75,7 +75,7 @@ public class AiChatRoleController {
75 75 @GetMapping("/category-list")
76 76 @Operation(summary = "获得聊天角色的分类列表")
77 77 public CommonResult<List<String>> getChatRoleCategoryList() {
78   - return success(chatRoleService.getChatRoleCategoryList());
  78 + return success(chatRoleService.getChatRoleCategoryList());
79 79 }
80 80  
81 81 // ========== 角色管理 ==========
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiModelController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.model;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  7 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelPageReqVO;
  8 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelRespVO;
  9 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelSaveReqVO;
  10 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
  11 +import cn.iocoder.yudao.module.ai.service.model.AiModelService;
  12 +import io.swagger.v3.oas.annotations.Operation;
  13 +import io.swagger.v3.oas.annotations.Parameter;
  14 +import io.swagger.v3.oas.annotations.tags.Tag;
  15 +import jakarta.annotation.Resource;
  16 +import jakarta.validation.Valid;
  17 +import org.springframework.security.access.prepost.PreAuthorize;
  18 +import org.springframework.validation.annotation.Validated;
  19 +import org.springframework.web.bind.annotation.*;
  20 +
  21 +import java.util.List;
  22 +
  23 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  24 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  25 +
  26 +@Tag(name = "管理后台 - AI 模型")
  27 +@RestController
  28 +@RequestMapping("/ai/model")
  29 +@Validated
  30 +public class AiModelController {
  31 +
  32 + @Resource
  33 + private AiModelService modelService;
  34 +
  35 + @PostMapping("/create")
  36 + @Operation(summary = "创建模型")
  37 + @PreAuthorize("@ss.hasPermission('ai:model:create')")
  38 + public CommonResult<Long> createModel(@Valid @RequestBody AiModelSaveReqVO createReqVO) {
  39 + return success(modelService.createModel(createReqVO));
  40 + }
  41 +
  42 + @PutMapping("/update")
  43 + @Operation(summary = "更新模型")
  44 + @PreAuthorize("@ss.hasPermission('ai:model:update')")
  45 + public CommonResult<Boolean> updateModel(@Valid @RequestBody AiModelSaveReqVO updateReqVO) {
  46 + modelService.updateModel(updateReqVO);
  47 + return success(true);
  48 + }
  49 +
  50 + @DeleteMapping("/delete")
  51 + @Operation(summary = "删除模型")
  52 + @Parameter(name = "id", description = "编号", required = true)
  53 + @PreAuthorize("@ss.hasPermission('ai:model:delete')")
  54 + public CommonResult<Boolean> deleteModel(@RequestParam("id") Long id) {
  55 + modelService.deleteModel(id);
  56 + return success(true);
  57 + }
  58 +
  59 + @GetMapping("/get")
  60 + @Operation(summary = "获得模型")
  61 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  62 + @PreAuthorize("@ss.hasPermission('ai:model:query')")
  63 + public CommonResult<AiModelRespVO> getModel(@RequestParam("id") Long id) {
  64 + AiModelDO model = modelService.getModel(id);
  65 + return success(BeanUtils.toBean(model, AiModelRespVO.class));
  66 + }
  67 +
  68 + @GetMapping("/page")
  69 + @Operation(summary = "获得模型分页")
  70 + @PreAuthorize("@ss.hasPermission('ai:model:query')")
  71 + public CommonResult<PageResult<AiModelRespVO>> getModelPage(@Valid AiModelPageReqVO pageReqVO) {
  72 + PageResult<AiModelDO> pageResult = modelService.getModelPage(pageReqVO);
  73 + return success(BeanUtils.toBean(pageResult, AiModelRespVO.class));
  74 + }
  75 +
  76 + @GetMapping("/simple-list")
  77 + @Operation(summary = "获得模型列表")
  78 + @Parameter(name = "type", description = "类型", required = true, example = "1")
  79 + @Parameter(name = "platform", description = "平台", example = "midjourney")
  80 + public CommonResult<List<AiModelRespVO>> getModelSimpleList(
  81 + @RequestParam("type") Integer type,
  82 + @RequestParam(value = "platform", required = false) String platform) {
  83 + List<AiModelDO> list = modelService.getModelListByStatusAndType(
  84 + CommonStatusEnum.ENABLE.getStatus(), type, platform);
  85 + return success(convertList(list, model -> new AiModelRespVO().setId(model.getId())
  86 + .setName(model.getName()).setModel(model.getModel()).setPlatform(model.getPlatform())));
  87 + }
  88 +
  89 +}
0 90 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiToolController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.model;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  7 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool.AiToolPageReqVO;
  8 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool.AiToolRespVO;
  9 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool.AiToolSaveReqVO;
  10 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiToolDO;
  11 +import cn.iocoder.yudao.module.ai.service.model.AiToolService;
  12 +import io.swagger.v3.oas.annotations.Operation;
  13 +import io.swagger.v3.oas.annotations.Parameter;
  14 +import io.swagger.v3.oas.annotations.tags.Tag;
  15 +import jakarta.annotation.Resource;
  16 +import jakarta.validation.Valid;
  17 +import org.springframework.security.access.prepost.PreAuthorize;
  18 +import org.springframework.validation.annotation.Validated;
  19 +import org.springframework.web.bind.annotation.*;
  20 +
  21 +import java.util.List;
  22 +
  23 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  24 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  25 +
  26 +@Tag(name = "管理后台 - AI 工具")
  27 +@RestController
  28 +@RequestMapping("/ai/tool")
  29 +@Validated
  30 +public class AiToolController {
  31 +
  32 + @Resource
  33 + private AiToolService toolService;
  34 +
  35 + @PostMapping("/create")
  36 + @Operation(summary = "创建工具")
  37 + @PreAuthorize("@ss.hasPermission('ai:tool:create')")
  38 + public CommonResult<Long> createTool(@Valid @RequestBody AiToolSaveReqVO createReqVO) {
  39 + return success(toolService.createTool(createReqVO));
  40 + }
  41 +
  42 + @PutMapping("/update")
  43 + @Operation(summary = "更新工具")
  44 + @PreAuthorize("@ss.hasPermission('ai:tool:update')")
  45 + public CommonResult<Boolean> updateTool(@Valid @RequestBody AiToolSaveReqVO updateReqVO) {
  46 + toolService.updateTool(updateReqVO);
  47 + return success(true);
  48 + }
  49 +
  50 + @DeleteMapping("/delete")
  51 + @Operation(summary = "删除工具")
  52 + @Parameter(name = "id", description = "编号", required = true)
  53 + @PreAuthorize("@ss.hasPermission('ai:tool:delete')")
  54 + public CommonResult<Boolean> deleteTool(@RequestParam("id") Long id) {
  55 + toolService.deleteTool(id);
  56 + return success(true);
  57 + }
  58 +
  59 + @GetMapping("/get")
  60 + @Operation(summary = "获得工具")
  61 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  62 + @PreAuthorize("@ss.hasPermission('ai:tool:query')")
  63 + public CommonResult<AiToolRespVO> getTool(@RequestParam("id") Long id) {
  64 + AiToolDO tool = toolService.getTool(id);
  65 + return success(BeanUtils.toBean(tool, AiToolRespVO.class));
  66 + }
  67 +
  68 + @GetMapping("/page")
  69 + @Operation(summary = "获得工具分页")
  70 + @PreAuthorize("@ss.hasPermission('ai:tool:query')")
  71 + public CommonResult<PageResult<AiToolRespVO>> getToolPage(@Valid AiToolPageReqVO pageReqVO) {
  72 + PageResult<AiToolDO> pageResult = toolService.getToolPage(pageReqVO);
  73 + return success(BeanUtils.toBean(pageResult, AiToolRespVO.class));
  74 + }
  75 +
  76 + @GetMapping("/simple-list")
  77 + @Operation(summary = "获得工具列表")
  78 + public CommonResult<List<AiToolRespVO>> getToolSimpleList() {
  79 + List<AiToolDO> list = toolService.getToolListByStatus(CommonStatusEnum.ENABLE.getStatus());
  80 + return success(convertList(list, tool -> new AiToolRespVO()
  81 + .setId(tool.getId()).setName(tool.getName())));
  82 + }
  83 +
  84 +}
0 85 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatRole/AiChatRoleRespVO.java
1 1 package cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole;
2 2  
3   -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
  3 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
4 4 import com.fhs.core.trans.anno.Trans;
5 5 import com.fhs.core.trans.constant.TransType;
6 6 import com.fhs.core.trans.vo.VO;
... ... @@ -8,6 +8,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
8 8 import lombok.Data;
9 9  
10 10 import java.time.LocalDateTime;
  11 +import java.util.List;
11 12  
12 13 @Schema(description = "管理后台 - AI 聊天角色 Response VO")
13 14 @Data
... ... @@ -20,7 +21,7 @@ public class AiChatRoleRespVO implements VO {
20 21 private Long userId;
21 22  
22 23 @Schema(description = "模型编号", example = "17640")
23   - @Trans(type = TransType.SIMPLE, target = AiChatModelDO.class, fields = {"name", "model"}, refs = {"modelName", "model"})
  24 + @Trans(type = TransType.SIMPLE, target = AiModelDO.class, fields = { "name", "model" }, refs = { "modelName", "model" })
24 25 private Long modelId;
25 26 @Schema(description = "模型名字", example = "张三")
26 27 private String modelName;
... ... @@ -45,6 +46,12 @@ public class AiChatRoleRespVO implements VO {
45 46 @Schema(description = "角色设定", requiredMode = Schema.RequiredMode.REQUIRED)
46 47 private String systemMessage;
47 48  
  49 + @Schema(description = "引用的知识库编号列表", example = "1,2,3")
  50 + private List<Long> knowledgeIds;
  51 +
  52 + @Schema(description = "引用的工具编号列表", example = "1,2,3")
  53 + private List<Long> toolIds;
  54 +
48 55 @Schema(description = "是否公开", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
49 56 private Boolean publicStatus;
50 57  
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatRole/AiChatRoleSaveMyReqVO.java
... ... @@ -5,6 +5,8 @@ import jakarta.validation.constraints.NotEmpty;
5 5 import lombok.Data;
6 6 import org.hibernate.validator.constraints.URL;
7 7  
  8 +import java.util.List;
  9 +
8 10 @Schema(description = "管理后台 - AI 聊天角色新增/修改【我的】 Request VO")
9 11 @Data
10 12 public class AiChatRoleSaveMyReqVO {
... ... @@ -29,4 +31,10 @@ public class AiChatRoleSaveMyReqVO {
29 31 @NotEmpty(message = "角色设定不能为空")
30 32 private String systemMessage;
31 33  
  34 + @Schema(description = "引用的知识库编号列表", example = "1,2,3")
  35 + private List<Long> knowledgeIds;
  36 +
  37 + @Schema(description = "引用的工具编号列表", example = "1,2,3")
  38 + private List<Long> toolIds;
  39 +
32 40 }
33 41 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatRole/AiChatRoleSaveReqVO.java
... ... @@ -7,6 +7,8 @@ import lombok.*;
7 7 import jakarta.validation.constraints.*;
8 8 import org.hibernate.validator.constraints.URL;
9 9  
  10 +import java.util.List;
  11 +
10 12 @Schema(description = "管理后台 - AI 聊天角色新增/修改 Request VO")
11 13 @Data
12 14 public class AiChatRoleSaveReqVO {
... ... @@ -42,6 +44,12 @@ public class AiChatRoleSaveReqVO {
42 44 @NotEmpty(message = "角色设定不能为空")
43 45 private String systemMessage;
44 46  
  47 + @Schema(description = "引用的知识库编号列表", example = "1,2,3")
  48 + private List<Long> knowledgeIds;
  49 +
  50 + @Schema(description = "引用的工具编号列表", example = "1,2,3")
  51 + private List<Long> toolIds;
  52 +
45 53 @Schema(description = "是否公开", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
46 54 @NotNull(message = "是否公开不能为空")
47 55 private Boolean publicStatus;
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatModel/AiChatModelPageReqVO.java renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/model/AiModelPageReqVO.java
1   -package cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel;
  1 +package cn.iocoder.yudao.module.ai.controller.admin.model.vo.model;
2 2  
3 3 import lombok.*;
4 4 import io.swagger.v3.oas.annotations.media.Schema;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageParam;
6 6  
7   -@Schema(description = "管理后台 - API 聊天模型分页 Request VO")
  7 +@Schema(description = "管理后台 - API 模型分页 Request VO")
8 8 @Data
9   -public class AiChatModelPageReqVO extends PageParam {
  9 +public class AiModelPageReqVO extends PageParam {
10 10  
11 11 @Schema(description = "模型名字", example = "张三")
12 12 private String name;
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatModel/AiChatModelRespVO.java renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/model/AiModelRespVO.java
1   -package cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel;
  1 +package cn.iocoder.yudao.module.ai.controller.admin.model.vo.model;
2 2  
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import lombok.Data;
5 5  
6 6 import java.time.LocalDateTime;
7 7  
8   -@Schema(description = "管理后台 - AI 聊天模型 Response VO")
  8 +@Schema(description = "管理后台 - AI 模型 Response VO")
9 9 @Data
10   -public class AiChatModelRespVO {
  10 +public class AiModelRespVO {
11 11  
12 12 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2630")
13 13 private Long id;
... ... @@ -24,6 +24,9 @@ public class AiChatModelRespVO {
24 24 @Schema(description = "模型平台", example = "OpenAI")
25 25 private String platform;
26 26  
  27 + @Schema(description = "模型类型", example = "1")
  28 + private Integer type;
  29 +
27 30 @Schema(description = "排序", example = "1")
28 31 private Integer sort;
29 32  
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatModel/AiChatModelSaveReqVO.java renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/model/AiModelSaveReqVO.java
1   -package cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel;
  1 +package cn.iocoder.yudao.module.ai.controller.admin.model.vo.model;
2 2  
  3 +import cn.iocoder.yudao.framework.ai.core.enums.AiModelTypeEnum;
  4 +import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
3 5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 6 import cn.iocoder.yudao.framework.common.validation.InEnum;
5 7 import io.swagger.v3.oas.annotations.media.Schema;
6   -import lombok.*;
7   -import jakarta.validation.constraints.*;
  8 +import jakarta.validation.constraints.NotEmpty;
  9 +import jakarta.validation.constraints.NotNull;
  10 +import lombok.Data;
8 11  
9   -@Schema(description = "管理后台 - API 聊天模型新增/修改 Request VO")
  12 +@Schema(description = "管理后台 - API 模型新增/修改 Request VO")
10 13 @Data
11   -public class AiChatModelSaveReqVO {
  14 +public class AiModelSaveReqVO {
12 15  
13 16 @Schema(description = "编号", example = "2630")
14 17 private Long id;
... ... @@ -27,8 +30,14 @@ public class AiChatModelSaveReqVO {
27 30  
28 31 @Schema(description = "模型平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "OpenAI")
29 32 @NotEmpty(message = "模型平台不能为空")
  33 + @InEnum(AiPlatformEnum.class)
30 34 private String platform;
31 35  
  36 + @Schema(description = "模型类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  37 + @NotNull(message = "模型类型不能为空")
  38 + @InEnum(AiModelTypeEnum.class)
  39 + private Integer type;
  40 +
32 41 @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
33 42 @NotNull(message = "排序不能为空")
34 43 private Integer sort;
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/tool/AiToolPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  5 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  6 +import io.swagger.v3.oas.annotations.media.Schema;
  7 +import lombok.Data;
  8 +import lombok.EqualsAndHashCode;
  9 +import lombok.ToString;
  10 +import org.springframework.format.annotation.DateTimeFormat;
  11 +
  12 +import java.time.LocalDateTime;
  13 +
  14 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  15 +
  16 +@Schema(description = "管理后台 - AI 工具分页 Request VO")
  17 +@Data
  18 +public class AiToolPageReqVO extends PageParam {
  19 +
  20 + @Schema(description = "工具名称", example = "王五")
  21 + private String name;
  22 +
  23 + @Schema(description = "工具描述", example = "你猜")
  24 + private String description;
  25 +
  26 + @Schema(description = "状态", example = "1")
  27 + @InEnum(CommonStatusEnum.class)
  28 + private Integer status;
  29 +
  30 + @Schema(description = "创建时间")
  31 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  32 + private LocalDateTime[] createTime;
  33 +
  34 +}
0 35 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/tool/AiToolRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.*;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "管理后台 - AI 工具 Response VO")
  9 +@Data
  10 +public class AiToolRespVO {
  11 +
  12 + @Schema(description = "工具编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "19661")
  13 + private Long id;
  14 +
  15 + @Schema(description = "工具名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
  16 + private String name;
  17 +
  18 + @Schema(description = "工具描述", example = "你猜")
  19 + private String description;
  20 +
  21 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  22 + private Integer status;
  23 +
  24 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  25 + private LocalDateTime createTime;
  26 +
  27 +}
0 28 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/tool/AiToolSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import jakarta.validation.constraints.NotEmpty;
  7 +import lombok.Data;
  8 +
  9 +@Schema(description = "管理后台 - AI 工具新增/修改 Request VO")
  10 +@Data
  11 +public class AiToolSaveReqVO {
  12 +
  13 + @Schema(description = "工具编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "19661")
  14 + private Long id;
  15 +
  16 + @Schema(description = "工具名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
  17 + @NotEmpty(message = "工具名称不能为空")
  18 + private String name;
  19 +
  20 + @Schema(description = "工具描述", example = "你猜")
  21 + private String description;
  22 +
  23 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  24 + @InEnum(CommonStatusEnum.class)
  25 + private Integer status;
  26 +
  27 +}
0 28 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/AiWriteController.java
... ... @@ -12,7 +12,6 @@ import io.swagger.v3.oas.annotations.Operation;
12 12 import io.swagger.v3.oas.annotations.Parameter;
13 13 import io.swagger.v3.oas.annotations.tags.Tag;
14 14 import jakarta.annotation.Resource;
15   -import jakarta.annotation.security.PermitAll;
16 15 import jakarta.validation.Valid;
17 16 import org.springframework.http.MediaType;
18 17 import org.springframework.security.access.prepost.PreAuthorize;
... ... @@ -32,7 +31,6 @@ public class AiWriteController {
32 31  
33 32 @PostMapping(value = "/generate-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
34 33 @Operation(summary = "写作生成(流式)", description = "流式返回,响应较快")
35   - @PermitAll // 解决 SSE 最终响应的时候,会被 Access Denied 拦截的问题
36 34 public Flux<CommonResult<String>> generateWriteContent(@RequestBody @Valid AiWriteGenerateReqVO generateReqVO) {
37 35 return writeService.generateWriteContent(generateReqVO, getLoginUserId());
38 36 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/vo/AiWritePageReqVO.java
... ... @@ -13,8 +13,6 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
13 13  
14 14 @Schema(description = "管理后台 - AI 写作分页 Request VO")
15 15 @Data
16   -@EqualsAndHashCode(callSuper = true)
17   -@ToString(callSuper = true)
18 16 public class AiWritePageReqVO extends PageParam {
19 17  
20 18 @Schema(description = "用户编号", example = "28404")
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatConversationDO.java
1 1 package cn.iocoder.yudao.module.ai.dal.dataobject.chat;
2 2  
3 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4   -import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
5   -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
6 4 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
  5 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
7 6 import com.baomidou.mybatisplus.annotation.KeySequence;
8 7 import com.baomidou.mybatisplus.annotation.TableId;
9 8 import com.baomidou.mybatisplus.annotation.TableName;
... ... @@ -22,7 +21,6 @@ import java.time.LocalDateTime;
22 21 @TableName("ai_chat_conversation")
23 22 @KeySequence("ai_chat_conversation_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
24 23 @Data
25   -@EqualsAndHashCode(callSuper = true)
26 24 @Builder
27 25 @NoArgsConstructor
28 26 @AllArgsConstructor
... ... @@ -66,20 +64,15 @@ public class AiChatConversationDO extends BaseDO {
66 64 private Long roleId;
67 65  
68 66 /**
69   - * 知识库编号
70   - * <p>
71   - * 关联 {@link AiKnowledgeDO#getId()}
72   - */
73   - private Long knowledgeId;
74   -
75   - /**
76 67 * 模型编号
77 68 *
78   - * 关联 {@link AiChatModelDO#getId()} 字段
  69 + * 关联 {@link AiModelDO#getId()} 字段
79 70 */
80 71 private Long modelId;
81 72 /**
82 73 * 模型标志
  74 + *
  75 + * 冗余 {@link AiModelDO#getModel()} 字段
83 76 */
84 77 private String model;
85 78  
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java
1 1 package cn.iocoder.yudao.module.ai.dal.dataobject.chat;
2 2  
3 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
4 5 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
5   -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
6 6 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
  7 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
7 8 import com.baomidou.mybatisplus.annotation.KeySequence;
8 9 import com.baomidou.mybatisplus.annotation.TableField;
9 10 import com.baomidou.mybatisplus.annotation.TableId;
10 11 import com.baomidou.mybatisplus.annotation.TableName;
11   -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
12 12 import lombok.*;
13 13 import org.springframework.ai.chat.messages.MessageType;
14 14  
... ... @@ -20,10 +20,9 @@ import java.util.List;
20 20 * @since 2024/4/14 17:35
21 21 * @since 2024/4/14 17:35
22 22 */
23   -@TableName("ai_chat_message")
  23 +@TableName(value = "ai_chat_message", autoResultMap = true)
24 24 @KeySequence("ai_chat_conversation_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
25 25 @Data
26   -@EqualsAndHashCode(callSuper = true)
27 26 @Builder
28 27 @NoArgsConstructor
29 28 @AllArgsConstructor
... ... @@ -71,23 +70,16 @@ public class AiChatMessageDO extends BaseDO {
71 70 */
72 71 private Long roleId;
73 72  
74   -
75   - /**
76   - * 段落编号数组
77   - *
78   - * 关联 {@link AiKnowledgeSegmentDO#getId()} 字段
79   - */
80   - @TableField(typeHandler = JacksonTypeHandler.class)
81   - private List<Long> segmentIds;
82   -
83 73 /**
84 74 * 模型标志
  75 + *
  76 + * 冗余 {@link AiModelDO#getModel()}
85 77 */
86 78 private String model;
87 79 /**
88 80 * 模型编号
89 81 *
90   - * 关联 {@link AiChatModelDO#getId()} 字段
  82 + * 关联 {@link AiModelDO#getId()} 字段
91 83 */
92 84 private Long modelId;
93 85  
... ... @@ -101,4 +93,12 @@ public class AiChatMessageDO extends BaseDO {
101 93 */
102 94 private Boolean useContext;
103 95  
  96 + /**
  97 + * 知识库段落编号数组
  98 + *
  99 + * 关联 {@link AiKnowledgeSegmentDO#getId()} 字段
  100 + */
  101 + @TableField(typeHandler = LongListTypeHandler.class)
  102 + private List<Long> segmentIds;
  103 +
104 104 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/image/AiImageDO.java
... ... @@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.image;
2 2  
3 3 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
4 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
5   -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
  5 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
6 6 import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum;
7 7 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
8 8 import com.baomidou.mybatisplus.annotation.KeySequence;
... ... @@ -53,9 +53,15 @@ public class AiImageDO extends BaseDO {
53 53 */
54 54 private String platform;
55 55 /**
56   - * 模型
  56 + * 模型编号
57 57 *
58   - * 冗余 {@link AiChatModelDO#getModel()}
  58 + * 关联 {@link AiModelDO#getId()}
  59 + */
  60 + private Long modelId;
  61 + /**
  62 + * 模型标识
  63 + *
  64 + * 冗余 {@link AiModelDO#getModel()}
59 65 */
60 66 private String model;
61 67  
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java
... ... @@ -2,15 +2,12 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.knowledge;
2 2  
3 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
5   -import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
  5 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
6 6 import com.baomidou.mybatisplus.annotation.KeySequence;
7   -import com.baomidou.mybatisplus.annotation.TableField;
8 7 import com.baomidou.mybatisplus.annotation.TableId;
9 8 import com.baomidou.mybatisplus.annotation.TableName;
10 9 import lombok.Data;
11 10  
12   -import java.util.List;
13   -
14 11 /**
15 12 * AI 知识库 DO
16 13 *
... ... @@ -27,12 +24,6 @@ public class AiKnowledgeDO extends BaseDO {
27 24 @TableId
28 25 private Long id;
29 26 /**
30   - * 用户编号
31   - * <p>
32   - * 关联 AdminUserDO 的 userId 字段
33   - */
34   - private Long userId;
35   - /**
36 27 * 知识库名称
37 28 */
38 29 private String name;
... ... @@ -42,20 +33,17 @@ public class AiKnowledgeDO extends BaseDO {
42 33 private String description;
43 34  
44 35 /**
45   - * 可见权限,选择哪些人可见
46   - * <p>
47   - * -1 所有人可见,其他为各自用户编号
48   - */
49   - @TableField(typeHandler = LongListTypeHandler.class)
50   - private List<Long> visibilityPermissions;
51   - /**
52   - * 嵌入模型编号
  36 + * 向量模型编号
  37 + *
  38 + * 关联 {@link AiModelDO#getId()}
53 39 */
54   - private Long modelId;
  40 + private Long embeddingModelId;
55 41 /**
56 42 * 模型标识
  43 + *
  44 + * 冗余 {@link AiModelDO#getModel()}
57 45 */
58   - private String model;
  46 + private String embeddingModel;
59 47  
60 48 /**
61 49 * topK
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDocumentDO.java
... ... @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.knowledge;
2 2  
3 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
5   -import cn.iocoder.yudao.module.ai.enums.knowledge.AiKnowledgeDocumentStatusEnum;
6 5 import com.baomidou.mybatisplus.annotation.KeySequence;
7 6 import com.baomidou.mybatisplus.annotation.TableId;
8 7 import com.baomidou.mybatisplus.annotation.TableName;
... ... @@ -30,57 +29,35 @@ public class AiKnowledgeDocumentDO extends BaseDO {
30 29 */
31 30 private Long knowledgeId;
32 31 /**
33   - * 文件名称
  32 + * 文档名称
34 33 */
35 34 private String name;
36 35 /**
37   - * 内容
38   - */
39   - private String content;
40   - /**
41 36 * 文件 URL
42 37 */
43 38 private String url;
44 39 /**
45   - * 文档 token 数量
  40 + * 内容
46 41 */
47   - private Integer tokens;
  42 + private String content;
48 43 /**
49   - * 文档字符数
  44 + * 文档长度
50 45 */
51   - private Integer wordCount;
52   -
  46 + private Integer contentLength;
53 47  
54   - // ========== 自定义分段所用参数 ==========
55   - // TODO @新:3)defaultChunkSize、defaultChunkSize、minChunkSizeChars、maxNumChunks 这几个字段的命名,可能要微信一起讨论下。尽量命名保持风格统一哈。
56 48 /**
57   - * 每个文本块的目标 token 数
58   - */
59   - private Integer defaultSegmentTokens;
60   - /**
61   - * 每个文本块的最小字符数
62   - */
63   - private Integer minSegmentWordCount;
64   - /**
65   - * 低于此值的块会被丢弃
66   - */
67   - private Integer minChunkLengthToEmbed;
68   - /**
69   - * 最大块数
  49 + * 文档 token 数量
70 50 */
71   - private Integer maxNumSegments;
  51 + private Integer tokens;
72 52 /**
73   - * 分块是否保留分隔符
  53 + * 分片最大 Token 数
74 54 */
75   - private Boolean keepSeparator;
76   - // ===================================
  55 + private Integer segmentMaxTokens;
77 56  
78 57 /**
79   - * 切片状态
80   - * <p>
81   - * 枚举 {@link AiKnowledgeDocumentStatusEnum}
  58 + * 召回次数
82 59 */
83   - private Integer sliceStatus;
  60 + private Integer retrievalCount;
84 61  
85 62 /**
86 63 * 状态
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeSegmentDO.java
... ... @@ -17,7 +17,10 @@ import lombok.Data;
17 17 @Data
18 18 public class AiKnowledgeSegmentDO extends BaseDO {
19 19  
20   - public static final String FIELD_KNOWLEDGE_ID = "knowledgeId";
  20 + /**
  21 + * 向量库的编号 - 空值
  22 + */
  23 + public static final String VECTOR_ID_EMPTY = "";
21 24  
22 25 /**
23 26 * 编号
... ... @@ -25,10 +28,6 @@ public class AiKnowledgeSegmentDO extends BaseDO {
25 28 @TableId
26 29 private Long id;
27 30 /**
28   - * 向量库的编号
29   - */
30   - private String vectorId;
31   - /**
32 31 * 知识库编号
33 32 * <p>
34 33 * 关联 {@link AiKnowledgeDO#getId()}
... ... @@ -45,13 +44,24 @@ public class AiKnowledgeSegmentDO extends BaseDO {
45 44 */
46 45 private String content;
47 46 /**
48   - * 字符数
  47 + * 切片内容长度
49 48 */
50   - private Integer wordCount;
  49 + private Integer contentLength;
  50 +
  51 + /**
  52 + * 向量库的编号
  53 + */
  54 + private String vectorId;
51 55 /**
52 56 * token 数量
53 57 */
54 58 private Integer tokens;
  59 +
  60 + /**
  61 + * 召回次数
  62 + */
  63 + private Integer retrievalCount;
  64 +
55 65 /**
56 66 * 状态
57 67 * <p>
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/mindmap/AiMindMapDO.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.mindmap;
2 2  
3 3 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
4 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  5 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
5 6 import com.baomidou.mybatisplus.annotation.KeySequence;
6 7 import com.baomidou.mybatisplus.annotation.TableId;
7 8 import com.baomidou.mybatisplus.annotation.TableName;
... ... @@ -37,6 +38,12 @@ public class AiMindMapDO extends BaseDO {
37 38 */
38 39 private String platform;
39 40 /**
  41 + * 模型编号
  42 + *
  43 + * 关联 {@link AiModelDO#getId()}
  44 + */
  45 + private Long modelId;
  46 + /**
40 47 * 模型
41 48 */
42 49 private String model;
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiApiKeyDO.java
... ... @@ -16,7 +16,6 @@ import lombok.*;
16 16 @TableName("ai_api_key")
17 17 @KeySequence("ai_chat_conversation_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
18 18 @Data
19   -@EqualsAndHashCode(callSuper = true)
20 19 @Builder
21 20 @NoArgsConstructor
22 21 @AllArgsConstructor
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatRoleDO.java
... ... @@ -2,11 +2,16 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.model;
2 2  
3 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  5 +import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
  6 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
5 7 import com.baomidou.mybatisplus.annotation.KeySequence;
  8 +import com.baomidou.mybatisplus.annotation.TableField;
6 9 import com.baomidou.mybatisplus.annotation.TableId;
7 10 import com.baomidou.mybatisplus.annotation.TableName;
8 11 import lombok.*;
9 12  
  13 +import java.util.List;
  14 +
10 15 /**
11 16 * AI 聊天角色 DO
12 17 *
... ... @@ -16,7 +21,6 @@ import lombok.*;
16 21 @TableName(value = "ai_chat_role", autoResultMap = true)
17 22 @KeySequence("ai_chat_role_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
18 23 @Data
19   -@EqualsAndHashCode(callSuper = true)
20 24 @Builder
21 25 @NoArgsConstructor
22 26 @AllArgsConstructor
... ... @@ -58,11 +62,26 @@ public class AiChatRoleDO extends BaseDO {
58 62 /**
59 63 * 模型编号
60 64 *
61   - * 关联 {@link AiChatModelDO#getId()} 字段
  65 + * 关联 {@link AiModelDO#getId()} 字段
62 66 */
63 67 private Long modelId;
64 68  
65 69 /**
  70 + * 引用的知识库编号列表
  71 + *
  72 + * 关联 {@link AiKnowledgeDO#getId()} 字段
  73 + */
  74 + @TableField(typeHandler = LongListTypeHandler.class)
  75 + private List<Long> knowledgeIds;
  76 + /**
  77 + * 引用的工具编号列表
  78 + *
  79 + * 关联 {@link AiToolDO#getId()} 字段
  80 + */
  81 + @TableField(typeHandler = LongListTypeHandler.class)
  82 + private List<Long> toolIds;
  83 +
  84 + /**
66 85 * 是否公开
67 86 *
68 87 * 1. true - 公开;由管理员在【角色管理】所创建
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatModelDO.java renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiModelDO.java
1 1 package cn.iocoder.yudao.module.ai.dal.dataobject.model;
2 2  
  3 +import cn.iocoder.yudao.framework.ai.core.enums.AiModelTypeEnum;
3 4 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
4 5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5 6 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
... ... @@ -9,21 +10,20 @@ import com.baomidou.mybatisplus.annotation.TableName;
9 10 import lombok.*;
10 11  
11 12 /**
12   - * AI 聊天模型 DO
  13 + * AI 模型 DO
13 14 *
14   - * 默认聊天模型:{@link #status} 为开启,并且 {@link #sort} 排序第一
  15 + * 默认模型:{@link #status} 为开启,并且 {@link #sort} 排序第一
15 16 *
16 17 * @author fansili
17 18 * @since 2024/4/24 19:39
18 19 */
19   -@TableName("ai_chat_model")
20   -@KeySequence("ai_chat_model_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
  20 +@TableName("ai_model")
  21 +@KeySequence("ai_model_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
21 22 @Data
22   -@EqualsAndHashCode(callSuper = true)
23 23 @Builder
24 24 @NoArgsConstructor
25 25 @AllArgsConstructor
26   -public class AiChatModelDO extends BaseDO {
  26 +public class AiModelDO extends BaseDO {
27 27  
28 28 /**
29 29 * 编号
... ... @@ -50,6 +50,12 @@ public class AiChatModelDO extends BaseDO {
50 50 * 枚举 {@link AiPlatformEnum}
51 51 */
52 52 private String platform;
  53 + /**
  54 + * 类型
  55 + *
  56 + * 枚举 {@link AiModelTypeEnum}
  57 + */
  58 + private Integer type;
53 59  
54 60 /**
55 61 * 排序值
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiToolDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.dal.dataobject.model;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.module.ai.service.model.tool.DirectoryListToolFunction;
  5 +import cn.iocoder.yudao.module.ai.service.model.tool.WeatherQueryToolFunction;
  6 +import com.baomidou.mybatisplus.annotation.KeySequence;
  7 +import com.baomidou.mybatisplus.annotation.TableId;
  8 +import com.baomidou.mybatisplus.annotation.TableName;
  9 +import lombok.*;
  10 +
  11 +/**
  12 + * AI 工具 DO
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +@TableName("ai_tool")
  17 +@KeySequence("ai_tool_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
  18 +@Data
  19 +@Builder
  20 +@NoArgsConstructor
  21 +@AllArgsConstructor
  22 +public class AiToolDO extends BaseDO {
  23 +
  24 + /**
  25 + * 工具编号
  26 + */
  27 + @TableId
  28 + private Long id;
  29 + /**
  30 + * 工具名称
  31 + *
  32 + * 对应 Bean 的名字,例如说:
  33 + * 1. {@link DirectoryListToolFunction} 的 Bean 名字是 directory_list
  34 + * 2. {@link WeatherQueryToolFunction} 的 Bean 名字是 weather_query
  35 + */
  36 + private String name;
  37 + /**
  38 + * 工具描述
  39 + */
  40 + private String description;
  41 + /**
  42 + * 状态
  43 + *
  44 + * 枚举 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
  45 + */
  46 + private Integer status;
  47 +
  48 +}
0 49 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java
... ... @@ -84,6 +84,7 @@ public class AiMusicDO extends BaseDO {
84 84 * 枚举 {@link AiPlatformEnum}
85 85 */
86 86 private String platform;
  87 + // TODO @芋艿:modelId?
87 88 /**
88 89 * 模型
89 90 */
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/write/AiWriteDO.java
... ... @@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.write;
2 2  
3 3 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
4 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  5 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
  6 +import cn.iocoder.yudao.module.ai.enums.DictTypeConstants;
5 7 import cn.iocoder.yudao.module.ai.enums.write.AiWriteTypeEnum;
6 8 import com.baomidou.mybatisplus.annotation.KeySequence;
7 9 import com.baomidou.mybatisplus.annotation.TableId;
... ... @@ -45,6 +47,12 @@ public class AiWriteDO extends BaseDO {
45 47 */
46 48 private String platform;
47 49 /**
  50 + * 模型编号
  51 + *
  52 + * 关联 {@link AiModelDO#getId()}
  53 + */
  54 + private Long modelId;
  55 + /**
48 56 * 模型
49 57 */
50 58 private String model;
... ... @@ -66,25 +74,25 @@ public class AiWriteDO extends BaseDO {
66 74 /**
67 75 * 长度提示词
68 76 *
69   - * 字典:{@link cn.iocoder.yudao.module.ai.enums.DictTypeConstants#AI_WRITE_LENGTH}
  77 + * 字典:{@link DictTypeConstants#AI_WRITE_LENGTH}
70 78 */
71 79 private Integer length;
72 80 /**
73 81 * 格式提示词
74 82 *
75   - * 字典:{@link cn.iocoder.yudao.module.ai.enums.DictTypeConstants#AI_WRITE_FORMAT}
  83 + * 字典:{@link DictTypeConstants#AI_WRITE_FORMAT}
76 84 */
77 85 private Integer format;
78 86 /**
79 87 * 语气提示词
80 88 *
81   - * 字典:{@link cn.iocoder.yudao.module.ai.enums.DictTypeConstants#AI_WRITE_TONE}
  89 + * 字典:{@link DictTypeConstants#AI_WRITE_TONE}
82 90 */
83 91 private Integer tone;
84 92 /**
85 93 * 语言提示词
86 94 *
87   - * 字典:{@link cn.iocoder.yudao.module.ai.enums.DictTypeConstants#AI_WRITE_LANGUAGE}
  95 + * 字典:{@link DictTypeConstants#AI_WRITE_LANGUAGE}
88 96 */
89 97 private Integer language;
90 98  
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeDocumentMapper.java
... ... @@ -5,10 +5,14 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 6 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO;
7 7 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
  8 +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
8 9 import org.apache.ibatis.annotations.Mapper;
9 10  
  11 +import java.util.Collection;
  12 +import java.util.List;
  13 +
10 14 /**
11   - * AI 知识库-文档 Mapper
  15 + * AI 知识库文档 Mapper
12 16 *
13 17 * @author xiaoxin
14 18 */
... ... @@ -17,8 +21,19 @@ public interface AiKnowledgeDocumentMapper extends BaseMapperX&lt;AiKnowledgeDocume
17 21  
18 22 default PageResult<AiKnowledgeDocumentDO> selectPage(AiKnowledgeDocumentPageReqVO reqVO) {
19 23 return selectPage(reqVO, new LambdaQueryWrapperX<AiKnowledgeDocumentDO>()
  24 + .eqIfPresent(AiKnowledgeDocumentDO::getKnowledgeId, reqVO.getKnowledgeId())
20 25 .likeIfPresent(AiKnowledgeDocumentDO::getName, reqVO.getName())
21 26 .orderByDesc(AiKnowledgeDocumentDO::getId));
22 27 }
23 28  
  29 + default void updateRetrievalCountIncr(Collection<Long> ids) {
  30 + update(new LambdaUpdateWrapper<AiKnowledgeDocumentDO>()
  31 + .setSql(" retrieval_count = retrieval_count + 1")
  32 + .in(AiKnowledgeDocumentDO::getId, ids));
  33 + }
  34 +
  35 + default List<AiKnowledgeDocumentDO> selectListByStatus(Integer status) {
  36 + return selectList(AiKnowledgeDocumentDO::getStatus, status);
  37 + }
  38 +
24 39 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeMapper.java
1 1 package cn.iocoder.yudao.module.ai.dal.mysql.knowledge;
2 2  
3   -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
6 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
... ... @@ -8,19 +7,26 @@ import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnow
8 7 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
9 8 import org.apache.ibatis.annotations.Mapper;
10 9  
  10 +import java.util.List;
  11 +
11 12 /**
12   - * AI 知识库基础信息 Mapper
  13 + * AI 知识库 Mapper
13 14 *
14 15 * @author xiaoxin
15 16 */
16 17 @Mapper
17 18 public interface AiKnowledgeMapper extends BaseMapperX<AiKnowledgeDO> {
18 19  
19   - default PageResult<AiKnowledgeDO> selectPage(Long userId, AiKnowledgePageReqVO pageReqVO) {
  20 + default PageResult<AiKnowledgeDO> selectPage(AiKnowledgePageReqVO pageReqVO) {
20 21 return selectPage(pageReqVO, new LambdaQueryWrapperX<AiKnowledgeDO>()
21   - .eq(AiKnowledgeDO::getStatus, CommonStatusEnum.ENABLE.getStatus())
22 22 .likeIfPresent(AiKnowledgeDO::getName, pageReqVO.getName())
23   - .and(e -> e.apply("FIND_IN_SET(" + userId + ",visibility_permissions)").or(m -> m.apply("FIND_IN_SET(-1,visibility_permissions)")))
  23 + .eqIfPresent(AiKnowledgeDO::getStatus, pageReqVO.getStatus())
  24 + .betweenIfPresent(AiKnowledgeDO::getCreateTime, pageReqVO.getCreateTime())
24 25 .orderByDesc(AiKnowledgeDO::getId));
25 26 }
  27 +
  28 + default List<AiKnowledgeDO> selectListByStatus(Integer status) {
  29 + return selectList(AiKnowledgeDO::getStatus, status);
  30 + }
  31 +
26 32 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeSegmentMapper.java
... ... @@ -3,14 +3,19 @@ package cn.iocoder.yudao.module.ai.dal.mysql.knowledge;
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
6 7 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO;
  8 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentProcessRespVO;
7 9 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
  10 +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
  11 +import com.github.yulichang.wrapper.MPJLambdaWrapper;
8 12 import org.apache.ibatis.annotations.Mapper;
9 13  
  14 +import java.util.Collection;
10 15 import java.util.List;
11 16  
12 17 /**
13   - * AI 知识库-分片 Mapper
  18 + * AI 知识库分片 Mapper
14 19 *
15 20 * @author xiaoxin
16 21 */
... ... @@ -20,8 +25,8 @@ public interface AiKnowledgeSegmentMapper extends BaseMapperX&lt;AiKnowledgeSegment
20 25 default PageResult<AiKnowledgeSegmentDO> selectPage(AiKnowledgeSegmentPageReqVO reqVO) {
21 26 return selectPage(reqVO, new LambdaQueryWrapperX<AiKnowledgeSegmentDO>()
22 27 .eq(AiKnowledgeSegmentDO::getDocumentId, reqVO.getDocumentId())
  28 + .likeIfPresent(AiKnowledgeSegmentDO::getContent, reqVO.getContent())
23 29 .eqIfPresent(AiKnowledgeSegmentDO::getStatus, reqVO.getStatus())
24   - .likeIfPresent(AiKnowledgeSegmentDO::getContent, reqVO.getKeyword())
25 30 .orderByDesc(AiKnowledgeSegmentDO::getId));
26 31 }
27 32  
... ... @@ -31,4 +36,32 @@ public interface AiKnowledgeSegmentMapper extends BaseMapperX&lt;AiKnowledgeSegment
31 36 .orderByDesc(AiKnowledgeSegmentDO::getId));
32 37 }
33 38  
  39 + default List<AiKnowledgeSegmentDO> selectListByDocumentId(Long documentId) {
  40 + return selectList(new LambdaQueryWrapperX<AiKnowledgeSegmentDO>()
  41 + .eq(AiKnowledgeSegmentDO::getDocumentId, documentId)
  42 + .orderByDesc(AiKnowledgeSegmentDO::getId));
  43 + }
  44 +
  45 + default List<AiKnowledgeSegmentDO> selectListByKnowledgeIdAndStatus(Long knowledgeId, Integer status) {
  46 + return selectList(AiKnowledgeSegmentDO::getKnowledgeId, knowledgeId,
  47 + AiKnowledgeSegmentDO::getStatus, status);
  48 + }
  49 +
  50 + default List<AiKnowledgeSegmentProcessRespVO> selectProcessList(Collection<Long> documentIds) {
  51 + MPJLambdaWrapper<AiKnowledgeSegmentDO> wrapper = new MPJLambdaWrapperX<AiKnowledgeSegmentDO>()
  52 + .selectAs(AiKnowledgeSegmentDO::getDocumentId, AiKnowledgeSegmentProcessRespVO::getDocumentId)
  53 + .selectCount(AiKnowledgeSegmentDO::getId, "count")
  54 + .select("COUNT(CASE WHEN vector_id > '" + AiKnowledgeSegmentDO.VECTOR_ID_EMPTY
  55 + + "' THEN 1 ELSE NULL END) AS embeddingCount")
  56 + .in(AiKnowledgeSegmentDO::getDocumentId, documentIds)
  57 + .groupBy(AiKnowledgeSegmentDO::getDocumentId);
  58 + return selectJoinList(AiKnowledgeSegmentProcessRespVO.class, wrapper);
  59 + }
  60 +
  61 + default void updateRetrievalCountIncrByIds(List<Long> ids) {
  62 + update(new LambdaUpdateWrapper<AiKnowledgeSegmentDO>()
  63 + .setSql(" retrieval_count = retrieval_count + 1")
  64 + .in(AiKnowledgeSegmentDO::getId, ids));
  65 + }
  66 +
34 67 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiChatMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.dal.mysql.model;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
  7 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelPageReqVO;
  8 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
  9 +import org.apache.ibatis.annotations.Mapper;
  10 +
  11 +import javax.annotation.Nullable;
  12 +import java.util.List;
  13 +
  14 +/**
  15 + * API 模型 Mapper
  16 + *
  17 + * @author fansili
  18 + */
  19 +@Mapper
  20 +public interface AiChatMapper extends BaseMapperX<AiModelDO> {
  21 +
  22 + default AiModelDO selectFirstByStatus(Integer type, Integer status) {
  23 + return selectOne(new QueryWrapperX<AiModelDO>()
  24 + .eq("type", type)
  25 + .eq("status", status)
  26 + .limitN(1)
  27 + .orderByAsc("sort"));
  28 + }
  29 +
  30 + default PageResult<AiModelDO> selectPage(AiModelPageReqVO reqVO) {
  31 + return selectPage(reqVO, new LambdaQueryWrapperX<AiModelDO>()
  32 + .likeIfPresent(AiModelDO::getName, reqVO.getName())
  33 + .eqIfPresent(AiModelDO::getModel, reqVO.getModel())
  34 + .eqIfPresent(AiModelDO::getPlatform, reqVO.getPlatform())
  35 + .orderByAsc(AiModelDO::getSort));
  36 + }
  37 +
  38 + default List<AiModelDO> selectListByStatusAndType(Integer status, Integer type,
  39 + @Nullable String platform) {
  40 + return selectList(new LambdaQueryWrapperX<AiModelDO>()
  41 + .eq(AiModelDO::getStatus, status)
  42 + .eq(AiModelDO::getType, type)
  43 + .eqIfPresent(AiModelDO::getPlatform, platform)
  44 + .orderByAsc(AiModelDO::getSort));
  45 + }
  46 +
  47 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiChatModelMapper.java deleted
1   -package cn.iocoder.yudao.module.ai.dal.mysql.model;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5   -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6   -import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
7   -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelPageReqVO;
8   -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
9   -import org.apache.ibatis.annotations.Mapper;
10   -
11   -import java.util.Collection;
12   -import java.util.List;
13   -
14   -/**
15   - * API 聊天模型 Mapper
16   - *
17   - * @author fansili
18   - */
19   -@Mapper
20   -public interface AiChatModelMapper extends BaseMapperX<AiChatModelDO> {
21   -
22   - default AiChatModelDO selectFirstByStatus(Integer status) {
23   - return selectOne(new QueryWrapperX<AiChatModelDO>()
24   - .eq("status", status)
25   - .limitN(1)
26   - .orderByAsc("sort"));
27   - }
28   -
29   - default PageResult<AiChatModelDO> selectPage(AiChatModelPageReqVO reqVO) {
30   - return selectPage(reqVO, new LambdaQueryWrapperX<AiChatModelDO>()
31   - .likeIfPresent(AiChatModelDO::getName, reqVO.getName())
32   - .eqIfPresent(AiChatModelDO::getModel, reqVO.getModel())
33   - .eqIfPresent(AiChatModelDO::getPlatform, reqVO.getPlatform())
34   - .orderByAsc(AiChatModelDO::getSort));
35   - }
36   -
37   - default List<AiChatModelDO> selectList(Integer status) {
38   - return selectList(new LambdaQueryWrapperX<AiChatModelDO>()
39   - .eq(AiChatModelDO::getStatus, status)
40   - .orderByAsc(AiChatModelDO::getSort));
41   - }
42   -
43   -}
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiToolMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.dal.mysql.model;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool.AiToolPageReqVO;
  7 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiToolDO;
  8 +import org.apache.ibatis.annotations.Mapper;
  9 +
  10 +import java.util.List;
  11 +
  12 +/**
  13 + * AI 工具 Mapper
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@Mapper
  18 +public interface AiToolMapper extends BaseMapperX<AiToolDO> {
  19 +
  20 + default PageResult<AiToolDO> selectPage(AiToolPageReqVO reqVO) {
  21 + return selectPage(reqVO, new LambdaQueryWrapperX<AiToolDO>()
  22 + .likeIfPresent(AiToolDO::getName, reqVO.getName())
  23 + .eqIfPresent(AiToolDO::getDescription, reqVO.getDescription())
  24 + .eqIfPresent(AiToolDO::getStatus, reqVO.getStatus())
  25 + .betweenIfPresent(AiToolDO::getCreateTime, reqVO.getCreateTime())
  26 + .orderByDesc(AiToolDO::getId));
  27 + }
  28 +
  29 + default List<AiToolDO> selectListByStatus(Integer status) {
  30 + return selectList(new LambdaQueryWrapperX<AiToolDO>()
  31 + .eq(AiToolDO::getStatus, status)
  32 + .orderByDesc(AiToolDO::getId));
  33 + }
  34 +
  35 +}
0 36 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/framework/web/package-info.java
1 1 /**
2   - * crm 模块的 web 拓展封装
  2 + * ai 模块的 web 拓展封装
3 3 */
4   -package cn.iocoder.yudao.module.crm.framework.web;
  4 +package cn.iocoder.yudao.module.ai.framework.web;
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationServiceImpl.java
... ... @@ -4,17 +4,18 @@ import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.lang.Assert;
5 5 import cn.hutool.core.util.ObjUtil;
6 6 import cn.hutool.core.util.ObjectUtil;
  7 +import cn.iocoder.yudao.framework.ai.core.enums.AiModelTypeEnum;
7 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
8 9 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
9 10 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationCreateMyReqVO;
10 11 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationPageReqVO;
11 12 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationUpdateMyReqVO;
12 13 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
13   -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
  14 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
14 15 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
15 16 import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatConversationMapper;
16 17 import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeService;
17   -import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
  18 +import cn.iocoder.yudao.module.ai.service.model.AiModelService;
18 19 import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
19 20 import jakarta.annotation.Resource;
20 21 import lombok.extern.slf4j.Slf4j;
... ... @@ -44,7 +45,7 @@ public class AiChatConversationServiceImpl implements AiChatConversationService
44 45 private AiChatConversationMapper chatConversationMapper;
45 46  
46 47 @Resource
47   - private AiChatModelService chatModalService;
  48 + private AiModelService modalService;
48 49 @Resource
49 50 private AiChatRoleService chatRoleService;
50 51 @Resource
... ... @@ -54,9 +55,9 @@ public class AiChatConversationServiceImpl implements AiChatConversationService
54 55 public Long createChatConversationMy(AiChatConversationCreateMyReqVO createReqVO, Long userId) {
55 56 // 1.1 获得 AiChatRoleDO 聊天角色
56 57 AiChatRoleDO role = createReqVO.getRoleId() != null ? chatRoleService.validateChatRole(createReqVO.getRoleId()) : null;
57   - // 1.2 获得 AiChatModelDO 聊天模型
58   - AiChatModelDO model = role != null && role.getModelId() != null ? chatModalService.validateChatModel(role.getModelId())
59   - : chatModalService.getRequiredDefaultChatModel();
  58 + // 1.2 获得 AiModelDO 聊天模型
  59 + AiModelDO model = role != null && role.getModelId() != null ? modalService.validateModel(role.getModelId())
  60 + : modalService.getRequiredDefaultModel(AiModelTypeEnum.CHAT.getType());
60 61 Assert.notNull(model, "必须找到默认模型");
61 62 validateChatModel(model);
62 63  
... ... @@ -67,7 +68,7 @@ public class AiChatConversationServiceImpl implements AiChatConversationService
67 68  
68 69 // 2. 创建 AiChatConversationDO 聊天对话
69 70 AiChatConversationDO conversation = new AiChatConversationDO().setUserId(userId).setPinned(false)
70   - .setModelId(model.getId()).setModel(model.getModel()).setKnowledgeId(createReqVO.getKnowledgeId())
  71 + .setModelId(model.getId()).setModel(model.getModel())
71 72 .setTemperature(model.getTemperature()).setMaxTokens(model.getMaxTokens()).setMaxContexts(model.getMaxContexts());
72 73 if (role != null) {
73 74 conversation.setTitle(role.getName()).setRoleId(role.getId()).setSystemMessage(role.getSystemMessage());
... ... @@ -86,9 +87,9 @@ public class AiChatConversationServiceImpl implements AiChatConversationService
86 87 throw exception(CHAT_CONVERSATION_NOT_EXISTS);
87 88 }
88 89 // 1.2 校验模型是否存在(修改模型的情况)
89   - AiChatModelDO model = null;
  90 + AiModelDO model = null;
90 91 if (updateReqVO.getModelId() != null) {
91   - model = chatModalService.validateChatModel(updateReqVO.getModelId());
  92 + model = modalService.validateModel(updateReqVO.getModelId());
92 93 }
93 94  
94 95 // 1.3 校验知识库是否存在
... ... @@ -139,10 +140,11 @@ public class AiChatConversationServiceImpl implements AiChatConversationService
139 140 chatConversationMapper.deleteById(id);
140 141 }
141 142  
142   - private void validateChatModel(AiChatModelDO model) {
  143 + private void validateChatModel(AiModelDO model) {
143 144 if (ObjectUtil.isAllNotEmpty(model.getTemperature(), model.getMaxTokens(), model.getMaxContexts())) {
144 145 return;
145 146 }
  147 + Assert.equals(model.getType(), AiModelTypeEnum.CHAT.getType(), "模型类型不正确:" + model);
146 148 throw exception(CHAT_CONVERSATION_MODEL_ERROR);
147 149 }
148 150  
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java
... ... @@ -10,19 +10,24 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
10 10 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
11 11 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
12 12 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO;
  13 +import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO;
13 14 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO;
14 15 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO;
15   -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentSearchReqVO;
16 16 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
17 17 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
18   -import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
19   -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
  18 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
  19 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
  20 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
  21 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiToolDO;
20 22 import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper;
21   -import cn.iocoder.yudao.module.ai.enums.AiChatRoleEnum;
22 23 import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants;
  24 +import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService;
23 25 import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService;
24   -import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
25   -import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
  26 +import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchReqBO;
  27 +import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchRespBO;
  28 +import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
  29 +import cn.iocoder.yudao.module.ai.service.model.AiModelService;
  30 +import cn.iocoder.yudao.module.ai.service.model.AiToolService;
26 31 import jakarta.annotation.Resource;
27 32 import lombok.extern.slf4j.Slf4j;
28 33 import org.springframework.ai.chat.messages.Message;
... ... @@ -34,18 +39,19 @@ import org.springframework.ai.chat.model.ChatResponse;
34 39 import org.springframework.ai.chat.model.StreamingChatModel;
35 40 import org.springframework.ai.chat.prompt.ChatOptions;
36 41 import org.springframework.ai.chat.prompt.Prompt;
37   -import org.springframework.ai.chat.prompt.PromptTemplate;
38 42 import org.springframework.stereotype.Service;
39 43 import org.springframework.transaction.annotation.Transactional;
40 44 import reactor.core.publisher.Flux;
41 45  
42 46 import java.time.LocalDateTime;
43 47 import java.util.*;
  48 +import java.util.stream.Collectors;
44 49  
45 50 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
46 51 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error;
47 52 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
48 53 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  54 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
49 55 import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_CONVERSATION_NOT_EXISTS;
50 56 import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_MESSAGE_NOT_EXIST;
51 57  
... ... @@ -58,138 +64,199 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_MESSAGE_N
58 64 @Slf4j
59 65 public class AiChatMessageServiceImpl implements AiChatMessageService {
60 66  
  67 + /**
  68 + * 知识库转 {@link UserMessage} 的内容模版
  69 + */
  70 + private static final String KNOWLEDGE_USER_MESSAGE_TEMPLATE = "使用 <Reference></Reference> 标记中的内容作为本次对话的参考:\n\n" +
  71 + "%s\n\n" + // 多个 <Reference></Reference> 的拼接
  72 + "回答要求:\n- 避免提及你是从 <Reference></Reference> 获取的知识。";
  73 +
61 74 @Resource
62 75 private AiChatMessageMapper chatMessageMapper;
63 76  
64 77 @Resource
65 78 private AiChatConversationService chatConversationService;
66 79 @Resource
67   - private AiChatModelService chatModalService;
  80 + private AiChatRoleService chatRoleService;
68 81 @Resource
69   - private AiApiKeyService apiKeyService;
  82 + private AiModelService modalService;
70 83 @Resource
71 84 private AiKnowledgeSegmentService knowledgeSegmentService;
  85 + @Resource
  86 + private AiKnowledgeDocumentService knowledgeDocumentService;
  87 + @Resource
  88 + private AiToolService toolService;
72 89  
73 90 @Transactional(rollbackFor = Exception.class)
74 91 public AiChatMessageSendRespVO sendMessage(AiChatMessageSendReqVO sendReqVO, Long userId) {
75 92 // 1.1 校验对话存在
76   - AiChatConversationDO conversation = chatConversationService.validateChatConversationExists(sendReqVO.getConversationId());
  93 + AiChatConversationDO conversation = chatConversationService
  94 + .validateChatConversationExists(sendReqVO.getConversationId());
77 95 if (ObjUtil.notEqual(conversation.getUserId(), userId)) {
78 96 throw exception(CHAT_CONVERSATION_NOT_EXISTS);
79 97 }
80 98 List<AiChatMessageDO> historyMessages = chatMessageMapper.selectListByConversationId(conversation.getId());
81 99 // 1.2 校验模型
82   - AiChatModelDO model = chatModalService.validateChatModel(conversation.getModelId());
83   - ChatModel chatModel = apiKeyService.getChatModel(model.getKeyId());
  100 + AiModelDO model = modalService.validateModel(conversation.getModelId());
  101 + ChatModel chatModel = modalService.getChatModel(model.getId());
  102 +
  103 + // 2. 知识库找回
  104 + List<AiKnowledgeSegmentSearchRespBO> knowledgeSegments = recallKnowledgeSegment(sendReqVO.getContent(),
  105 + conversation);
84 106  
85   - // 2. 插入 user 发送消息
  107 + // 3. 插入 user 发送消息
86 108 AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model,
87   - userId, conversation.getRoleId(), MessageType.USER, sendReqVO.getContent(), sendReqVO.getUseContext());
  109 + userId, conversation.getRoleId(), MessageType.USER, sendReqVO.getContent(), sendReqVO.getUseContext(),
  110 + null);
88 111  
89 112 // 3.1 插入 assistant 接收消息
90 113 AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model,
91   - userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext());
92   -
93   - // 3.2 召回段落
94   - List<AiKnowledgeSegmentDO> segmentList = recallSegment(sendReqVO.getContent(), conversation.getKnowledgeId());
  114 + userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext(),
  115 + knowledgeSegments);
95 116  
96   - // 3.3 创建 chat 需要的 Prompt
97   - Prompt prompt = buildPrompt(conversation, historyMessages, segmentList, model, sendReqVO);
  117 + // 3.2 创建 chat 需要的 Prompt
  118 + Prompt prompt = buildPrompt(conversation, historyMessages, knowledgeSegments, model, sendReqVO);
98 119 ChatResponse chatResponse = chatModel.call(prompt);
99 120  
100   - // 3.4 段式返回
101   - String newContent = chatResponse.getResult().getOutput().getContent();
102   - chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setSegmentIds(convertList(segmentList, AiKnowledgeSegmentDO::getId)).setContent(newContent));
103   - return new AiChatMessageSendRespVO().setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class))
104   - .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class).setContent(newContent));
  121 + // 3.3 更新响应内容
  122 + String newContent = chatResponse.getResult().getOutput().getText();
  123 + chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(newContent));
  124 + // 3.4 响应结果
  125 + List<AiChatMessageRespVO.KnowledgeSegment> segments = BeanUtils.toBean(knowledgeSegments,
  126 + AiChatMessageRespVO.KnowledgeSegment.class,
  127 + segment -> {
  128 + AiKnowledgeDocumentDO document = knowledgeDocumentService
  129 + .getKnowledgeDocument(segment.getDocumentId());
  130 + segment.setDocumentName(document != null ? document.getName() : null);
  131 + });
  132 + return new AiChatMessageSendRespVO()
  133 + .setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class))
  134 + .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class)
  135 + .setContent(newContent).setSegments(segments));
105 136 }
106 137  
107 138 @Override
108   - public Flux<CommonResult<AiChatMessageSendRespVO>> sendChatMessageStream(AiChatMessageSendReqVO sendReqVO, Long userId) {
  139 + public Flux<CommonResult<AiChatMessageSendRespVO>> sendChatMessageStream(AiChatMessageSendReqVO sendReqVO,
  140 + Long userId) {
109 141 // 1.1 校验对话存在
110   - AiChatConversationDO conversation = chatConversationService.validateChatConversationExists(sendReqVO.getConversationId());
  142 + AiChatConversationDO conversation = chatConversationService
  143 + .validateChatConversationExists(sendReqVO.getConversationId());
111 144 if (ObjUtil.notEqual(conversation.getUserId(), userId)) {
112 145 throw exception(CHAT_CONVERSATION_NOT_EXISTS);
113 146 }
114 147 List<AiChatMessageDO> historyMessages = chatMessageMapper.selectListByConversationId(conversation.getId());
115 148 // 1.2 校验模型
116   - AiChatModelDO model = chatModalService.validateChatModel(conversation.getModelId());
117   - StreamingChatModel chatModel = apiKeyService.getChatModel(model.getKeyId());
  149 + AiModelDO model = modalService.validateModel(conversation.getModelId());
  150 + StreamingChatModel chatModel = modalService.getChatModel(model.getId());
118 151  
119   - // 2. 插入 user 发送消息
  152 + // 2. 知识库找回
  153 + List<AiKnowledgeSegmentSearchRespBO> knowledgeSegments = recallKnowledgeSegment(sendReqVO.getContent(),
  154 + conversation);
  155 +
  156 + // 3. 插入 user 发送消息
120 157 AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model,
121   - userId, conversation.getRoleId(), MessageType.USER, sendReqVO.getContent(), sendReqVO.getUseContext());
  158 + userId, conversation.getRoleId(), MessageType.USER, sendReqVO.getContent(), sendReqVO.getUseContext(),
  159 + null);
122 160  
123   - // 3.1 插入 assistant 接收消息
  161 + // 4.1 插入 assistant 接收消息
124 162 AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model,
125   - userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext());
126   -
127   -
128   - // 3.2 召回段落
129   - List<AiKnowledgeSegmentDO> segmentList = recallSegment(sendReqVO.getContent(), conversation.getKnowledgeId());
  163 + userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext(),
  164 + knowledgeSegments);
130 165  
131   - // 3.3 构建 Prompt,并进行调用
132   - Prompt prompt = buildPrompt(conversation, historyMessages, segmentList, model, sendReqVO);
  166 + // 4.2 构建 Prompt,并进行调用
  167 + Prompt prompt = buildPrompt(conversation, historyMessages, knowledgeSegments, model, sendReqVO);
133 168 Flux<ChatResponse> streamResponse = chatModel.stream(prompt);
134 169  
135   - // 3.4 流式返回
136   - // TODO 注意:Schedulers.immediate() 目的是,避免默认 Schedulers.parallel() 并发消费 chunk 导致 SSE 响应前端会乱序问题
  170 + // 4.3 流式返回
137 171 StringBuffer contentBuffer = new StringBuffer();
138 172 return streamResponse.map(chunk -> {
139   - String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getContent() : null;
  173 + // 处理知识库的返回,只有首次才有
  174 + List<AiChatMessageRespVO.KnowledgeSegment> segments = null;
  175 + if (StrUtil.isEmpty(contentBuffer)) {
  176 + segments = BeanUtils.toBean(knowledgeSegments, AiChatMessageRespVO.KnowledgeSegment.class,
  177 + segment -> TenantUtils.executeIgnore(() -> {
  178 + AiKnowledgeDocumentDO document = knowledgeDocumentService
  179 + .getKnowledgeDocument(segment.getDocumentId());
  180 + segment.setDocumentName(document != null ? document.getName() : null);
  181 + }));
  182 + }
  183 + // 响应结果
  184 + String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getText() : null;
140 185 newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况
141 186 contentBuffer.append(newContent);
142   - // 响应结果
143   - return success(new AiChatMessageSendRespVO().setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class))
144   - .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class).setContent(newContent)));
  187 + return success(new AiChatMessageSendRespVO()
  188 + .setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class))
  189 + .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class)
  190 + .setContent(newContent).setSegments(segments)));
145 191 }).doOnComplete(() -> {
146 192 // 忽略租户,因为 Flux 异步无法透传租户
147   - TenantUtils.executeIgnore(() ->
148   - chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setSegmentIds(convertList(segmentList, AiKnowledgeSegmentDO::getId))
149   - .setContent(contentBuffer.toString())));
  193 + TenantUtils.executeIgnore(() -> chatMessageMapper.updateById(
  194 + new AiChatMessageDO().setId(assistantMessage.getId()).setContent(contentBuffer.toString())));
150 195 }).doOnError(throwable -> {
151 196 log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable);
152 197 // 忽略租户,因为 Flux 异步无法透传租户
153   - TenantUtils.executeIgnore(() ->
154   - chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(throwable.getMessage())));
  198 + TenantUtils.executeIgnore(() -> chatMessageMapper.updateById(
  199 + new AiChatMessageDO().setId(assistantMessage.getId()).setContent(throwable.getMessage())));
155 200 }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.CHAT_STREAM_ERROR)));
156 201 }
157 202  
158   - private List<AiKnowledgeSegmentDO> recallSegment(String content, Long knowledgeId) {
159   - if (Objects.isNull(knowledgeId)) {
  203 + private List<AiKnowledgeSegmentSearchRespBO> recallKnowledgeSegment(String content,
  204 + AiChatConversationDO conversation) {
  205 + // 1. 查询聊天角色
  206 + if (conversation == null || conversation.getRoleId() == null) {
  207 + return Collections.emptyList();
  208 + }
  209 + AiChatRoleDO role = chatRoleService.getChatRole(conversation.getRoleId());
  210 + if (role == null || CollUtil.isEmpty(role.getKnowledgeIds())) {
160 211 return Collections.emptyList();
161 212 }
162   - return knowledgeSegmentService.similaritySearch(new AiKnowledgeSegmentSearchReqVO().setKnowledgeId(knowledgeId).setContent(content));
163   - }
164   -
165   - private Prompt buildPrompt(AiChatConversationDO conversation, List<AiChatMessageDO> messages,List<AiKnowledgeSegmentDO> segmentList,
166   - AiChatModelDO model, AiChatMessageSendReqVO sendReqVO) {
167   - // 1. 构建 Prompt Message 列表
168   - List<Message> chatMessages = new ArrayList<>();
169 213  
170   - // 1.1 召回内容消息构建
171   - if (CollUtil.isNotEmpty(segmentList)) {
172   - PromptTemplate promptTemplate = new PromptTemplate(AiChatRoleEnum.AI_KNOWLEDGE_ROLE.getSystemMessage());
173   - StringBuilder infoBuilder = StrUtil.builder();
174   - segmentList.forEach(segment -> infoBuilder.append(System.lineSeparator()).append(segment.getContent()));
175   - Message message = promptTemplate.createMessage(Map.of("info", infoBuilder.toString()));
176   - chatMessages.add(message);
  214 + // 2. 遍历找回
  215 + List<AiKnowledgeSegmentSearchRespBO> knowledgeSegments = new ArrayList<>();
  216 + for (Long knowledgeId : role.getKnowledgeIds()) {
  217 + knowledgeSegments.addAll(knowledgeSegmentService.searchKnowledgeSegment(new AiKnowledgeSegmentSearchReqBO()
  218 + .setKnowledgeId(knowledgeId).setContent(content)));
177 219 }
  220 + return knowledgeSegments;
  221 + }
178 222  
179   - // 1.2 system context 角色设定
  223 + private Prompt buildPrompt(AiChatConversationDO conversation, List<AiChatMessageDO> messages,
  224 + List<AiKnowledgeSegmentSearchRespBO> knowledgeSegments,
  225 + AiModelDO model, AiChatMessageSendReqVO sendReqVO) {
  226 + List<Message> chatMessages = new ArrayList<>();
  227 + // 1.1 System Context 角色设定
180 228 if (StrUtil.isNotBlank(conversation.getSystemMessage())) {
181 229 chatMessages.add(new SystemMessage(conversation.getSystemMessage()));
182 230 }
183   - // 1.3 history message 历史消息
  231 +
  232 + // 1.2 历史 history message 历史消息
184 233 List<AiChatMessageDO> contextMessages = filterContextMessages(messages, conversation, sendReqVO);
185   - contextMessages.forEach(message -> chatMessages.add(AiUtils.buildMessage(message.getType(), message.getContent())));
186   - // 1.4 user message 新发送消息
  234 + contextMessages
  235 + .forEach(message -> chatMessages.add(AiUtils.buildMessage(message.getType(), message.getContent())));
  236 +
  237 + // 1.3 当前 user message 新发送消息
187 238 chatMessages.add(new UserMessage(sendReqVO.getContent()));
188 239  
189   - // 2. 构建 ChatOptions 对象
  240 + // 1.4 知识库,通过 UserMessage 实现
  241 + if (CollUtil.isNotEmpty(knowledgeSegments)) {
  242 + String reference = knowledgeSegments.stream()
  243 + .map(segment -> "<Reference>" + segment.getContent() + "</Reference>")
  244 + .collect(Collectors.joining("\n\n"));
  245 + chatMessages.add(new UserMessage(String.format(KNOWLEDGE_USER_MESSAGE_TEMPLATE, reference)));
  246 + }
  247 +
  248 + // 2.1 查询 tool 工具
  249 + Set<String> toolNames = null;
  250 + if (conversation.getRoleId() != null) {
  251 + AiChatRoleDO chatRole = chatRoleService.getChatRole(conversation.getRoleId());
  252 + if (chatRole != null && CollUtil.isNotEmpty(chatRole.getToolIds())) {
  253 + toolNames = convertSet(toolService.getToolList(chatRole.getToolIds()), AiToolDO::getName);
  254 + }
  255 + }
  256 + // 2.2 构建 ChatOptions 对象
190 257 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform());
191 258 ChatOptions chatOptions = AiUtils.buildChatOptions(platform, model.getModel(),
192   - conversation.getTemperature(), conversation.getMaxTokens());
  259 + conversation.getTemperature(), conversation.getMaxTokens(), toolNames);
193 260 return new Prompt(chatMessages, chatOptions);
194 261 }
195 262  
... ... @@ -204,8 +271,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
204 271 * @return 消息上下文
205 272 */
206 273 private List<AiChatMessageDO> filterContextMessages(List<AiChatMessageDO> messages,
207   - AiChatConversationDO conversation,
208   - AiChatMessageSendReqVO sendReqVO) {
  274 + AiChatConversationDO conversation,
  275 + AiChatMessageSendReqVO sendReqVO) {
209 276 if (conversation.getMaxContexts() == null || ObjUtil.notEqual(sendReqVO.getUseContext(), Boolean.TRUE)) {
210 277 return Collections.emptyList();
211 278 }
... ... @@ -216,7 +283,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
216 283 continue;
217 284 }
218 285 AiChatMessageDO userMessage = CollUtil.get(messages, i - 1);
219   - if (userMessage == null || ObjUtil.notEqual(assistantMessage.getReplyId(), userMessage.getId())
  286 + if (userMessage == null
  287 + || ObjUtil.notEqual(assistantMessage.getReplyId(), userMessage.getId())
220 288 || StrUtil.isEmpty(assistantMessage.getContent())) {
221 289 continue;
222 290 }
... ... @@ -233,11 +301,13 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
233 301 }
234 302  
235 303 private AiChatMessageDO createChatMessage(Long conversationId, Long replyId,
236   - AiChatModelDO model, Long userId, Long roleId,
237   - MessageType messageType, String content, Boolean useContext) {
  304 + AiModelDO model, Long userId, Long roleId,
  305 + MessageType messageType, String content, Boolean useContext,
  306 + List<AiKnowledgeSegmentSearchRespBO> knowledgeSegments) {
238 307 AiChatMessageDO message = new AiChatMessageDO().setConversationId(conversationId).setReplyId(replyId)
239 308 .setModel(model.getModel()).setModelId(model.getId()).setUserId(userId).setRoleId(roleId)
240   - .setType(messageType.getValue()).setContent(content).setUseContext(useContext);
  309 + .setType(messageType.getValue()).setContent(content).setUseContext(useContext)
  310 + .setSegmentIds(convertList(knowledgeSegments, AiKnowledgeSegmentSearchRespBO::getId));
241 311 message.setCreateTime(LocalDateTime.now());
242 312 chatMessageMapper.insert(message);
243 313 return message;
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.ai.service.image;
3 3 import cn.hutool.core.bean.BeanUtil;
4 4 import cn.hutool.core.codec.Base64;
5 5 import cn.hutool.core.collection.CollUtil;
  6 +import cn.hutool.core.lang.Assert;
6 7 import cn.hutool.core.map.MapUtil;
7 8 import cn.hutool.core.util.ObjUtil;
8 9 import cn.hutool.core.util.StrUtil;
... ... @@ -12,15 +13,19 @@ import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
12 13 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
13 14 import cn.iocoder.yudao.framework.common.pojo.PageResult;
14 15 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
15   -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.*;
  16 +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO;
  17 +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImagePageReqVO;
  18 +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImagePublicPageReqVO;
  19 +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageUpdateReqVO;
16 20 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyActionReqVO;
17 21 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyImagineReqVO;
18 22 import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO;
  23 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
19 24 import cn.iocoder.yudao.module.ai.dal.mysql.image.AiImageMapper;
20 25 import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum;
21   -import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
  26 +import cn.iocoder.yudao.module.ai.service.model.AiModelService;
22 27 import cn.iocoder.yudao.module.infra.api.file.FileApi;
23   -import com.alibaba.cloud.ai.tongyi.image.TongYiImagesOptions;
  28 +import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions;
24 29 import jakarta.annotation.Resource;
25 30 import lombok.extern.slf4j.Slf4j;
26 31 import org.springframework.ai.image.ImageModel;
... ... @@ -55,13 +60,13 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*;
55 60 public class AiImageServiceImpl implements AiImageService {
56 61  
57 62 @Resource
58   - private AiImageMapper imageMapper;
  63 + private AiModelService modelService;
59 64  
60 65 @Resource
61   - private FileApi fileApi;
  66 + private AiImageMapper imageMapper;
62 67  
63 68 @Resource
64   - private AiApiKeyService apiKeyService;
  69 + private FileApi fileApi;
65 70  
66 71 @Override
67 72 public PageResult<AiImageDO> getImagePageMy(Long userId, AiImagePageReqVO pageReqVO) {
... ... @@ -88,23 +93,31 @@ public class AiImageServiceImpl implements AiImageService {
88 93  
89 94 @Override
90 95 public Long drawImage(Long userId, AiImageDrawReqVO drawReqVO) {
91   - // 1. 保存数据库
92   - AiImageDO image = BeanUtils.toBean(drawReqVO, AiImageDO.class).setUserId(userId).setPublicStatus(false)
93   - .setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus());
  96 + // 1. 校验模型
  97 + AiModelDO model = modelService.validateModel(drawReqVO.getModelId());
  98 +
  99 + // 2. 保存数据库
  100 + AiImageDO image = BeanUtils.toBean(drawReqVO, AiImageDO.class).setUserId(userId)
  101 + .setPlatform(model.getPlatform()).setModelId(model.getId()).setModel(model.getModel())
  102 + .setPublicStatus(false).setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus());
94 103 imageMapper.insert(image);
95   - // 2. 异步绘制,后续前端通过返回的 id 进行轮询结果
96   - getSelf().executeDrawImage(image, drawReqVO);
  104 +
  105 + // 3. 异步绘制,后续前端通过返回的 id 进行轮询结果
  106 + getSelf().executeDrawImage(image, drawReqVO, model);
97 107 return image.getId();
98 108 }
99 109  
100 110 @Async
101   - public void executeDrawImage(AiImageDO image, AiImageDrawReqVO req) {
  111 + public void executeDrawImage(AiImageDO image, AiImageDrawReqVO reqVO, AiModelDO model) {
102 112 try {
103 113 // 1.1 构建请求
104   - ImageOptions request = buildImageOptions(req);
  114 + ImageOptions request = buildImageOptions(reqVO, model);
105 115 // 1.2 执行请求
106   - ImageModel imageModel = apiKeyService.getImageModel(AiPlatformEnum.validatePlatform(req.getPlatform()));
107   - ImageResponse response = imageModel.call(new ImagePrompt(req.getPrompt(), request));
  116 + ImageModel imageModel = modelService.getImageModel(model.getId());
  117 + ImageResponse response = imageModel.call(new ImagePrompt(reqVO.getPrompt(), request));
  118 + if (response.getResult() == null) {
  119 + throw new IllegalArgumentException("生成结果为空");
  120 + }
108 121  
109 122 // 2. 上传到文件服务
110 123 String b64Json = response.getResult().getOutput().getB64Json();
... ... @@ -116,49 +129,49 @@ public class AiImageServiceImpl implements AiImageService {
116 129 imageMapper.updateById(new AiImageDO().setId(image.getId()).setStatus(AiImageStatusEnum.SUCCESS.getStatus())
117 130 .setPicUrl(filePath).setFinishTime(LocalDateTime.now()));
118 131 } catch (Exception ex) {
119   - log.error("[doDall][image({}) 生成异常]", image, ex);
  132 + log.error("[executeDrawImage][image({}) 生成异常]", image, ex);
120 133 imageMapper.updateById(new AiImageDO().setId(image.getId())
121 134 .setStatus(AiImageStatusEnum.FAIL.getStatus())
122 135 .setErrorMessage(ex.getMessage()).setFinishTime(LocalDateTime.now()));
123 136 }
124 137 }
125 138  
126   - private static ImageOptions buildImageOptions(AiImageDrawReqVO draw) {
127   - if (ObjUtil.equal(draw.getPlatform(), AiPlatformEnum.OPENAI.getPlatform())) {
  139 + private static ImageOptions buildImageOptions(AiImageDrawReqVO draw, AiModelDO model) {
  140 + if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.OPENAI.getPlatform())) {
128 141 // https://platform.openai.com/docs/api-reference/images/create
129   - return OpenAiImageOptions.builder().withModel(draw.getModel())
  142 + return OpenAiImageOptions.builder().withModel(model.getModel())
130 143 .withHeight(draw.getHeight()).withWidth(draw.getWidth())
131 144 .withStyle(MapUtil.getStr(draw.getOptions(), "style")) // 风格
132 145 .withResponseFormat("b64_json")
133 146 .build();
134   - } else if (ObjUtil.equal(draw.getPlatform(), AiPlatformEnum.STABLE_DIFFUSION.getPlatform())) {
  147 + } else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.STABLE_DIFFUSION.getPlatform())) {
135 148 // https://platform.stability.ai/docs/api-reference#tag/SDXL-and-SD1.6/operation/textToImage
136 149 // https://platform.stability.ai/docs/api-reference#tag/Text-to-Image/operation/textToImage
137   - return StabilityAiImageOptions.builder().withModel(draw.getModel())
138   - .withHeight(draw.getHeight()).withWidth(draw.getWidth())
139   - .withSeed(Long.valueOf(draw.getOptions().get("seed")))
140   - .withCfgScale(Float.valueOf(draw.getOptions().get("scale")))
141   - .withSteps(Integer.valueOf(draw.getOptions().get("steps")))
142   - .withSampler(String.valueOf(draw.getOptions().get("sampler")))
143   - .withStylePreset(String.valueOf(draw.getOptions().get("stylePreset")))
144   - .withClipGuidancePreset(String.valueOf(draw.getOptions().get("clipGuidancePreset")))
  150 + return StabilityAiImageOptions.builder().model(model.getModel())
  151 + .height(draw.getHeight()).width(draw.getWidth())
  152 + .seed(Long.valueOf(draw.getOptions().get("seed")))
  153 + .cfgScale(Float.valueOf(draw.getOptions().get("scale")))
  154 + .steps(Integer.valueOf(draw.getOptions().get("steps")))
  155 + .sampler(String.valueOf(draw.getOptions().get("sampler")))
  156 + .stylePreset(String.valueOf(draw.getOptions().get("stylePreset")))
  157 + .clipGuidancePreset(String.valueOf(draw.getOptions().get("clipGuidancePreset")))
145 158 .build();
146   - } else if (ObjUtil.equal(draw.getPlatform(), AiPlatformEnum.TONG_YI.getPlatform())) {
147   - return TongYiImagesOptions.builder()
148   - .withModel(draw.getModel()).withN(1)
  159 + } else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.TONG_YI.getPlatform())) {
  160 + return DashScopeImageOptions.builder()
  161 + .withModel(model.getModel()).withN(1)
149 162 .withHeight(draw.getHeight()).withWidth(draw.getWidth())
150 163 .build();
151   - } else if (ObjUtil.equal(draw.getPlatform(), AiPlatformEnum.YI_YAN.getPlatform())) {
  164 + } else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.YI_YAN.getPlatform())) {
152 165 return QianFanImageOptions.builder()
153   - .withModel(draw.getModel()).withN(1)
154   - .withHeight(draw.getHeight()).withWidth(draw.getWidth())
  166 + .model(model.getModel()).N(1)
  167 + .height(draw.getHeight()).width(draw.getWidth())
155 168 .build();
156   - } else if (ObjUtil.equal(draw.getPlatform(), AiPlatformEnum.ZHI_PU.getPlatform())) {
  169 + } else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.ZHI_PU.getPlatform())) {
157 170 return ZhiPuAiImageOptions.builder()
158   - .withModel(draw.getModel())
  171 + .model(model.getModel())
159 172 .build();
160 173 }
161   - throw new IllegalArgumentException("不支持的 AI 平台:" + draw.getPlatform());
  174 + throw new IllegalArgumentException("不支持的 AI 平台:" + model.getPlatform());
162 175 }
163 176  
164 177 @Override
... ... @@ -205,52 +218,56 @@ public class AiImageServiceImpl implements AiImageService {
205 218  
206 219 @Override
207 220 @Transactional(rollbackFor = Exception.class)
208   - public Long midjourneyImagine(Long userId, AiMidjourneyImagineReqVO reqVO) {
209   - MidjourneyApi midjourneyApi = apiKeyService.getMidjourneyApi();
210   - // 1. 保存数据库
211   - AiImageDO image = BeanUtils.toBean(reqVO, AiImageDO.class).setUserId(userId).setPublicStatus(false)
  221 + public Long midjourneyImagine(Long userId, AiMidjourneyImagineReqVO drawReqVO) {
  222 + // 1. 校验模型
  223 + AiModelDO model = modelService.validateModel(drawReqVO.getModelId());
  224 + Assert.equals(model.getPlatform(), AiPlatformEnum.MIDJOURNEY.getPlatform(), "平台不匹配");
  225 + MidjourneyApi midjourneyApi = modelService.getMidjourneyApi(model.getId());
  226 +
  227 + // 2. 保存数据库
  228 + AiImageDO image = BeanUtils.toBean(drawReqVO, AiImageDO.class).setUserId(userId).setPublicStatus(false)
212 229 .setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus())
213   - .setPlatform(AiPlatformEnum.MIDJOURNEY.getPlatform());
  230 + .setPlatform(AiPlatformEnum.MIDJOURNEY.getPlatform()).setModelId(model.getId()).setModel(model.getName());
214 231 imageMapper.insert(image);
215 232  
216   - // 2. 调用 Midjourney Proxy 提交任务
217   - List<String> base64Array = StrUtil.isBlank(reqVO.getReferImageUrl()) ? null :
218   - Collections.singletonList("data:image/jpeg;base64,".concat(Base64.encode(HttpUtil.downloadBytes(reqVO.getReferImageUrl()))));
  233 + // 3. 调用 Midjourney Proxy 提交任务
  234 + List<String> base64Array = StrUtil.isBlank(drawReqVO.getReferImageUrl()) ? null :
  235 + Collections.singletonList("data:image/jpeg;base64,".concat(Base64.encode(HttpUtil.downloadBytes(drawReqVO.getReferImageUrl()))));
219 236 MidjourneyApi.ImagineRequest imagineRequest = new MidjourneyApi.ImagineRequest(
220   - base64Array, reqVO.getPrompt(),null,
221   - MidjourneyApi.ImagineRequest.buildState(reqVO.getWidth(),
222   - reqVO.getHeight(), reqVO.getVersion(), reqVO.getModel()));
  237 + base64Array, drawReqVO.getPrompt(),null,
  238 + MidjourneyApi.ImagineRequest.buildState(drawReqVO.getWidth(),
  239 + drawReqVO.getHeight(), drawReqVO.getVersion(), model.getModel()));
223 240 MidjourneyApi.SubmitResponse imagineResponse = midjourneyApi.imagine(imagineRequest);
224 241  
225   - // 3. 情况一【失败】:抛出业务异常
  242 + // 4.1 情况一【失败】:抛出业务异常
226 243 if (!MidjourneyApi.SubmitCodeEnum.SUCCESS_CODES.contains(imagineResponse.code())) {
227 244 String description = imagineResponse.description().contains("quota_not_enough") ?
228 245 "账户余额不足" : imagineResponse.description();
229 246 throw exception(IMAGE_MIDJOURNEY_SUBMIT_FAIL, description);
230 247 }
231 248  
232   - // 4. 情况二【成功】:更新 taskId 和参数
  249 + // 4.2 情况二【成功】:更新 taskId 和参数
233 250 imageMapper.updateById(new AiImageDO().setId(image.getId())
234   - .setTaskId(imagineResponse.result()).setOptions(BeanUtil.beanToMap(reqVO)));
  251 + .setTaskId(imagineResponse.result()).setOptions(BeanUtil.beanToMap(drawReqVO)));
235 252 return image.getId();
236 253 }
237 254  
238 255 @Override
239 256 public Integer midjourneySync() {
240   - MidjourneyApi midjourneyApi = apiKeyService.getMidjourneyApi();
241 257 // 1.1 获取 Midjourney 平台,状态在 “进行中” 的 image
242   - List<AiImageDO> imageList = imageMapper.selectListByStatusAndPlatform(
  258 + List<AiImageDO> images = imageMapper.selectListByStatusAndPlatform(
243 259 AiImageStatusEnum.IN_PROGRESS.getStatus(), AiPlatformEnum.MIDJOURNEY.getPlatform());
244   - if (CollUtil.isEmpty(imageList)) {
  260 + if (CollUtil.isEmpty(images)) {
245 261 return 0;
246 262 }
247 263 // 1.2 调用 Midjourney Proxy 获取任务进展
248   - List<MidjourneyApi.Notify> taskList = midjourneyApi.getTaskList(convertSet(imageList, AiImageDO::getTaskId));
  264 + MidjourneyApi midjourneyApi = modelService.getMidjourneyApi(images.get(0).getModelId());
  265 + List<MidjourneyApi.Notify> taskList = midjourneyApi.getTaskList(convertSet(images, AiImageDO::getTaskId));
249 266 Map<String, MidjourneyApi.Notify> taskMap = convertMap(taskList, MidjourneyApi.Notify::id);
250 267  
251 268 // 2. 逐个处理,更新进展
252 269 int count = 0;
253   - for (AiImageDO image : imageList) {
  270 + for (AiImageDO image : images) {
254 271 MidjourneyApi.Notify notify = taskMap.get(image.getTaskId());
255 272 if (notify == null) {
256 273 log.error("[midjourneySync][image({}) 查询不到进展]", image);
... ... @@ -308,12 +325,12 @@ public class AiImageServiceImpl implements AiImageService {
308 325  
309 326 @Override
310 327 public Long midjourneyAction(Long userId, AiMidjourneyActionReqVO reqVO) {
311   - MidjourneyApi midjourneyApi = apiKeyService.getMidjourneyApi();
312 328 // 1.1 检查 image
313 329 AiImageDO image = validateImageExists(reqVO.getId());
314 330 if (ObjUtil.notEqual(userId, image.getUserId())) {
315 331 throw exception(IMAGE_NOT_EXISTS);
316 332 }
  333 + MidjourneyApi midjourneyApi = modelService.getMidjourneyApi(image.getModelId());
317 334 // 1.2 检查 customId
318 335 MidjourneyApi.Button button = CollUtil.findOne(image.getButtons(),
319 336 buttonX -> buttonX.customId().equals(reqVO.getCustomId()));
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentService.java
1 1 package cn.iocoder.yudao.module.ai.service.knowledge;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentCreateListReqVO;
4 5 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO;
5 6 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateReqVO;
  7 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateStatusReqVO;
6 8 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeDocumentCreateReqVO;
7 9 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
8 10  
  11 +import java.util.Collection;
  12 +import java.util.List;
  13 +import java.util.Map;
  14 +
  15 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  16 +
9 17 /**
10   - * AI 知识库-文档 Service 接口
  18 + * AI 知识库文档 Service 接口
11 19 *
12 20 * @author xiaoxin
13 21 */
14 22 public interface AiKnowledgeDocumentService {
15 23  
16 24 /**
17   - * 创建文档
  25 + * 创建文档(单个)
18 26 *
19 27 * @param createReqVO 文档创建 Request VO
20 28 * @return 文档编号
21 29 */
22 30 Long createKnowledgeDocument(AiKnowledgeDocumentCreateReqVO createReqVO);
23 31  
  32 + /**
  33 + * 创建文档(多个)
  34 + *
  35 + * @param createListReqVO 批量创建 Request VO
  36 + * @return 文档编号列表
  37 + */
  38 + List<Long> createKnowledgeDocumentList(AiKnowledgeDocumentCreateListReqVO createListReqVO);
24 39  
25 40 /**
26 41 * 获取文档分页
... ... @@ -31,9 +46,73 @@ public interface AiKnowledgeDocumentService {
31 46 PageResult<AiKnowledgeDocumentDO> getKnowledgeDocumentPage(AiKnowledgeDocumentPageReqVO pageReqVO);
32 47  
33 48 /**
  49 + * 获取文档详情
  50 + *
  51 + * @param id 文档编号
  52 + * @return 文档详情
  53 + */
  54 + AiKnowledgeDocumentDO getKnowledgeDocument(Long id);
  55 +
  56 + /**
34 57 * 更新文档
35 58 *
36 59 * @param reqVO 更新信息
37 60 */
38 61 void updateKnowledgeDocument(AiKnowledgeDocumentUpdateReqVO reqVO);
  62 +
  63 + /**
  64 + * 更新文档状态
  65 + *
  66 + * @param reqVO 更新状态信息
  67 + */
  68 + void updateKnowledgeDocumentStatus(AiKnowledgeDocumentUpdateStatusReqVO reqVO);
  69 +
  70 + /**
  71 + * 更新文档检索次数(增加 +1)
  72 + *
  73 + * @param ids 文档编号列表
  74 + */
  75 + void updateKnowledgeDocumentRetrievalCountIncr(Collection<Long> ids);
  76 +
  77 + /**
  78 + * 删除文档
  79 + *
  80 + * @param id 文档编号
  81 + */
  82 + void deleteKnowledgeDocument(Long id);
  83 +
  84 + /**
  85 + * 校验文档是否存在
  86 + *
  87 + * @param id 文档编号
  88 + * @return 文档信息
  89 + */
  90 + AiKnowledgeDocumentDO validateKnowledgeDocumentExists(Long id);
  91 +
  92 + /**
  93 + * 读取 URL 内容
  94 + *
  95 + * @param url URL
  96 + * @return 内容
  97 + */
  98 + String readUrl(String url);
  99 +
  100 + /**
  101 + * 获取文档列表
  102 + *
  103 + * @param ids 文档编号列表
  104 + * @return 文档列表
  105 + */
  106 + List<AiKnowledgeDocumentDO> getKnowledgeDocumentList(Collection<Long> ids);
  107 +
  108 + /**
  109 + * 获取文档 Map
  110 + *
  111 + * @param ids 文档编号列表
  112 + * @return 文档 Map
  113 + */
  114 + default Map<Long, AiKnowledgeDocumentDO> getKnowledgeDocumentMap(Collection<Long> ids) {
  115 + return convertMap(getKnowledgeDocumentList(ids), AiKnowledgeDocumentDO::getId);
  116 + }
  117 +
39 118 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentServiceImpl.java
1 1 package cn.iocoder.yudao.module.ai.service.knowledge;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjUtil;
  5 +import cn.hutool.core.util.StrUtil;
4 6 import cn.hutool.http.HttpUtil;
5 7 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
6 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7   -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
8 9 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  10 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentCreateListReqVO;
9 11 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO;
10 12 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateReqVO;
  13 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateStatusReqVO;
11 14 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeDocumentCreateReqVO;
12 15 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
13   -import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
14 16 import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeDocumentMapper;
15   -import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeSegmentMapper;
16   -import cn.iocoder.yudao.module.ai.enums.knowledge.AiKnowledgeDocumentStatusEnum;
17 17 import jakarta.annotation.Resource;
18 18 import lombok.extern.slf4j.Slf4j;
19 19 import org.springframework.ai.document.Document;
20 20 import org.springframework.ai.reader.tika.TikaDocumentReader;
21 21 import org.springframework.ai.tokenizer.TokenCountEstimator;
22   -import org.springframework.ai.transformer.splitter.TokenTextSplitter;
23   -import org.springframework.ai.vectorstore.VectorStore;
  22 +import org.springframework.context.annotation.Lazy;
24 23 import org.springframework.core.io.ByteArrayResource;
25 24 import org.springframework.stereotype.Service;
26 25 import org.springframework.transaction.annotation.Transactional;
27 26  
  27 +import java.util.ArrayList;
  28 +import java.util.Collection;
  29 +import java.util.Collections;
28 30 import java.util.List;
29 31  
30 32 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
31   -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_DOCUMENT_NOT_EXISTS;
  33 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  34 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*;
32 35  
33 36 /**
34 37 * AI 知识库文档 Service 实现类
... ... @@ -40,91 +43,172 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_DOCU
40 43 public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentService {
41 44  
42 45 @Resource
43   - private AiKnowledgeDocumentMapper documentMapper;
44   - @Resource
45   - private AiKnowledgeSegmentMapper segmentMapper;
  46 + private AiKnowledgeDocumentMapper knowledgeDocumentMapper;
46 47  
47 48 @Resource
48 49 private TokenCountEstimator tokenCountEstimator;
  50 +
  51 + @Resource
  52 + private AiKnowledgeSegmentService knowledgeSegmentService;
49 53 @Resource
  54 + @Lazy // 延迟加载,避免循环依赖
50 55 private AiKnowledgeService knowledgeService;
51 56  
52 57 @Override
53   - @Transactional(rollbackFor = Exception.class)
54 58 public Long createKnowledgeDocument(AiKnowledgeDocumentCreateReqVO createReqVO) {
55   - // 0. 校验并获取向量存储实例
56   - VectorStore vectorStore = knowledgeService.getVectorStoreById(createReqVO.getKnowledgeId());
  59 + // 1. 校验参数
  60 + knowledgeService.validateKnowledgeExists(createReqVO.getKnowledgeId());
57 61  
58   - // 1.1 下载文档
59   - TikaDocumentReader loader = new TikaDocumentReader(downloadFile(createReqVO.getUrl()));
60   - List<Document> documents = loader.get();
61   - Document document = CollUtil.getFirst(documents);
62   - // 1.2 文档记录入库
63   - String content = document.getContent();
  62 + // 2. 下载文档
  63 + String content = readUrl(createReqVO.getUrl());
  64 +
  65 + // 3. 文档记录入库
64 66 AiKnowledgeDocumentDO documentDO = BeanUtils.toBean(createReqVO, AiKnowledgeDocumentDO.class)
65   - .setTokens(tokenCountEstimator.estimate(content)).setWordCount(content.length())
66   - .setStatus(CommonStatusEnum.ENABLE.getStatus()).setSliceStatus(AiKnowledgeDocumentStatusEnum.SUCCESS.getStatus());
67   - documentMapper.insert(documentDO);
68   - Long documentId = documentDO.getId();
69   - if (CollUtil.isEmpty(documents)) {
70   - return documentId;
  67 + .setContent(content).setContentLength(content.length()).setTokens(tokenCountEstimator.estimate(content))
  68 + .setStatus(CommonStatusEnum.ENABLE.getStatus());
  69 + knowledgeDocumentMapper.insert(documentDO);
  70 +
  71 + // 4. 文档切片入库(异步)
  72 + knowledgeSegmentService.createKnowledgeSegmentBySplitContentAsync(documentDO.getId(), content);
  73 + return documentDO.getId();
  74 + }
  75 +
  76 + @Override
  77 + public List<Long> createKnowledgeDocumentList(AiKnowledgeDocumentCreateListReqVO createListReqVO) {
  78 + // 1. 校验参数
  79 + knowledgeService.validateKnowledgeExists(createListReqVO.getKnowledgeId());
  80 +
  81 + // 2. 下载文档
  82 + List<String> contents = convertList(createListReqVO.getList(), document -> readUrl(document.getUrl()));
  83 +
  84 + // 3. 文档记录入库
  85 + List<AiKnowledgeDocumentDO> documentDOs = new ArrayList<>(createListReqVO.getList().size());
  86 + for (int i = 0; i < createListReqVO.getList().size(); i++) {
  87 + AiKnowledgeDocumentCreateListReqVO.Document documentVO = createListReqVO.getList().get(i);
  88 + String content = contents.get(i);
  89 + documentDOs.add(BeanUtils.toBean(documentVO, AiKnowledgeDocumentDO.class)
  90 + .setKnowledgeId(createListReqVO.getKnowledgeId())
  91 + .setContent(content).setContentLength(content.length())
  92 + .setTokens(tokenCountEstimator.estimate(content))
  93 + .setSegmentMaxTokens(createListReqVO.getSegmentMaxTokens())
  94 + .setStatus(CommonStatusEnum.ENABLE.getStatus()));
71 95 }
  96 + knowledgeDocumentMapper.insertBatch(documentDOs);
72 97  
73   - // 2 构造文本分段器
74   - TokenTextSplitter tokenTextSplitter = new TokenTextSplitter(createReqVO.getDefaultSegmentTokens(), createReqVO.getMinSegmentWordCount(), createReqVO.getMinChunkLengthToEmbed(),
75   - createReqVO.getMaxNumSegments(), createReqVO.getKeepSeparator());
76   - // 2.1 文档分段
77   - List<Document> segments = tokenTextSplitter.apply(documents);
78   - // 2.2 分段内容入库
79   - List<AiKnowledgeSegmentDO> segmentDOList = CollectionUtils.convertList(segments,
80   - segment -> new AiKnowledgeSegmentDO().setContent(segment.getContent()).setDocumentId(documentId)
81   - .setKnowledgeId(createReqVO.getKnowledgeId()).setVectorId(segment.getId())
82   - .setTokens(tokenCountEstimator.estimate(segment.getContent())).setWordCount(segment.getContent().length())
83   - .setStatus(CommonStatusEnum.ENABLE.getStatus()));
84   - segmentMapper.insertBatch(segmentDOList);
85   -
86   - // 3. 向量化并存储
87   - segments.forEach(segment -> segment.getMetadata().put(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, createReqVO.getKnowledgeId()));
88   - vectorStore.add(segments);
89   - return documentId;
  98 + // 4. 批量创建文档切片(异步)
  99 + documentDOs.forEach(documentDO -> knowledgeSegmentService
  100 + .createKnowledgeSegmentBySplitContentAsync(documentDO.getId(), documentDO.getContent()));
  101 + return convertList(documentDOs, AiKnowledgeDocumentDO::getId);
90 102 }
91 103  
92 104 @Override
93 105 public PageResult<AiKnowledgeDocumentDO> getKnowledgeDocumentPage(AiKnowledgeDocumentPageReqVO pageReqVO) {
94   - return documentMapper.selectPage(pageReqVO);
  106 + return knowledgeDocumentMapper.selectPage(pageReqVO);
  107 + }
  108 +
  109 + @Override
  110 + public AiKnowledgeDocumentDO getKnowledgeDocument(Long id) {
  111 + return knowledgeDocumentMapper.selectById(id);
95 112 }
96 113  
97 114 @Override
98 115 public void updateKnowledgeDocument(AiKnowledgeDocumentUpdateReqVO reqVO) {
99 116 // 1. 校验文档是否存在
100   - validateKnowledgeDocumentExists(reqVO.getId());
  117 + AiKnowledgeDocumentDO oldDocument = validateKnowledgeDocumentExists(reqVO.getId());
  118 +
101 119 // 2. 更新文档
102 120 AiKnowledgeDocumentDO document = BeanUtils.toBean(reqVO, AiKnowledgeDocumentDO.class);
103   - documentMapper.updateById(document);
  121 + knowledgeDocumentMapper.updateById(document);
  122 +
  123 + // 3. 如果处于开启状态,并且最大 tokens 发生变化,则 segment 需要重新索引
  124 + if (CommonStatusEnum.isEnable(oldDocument.getStatus())
  125 + && reqVO.getSegmentMaxTokens() != null
  126 + && ObjUtil.notEqual(reqVO.getSegmentMaxTokens(), oldDocument.getSegmentMaxTokens())) {
  127 + // 删除旧的文档切片
  128 + knowledgeSegmentService.deleteKnowledgeSegmentByDocumentId(reqVO.getId());
  129 + // 重新创建文档切片
  130 + knowledgeSegmentService.createKnowledgeSegmentBySplitContentAsync(reqVO.getId(), oldDocument.getContent());
  131 + }
104 132 }
105 133  
106   - /**
107   - * 校验文档是否存在
108   - *
109   - * @param id 文档编号
110   - * @return 文档信息
111   - */
112   - private AiKnowledgeDocumentDO validateKnowledgeDocumentExists(Long id) {
113   - AiKnowledgeDocumentDO knowledgeDocument = documentMapper.selectById(id);
  134 + @Override
  135 + public void updateKnowledgeDocumentStatus(AiKnowledgeDocumentUpdateStatusReqVO reqVO) {
  136 + // 1. 校验存在
  137 + AiKnowledgeDocumentDO document = validateKnowledgeDocumentExists(reqVO.getId());
  138 +
  139 + // 2. 更新状态
  140 + knowledgeDocumentMapper.updateById(new AiKnowledgeDocumentDO()
  141 + .setId(reqVO.getId()).setStatus(reqVO.getStatus()));
  142 +
  143 + // 3. 处理文档切片
  144 + if (CommonStatusEnum.isEnable(reqVO.getStatus())) {
  145 + knowledgeSegmentService.createKnowledgeSegmentBySplitContentAsync(reqVO.getId(), document.getContent());
  146 + } else {
  147 + knowledgeSegmentService.deleteKnowledgeSegmentByDocumentId(reqVO.getId());
  148 + }
  149 + }
  150 +
  151 + @Override
  152 + @Transactional(rollbackFor = Exception.class)
  153 + public void deleteKnowledgeDocument(Long id) {
  154 + // 1. 校验存在
  155 + validateKnowledgeDocumentExists(id);
  156 +
  157 + // 2. 删除
  158 + knowledgeDocumentMapper.deleteById(id);
  159 +
  160 + // 3. 删除对应的段落
  161 + knowledgeSegmentService.deleteKnowledgeSegmentByDocumentId(id);
  162 + }
  163 +
  164 + @Override
  165 + public void updateKnowledgeDocumentRetrievalCountIncr(Collection<Long> ids) {
  166 + if (CollUtil.isEmpty(ids)) {
  167 + return;
  168 + }
  169 + knowledgeDocumentMapper.updateRetrievalCountIncr(ids);
  170 + }
  171 +
  172 + @Override
  173 + public AiKnowledgeDocumentDO validateKnowledgeDocumentExists(Long id) {
  174 + AiKnowledgeDocumentDO knowledgeDocument = knowledgeDocumentMapper.selectById(id);
114 175 if (knowledgeDocument == null) {
115 176 throw exception(KNOWLEDGE_DOCUMENT_NOT_EXISTS);
116 177 }
117 178 return knowledgeDocument;
118 179 }
119 180  
120   - private org.springframework.core.io.Resource downloadFile(String url) {
  181 + @Override
  182 + public String readUrl(String url) {
  183 + // 下载文件
  184 + ByteArrayResource resource;
121 185 try {
122 186 byte[] bytes = HttpUtil.downloadBytes(url);
123   - return new ByteArrayResource(bytes);
  187 + if (bytes.length == 0) {
  188 + throw exception(KNOWLEDGE_DOCUMENT_FILE_EMPTY);
  189 + }
  190 + resource = new ByteArrayResource(bytes);
124 191 } catch (Exception e) {
125   - log.error("[downloadFile][url({}) 下载失败]", url, e);
126   - throw new RuntimeException(e);
  192 + log.error("[readUrl][url({}) 读取失败]", url, e);
  193 + throw exception(KNOWLEDGE_DOCUMENT_FILE_DOWNLOAD_FAIL);
  194 + }
  195 +
  196 + // 读取文件
  197 + TikaDocumentReader loader = new TikaDocumentReader(resource);
  198 + List<Document> documents = loader.get();
  199 + Document document = CollUtil.getFirst(documents);
  200 + if (document == null || StrUtil.isEmpty(document.getText())) {
  201 + throw exception(KNOWLEDGE_DOCUMENT_FILE_READ_FAIL);
  202 + }
  203 + return document.getText();
  204 + }
  205 +
  206 + @Override
  207 + public List<AiKnowledgeDocumentDO> getKnowledgeDocumentList(Collection<Long> ids) {
  208 + if (CollUtil.isEmpty(ids)) {
  209 + return Collections.emptyList();
127 210 }
  211 + return knowledgeDocumentMapper.selectBatchIds(ids);
128 212 }
129 213  
130 214 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentService.java
... ... @@ -2,12 +2,19 @@ package cn.iocoder.yudao.module.ai.service.knowledge;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO;
5   -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentSearchReqVO;
6   -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateReqVO;
  5 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentProcessRespVO;
  6 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentSaveReqVO;
7 7 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO;
8 8 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
  9 +import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchReqBO;
  10 +import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchRespBO;
  11 +import org.springframework.scheduling.annotation.Async;
9 12  
  13 +import java.util.Collection;
10 14 import java.util.List;
  15 +import java.util.Map;
  16 +
  17 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
11 18  
12 19 /**
13 20 * AI 知识库段落 Service 接口
... ... @@ -17,6 +24,32 @@ import java.util.List;
17 24 public interface AiKnowledgeSegmentService {
18 25  
19 26 /**
  27 + * 获取知识库段落详情
  28 + *
  29 + * @param id 段落编号
  30 + * @return 段落详情
  31 + */
  32 + AiKnowledgeSegmentDO getKnowledgeSegment(Long id);
  33 +
  34 + /**
  35 + * 获取知识库段落列表
  36 + *
  37 + * @param ids 段落编号列表
  38 + * @return 段落列表
  39 + */
  40 + List<AiKnowledgeSegmentDO> getKnowledgeSegmentList(Collection<Long> ids);
  41 +
  42 + /**
  43 + * 获取知识库段落 Map
  44 + *
  45 + * @param ids 段落编号列表
  46 + * @return 段落 Map
  47 + */
  48 + default Map<Long, AiKnowledgeSegmentDO> getKnowledgeSegmentMap(Collection<Long> ids) {
  49 + return convertMap(getKnowledgeSegmentList(ids), AiKnowledgeSegmentDO::getId);
  50 + }
  51 +
  52 + /**
20 53 * 获取段落分页
21 54 *
22 55 * @param pageReqVO 分页查询
... ... @@ -25,11 +58,38 @@ public interface AiKnowledgeSegmentService {
25 58 PageResult<AiKnowledgeSegmentDO> getKnowledgeSegmentPage(AiKnowledgeSegmentPageReqVO pageReqVO);
26 59  
27 60 /**
  61 + * 基于 content 内容,切片创建多个段落
  62 + *
  63 + * @param documentId 知识库文档编号
  64 + * @param content 文档内容
  65 + */
  66 + void createKnowledgeSegmentBySplitContent(Long documentId, String content);
  67 +
  68 + /**
  69 + * 【异步】基于 content 内容,切片创建多个段落
  70 + *
  71 + * @param documentId 知识库文档编号
  72 + * @param content 文档内容
  73 + */
  74 + @Async
  75 + default void createKnowledgeSegmentBySplitContentAsync(Long documentId, String content) {
  76 + createKnowledgeSegmentBySplitContent(documentId, content);
  77 + }
  78 +
  79 + /**
  80 + * 创建知识库段落
  81 + *
  82 + * @param createReqVO 创建信息
  83 + * @return 段落编号
  84 + */
  85 + Long createKnowledgeSegment(AiKnowledgeSegmentSaveReqVO createReqVO);
  86 +
  87 + /**
28 88 * 更新段落的内容
29 89 *
30 90 * @param reqVO 更新内容
31 91 */
32   - void updateKnowledgeSegment(AiKnowledgeSegmentUpdateReqVO reqVO);
  92 + void updateKnowledgeSegment(AiKnowledgeSegmentSaveReqVO reqVO);
33 93  
34 94 /**
35 95 * 更新段落的状态
... ... @@ -39,11 +99,52 @@ public interface AiKnowledgeSegmentService {
39 99 void updateKnowledgeSegmentStatus(AiKnowledgeSegmentUpdateStatusReqVO reqVO);
40 100  
41 101 /**
42   - * 召回段落
  102 + * 重新索引知识库下的所有文档段落
  103 + *
  104 + * @param knowledgeId 知识库编号
  105 + */
  106 + void reindexKnowledgeSegmentByKnowledgeId(Long knowledgeId);
  107 +
  108 + /**
  109 + * 【异步】重新索引知识库下的所有文档段落
  110 + *
  111 + * @param knowledgeId 知识库编号
  112 + */
  113 + @Async
  114 + default void reindexByKnowledgeIdAsync(Long knowledgeId) {
  115 + reindexKnowledgeSegmentByKnowledgeId(knowledgeId);
  116 + }
  117 +
  118 + /**
  119 + * 根据文档编号删除段落
  120 + *
  121 + * @param documentId 文档编号
  122 + */
  123 + void deleteKnowledgeSegmentByDocumentId(Long documentId);
  124 +
  125 + /**
  126 + * 搜索知识库段落,并返回结果
  127 + *
  128 + * @param reqBO 搜索请求信息
  129 + * @return 搜索结果段落列表
  130 + */
  131 + List<AiKnowledgeSegmentSearchRespBO> searchKnowledgeSegment(AiKnowledgeSegmentSearchReqBO reqBO);
  132 +
  133 + /**
  134 + * 根据 URL 内容,切片创建多个段落
  135 + *
  136 + * @param url URL 地址
  137 + * @param segmentMaxTokens 段落最大 Token 数
  138 + * @return 切片后的段落列表
  139 + */
  140 + List<AiKnowledgeSegmentDO> splitContent(String url, Integer segmentMaxTokens);
  141 +
  142 + /**
  143 + * 获取文档处理进度(多个)
43 144 *
44   - * @param reqVO 召回请求信息
45   - * @return 召回的段落
  145 + * @param documentIds 文档编号列表
  146 + * @return 文档处理列表
46 147 */
47   - List<AiKnowledgeSegmentDO> similaritySearch(AiKnowledgeSegmentSearchReqVO reqVO);
  148 + List<AiKnowledgeSegmentProcessRespVO> getKnowledgeSegmentProcessList(List<Long> documentIds);
48 149  
49 150 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java
... ... @@ -2,31 +2,39 @@ package cn.iocoder.yudao.module.ai.service.knowledge;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.collection.ListUtil;
  5 +import cn.hutool.core.util.ObjUtil;
  6 +import cn.hutool.core.util.StrUtil;
5 7 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
6 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 9 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
8 10 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO;
9   -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentSearchReqVO;
10   -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateReqVO;
  11 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentProcessRespVO;
  12 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentSaveReqVO;
11 13 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO;
12 14 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
  15 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
13 16 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
14   -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
15 17 import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeSegmentMapper;
16   -import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
17   -import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
  18 +import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchReqBO;
  19 +import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchRespBO;
  20 +import cn.iocoder.yudao.module.ai.service.model.AiModelService;
18 21 import jakarta.annotation.Resource;
19 22 import lombok.extern.slf4j.Slf4j;
20 23 import org.springframework.ai.document.Document;
  24 +import org.springframework.ai.tokenizer.TokenCountEstimator;
  25 +import org.springframework.ai.transformer.splitter.TextSplitter;
  26 +import org.springframework.ai.transformer.splitter.TokenTextSplitter;
21 27 import org.springframework.ai.vectorstore.SearchRequest;
22 28 import org.springframework.ai.vectorstore.VectorStore;
23 29 import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder;
  30 +import org.springframework.context.annotation.Lazy;
24 31 import org.springframework.stereotype.Service;
25 32  
26   -import java.util.List;
27   -import java.util.Objects;
  33 +import java.util.*;
28 34  
29 35 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  36 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  37 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_SEGMENT_CONTENT_TOO_LONG;
30 38 import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_SEGMENT_NOT_EXISTS;
31 39  
32 40 /**
... ... @@ -38,15 +46,28 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_SEGM
38 46 @Slf4j
39 47 public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService {
40 48  
  49 + private static final String VECTOR_STORE_METADATA_KNOWLEDGE_ID = "knowledgeId";
  50 + private static final String VECTOR_STORE_METADATA_DOCUMENT_ID = "documentId";
  51 + private static final String VECTOR_STORE_METADATA_SEGMENT_ID = "segmentId";
  52 +
  53 + private static final Map<String, Class<?>> VECTOR_STORE_METADATA_TYPES = Map.of(
  54 + VECTOR_STORE_METADATA_KNOWLEDGE_ID, String.class,
  55 + VECTOR_STORE_METADATA_DOCUMENT_ID, String.class,
  56 + VECTOR_STORE_METADATA_SEGMENT_ID, String.class);
  57 +
41 58 @Resource
42 59 private AiKnowledgeSegmentMapper segmentMapper;
43 60  
44 61 @Resource
45 62 private AiKnowledgeService knowledgeService;
46 63 @Resource
47   - private AiChatModelService chatModelService;
  64 + @Lazy // 延迟加载,避免循环依赖
  65 + private AiKnowledgeDocumentService knowledgeDocumentService;
48 66 @Resource
49   - private AiApiKeyService apiKeyService;
  67 + private AiModelService modelService;
  68 +
  69 + @Resource
  70 + private TokenCountEstimator tokenCountEstimator;
50 71  
51 72 @Override
52 73 public PageResult<AiKnowledgeSegmentDO> getKnowledgeSegmentPage(AiKnowledgeSegmentPageReqVO pageReqVO) {
... ... @@ -54,67 +75,198 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService
54 75 }
55 76  
56 77 @Override
57   - public void updateKnowledgeSegment(AiKnowledgeSegmentUpdateReqVO reqVO) {
  78 + public void createKnowledgeSegmentBySplitContent(Long documentId, String content) {
  79 + // 1. 校验
  80 + AiKnowledgeDocumentDO documentDO = knowledgeDocumentService.validateKnowledgeDocumentExists(documentId);
  81 + AiKnowledgeDO knowledgeDO = knowledgeService.validateKnowledgeExists(documentDO.getKnowledgeId());
  82 + VectorStore vectorStore = getVectorStoreById(knowledgeDO);
  83 +
  84 + // 2. 文档切片
  85 + List<Document> documentSegments = splitContentByToken(content, documentDO.getSegmentMaxTokens());
  86 +
  87 + // 3.1 存储切片
  88 + List<AiKnowledgeSegmentDO> segmentDOs = convertList(documentSegments, segment -> {
  89 + if (StrUtil.isEmpty(segment.getText())) {
  90 + return null;
  91 + }
  92 + return new AiKnowledgeSegmentDO().setKnowledgeId(documentDO.getKnowledgeId()).setDocumentId(documentId)
  93 + .setContent(segment.getText()).setContentLength(segment.getText().length())
  94 + .setVectorId(AiKnowledgeSegmentDO.VECTOR_ID_EMPTY)
  95 + .setTokens(tokenCountEstimator.estimate(segment.getText()))
  96 + .setStatus(CommonStatusEnum.ENABLE.getStatus());
  97 + });
  98 + segmentMapper.insertBatch(segmentDOs);
  99 + // 3.2 切片向量化
  100 + for (int i = 0; i < documentSegments.size(); i++) {
  101 + Document segment = documentSegments.get(i);
  102 + AiKnowledgeSegmentDO segmentDO = segmentDOs.get(i);
  103 + writeVectorStore(vectorStore, segmentDO, segment);
  104 + }
  105 + }
  106 +
  107 + @Override
  108 + public void updateKnowledgeSegment(AiKnowledgeSegmentSaveReqVO reqVO) {
58 109 // 1. 校验
59   - AiKnowledgeSegmentDO oldKnowledgeSegment = validateKnowledgeSegmentExists(reqVO.getId());
  110 + AiKnowledgeSegmentDO oldSegment = validateKnowledgeSegmentExists(reqVO.getId());
  111 +
  112 + // 2. 删除向量
  113 + VectorStore vectorStore = getVectorStoreById(oldSegment.getKnowledgeId());
  114 + deleteVectorStore(vectorStore, oldSegment);
  115 +
  116 + // 3.1 更新切片
  117 + AiKnowledgeSegmentDO newSegment = BeanUtils.toBean(reqVO, AiKnowledgeSegmentDO.class);
  118 + segmentMapper.updateById(newSegment);
  119 + // 3.2 重新向量化,必须开启状态
  120 + if (CommonStatusEnum.isEnable(oldSegment.getStatus())) {
  121 + newSegment.setKnowledgeId(oldSegment.getKnowledgeId()).setDocumentId(oldSegment.getDocumentId());
  122 + writeVectorStore(vectorStore, newSegment, new Document(newSegment.getContent()));
  123 + }
  124 + }
  125 +
  126 + @Override
  127 + public void deleteKnowledgeSegmentByDocumentId(Long documentId) {
  128 + // 1. 查询需要删除的段落
  129 + List<AiKnowledgeSegmentDO> segments = segmentMapper.selectListByDocumentId(documentId);
  130 + if (CollUtil.isEmpty(segments)) {
  131 + return;
  132 + }
60 133  
61   - // 2.1 获取知识库向量实例
62   - VectorStore vectorStore = knowledgeService.getVectorStoreById(oldKnowledgeSegment.getKnowledgeId());
63   - // 2.2 删除原向量
64   - vectorStore.delete(List.of(oldKnowledgeSegment.getVectorId()));
65   - // 2.3 重新向量化
66   - Document document = new Document(reqVO.getContent());
67   - document.getMetadata().put(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, oldKnowledgeSegment.getKnowledgeId());
68   - vectorStore.add(List.of(document));
  134 + // 2. 批量删除段落记录
  135 + segmentMapper.deleteByIds(convertList(segments, AiKnowledgeSegmentDO::getId));
69 136  
70   - // 3. 更新段落内容
71   - AiKnowledgeSegmentDO knowledgeSegment = BeanUtils.toBean(reqVO, AiKnowledgeSegmentDO.class);
72   - knowledgeSegment.setVectorId(document.getId());
73   - segmentMapper.updateById(knowledgeSegment);
  137 + // 3. 删除向量存储中的段落
  138 + VectorStore vectorStore = getVectorStoreById(segments.get(0).getKnowledgeId());
  139 + vectorStore.delete(convertList(segments, AiKnowledgeSegmentDO::getVectorId));
74 140 }
75 141  
76 142 @Override
77 143 public void updateKnowledgeSegmentStatus(AiKnowledgeSegmentUpdateStatusReqVO reqVO) {
78   - // 0 校验
79   - AiKnowledgeSegmentDO oldKnowledgeSegment = validateKnowledgeSegmentExists(reqVO.getId());
80   - // 1 获取知识库向量实例
81   - VectorStore vectorStore = knowledgeService.getVectorStoreById(oldKnowledgeSegment.getKnowledgeId());
82   - AiKnowledgeSegmentDO knowledgeSegment = BeanUtils.toBean(reqVO, AiKnowledgeSegmentDO.class);
83   -
84   - if (Objects.equals(reqVO.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
85   - // 2.1 启用重新向量化
86   - Document document = new Document(oldKnowledgeSegment.getContent());
87   - document.getMetadata().put(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, oldKnowledgeSegment.getKnowledgeId());
88   - vectorStore.add(List.of(document));
89   - knowledgeSegment.setVectorId(document.getId());
  144 + // 1. 校验
  145 + AiKnowledgeSegmentDO segment = validateKnowledgeSegmentExists(reqVO.getId());
  146 +
  147 + // 2. 获取知识库向量实例
  148 + VectorStore vectorStore = getVectorStoreById(segment.getKnowledgeId());
  149 +
  150 + // 3. 更新状态
  151 + segmentMapper.updateById(new AiKnowledgeSegmentDO().setId(reqVO.getId()).setStatus(reqVO.getStatus()));
  152 +
  153 + // 4. 更新向量
  154 + if (CommonStatusEnum.isEnable(reqVO.getStatus())) {
  155 + writeVectorStore(vectorStore, segment, new Document(segment.getContent()));
90 156 } else {
91   - // 2.2 禁用删除向量
92   - vectorStore.delete(List.of(oldKnowledgeSegment.getVectorId()));
93   - knowledgeSegment.setVectorId("");
  157 + deleteVectorStore(vectorStore, segment);
  158 + }
  159 + }
  160 +
  161 + @Override
  162 + public void reindexKnowledgeSegmentByKnowledgeId(Long knowledgeId) {
  163 + // 1.1 校验知识库存在
  164 + AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(knowledgeId);
  165 + // 1.2 获取知识库向量实例
  166 + VectorStore vectorStore = getVectorStoreById(knowledge);
  167 +
  168 + // 2.1 查询知识库下的所有启用状态的段落
  169 + List<AiKnowledgeSegmentDO> segments = segmentMapper.selectListByKnowledgeIdAndStatus(
  170 + knowledgeId, CommonStatusEnum.ENABLE.getStatus());
  171 + if (CollUtil.isEmpty(segments)) {
  172 + return;
94 173 }
95   - // 3 更新段落状态
96   - segmentMapper.updateById(knowledgeSegment);
  174 + // 2.2 遍历所有段落,重新索引
  175 + for (AiKnowledgeSegmentDO segment : segments) {
  176 + // 删除旧的向量
  177 + deleteVectorStore(vectorStore, segment);
  178 + // 重新创建向量
  179 + writeVectorStore(vectorStore, segment, new Document(segment.getContent()));
  180 + }
  181 + log.info("[reindexKnowledgeSegmentByKnowledgeId][知识库({}) 重新索引完成,共处理 {} 个段落]",
  182 + knowledgeId, segments.size());
  183 + }
  184 +
  185 + private void writeVectorStore(VectorStore vectorStore, AiKnowledgeSegmentDO segmentDO, Document segment) {
  186 + // 1. 向量存储
  187 + // 为什么要 toString 呢?因为部分 VectorStore 实现,不支持 Long 类型,例如说 QdrantVectorStore
  188 + segment.getMetadata().put(VECTOR_STORE_METADATA_KNOWLEDGE_ID, segmentDO.getKnowledgeId().toString());
  189 + segment.getMetadata().put(VECTOR_STORE_METADATA_DOCUMENT_ID, segmentDO.getDocumentId().toString());
  190 + segment.getMetadata().put(VECTOR_STORE_METADATA_SEGMENT_ID, segmentDO.getId().toString());
  191 + vectorStore.add(List.of(segment));
  192 +
  193 + // 2. 更新向量 ID
  194 + segmentMapper.updateById(new AiKnowledgeSegmentDO().setId(segmentDO.getId()).setVectorId(segment.getId()));
  195 + }
  196 +
  197 + private void deleteVectorStore(VectorStore vectorStore, AiKnowledgeSegmentDO segmentDO) {
  198 + // 1. 更新向量 ID
  199 + if (StrUtil.isEmpty(segmentDO.getVectorId())) {
  200 + return;
  201 + }
  202 + segmentMapper.updateById(new AiKnowledgeSegmentDO().setId(segmentDO.getId())
  203 + .setVectorId(AiKnowledgeSegmentDO.VECTOR_ID_EMPTY));
  204 +
  205 + // 2. 删除向量
  206 + vectorStore.delete(List.of(segmentDO.getVectorId()));
97 207 }
98 208  
99 209 @Override
100   - public List<AiKnowledgeSegmentDO> similaritySearch(AiKnowledgeSegmentSearchReqVO reqVO) {
  210 + public List<AiKnowledgeSegmentSearchRespBO> searchKnowledgeSegment(AiKnowledgeSegmentSearchReqBO reqBO) {
101 211 // 1. 校验
102   - AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(reqVO.getKnowledgeId());
103   - AiChatModelDO model = chatModelService.validateChatModel(knowledge.getModelId());
104   -
105   - // 2. 获取向量存储实例
106   - VectorStore vectorStore = apiKeyService.getOrCreateVectorStore(model.getKeyId());
107   -
108   - // 3.1 向量检索
109   - List<Document> documentList = vectorStore.similaritySearch(SearchRequest.query(reqVO.getContent())
110   - .withTopK(knowledge.getTopK())
111   - .withSimilarityThreshold(knowledge.getSimilarityThreshold())
112   - .withFilterExpression(new FilterExpressionBuilder().eq(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, reqVO.getKnowledgeId()).build()));
113   - if (CollUtil.isEmpty(documentList)) {
  212 + AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(reqBO.getKnowledgeId());
  213 +
  214 + // 2.1 向量检索
  215 + VectorStore vectorStore = getVectorStoreById(knowledge);
  216 + List<Document> documents = vectorStore.similaritySearch(SearchRequest.builder()
  217 + .query(reqBO.getContent())
  218 + .topK(ObjUtil.defaultIfNull(reqBO.getTopK(), knowledge.getTopK()))
  219 + .similarityThreshold(
  220 + ObjUtil.defaultIfNull(reqBO.getSimilarityThreshold(), knowledge.getSimilarityThreshold()))
  221 + .filterExpression(new FilterExpressionBuilder()
  222 + .eq(VECTOR_STORE_METADATA_KNOWLEDGE_ID, reqBO.getKnowledgeId().toString())
  223 + .build())
  224 + .build());
  225 + if (CollUtil.isEmpty(documents)) {
  226 + return ListUtil.empty();
  227 + }
  228 + // 2.2 段落召回
  229 + List<AiKnowledgeSegmentDO> segments = segmentMapper
  230 + .selectListByVectorIds(convertList(documents, Document::getId));
  231 + if (CollUtil.isEmpty(segments)) {
114 232 return ListUtil.empty();
115 233 }
116   - // 3.2 段落召回
117   - return segmentMapper.selectListByVectorIds(CollUtil.getFieldValues(documentList, "id", String.class));
  234 +
  235 + // 3. 增加召回次数
  236 + segmentMapper.updateRetrievalCountIncrByIds(convertList(segments, AiKnowledgeSegmentDO::getId));
  237 +
  238 + // 4. 构建结果
  239 + List<AiKnowledgeSegmentSearchRespBO> result = convertList(segments, segment -> {
  240 + Document document = CollUtil.findOne(documents, // 找到对应的文档
  241 + doc -> Objects.equals(doc.getId(), segment.getVectorId()));
  242 + if (document == null) {
  243 + return null;
  244 + }
  245 + return BeanUtils.toBean(segment, AiKnowledgeSegmentSearchRespBO.class)
  246 + .setScore(document.getScore());
  247 + });
  248 + result.sort((o1, o2) -> Double.compare(o2.getScore(), o1.getScore())); // 按照分数降序排序
  249 + return result;
  250 + }
  251 +
  252 + @Override
  253 + public List<AiKnowledgeSegmentDO> splitContent(String url, Integer segmentMaxTokens) {
  254 + // 1. 读取 URL 内容
  255 + String content = knowledgeDocumentService.readUrl(url);
  256 +
  257 + // 2. 文档切片
  258 + List<Document> documentSegments = splitContentByToken(content, segmentMaxTokens);
  259 +
  260 + // 3. 转换为段落对象
  261 + return convertList(documentSegments, segment -> {
  262 + if (StrUtil.isEmpty(segment.getText())) {
  263 + return null;
  264 + }
  265 + return new AiKnowledgeSegmentDO()
  266 + .setContent(segment.getText())
  267 + .setContentLength(segment.getText().length())
  268 + .setTokens(tokenCountEstimator.estimate(segment.getText()));
  269 + });
118 270 }
119 271  
120 272 /**
... ... @@ -131,4 +283,75 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService
131 283 return knowledgeSegment;
132 284 }
133 285  
  286 + private VectorStore getVectorStoreById(AiKnowledgeDO knowledge) {
  287 + return modelService.getOrCreateVectorStore(knowledge.getEmbeddingModelId(), VECTOR_STORE_METADATA_TYPES);
  288 + }
  289 +
  290 + private VectorStore getVectorStoreById(Long knowledgeId) {
  291 + AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(knowledgeId);
  292 + return getVectorStoreById(knowledge);
  293 + }
  294 +
  295 + private static List<Document> splitContentByToken(String content, Integer segmentMaxTokens) {
  296 + TextSplitter textSplitter = buildTokenTextSplitter(segmentMaxTokens);
  297 + return textSplitter.apply(Collections.singletonList(new Document(content)));
  298 + }
  299 +
  300 + private static TextSplitter buildTokenTextSplitter(Integer segmentMaxTokens) {
  301 + return TokenTextSplitter.builder()
  302 + .withChunkSize(segmentMaxTokens)
  303 + .withMinChunkSizeChars(Integer.MAX_VALUE) // 忽略字符的截断
  304 + .withMinChunkLengthToEmbed(1) // 允许的最小有效分段长度
  305 + .withMaxNumChunks(Integer.MAX_VALUE)
  306 + .withKeepSeparator(true) // 保留分隔符
  307 + .build();
  308 + }
  309 +
  310 + @Override
  311 + public List<AiKnowledgeSegmentProcessRespVO> getKnowledgeSegmentProcessList(List<Long> documentIds) {
  312 + if (CollUtil.isEmpty(documentIds)) {
  313 + return Collections.emptyList();
  314 + }
  315 + return segmentMapper.selectProcessList(documentIds);
  316 + }
  317 +
  318 + @Override
  319 + public Long createKnowledgeSegment(AiKnowledgeSegmentSaveReqVO createReqVO) {
  320 + // 1.1 校验文档是否存在
  321 + AiKnowledgeDocumentDO document = knowledgeDocumentService
  322 + .validateKnowledgeDocumentExists(createReqVO.getDocumentId());
  323 + // 1.2 获取知识库信息
  324 + AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(document.getKnowledgeId());
  325 + // 1.3 校验 token 熟练
  326 + Integer tokens = tokenCountEstimator.estimate(createReqVO.getContent());
  327 + if (tokens > document.getSegmentMaxTokens()) {
  328 + throw exception(KNOWLEDGE_SEGMENT_CONTENT_TOO_LONG, tokens, document.getSegmentMaxTokens());
  329 + }
  330 +
  331 + // 2. 保存段落
  332 + AiKnowledgeSegmentDO segment = BeanUtils.toBean(createReqVO, AiKnowledgeSegmentDO.class)
  333 + .setKnowledgeId(knowledge.getId()).setDocumentId(document.getId())
  334 + .setContentLength(createReqVO.getContent().length()).setTokens(tokens)
  335 + .setVectorId(AiKnowledgeSegmentDO.VECTOR_ID_EMPTY)
  336 + .setRetrievalCount(0).setStatus(CommonStatusEnum.ENABLE.getStatus());
  337 + segmentMapper.insert(segment);
  338 +
  339 + // 3. 向量化
  340 + writeVectorStore(getVectorStoreById(knowledge), segment, new Document(segment.getContent()));
  341 + return segment.getId();
  342 + }
  343 +
  344 + @Override
  345 + public AiKnowledgeSegmentDO getKnowledgeSegment(Long id) {
  346 + return segmentMapper.selectById(id);
  347 + }
  348 +
  349 + @Override
  350 + public List<AiKnowledgeSegmentDO> getKnowledgeSegmentList(Collection<Long> ids) {
  351 + if (CollUtil.isEmpty(ids)) {
  352 + return Collections.emptyList();
  353 + }
  354 + return segmentMapper.selectBatchIds(ids);
  355 + }
  356 +
134 357 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeService.java
1 1 package cn.iocoder.yudao.module.ai.service.knowledge;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeCreateReqVO;
5 4 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO;
6   -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateReqVO;
  5 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeSaveReqVO;
7 6 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
8   -import org.springframework.ai.vectorstore.VectorStore;
  7 +
  8 +import java.util.List;
9 9  
10 10 /**
11 11 * AI 知识库-基础信息 Service 接口
... ... @@ -18,18 +18,24 @@ public interface AiKnowledgeService {
18 18 * 创建知识库
19 19 *
20 20 * @param createReqVO 创建信息
21   - * @param userId 用户编号
22 21 * @return 编号
23 22 */
24   - Long createKnowledge(AiKnowledgeCreateReqVO createReqVO, Long userId);
  23 + Long createKnowledge(AiKnowledgeSaveReqVO createReqVO);
25 24  
26 25 /**
27 26 * 更新知识库
28 27 *
29 28 * @param updateReqVO 更新信息
30   - * @param userId 用户编号
31 29 */
32   - void updateKnowledge(AiKnowledgeUpdateReqVO updateReqVO, Long userId);
  30 + void updateKnowledge(AiKnowledgeSaveReqVO updateReqVO);
  31 +
  32 + /**
  33 + * 获得知识库
  34 + *
  35 + * @param id 编号
  36 + * @return 知识库
  37 + */
  38 + AiKnowledgeDO getKnowledge(Long id);
33 39  
34 40 /**
35 41 * 校验知识库是否存在
... ... @@ -41,18 +47,17 @@ public interface AiKnowledgeService {
41 47 /**
42 48 * 获得知识库分页
43 49 *
44   - * @param userId 用户编号
45 50 * @param pageReqVO 分页查询
46 51 * @return 知识库分页
47 52 */
48   - PageResult<AiKnowledgeDO> getKnowledgePage(Long userId, AiKnowledgePageReqVO pageReqVO);
  53 + PageResult<AiKnowledgeDO> getKnowledgePage(AiKnowledgePageReqVO pageReqVO);
49 54  
50 55 /**
51   - * 根据知识库编号获取向量存储实例
  56 + * 获得指定状态的知识库列表
52 57 *
53   - * @param id 知识库编号
54   - * @return 向量存储实例
  58 + * @param status 状态
  59 + * @return 知识库列表
55 60 */
56   - VectorStore getVectorStoreById(Long id);
  61 + List<AiKnowledgeDO> getKnowledgeSimpleListByStatus(Integer status);
57 62  
58 63 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java
... ... @@ -4,19 +4,19 @@ import cn.hutool.core.util.ObjUtil;
4 4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7   -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeCreateReqVO;
8 7 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO;
9   -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateReqVO;
  8 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeSaveReqVO;
10 9 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
11   -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
  10 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
  11 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
12 12 import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeMapper;
13   -import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
14   -import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
  13 +import cn.iocoder.yudao.module.ai.service.model.AiModelService;
15 14 import jakarta.annotation.Resource;
16 15 import lombok.extern.slf4j.Slf4j;
17   -import org.springframework.ai.vectorstore.VectorStore;
18 16 import org.springframework.stereotype.Service;
19 17  
  18 +import java.util.List;
  19 +
20 20 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
21 21 import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_NOT_EXISTS;
22 22  
... ... @@ -33,36 +33,43 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService {
33 33 private AiKnowledgeMapper knowledgeMapper;
34 34  
35 35 @Resource
36   - private AiChatModelService chatModelService;
  36 + private AiModelService modelService;
37 37 @Resource
38   - private AiApiKeyService apiKeyService;
  38 + private AiKnowledgeSegmentService knowledgeSegmentService;
39 39  
40 40 @Override
41   - public Long createKnowledge(AiKnowledgeCreateReqVO createReqVO, Long userId) {
  41 + public Long createKnowledge(AiKnowledgeSaveReqVO createReqVO) {
42 42 // 1. 校验模型配置
43   - AiChatModelDO model = chatModelService.validateChatModel(createReqVO.getModelId());
  43 + AiModelDO model = modelService.validateModel(createReqVO.getEmbeddingModelId());
44 44  
45 45 // 2. 插入知识库
46   - AiKnowledgeDO knowledgeBase = BeanUtils.toBean(createReqVO, AiKnowledgeDO.class)
47   - .setModel(model.getModel()).setUserId(userId).setStatus(CommonStatusEnum.ENABLE.getStatus());
48   - knowledgeMapper.insert(knowledgeBase);
49   - return knowledgeBase.getId();
  46 + AiKnowledgeDO knowledge = BeanUtils.toBean(createReqVO, AiKnowledgeDO.class)
  47 + .setEmbeddingModel(model.getModel());
  48 + knowledgeMapper.insert(knowledge);
  49 + return knowledge.getId();
50 50 }
51 51  
52 52 @Override
53   - public void updateKnowledge(AiKnowledgeUpdateReqVO updateReqVO, Long userId) {
  53 + public void updateKnowledge(AiKnowledgeSaveReqVO updateReqVO) {
54 54 // 1.1 校验知识库存在
55   - AiKnowledgeDO knowledgeBaseDO = validateKnowledgeExists(updateReqVO.getId());
56   - if (ObjUtil.notEqual(knowledgeBaseDO.getUserId(), userId)) {
57   - throw exception(KNOWLEDGE_NOT_EXISTS);
58   - }
  55 + AiKnowledgeDO oldKnowledge = validateKnowledgeExists(updateReqVO.getId());
59 56 // 1.2 校验模型配置
60   - AiChatModelDO model = chatModelService.validateChatModel(updateReqVO.getModelId());
  57 + AiModelDO model = modelService.validateModel(updateReqVO.getEmbeddingModelId());
61 58  
62 59 // 2. 更新知识库
63   - AiKnowledgeDO updateDO = BeanUtils.toBean(updateReqVO, AiKnowledgeDO.class);
64   - updateDO.setModel(model.getModel());
65   - knowledgeMapper.updateById(updateDO);
  60 + AiKnowledgeDO updateObj = BeanUtils.toBean(updateReqVO, AiKnowledgeDO.class)
  61 + .setEmbeddingModel(model.getModel());
  62 + knowledgeMapper.updateById(updateObj);
  63 +
  64 + // 3. 如果模型变化,需要 reindex 所有的文档
  65 + if (ObjUtil.notEqual(oldKnowledge.getEmbeddingModelId(), updateReqVO.getEmbeddingModelId())) {
  66 + knowledgeSegmentService.reindexByKnowledgeIdAsync(updateReqVO.getId());
  67 + }
  68 + }
  69 +
  70 + @Override
  71 + public AiKnowledgeDO getKnowledge(Long id) {
  72 + return knowledgeMapper.selectById(id);
66 73 }
67 74  
68 75 @Override
... ... @@ -75,16 +82,13 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService {
75 82 }
76 83  
77 84 @Override
78   - public PageResult<AiKnowledgeDO> getKnowledgePage(Long userId, AiKnowledgePageReqVO pageReqVO) {
79   - return knowledgeMapper.selectPage(userId, pageReqVO);
  85 + public PageResult<AiKnowledgeDO> getKnowledgePage(AiKnowledgePageReqVO pageReqVO) {
  86 + return knowledgeMapper.selectPage(pageReqVO);
80 87 }
81 88  
82 89 @Override
83   - public VectorStore getVectorStoreById(Long id) {
84   - AiKnowledgeDO knowledge = validateKnowledgeExists(id);
85   - AiChatModelDO model = chatModelService.validateChatModel(knowledge.getModelId());
86   - // 创建或获取 VectorStore 对象
87   - return apiKeyService.getOrCreateVectorStore(model.getKeyId());
  90 + public List<AiKnowledgeDO> getKnowledgeSimpleListByStatus(Integer status) {
  91 + return knowledgeMapper.selectListByStatus(status);
88 92 }
89 93  
90 94 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/bo/AiKnowledgeSegmentSearchReqBO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.service.knowledge.bo;
  2 +
  3 +import lombok.Data;
  4 +
  5 +import javax.validation.constraints.NotNull;
  6 +
  7 +import jakarta.validation.constraints.NotEmpty;
  8 +
  9 +/**
  10 + * AI 知识库段落搜索 Request BO
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +@Data
  15 +public class AiKnowledgeSegmentSearchReqBO {
  16 +
  17 + /**
  18 + * 知识库编号
  19 + */
  20 + @NotNull(message = "知识库编号不能为空")
  21 + private Long knowledgeId;
  22 +
  23 + /**
  24 + * 内容
  25 + */
  26 + @NotEmpty(message = "内容不能为空")
  27 + private String content;
  28 +
  29 + /**
  30 + * 最大返回数量
  31 + */
  32 + private Integer topK;
  33 +
  34 + /**
  35 + * 相似度阈值
  36 + */
  37 + private Double similarityThreshold;
  38 +
  39 +}
0 40 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/bo/AiKnowledgeSegmentSearchRespBO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.service.knowledge.bo;
  2 +
  3 +import lombok.Data;
  4 +
  5 +/**
  6 + * AI 知识库段落搜索 Response BO
  7 + *
  8 + * @author 芋道源码
  9 + */
  10 +@Data
  11 +public class AiKnowledgeSegmentSearchRespBO {
  12 +
  13 + /**
  14 + * 段落编号
  15 + */
  16 + private Long id;
  17 + /**
  18 + * 文档编号
  19 + */
  20 + private Long documentId;
  21 + /**
  22 + * 知识库编号
  23 + */
  24 + private Long knowledgeId;
  25 +
  26 + /**
  27 + * 内容
  28 + */
  29 + private String content;
  30 + /**
  31 + * 内容长度
  32 + */
  33 + private Integer contentLength;
  34 +
  35 + /**
  36 + * Token 数量
  37 + */
  38 + private Integer tokens;
  39 +
  40 + /**
  41 + * 相似度分数
  42 + */
  43 + private Double score;
  44 +
  45 +}
0 46 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/mindmap/AiMindMapServiceImpl.java
1 1 package cn.iocoder.yudao.module.ai.service.mindmap;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4   -import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.core.util.ObjUtil;
5 5 import cn.hutool.core.util.StrUtil;
  6 +import cn.iocoder.yudao.framework.ai.core.enums.AiModelTypeEnum;
6 7 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
7 8 import cn.iocoder.yudao.framework.ai.core.util.AiUtils;
8 9 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
... ... @@ -12,14 +13,13 @@ import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
12 13 import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapGenerateReqVO;
13 14 import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapPageReqVO;
14 15 import cn.iocoder.yudao.module.ai.dal.dataobject.mindmap.AiMindMapDO;
15   -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
16 16 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
  17 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
17 18 import cn.iocoder.yudao.module.ai.dal.mysql.mindmap.AiMindMapMapper;
18 19 import cn.iocoder.yudao.module.ai.enums.AiChatRoleEnum;
19 20 import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants;
20   -import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
21   -import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
22 21 import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
  22 +import cn.iocoder.yudao.module.ai.service.model.AiModelService;
23 23 import jakarta.annotation.Resource;
24 24 import lombok.extern.slf4j.Slf4j;
25 25 import org.springframework.ai.chat.messages.Message;
... ... @@ -38,7 +38,7 @@ import java.util.List;
38 38 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
39 39 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error;
40 40 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
41   -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.MIND_MAP_NOT_EXISTS;
  41 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*;
42 42  
43 43 /**
44 44 * AI 思维导图 Service 实现类
... ... @@ -50,9 +50,7 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.MIND_MAP_NOT_E
50 50 public class AiMindMapServiceImpl implements AiMindMapService {
51 51  
52 52 @Resource
53   - private AiApiKeyService apiKeyService;
54   - @Resource
55   - private AiChatModelService chatModalService;
  53 + private AiModelService modalService;
56 54 @Resource
57 55 private AiChatRoleService chatRoleService;
58 56  
... ... @@ -65,17 +63,17 @@ public class AiMindMapServiceImpl implements AiMindMapService {
65 63 AiChatRoleDO role = CollUtil.getFirst(
66 64 chatRoleService.getChatRoleListByName(AiChatRoleEnum.AI_MIND_MAP_ROLE.getName()));
67 65 // 1.1 获取导图执行模型
68   - AiChatModelDO model = getModel(role);
  66 + AiModelDO model = getModel(role);
69 67 // 1.2 获取角色设定消息
70 68 String systemMessage = role != null && StrUtil.isNotBlank(role.getSystemMessage())
71 69 ? role.getSystemMessage() : AiChatRoleEnum.AI_MIND_MAP_ROLE.getSystemMessage();
72 70 // 1.3 校验平台
73 71 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform());
74   - ChatModel chatModel = apiKeyService.getChatModel(model.getKeyId());
  72 + ChatModel chatModel = modalService.getChatModel(model.getId());
75 73  
76 74 // 2. 插入思维导图信息
77   - AiMindMapDO mindMapDO = BeanUtils.toBean(generateReqVO, AiMindMapDO.class,
78   - mindMap -> mindMap.setUserId(userId).setModel(model.getModel()).setPlatform(platform.getPlatform()));
  75 + AiMindMapDO mindMapDO = BeanUtils.toBean(generateReqVO, AiMindMapDO.class, mindMap -> mindMap.setUserId(userId)
  76 + .setPlatform(platform.getPlatform()).setModelId(model.getId()).setModel(model.getModel()));
79 77 mindMapMapper.insert(mindMapDO);
80 78  
81 79 // 3.1 构建 Prompt,并进行调用
... ... @@ -85,7 +83,7 @@ public class AiMindMapServiceImpl implements AiMindMapService {
85 83 // 3.2 流式返回
86 84 StringBuffer contentBuffer = new StringBuffer();
87 85 return streamResponse.map(chunk -> {
88   - String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getContent() : null;
  86 + String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getText() : null;
89 87 newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况
90 88 contentBuffer.append(newContent);
91 89 // 响应结果
... ... @@ -103,7 +101,7 @@ public class AiMindMapServiceImpl implements AiMindMapService {
103 101  
104 102 }
105 103  
106   - private Prompt buildPrompt(AiMindMapGenerateReqVO generateReqVO, AiChatModelDO model, String systemMessage) {
  104 + private Prompt buildPrompt(AiMindMapGenerateReqVO generateReqVO, AiModelDO model, String systemMessage) {
107 105 // 1. 构建 message 列表
108 106 List<Message> chatMessages = buildMessages(generateReqVO, systemMessage);
109 107 // 2. 构建 options 对象
... ... @@ -123,15 +121,21 @@ public class AiMindMapServiceImpl implements AiMindMapService {
123 121 return chatMessages;
124 122 }
125 123  
126   - private AiChatModelDO getModel(AiChatRoleDO role) {
127   - AiChatModelDO model = null;
  124 + private AiModelDO getModel(AiChatRoleDO role) {
  125 + AiModelDO model = null;
128 126 if (role != null && role.getModelId() != null) {
129   - model = chatModalService.getChatModel(role.getModelId());
  127 + model = modalService.getModel(role.getModelId());
  128 + }
  129 + if (model == null) {
  130 + model = modalService.getRequiredDefaultModel(AiModelTypeEnum.CHAT.getType());
130 131 }
  132 + // 校验模型存在、且合法
131 133 if (model == null) {
132   - model = chatModalService.getRequiredDefaultChatModel();
  134 + throw exception(MODEL_NOT_EXISTS);
  135 + }
  136 + if (ObjUtil.notEqual(model.getType(), AiModelTypeEnum.CHAT.getType())) {
  137 + throw exception(MODEL_USE_TYPE_ERROR);
133 138 }
134   - Assert.notNull(model, "[AI] 获取不到模型");
135 139 return model;
136 140 }
137 141  
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyService.java
1 1 package cn.iocoder.yudao.module.ai.service.model;
2 2  
3   -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
4   -import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
5   -import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
6 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 4 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyPageReqVO;
8 5 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveReqVO;
9 6 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
10 7 import jakarta.validation.Valid;
11   -import org.springframework.ai.chat.model.ChatModel;
12   -import org.springframework.ai.embedding.EmbeddingModel;
13   -import org.springframework.ai.image.ImageModel;
14   -import org.springframework.ai.vectorstore.VectorStore;
15 8  
16 9 import java.util.List;
17 10  
... ... @@ -75,58 +68,13 @@ public interface AiApiKeyService {
75 68 */
76 69 List<AiApiKeyDO> getApiKeyList();
77 70  
78   - // ========== 与 spring-ai 集成 ==========
79   -
80 71 /**
81   - * 获得 ChatModel 对象
82   - *
83   - * @param id 编号
84   - * @return ChatModel 对象
85   - */
86   - ChatModel getChatModel(Long id);
87   -
88   - /**
89   - * 获得 ImageModel 对象
90   - *
91   - * TODO 可优化点:目前默认获取 platform 对应的第一个开启的配置用于绘画;后续可以支持配置选择
  72 + * 获得默认的 API 密钥
92 73 *
93 74 * @param platform 平台
94   - * @return ImageModel 对象
95   - */
96   - ImageModel getImageModel(AiPlatformEnum platform);
97   -
98   - /**
99   - * 获得 MidjourneyApi 对象
100   - *
101   - * TODO 可优化点:目前默认获取 Midjourney 对应的第一个开启的配置用于绘画;后续可以支持配置选择
102   - *
103   - * @return MidjourneyApi 对象
104   - */
105   - MidjourneyApi getMidjourneyApi();
106   -
107   - /**
108   - * 获得 SunoApi 对象
109   - *
110   - * TODO 可优化点:目前默认获取 Suno 对应的第一个开启的配置用于音乐;后续可以支持配置选择
111   - *
112   - * @return SunoApi 对象
113   - */
114   - SunoApi getSunoApi();
115   -
116   - /**
117   - * 获得 EmbeddingModel 对象
118   - *
119   - * @param id 编号
120   - * @return EmbeddingModel 对象
121   - */
122   - EmbeddingModel getEmbeddingModel(Long id);
123   -
124   - /**
125   - * 获得 VectorStore 对象
126   - *
127   - * @param id 编号
128   - * @return VectorStore 对象
  75 + * @param status 状态
  76 + * @return API 密钥
129 77 */
130   - VectorStore getOrCreateVectorStore(Long id);
  78 + AiApiKeyDO getRequiredDefaultApiKey(String platform, Integer status);
131 79  
132 80 }
133 81 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyServiceImpl.java
1 1 package cn.iocoder.yudao.module.ai.service.model;
2 2  
3   -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
4   -import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory;
5   -import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
6   -import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
7 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
8 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
9 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
... ... @@ -12,17 +8,14 @@ import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveR
12 8 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
13 9 import cn.iocoder.yudao.module.ai.dal.mysql.model.AiApiKeyMapper;
14 10 import jakarta.annotation.Resource;
15   -import org.springframework.ai.chat.model.ChatModel;
16   -import org.springframework.ai.embedding.EmbeddingModel;
17   -import org.springframework.ai.image.ImageModel;
18   -import org.springframework.ai.vectorstore.VectorStore;
19 11 import org.springframework.stereotype.Service;
20 12 import org.springframework.validation.annotation.Validated;
21 13  
22 14 import java.util.List;
23 15  
24 16 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
25   -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*;
  17 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.API_KEY_DISABLE;
  18 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.API_KEY_NOT_EXISTS;
26 19  
27 20 /**
28 21 * AI API 密钥 Service 实现类
... ... @@ -36,9 +29,6 @@ public class AiApiKeyServiceImpl implements AiApiKeyService {
36 29 @Resource
37 30 private AiApiKeyMapper apiKeyMapper;
38 31  
39   - @Resource
40   - private AiModelFactory modelFactory;
41   -
42 32 @Override
43 33 public Long createApiKey(AiApiKeySaveReqVO createReqVO) {
44 34 // 插入
... ... @@ -97,57 +87,13 @@ public class AiApiKeyServiceImpl implements AiApiKeyService {
97 87 return apiKeyMapper.selectList();
98 88 }
99 89  
100   - // ========== 与 spring-ai 集成 ==========
101   -
102   - @Override
103   - public ChatModel getChatModel(Long id) {
104   - AiApiKeyDO apiKey = validateApiKey(id);
105   - AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform());
106   - return modelFactory.getOrCreateChatModel(platform, apiKey.getApiKey(), apiKey.getUrl());
107   - }
108   -
109   - @Override
110   - public ImageModel getImageModel(AiPlatformEnum platform) {
111   - AiApiKeyDO apiKey = apiKeyMapper.selectFirstByPlatformAndStatus(platform.getPlatform(), CommonStatusEnum.ENABLE.getStatus());
112   - if (apiKey == null) {
113   - throw exception(API_KEY_IMAGE_NODE_FOUND, platform.getName());
114   - }
115   - return modelFactory.getOrCreateImageModel(platform, apiKey.getApiKey(), apiKey.getUrl());
116   - }
117   -
118   - @Override
119   - public MidjourneyApi getMidjourneyApi() {
120   - AiApiKeyDO apiKey = apiKeyMapper.selectFirstByPlatformAndStatus(
121   - AiPlatformEnum.MIDJOURNEY.getPlatform(), CommonStatusEnum.ENABLE.getStatus());
122   - if (apiKey == null) {
123   - throw exception(API_KEY_MIDJOURNEY_NOT_FOUND);
124   - }
125   - return modelFactory.getOrCreateMidjourneyApi(apiKey.getApiKey(), apiKey.getUrl());
126   - }
127   -
128 90 @Override
129   - public SunoApi getSunoApi() {
130   - AiApiKeyDO apiKey = apiKeyMapper.selectFirstByPlatformAndStatus(
131   - AiPlatformEnum.SUNO.getPlatform(), CommonStatusEnum.ENABLE.getStatus());
  91 + public AiApiKeyDO getRequiredDefaultApiKey(String platform, Integer status) {
  92 + AiApiKeyDO apiKey = apiKeyMapper.selectFirstByPlatformAndStatus(platform, status);
132 93 if (apiKey == null) {
133   - throw exception(API_KEY_SUNO_NOT_FOUND);
  94 + throw exception(API_KEY_NOT_EXISTS);
134 95 }
135   - return modelFactory.getOrCreateSunoApi(apiKey.getApiKey(), apiKey.getUrl());
136   - }
137   -
138   - @Override
139   - public EmbeddingModel getEmbeddingModel(Long id) {
140   - AiApiKeyDO apiKey = validateApiKey(id);
141   - AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform());
142   - return modelFactory.getOrCreateEmbeddingModel(platform, apiKey.getApiKey(), apiKey.getUrl());
143   - }
144   -
145   - @Override
146   - public VectorStore getOrCreateVectorStore(Long id) {
147   - AiApiKeyDO apiKey = validateApiKey(id);
148   - AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform());
149   - // 创建或获取 VectorStore 对象
150   - return modelFactory.getOrCreateVectorStore(getEmbeddingModel(id), platform, apiKey.getApiKey(), apiKey.getUrl());
  96 + return apiKey;
151 97 }
152 98  
153 99 }
154 100 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatModelServiceImpl.java deleted
1   -package cn.iocoder.yudao.module.ai.service.model;
2   -
3   -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
4   -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
6   -import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7   -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelPageReqVO;
8   -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelSaveReqVO;
9   -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
10   -import cn.iocoder.yudao.module.ai.dal.mysql.model.AiChatModelMapper;
11   -import jakarta.annotation.Resource;
12   -import org.springframework.stereotype.Service;
13   -import org.springframework.validation.annotation.Validated;
14   -
15   -import java.util.Collection;
16   -import java.util.List;
17   -
18   -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
19   -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*;
20   -
21   -/**
22   - * AI 聊天模型 Service 实现类
23   - *
24   - * @author fansili
25   - */
26   -@Service
27   -@Validated
28   -public class AiChatModelServiceImpl implements AiChatModelService {
29   -
30   - @Resource
31   - private AiApiKeyService apiKeyService;
32   -
33   - @Resource
34   - private AiChatModelMapper chatModelMapper;
35   -
36   - @Override
37   - public Long createChatModel(AiChatModelSaveReqVO createReqVO) {
38   - // 1. 校验
39   - AiPlatformEnum.validatePlatform(createReqVO.getPlatform());
40   - apiKeyService.validateApiKey(createReqVO.getKeyId());
41   -
42   - // 2. 插入
43   - AiChatModelDO chatModel = BeanUtils.toBean(createReqVO, AiChatModelDO.class);
44   - chatModelMapper.insert(chatModel);
45   - return chatModel.getId();
46   - }
47   -
48   - @Override
49   - public void updateChatModel(AiChatModelSaveReqVO updateReqVO) {
50   - // 1. 校验
51   - validateChatModelExists(updateReqVO.getId());
52   - AiPlatformEnum.validatePlatform(updateReqVO.getPlatform());
53   - apiKeyService.validateApiKey(updateReqVO.getKeyId());
54   -
55   - // 2. 更新
56   - AiChatModelDO updateObj = BeanUtils.toBean(updateReqVO, AiChatModelDO.class);
57   - chatModelMapper.updateById(updateObj);
58   - }
59   -
60   - @Override
61   - public void deleteChatModel(Long id) {
62   - // 校验存在
63   - validateChatModelExists(id);
64   - // 删除
65   - chatModelMapper.deleteById(id);
66   - }
67   -
68   - private AiChatModelDO validateChatModelExists(Long id) {
69   - AiChatModelDO model = chatModelMapper.selectById(id);
70   - if (chatModelMapper.selectById(id) == null) {
71   - throw exception(CHAT_MODEL_NOT_EXISTS);
72   - }
73   - return model;
74   - }
75   -
76   - @Override
77   - public AiChatModelDO getChatModel(Long id) {
78   - return chatModelMapper.selectById(id);
79   - }
80   -
81   - @Override
82   - public AiChatModelDO getRequiredDefaultChatModel() {
83   - AiChatModelDO model = chatModelMapper.selectFirstByStatus(CommonStatusEnum.ENABLE.getStatus());
84   - if (model == null) {
85   - throw exception(CHAT_MODEL_DEFAULT_NOT_EXISTS);
86   - }
87   - return model;
88   - }
89   -
90   - @Override
91   - public PageResult<AiChatModelDO> getChatModelPage(AiChatModelPageReqVO pageReqVO) {
92   - return chatModelMapper.selectPage(pageReqVO);
93   - }
94   -
95   - @Override
96   - public AiChatModelDO validateChatModel(Long id) {
97   - AiChatModelDO model = validateChatModelExists(id);
98   - if (CommonStatusEnum.isDisable(model.getStatus())) {
99   - throw exception(CHAT_MODEL_DISABLE);
100   - }
101   - return model;
102   - }
103   -
104   - @Override
105   - public List<AiChatModelDO> getChatModelListByStatus(Integer status) {
106   - return chatModelMapper.selectList(status);
107   - }
108   -
109   - @Override
110   - public List<AiChatModelDO> getChatModelList(Collection<Long> ids) {
111   - return chatModelMapper.selectBatchIds(ids);
112   - }
113   -
114   -}
115 0 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatRoleService.java
... ... @@ -32,7 +32,7 @@ public interface AiChatRoleService {
32 32 * 创建【我的】聊天角色
33 33 *
34 34 * @param createReqVO 创建信息
35   - * @param userId 用户编号
  35 + * @param userId 用户编号
36 36 * @return 编号
37 37 */
38 38 Long createChatRoleMy(AiChatRoleSaveMyReqVO createReqVO, Long userId);
... ... @@ -48,7 +48,7 @@ public interface AiChatRoleService {
48 48 * 创建【我的】聊天角色
49 49 *
50 50 * @param updateReqVO 更新信息
51   - * @param userId 用户编号
  51 + * @param userId 用户编号
52 52 */
53 53 void updateChatRoleMy(AiChatRoleSaveMyReqVO updateReqVO, Long userId);
54 54  
... ... @@ -62,7 +62,7 @@ public interface AiChatRoleService {
62 62 /**
63 63 * 删除【我的】聊天角色
64 64 *
65   - * @param id 编号
  65 + * @param id 编号
66 66 * @param userId 用户编号
67 67 */
68 68 void deleteChatRoleMy(Long id, Long userId);
... ... @@ -106,7 +106,7 @@ public interface AiChatRoleService {
106 106 * 获得【我的】聊天角色分页
107 107 *
108 108 * @param pageReqVO 分页查询
109   - * @param userId 用户编号
  109 + * @param userId 用户编号
110 110 * @return 聊天角色分页
111 111 */
112 112 PageResult<AiChatRoleDO> getChatRoleMyPage(AiChatRolePageReqVO pageReqVO, Long userId);
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatRoleServiceImpl.java
... ... @@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleS
11 11 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleSaveReqVO;
12 12 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
13 13 import cn.iocoder.yudao.module.ai.dal.mysql.model.AiChatRoleMapper;
  14 +import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeService;
14 15 import jakarta.annotation.Resource;
15 16 import lombok.extern.slf4j.Slf4j;
16 17 import org.springframework.stereotype.Service;
... ... @@ -21,7 +22,8 @@ import java.util.List;
21 22  
22 23 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
23 24 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
24   -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*;
  25 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_ROLE_DISABLE;
  26 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_ROLE_NOT_EXISTS;
25 27  
26 28 /**
27 29 * AI 聊天角色 Service 实现类
... ... @@ -35,8 +37,19 @@ public class AiChatRoleServiceImpl implements AiChatRoleService {
35 37 @Resource
36 38 private AiChatRoleMapper chatRoleMapper;
37 39  
  40 + @Resource
  41 + private AiKnowledgeService knowledgeService;
  42 + @Resource
  43 + private AiToolService toolService;
  44 +
38 45 @Override
39 46 public Long createChatRole(AiChatRoleSaveReqVO createReqVO) {
  47 + // 校验文档
  48 + validateDocuments(createReqVO.getKnowledgeIds());
  49 + // 校验工具
  50 + validateTools(createReqVO.getToolIds());
  51 +
  52 + // 保存角色
40 53 AiChatRoleDO chatRole = BeanUtils.toBean(createReqVO, AiChatRoleDO.class);
41 54 chatRoleMapper.insert(chatRole);
42 55 return chatRole.getId();
... ... @@ -44,6 +57,12 @@ public class AiChatRoleServiceImpl implements AiChatRoleService {
44 57  
45 58 @Override
46 59 public Long createChatRoleMy(AiChatRoleSaveMyReqVO createReqVO, Long userId) {
  60 + // 校验文档
  61 + validateDocuments(createReqVO.getKnowledgeIds());
  62 + // 校验工具
  63 + validateTools(createReqVO.getToolIds());
  64 +
  65 + // 保存角色
47 66 AiChatRoleDO chatRole = BeanUtils.toBean(createReqVO, AiChatRoleDO.class).setUserId(userId)
48 67 .setStatus(CommonStatusEnum.ENABLE.getStatus()).setPublicStatus(false);
49 68 chatRoleMapper.insert(chatRole);
... ... @@ -54,7 +73,12 @@ public class AiChatRoleServiceImpl implements AiChatRoleService {
54 73 public void updateChatRole(AiChatRoleSaveReqVO updateReqVO) {
55 74 // 校验存在
56 75 validateChatRoleExists(updateReqVO.getId());
57   - // 更新
  76 + // 校验文档
  77 + validateDocuments(updateReqVO.getKnowledgeIds());
  78 + // 校验工具
  79 + validateTools(updateReqVO.getToolIds());
  80 +
  81 + // 更新角色
58 82 AiChatRoleDO updateObj = BeanUtils.toBean(updateReqVO, AiChatRoleDO.class);
59 83 chatRoleMapper.updateById(updateObj);
60 84 }
... ... @@ -66,12 +90,42 @@ public class AiChatRoleServiceImpl implements AiChatRoleService {
66 90 if (ObjectUtil.notEqual(chatRole.getUserId(), userId)) {
67 91 throw exception(CHAT_ROLE_NOT_EXISTS);
68 92 }
  93 + // 校验文档
  94 + validateDocuments(updateReqVO.getKnowledgeIds());
  95 + // 校验工具
  96 + validateTools(updateReqVO.getToolIds());
69 97  
70 98 // 更新
71 99 AiChatRoleDO updateObj = BeanUtils.toBean(updateReqVO, AiChatRoleDO.class);
72 100 chatRoleMapper.updateById(updateObj);
73 101 }
74 102  
  103 + /**
  104 + * 校验知识库是否存在
  105 + *
  106 + * @param knowledgeIds 知识库编号列表
  107 + */
  108 + private void validateDocuments(List<Long> knowledgeIds) {
  109 + if (CollUtil.isEmpty(knowledgeIds)) {
  110 + return;
  111 + }
  112 + // 校验文档是否存在
  113 + knowledgeIds.forEach(knowledgeService::validateKnowledgeExists);
  114 + }
  115 +
  116 + /**
  117 + * 校验工具是否存在
  118 + *
  119 + * @param toolIds 工具编号列表
  120 + */
  121 + private void validateTools(List<Long> toolIds) {
  122 + if (CollUtil.isEmpty(toolIds)) {
  123 + return;
  124 + }
  125 + // 遍历校验每个工具是否存在
  126 + toolIds.forEach(toolService::validateToolExists);
  127 + }
  128 +
75 129 @Override
76 130 public void deleteChatRole(Long id) {
77 131 // 校验存在
... ... @@ -134,7 +188,8 @@ public class AiChatRoleServiceImpl implements AiChatRoleService {
134 188 @Override
135 189 public List<String> getChatRoleCategoryList() {
136 190 List<AiChatRoleDO> list = chatRoleMapper.selectListGroupByCategory(CommonStatusEnum.ENABLE.getStatus());
137   - return convertList(list, AiChatRoleDO::getCategory, role -> role != null && StrUtil.isNotBlank(role.getCategory()));
  191 + return convertList(list, AiChatRoleDO::getCategory,
  192 + role -> role != null && StrUtil.isNotBlank(role.getCategory()));
138 193 }
139 194  
140 195 @Override
... ... @@ -143,4 +198,3 @@ public class AiChatRoleServiceImpl implements AiChatRoleService {
143 198 }
144 199  
145 200 }
146   -
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.service.model;
  2 +
  3 +import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
  4 +import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelPageReqVO;
  7 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelSaveReqVO;
  8 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
  9 +import jakarta.validation.Valid;
  10 +import org.springframework.ai.chat.model.ChatModel;
  11 +import org.springframework.ai.image.ImageModel;
  12 +import org.springframework.ai.vectorstore.VectorStore;
  13 +
  14 +import javax.annotation.Nullable;
  15 +import java.util.List;
  16 +import java.util.Map;
  17 +
  18 +/**
  19 + * AI 模型 Service 接口
  20 + *
  21 + * @author fansili
  22 + * @since 2024/4/24 19:42
  23 + */
  24 +public interface AiModelService {
  25 +
  26 + /**
  27 + * 创建模型
  28 + *
  29 + * @param createReqVO 创建信息
  30 + * @return 编号
  31 + */
  32 + Long createModel(@Valid AiModelSaveReqVO createReqVO);
  33 +
  34 + /**
  35 + * 更新模型
  36 + *
  37 + * @param updateReqVO 更新信息
  38 + */
  39 + void updateModel(@Valid AiModelSaveReqVO updateReqVO);
  40 +
  41 + /**
  42 + * 删除模型
  43 + *
  44 + * @param id 编号
  45 + */
  46 + void deleteModel(Long id);
  47 +
  48 + /**
  49 + * 获得模型
  50 + *
  51 + * @param id 编号
  52 + * @return 模型
  53 + */
  54 + AiModelDO getModel(Long id);
  55 +
  56 + /**
  57 + * 获得默认的模型
  58 + *
  59 + * 如果获取不到,则抛出 {@link cn.iocoder.yudao.framework.common.exception.ServiceException} 业务异常
  60 + *
  61 + * @return 模型
  62 + */
  63 + AiModelDO getRequiredDefaultModel(Integer type);
  64 +
  65 + /**
  66 + * 获得模型分页
  67 + *
  68 + * @param pageReqVO 分页查询
  69 + * @return 模型分页
  70 + */
  71 + PageResult<AiModelDO> getModelPage(AiModelPageReqVO pageReqVO);
  72 +
  73 + /**
  74 + * 校验模型是否可使用
  75 + *
  76 + * @param id 编号
  77 + * @return 模型
  78 + */
  79 + AiModelDO validateModel(Long id);
  80 +
  81 + /**
  82 + * 获得模型列表
  83 + *
  84 + * @param status 状态
  85 + * @param type 类型
  86 + * @param platform 平台,允许空
  87 + * @return 模型列表
  88 + */
  89 + List<AiModelDO> getModelListByStatusAndType(Integer status, Integer type,
  90 + @Nullable String platform);
  91 +
  92 + // ========== 与 Spring AI 集成 ==========
  93 +
  94 + /**
  95 + * 获得 ChatModel 对象
  96 + *
  97 + * @param id 编号
  98 + * @return ChatModel 对象
  99 + */
  100 + ChatModel getChatModel(Long id);
  101 +
  102 + /**
  103 + * 获得 ImageModel 对象
  104 + *
  105 + * @param id 编号
  106 + * @return ImageModel 对象
  107 + */
  108 + ImageModel getImageModel(Long id);
  109 +
  110 + /**
  111 + * 获得 MidjourneyApi 对象
  112 + *
  113 + * @param id 编号
  114 + * @return MidjourneyApi 对象
  115 + */
  116 + MidjourneyApi getMidjourneyApi(Long id);
  117 +
  118 + /**
  119 + * 获得 SunoApi 对象
  120 + *
  121 + * @return SunoApi 对象
  122 + */
  123 + SunoApi getSunoApi();
  124 +
  125 + /**
  126 + * 获得 VectorStore 对象
  127 + *
  128 + * @param id 编号
  129 + * @param metadataFields 元数据的定义
  130 + * @return VectorStore 对象
  131 + */
  132 + VectorStore getOrCreateVectorStore(Long id, Map<String, Class<?>> metadataFields);
  133 +
  134 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.service.model;
  2 +
  3 +import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
  4 +import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory;
  5 +import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
  6 +import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
  7 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  8 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  9 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  10 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelPageReqVO;
  11 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelSaveReqVO;
  12 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
  13 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
  14 +import cn.iocoder.yudao.module.ai.dal.mysql.model.AiChatMapper;
  15 +import jakarta.annotation.Resource;
  16 +import org.springframework.ai.chat.model.ChatModel;
  17 +import org.springframework.ai.embedding.EmbeddingModel;
  18 +import org.springframework.ai.image.ImageModel;
  19 +import org.springframework.ai.vectorstore.SimpleVectorStore;
  20 +import org.springframework.ai.vectorstore.VectorStore;
  21 +import org.springframework.stereotype.Service;
  22 +import org.springframework.validation.annotation.Validated;
  23 +
  24 +import java.util.List;
  25 +import java.util.Map;
  26 +
  27 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  28 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*;
  29 +
  30 +/**
  31 + * AI 模型 Service 实现类
  32 + *
  33 + * @author fansili
  34 + */
  35 +@Service
  36 +@Validated
  37 +public class AiModelServiceImpl implements AiModelService {
  38 +
  39 + @Resource
  40 + private AiApiKeyService apiKeyService;
  41 +
  42 + @Resource
  43 + private AiChatMapper modelMapper;
  44 +
  45 + @Resource
  46 + private AiModelFactory modelFactory;
  47 +
  48 + @Override
  49 + public Long createModel(AiModelSaveReqVO createReqVO) {
  50 + // 1. 校验
  51 + AiPlatformEnum.validatePlatform(createReqVO.getPlatform());
  52 + apiKeyService.validateApiKey(createReqVO.getKeyId());
  53 +
  54 + // 2. 插入
  55 + AiModelDO model = BeanUtils.toBean(createReqVO, AiModelDO.class);
  56 + modelMapper.insert(model);
  57 + return model.getId();
  58 + }
  59 +
  60 + @Override
  61 + public void updateModel(AiModelSaveReqVO updateReqVO) {
  62 + // 1. 校验
  63 + validateModelExists(updateReqVO.getId());
  64 + AiPlatformEnum.validatePlatform(updateReqVO.getPlatform());
  65 + apiKeyService.validateApiKey(updateReqVO.getKeyId());
  66 +
  67 + // 2. 更新
  68 + AiModelDO updateObj = BeanUtils.toBean(updateReqVO, AiModelDO.class);
  69 + modelMapper.updateById(updateObj);
  70 + }
  71 +
  72 + @Override
  73 + public void deleteModel(Long id) {
  74 + // 校验存在
  75 + validateModelExists(id);
  76 + // 删除
  77 + modelMapper.deleteById(id);
  78 + }
  79 +
  80 + private AiModelDO validateModelExists(Long id) {
  81 + AiModelDO model = modelMapper.selectById(id);
  82 + if (modelMapper.selectById(id) == null) {
  83 + throw exception(MODEL_NOT_EXISTS);
  84 + }
  85 + return model;
  86 + }
  87 +
  88 + @Override
  89 + public AiModelDO getModel(Long id) {
  90 + return modelMapper.selectById(id);
  91 + }
  92 +
  93 + @Override
  94 + public AiModelDO getRequiredDefaultModel(Integer type) {
  95 + AiModelDO model = modelMapper.selectFirstByStatus(type, CommonStatusEnum.ENABLE.getStatus());
  96 + if (model == null) {
  97 + throw exception(MODEL_DEFAULT_NOT_EXISTS);
  98 + }
  99 + return model;
  100 + }
  101 +
  102 + @Override
  103 + public PageResult<AiModelDO> getModelPage(AiModelPageReqVO pageReqVO) {
  104 + return modelMapper.selectPage(pageReqVO);
  105 + }
  106 +
  107 + @Override
  108 + public AiModelDO validateModel(Long id) {
  109 + AiModelDO model = validateModelExists(id);
  110 + if (CommonStatusEnum.isDisable(model.getStatus())) {
  111 + throw exception(MODEL_DISABLE);
  112 + }
  113 + return model;
  114 + }
  115 +
  116 + @Override
  117 + public List<AiModelDO> getModelListByStatusAndType(Integer status, Integer type, String platform) {
  118 + return modelMapper.selectListByStatusAndType(status, type, platform);
  119 + }
  120 +
  121 + // ========== 与 Spring AI 集成 ==========
  122 +
  123 + @Override
  124 + public ChatModel getChatModel(Long id) {
  125 + AiModelDO model = validateModel(id);
  126 + AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId());
  127 + AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform());
  128 + return modelFactory.getOrCreateChatModel(platform, apiKey.getApiKey(), apiKey.getUrl());
  129 + }
  130 +
  131 + @Override
  132 + public ImageModel getImageModel(Long id) {
  133 + AiModelDO model = validateModel(id);
  134 + AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId());
  135 + AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform());
  136 + return modelFactory.getOrCreateImageModel(platform, apiKey.getApiKey(), apiKey.getUrl());
  137 + }
  138 +
  139 + @Override
  140 + public MidjourneyApi getMidjourneyApi(Long id) {
  141 + AiModelDO model = validateModel(id);
  142 + AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId());
  143 + return modelFactory.getOrCreateMidjourneyApi(apiKey.getApiKey(), apiKey.getUrl());
  144 + }
  145 +
  146 + @Override
  147 + public SunoApi getSunoApi() {
  148 + AiApiKeyDO apiKey = apiKeyService.getRequiredDefaultApiKey(
  149 + AiPlatformEnum.SUNO.getPlatform(), CommonStatusEnum.ENABLE.getStatus());
  150 + return modelFactory.getOrCreateSunoApi(apiKey.getApiKey(), apiKey.getUrl());
  151 + }
  152 +
  153 + @Override
  154 + public VectorStore getOrCreateVectorStore(Long id, Map<String, Class<?>> metadataFields) {
  155 + // 获取模型 + 密钥
  156 + AiModelDO model = validateModel(id);
  157 + AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId());
  158 + AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform());
  159 +
  160 + // 创建或获取 EmbeddingModel 对象
  161 + EmbeddingModel embeddingModel = modelFactory.getOrCreateEmbeddingModel(
  162 + platform, apiKey.getApiKey(), apiKey.getUrl(), model.getModel());
  163 +
  164 + // 创建或获取 VectorStore 对象
  165 + return modelFactory.getOrCreateVectorStore(SimpleVectorStore.class, embeddingModel, metadataFields);
  166 +// return modelFactory.getOrCreateVectorStore(QdrantVectorStore.class, embeddingModel, metadataFields);
  167 +// return modelFactory.getOrCreateVectorStore(RedisVectorStore.class, embeddingModel, metadataFields);
  168 +// return modelFactory.getOrCreateVectorStore(MilvusVectorStore.class, embeddingModel, metadataFields);
  169 + }
  170 +
  171 +}
0 172 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatModelService.java renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiToolService.java
1 1 package cn.iocoder.yudao.module.ai.service.model;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelPageReqVO;
5   -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelSaveReqVO;
6   -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
  4 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool.AiToolPageReqVO;
  5 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool.AiToolSaveReqVO;
  6 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiToolDO;
7 7 import jakarta.validation.Valid;
8 8  
9 9 import java.util.Collection;
10 10 import java.util.List;
11 11  
12   -import java.util.Set;
13   -
14 12 /**
15   - * AI 聊天模型 Service 接口
  13 + * AI 工具 Service 接口
16 14 *
17   - * @author fansili
18   - * @since 2024/4/24 19:42
  15 + * @author 芋道源码
19 16 */
20   -public interface AiChatModelService {
  17 +public interface AiToolService {
21 18  
22 19 /**
23   - * 创建聊天模型
  20 + * 创建工具
24 21 *
25 22 * @param createReqVO 创建信息
26 23 * @return 编号
27 24 */
28   - Long createChatModel(@Valid AiChatModelSaveReqVO createReqVO);
  25 + Long createTool(@Valid AiToolSaveReqVO createReqVO);
29 26  
30 27 /**
31   - * 更新聊天模型
  28 + * 更新工具
32 29 *
33 30 * @param updateReqVO 更新信息
34 31 */
35   - void updateChatModel(@Valid AiChatModelSaveReqVO updateReqVO);
  32 + void updateTool(@Valid AiToolSaveReqVO updateReqVO);
36 33  
37 34 /**
38   - * 删除聊天模型
  35 + * 删除工具
39 36 *
40 37 * @param id 编号
41 38 */
42   - void deleteChatModel(Long id);
  39 + void deleteTool(Long id);
43 40  
44 41 /**
45   - * 获得聊天模型
  42 + * 校验工具是否存在
46 43 *
47 44 * @param id 编号
48   - * @return 聊天模型
49 45 */
50   - AiChatModelDO getChatModel(Long id);
  46 + void validateToolExists(Long id);
51 47  
52 48 /**
53   - * 获得默认的聊天模型
54   - *
55   - * 如果获取不到,则抛出 {@link cn.iocoder.yudao.framework.common.exception.ServiceException} 业务异常
  49 + * 获得工具
56 50 *
57   - * @return 聊天模型
  51 + * @param id 编号
  52 + * @return 工具
58 53 */
59   - AiChatModelDO getRequiredDefaultChatModel();
  54 + AiToolDO getTool(Long id);
60 55  
61 56 /**
62   - * 获得聊天模型分页
  57 + * 获得工具列表
63 58 *
64   - * @param pageReqVO 分页查询
65   - * @return 聊天模型分页
  59 + * @param ids 编号列表
  60 + * @return 工具列表
66 61 */
67   - PageResult<AiChatModelDO> getChatModelPage(AiChatModelPageReqVO pageReqVO);
  62 + List<AiToolDO> getToolList(Collection<Long> ids);
68 63  
69 64 /**
70   - * 校验聊天模型
  65 + * 获得工具分页
71 66 *
72   - * @param id 编号
73   - * @return 聊天模型
  67 + * @param pageReqVO 分页查询
  68 + * @return 工具分页
74 69 */
75   - AiChatModelDO validateChatModel(Long id);
  70 + PageResult<AiToolDO> getToolPage(AiToolPageReqVO pageReqVO);
76 71  
77 72 /**
78   - * 获得聊天模型列表
  73 + * 获得工具列表
79 74 *
80 75 * @param status 状态
81   - * @return 聊天模型列表
  76 + * @return 工具列表
82 77 */
83   - List<AiChatModelDO> getChatModelListByStatus(Integer status);
  78 + List<AiToolDO> getToolListByStatus(Integer status);
84 79  
85   - /**
86   - * 获得聊天模型列表
87   - *
88   - * @param ids 编号数组
89   - * @return 模型列表
90   - */
91   - List<AiChatModelDO> getChatModelList(Collection<Long> ids);
92   -}
  80 +}
93 81 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiToolServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.service.model;
  2 +
  3 +import cn.hutool.extra.spring.SpringUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  6 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool.AiToolPageReqVO;
  7 +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool.AiToolSaveReqVO;
  8 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiToolDO;
  9 +import cn.iocoder.yudao.module.ai.dal.mysql.model.AiToolMapper;
  10 +import jakarta.annotation.Resource;
  11 +import org.springframework.beans.factory.NoSuchBeanDefinitionException;
  12 +import org.springframework.stereotype.Service;
  13 +import org.springframework.validation.annotation.Validated;
  14 +
  15 +import java.util.Collection;
  16 +import java.util.List;
  17 +
  18 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  19 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.TOOL_NAME_NOT_EXISTS;
  20 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.TOOL_NOT_EXISTS;
  21 +
  22 +/**
  23 + * AI 工具 Service 实现类
  24 + *
  25 + * @author 芋道源码
  26 + */
  27 +@Service
  28 +@Validated
  29 +public class AiToolServiceImpl implements AiToolService {
  30 +
  31 + @Resource
  32 + private AiToolMapper toolMapper;
  33 +
  34 + @Override
  35 + public Long createTool(AiToolSaveReqVO createReqVO) {
  36 + // 校验名称是否存在
  37 + validateToolNameExists(createReqVO.getName());
  38 +
  39 + // 插入
  40 + AiToolDO tool = BeanUtils.toBean(createReqVO, AiToolDO.class);
  41 + toolMapper.insert(tool);
  42 + return tool.getId();
  43 + }
  44 +
  45 + @Override
  46 + public void updateTool(AiToolSaveReqVO updateReqVO) {
  47 + // 1.1 校验存在
  48 + validateToolExists(updateReqVO.getId());
  49 + // 1.2 校验名称是否存在
  50 + validateToolNameExists(updateReqVO.getName());
  51 +
  52 + // 2. 更新
  53 + AiToolDO updateObj = BeanUtils.toBean(updateReqVO, AiToolDO.class);
  54 + toolMapper.updateById(updateObj);
  55 + }
  56 +
  57 + @Override
  58 + public void deleteTool(Long id) {
  59 + // 校验存在
  60 + validateToolExists(id);
  61 + // 删除
  62 + toolMapper.deleteById(id);
  63 + }
  64 +
  65 + @Override
  66 + public void validateToolExists(Long id) {
  67 + if (toolMapper.selectById(id) == null) {
  68 + throw exception(TOOL_NOT_EXISTS);
  69 + }
  70 + }
  71 +
  72 + private void validateToolNameExists(String name) {
  73 + try {
  74 + SpringUtil.getBean(name);
  75 + } catch (NoSuchBeanDefinitionException e) {
  76 + throw exception(TOOL_NAME_NOT_EXISTS, name);
  77 + }
  78 + }
  79 +
  80 + @Override
  81 + public AiToolDO getTool(Long id) {
  82 + return toolMapper.selectById(id);
  83 + }
  84 +
  85 + @Override
  86 + public List<AiToolDO> getToolList(Collection<Long> ids) {
  87 + return toolMapper.selectBatchIds(ids);
  88 + }
  89 +
  90 + @Override
  91 + public PageResult<AiToolDO> getToolPage(AiToolPageReqVO pageReqVO) {
  92 + return toolMapper.selectPage(pageReqVO);
  93 + }
  94 +
  95 + @Override
  96 + public List<AiToolDO> getToolListByStatus(Integer status) {
  97 + return toolMapper.selectListByStatus(status);
  98 + }
  99 +
  100 +}
0 101 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/tool/DirectoryListToolFunction.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.service.model.tool;
  2 +
  3 +import cn.hutool.core.date.LocalDateTimeUtil;
  4 +import cn.hutool.core.io.FileUtil;
  5 +import cn.hutool.core.util.ArrayUtil;
  6 +import cn.hutool.core.util.StrUtil;
  7 +import com.fasterxml.jackson.annotation.JsonClassDescription;
  8 +import com.fasterxml.jackson.annotation.JsonProperty;
  9 +import com.fasterxml.jackson.annotation.JsonPropertyDescription;
  10 +import lombok.AllArgsConstructor;
  11 +import lombok.Data;
  12 +import lombok.NoArgsConstructor;
  13 +import org.springframework.stereotype.Component;
  14 +
  15 +import java.io.File;
  16 +import java.util.Arrays;
  17 +import java.util.Collections;
  18 +import java.util.List;
  19 +import java.util.function.Function;
  20 +
  21 +import static cn.hutool.core.date.DatePattern.NORM_DATETIME_PATTERN;
  22 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  23 +
  24 +/**
  25 + * 工具:列出指定目录的文件列表
  26 + *
  27 + * @author 芋道源码
  28 + */
  29 +@Component("directory_list")
  30 +public class DirectoryListToolFunction implements Function<DirectoryListToolFunction.Request, DirectoryListToolFunction.Response> {
  31 +
  32 + @Data
  33 + @JsonClassDescription("列出指定目录的文件列表")
  34 + public static class Request {
  35 +
  36 + /**
  37 + * 目录路径
  38 + */
  39 + @JsonProperty(required = true, value = "path")
  40 + @JsonPropertyDescription("目录路径,例如说:/Users/yunai")
  41 + private String path;
  42 +
  43 + }
  44 +
  45 + @Data
  46 + @AllArgsConstructor
  47 + @NoArgsConstructor
  48 + public static class Response {
  49 +
  50 + /**
  51 + * 文件列表
  52 + */
  53 + private List<File> files;
  54 +
  55 + @Data
  56 + public static class File {
  57 +
  58 + /**
  59 + * 是否为目录
  60 + */
  61 + private Boolean directory;
  62 +
  63 + /**
  64 + * 名称
  65 + */
  66 + private String name;
  67 +
  68 + /**
  69 + * 大小,仅对文件有效
  70 + */
  71 + private String size;
  72 +
  73 + /**
  74 + * 最后修改时间
  75 + */
  76 + private String lastModified;
  77 +
  78 + }
  79 +
  80 + }
  81 +
  82 + @Override
  83 + public Response apply(Request request) {
  84 + // 校验目录存在
  85 + String path = StrUtil.blankToDefault(request.getPath(), "/");
  86 + if (!FileUtil.exist(path) || !FileUtil.isDirectory(path)) {
  87 + return new Response(Collections.emptyList());
  88 + }
  89 + // 列出目录
  90 + File[] files = FileUtil.ls(path);
  91 + if (ArrayUtil.isEmpty(files)) {
  92 + return new Response(Collections.emptyList());
  93 + }
  94 + return new Response(convertList(Arrays.asList(files), file ->
  95 + new Response.File().setDirectory(file.isDirectory()).setName(file.getName())
  96 + .setLastModified(LocalDateTimeUtil.format(LocalDateTimeUtil.of(file.lastModified()), NORM_DATETIME_PATTERN))));
  97 + }
  98 +
  99 +}
0 100 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/tool/WeatherQueryToolFunction.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.service.model.tool;
  2 +
  3 +import cn.hutool.core.date.LocalDateTimeUtil;
  4 +import cn.hutool.core.util.RandomUtil;
  5 +import cn.hutool.core.util.StrUtil;
  6 +import com.fasterxml.jackson.annotation.JsonClassDescription;
  7 +import com.fasterxml.jackson.annotation.JsonProperty;
  8 +import com.fasterxml.jackson.annotation.JsonPropertyDescription;
  9 +import lombok.AllArgsConstructor;
  10 +import lombok.Data;
  11 +import lombok.NoArgsConstructor;
  12 +import org.springframework.stereotype.Component;
  13 +
  14 +import java.time.LocalDateTime;
  15 +import java.util.function.Function;
  16 +
  17 +import static cn.hutool.core.date.DatePattern.NORM_DATETIME_PATTERN;
  18 +
  19 +/**
  20 + * 工具:查询指定城市的天气信息
  21 + *
  22 + * @author 芋道源码
  23 + */
  24 +@Component("weather_query")
  25 +public class WeatherQueryToolFunction
  26 + implements Function<WeatherQueryToolFunction.Request, WeatherQueryToolFunction.Response> {
  27 +
  28 + private static final String[] WEATHER_CONDITIONS = { "晴朗", "多云", "阴天", "小雨", "大雨", "雷雨", "小雪", "大雪" };
  29 +
  30 + @Data
  31 + @JsonClassDescription("查询指定城市的天气信息")
  32 + public static class Request {
  33 +
  34 + /**
  35 + * 城市名称
  36 + */
  37 + @JsonProperty(required = true, value = "city")
  38 + @JsonPropertyDescription("城市名称,例如:北京、上海、广州")
  39 + private String city;
  40 +
  41 + }
  42 +
  43 + @Data
  44 + @AllArgsConstructor
  45 + @NoArgsConstructor
  46 + public static class Response {
  47 +
  48 + /**
  49 + * 城市名称
  50 + */
  51 + private String city;
  52 +
  53 + /**
  54 + * 天气信息
  55 + */
  56 + private WeatherInfo weatherInfo;
  57 +
  58 + @Data
  59 + @AllArgsConstructor
  60 + @NoArgsConstructor
  61 + public static class WeatherInfo {
  62 +
  63 + /**
  64 + * 温度(摄氏度)
  65 + */
  66 + private Integer temperature;
  67 +
  68 + /**
  69 + * 天气状况
  70 + */
  71 + private String condition;
  72 +
  73 + /**
  74 + * 湿度百分比
  75 + */
  76 + private Integer humidity;
  77 +
  78 + /**
  79 + * 风速(km/h)
  80 + */
  81 + private Integer windSpeed;
  82 +
  83 + /**
  84 + * 查询时间
  85 + */
  86 + private String queryTime;
  87 +
  88 + }
  89 +
  90 + }
  91 +
  92 + @Override
  93 + public Response apply(Request request) {
  94 + // 检查城市名称是否为空
  95 + if (StrUtil.isBlank(request.getCity())) {
  96 + return new Response("未知城市", null);
  97 + }
  98 +
  99 + // 获取天气数据
  100 + String city = request.getCity();
  101 + Response.WeatherInfo weatherInfo = generateMockWeatherInfo();
  102 + return new Response(city, weatherInfo);
  103 + }
  104 +
  105 + /**
  106 + * 生成模拟的天气数据
  107 + * 在实际应用中,应替换为真实 API 调用
  108 + */
  109 + private Response.WeatherInfo generateMockWeatherInfo() {
  110 + int temperature = RandomUtil.randomInt(-5, 30);
  111 + int humidity = RandomUtil.randomInt(1, 100);
  112 + int windSpeed = RandomUtil.randomInt(1, 30);
  113 + String condition = RandomUtil.randomEle(WEATHER_CONDITIONS);
  114 + return new Response.WeatherInfo(temperature, condition, humidity, windSpeed,
  115 + LocalDateTimeUtil.format(LocalDateTime.now(), NORM_DATETIME_PATTERN));
  116 + }
  117 +
  118 +}
0 119 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicServiceImpl.java
... ... @@ -16,7 +16,7 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO;
16 16 import cn.iocoder.yudao.module.ai.dal.mysql.music.AiMusicMapper;
17 17 import cn.iocoder.yudao.module.ai.enums.music.AiMusicGenerateModeEnum;
18 18 import cn.iocoder.yudao.module.ai.enums.music.AiMusicStatusEnum;
19   -import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
  19 +import cn.iocoder.yudao.module.ai.service.model.AiModelService;
20 20 import cn.iocoder.yudao.module.infra.api.file.FileApi;
21 21 import jakarta.annotation.Resource;
22 22 import lombok.extern.slf4j.Slf4j;
... ... @@ -41,7 +41,7 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.MUSIC_NOT_EXIS
41 41 public class AiMusicServiceImpl implements AiMusicService {
42 42  
43 43 @Resource
44   - private AiApiKeyService apiKeyService;
  44 + private AiModelService modelService;
45 45  
46 46 @Resource
47 47 private AiMusicMapper musicMapper;
... ... @@ -53,7 +53,7 @@ public class AiMusicServiceImpl implements AiMusicService {
53 53 @Transactional(rollbackFor = Exception.class)
54 54 public List<Long> generateMusic(Long userId, AiSunoGenerateReqVO reqVO) {
55 55 // 1. 调用 Suno 生成音乐
56   - SunoApi sunoApi = apiKeyService.getSunoApi();
  56 + SunoApi sunoApi = modelService.getSunoApi();
57 57 List<SunoApi.MusicData> musicDataList;
58 58 if (Objects.equals(AiMusicGenerateModeEnum.DESCRIPTION.getMode(), reqVO.getGenerateMode())) {
59 59 // 1.1 描述模式
... ... @@ -88,7 +88,7 @@ public class AiMusicServiceImpl implements AiMusicService {
88 88 log.info("[syncMusic][Suno 开始同步, 共 ({}) 个任务]", streamingTask.size());
89 89  
90 90 // GET 请求,为避免参数过长,分批次处理
91   - SunoApi sunoApi = apiKeyService.getSunoApi();
  91 + SunoApi sunoApi = modelService.getSunoApi();
92 92 CollUtil.split(streamingTask, 36).forEach(chunkList -> {
93 93 Map<String, Long> taskIdMap = convertMap(chunkList, AiMusicDO::getTaskId, AiMusicDO::getId);
94 94 List<SunoApi.MusicData> musicTaskList = sunoApi.getMusicList(new ArrayList<>(taskIdMap.keySet()));
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteServiceImpl.java
1 1 package cn.iocoder.yudao.module.ai.service.write;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4   -import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.core.util.ObjUtil;
5 5 import cn.hutool.core.util.StrUtil;
  6 +import cn.iocoder.yudao.framework.ai.core.enums.AiModelTypeEnum;
6 7 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
7 8 import cn.iocoder.yudao.framework.ai.core.util.AiUtils;
8 9 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
... ... @@ -11,17 +12,16 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
11 12 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
12 13 import cn.iocoder.yudao.module.ai.controller.admin.write.vo.AiWriteGenerateReqVO;
13 14 import cn.iocoder.yudao.module.ai.controller.admin.write.vo.AiWritePageReqVO;
14   -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
15 15 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
  16 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
16 17 import cn.iocoder.yudao.module.ai.dal.dataobject.write.AiWriteDO;
17 18 import cn.iocoder.yudao.module.ai.dal.mysql.write.AiWriteMapper;
18 19 import cn.iocoder.yudao.module.ai.enums.AiChatRoleEnum;
19 20 import cn.iocoder.yudao.module.ai.enums.DictTypeConstants;
20 21 import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants;
21 22 import cn.iocoder.yudao.module.ai.enums.write.AiWriteTypeEnum;
22   -import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
23   -import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
24 23 import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
  24 +import cn.iocoder.yudao.module.ai.service.model.AiModelService;
25 25 import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
26 26 import jakarta.annotation.Resource;
27 27 import lombok.extern.slf4j.Slf4j;
... ... @@ -42,7 +42,7 @@ import java.util.Objects;
42 42 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
43 43 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error;
44 44 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
45   -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.WRITE_NOT_EXISTS;
  45 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*;
46 46  
47 47 /**
48 48 * AI 写作 Service 实现类
... ... @@ -54,17 +54,15 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.WRITE_NOT_EXIS
54 54 public class AiWriteServiceImpl implements AiWriteService {
55 55  
56 56 @Resource
57   - private AiApiKeyService apiKeyService;
58   - @Resource
59   - private AiChatModelService chatModalService;
  57 + private AiModelService modalService;
60 58 @Resource
61 59 private AiChatRoleService chatRoleService;
62 60  
63 61 @Resource
64   - private DictDataApi dictDataApi;
  62 + private AiWriteMapper writeMapper;
65 63  
66 64 @Resource
67   - private AiWriteMapper writeMapper;
  65 + private DictDataApi dictDataApi;
68 66  
69 67 @Override
70 68 public Flux<CommonResult<String>> generateWriteContent(AiWriteGenerateReqVO generateReqVO, Long userId) {
... ... @@ -72,17 +70,17 @@ public class AiWriteServiceImpl implements AiWriteService {
72 70 AiChatRoleDO writeRole = CollUtil.getFirst(
73 71 chatRoleService.getChatRoleListByName(AiChatRoleEnum.AI_WRITE_ROLE.getName()));
74 72 // 1.1 获取写作执行模型
75   - AiChatModelDO model = getModel(writeRole);
  73 + AiModelDO model = getModel(writeRole);
76 74 // 1.2 获取角色设定消息
77 75 String systemMessage = Objects.nonNull(writeRole) && StrUtil.isNotBlank(writeRole.getSystemMessage())
78 76 ? writeRole.getSystemMessage() : AiChatRoleEnum.AI_WRITE_ROLE.getSystemMessage();
79 77 // 1.3 校验平台
80 78 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform());
81   - StreamingChatModel chatModel = apiKeyService.getChatModel(model.getKeyId());
  79 + StreamingChatModel chatModel = modalService.getChatModel(model.getKeyId());
82 80  
83 81 // 2. 插入写作信息
84   - AiWriteDO writeDO = BeanUtils.toBean(generateReqVO, AiWriteDO.class,
85   - write -> write.setUserId(userId).setPlatform(platform.getPlatform()).setModel(model.getModel()));
  82 + AiWriteDO writeDO = BeanUtils.toBean(generateReqVO, AiWriteDO.class, write -> write.setUserId(userId)
  83 + .setPlatform(platform.getPlatform()).setModelId(model.getId()).setModel(model.getModel()));
86 84 writeMapper.insert(writeDO);
87 85  
88 86 // 3.1 构建 Prompt,并进行调用
... ... @@ -92,7 +90,7 @@ public class AiWriteServiceImpl implements AiWriteService {
92 90 // 3.2 流式返回
93 91 StringBuffer contentBuffer = new StringBuffer();
94 92 return streamResponse.map(chunk -> {
95   - String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getContent() : null;
  93 + String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getText() : null;
96 94 newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况
97 95 contentBuffer.append(newContent);
98 96 // 响应结果
... ... @@ -109,19 +107,25 @@ public class AiWriteServiceImpl implements AiWriteService {
109 107 }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.WRITE_STREAM_ERROR)));
110 108 }
111 109  
112   - private AiChatModelDO getModel(AiChatRoleDO writeRole) {
113   - AiChatModelDO model = null;
  110 + private AiModelDO getModel(AiChatRoleDO writeRole) {
  111 + AiModelDO model = null;
114 112 if (Objects.nonNull(writeRole) && Objects.nonNull(writeRole.getModelId())) {
115   - model = chatModalService.getChatModel(writeRole.getModelId());
  113 + model = modalService.getModel(writeRole.getModelId());
  114 + }
  115 + if (model == null) {
  116 + model = modalService.getRequiredDefaultModel(AiModelTypeEnum.CHAT.getType());
116 117 }
  118 + // 校验模型存在、且合法
117 119 if (model == null) {
118   - model = chatModalService.getRequiredDefaultChatModel();
  120 + throw exception(MODEL_NOT_EXISTS);
  121 + }
  122 + if (ObjUtil.notEqual(model.getType(), AiModelTypeEnum.CHAT.getType())) {
  123 + throw exception(MODEL_USE_TYPE_ERROR);
119 124 }
120   - Assert.notNull(model, "[AI] 获取不到模型");
121 125 return model;
122 126 }
123 127  
124   - private Prompt buildPrompt(AiWriteGenerateReqVO generateReqVO, AiChatModelDO model, String systemMessage) {
  128 + private Prompt buildPrompt(AiWriteGenerateReqVO generateReqVO, AiModelDO model, String systemMessage) {
125 129 // 1. 构建 message 列表
126 130 List<Message> chatMessages = buildMessages(generateReqVO, systemMessage);
127 131 // 2. 构建 options 对象
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml
... ... @@ -14,93 +14,105 @@
14 14 <name>${project.artifactId}</name>
15 15 <description>AI 大模型拓展,接入国内外大模型</description>
16 16 <properties>
17   - <spring-ai.groupId>group.springframework.ai</spring-ai.groupId>
18   - <spring-ai.version>1.1.0</spring-ai.version>
  17 + <spring-ai.version>1.0.0-M6</spring-ai.version>
19 18 </properties>
20 19  
21 20 <dependencies>
22 21 <dependency>
23   - <groupId>${spring-ai.groupId}</groupId>
24   - <artifactId>spring-ai-zhipuai-spring-boot-starter</artifactId>
25   - <version>${spring-ai.version}</version>
  22 + <groupId>cn.iocoder.boot</groupId>
  23 + <artifactId>yudao-common</artifactId>
26 24 </dependency>
  25 +
  26 + <!-- Spring AI Model 模型接入 -->
27 27 <dependency>
28   - <groupId>${spring-ai.groupId}</groupId>
  28 + <groupId>org.springframework.ai</groupId>
29 29 <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
30 30 <version>${spring-ai.version}</version>
31 31 </dependency>
32 32 <dependency>
33   - <groupId>${spring-ai.groupId}</groupId>
  33 + <groupId>org.springframework.ai</groupId>
34 34 <artifactId>spring-ai-azure-openai-spring-boot-starter</artifactId>
35 35 <version>${spring-ai.version}</version>
36 36 </dependency>
37 37 <dependency>
38   - <groupId>${spring-ai.groupId}</groupId>
  38 + <groupId>org.springframework.ai</groupId>
39 39 <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
40 40 <version>${spring-ai.version}</version>
41 41 </dependency>
42 42 <dependency>
43   - <groupId>${spring-ai.groupId}</groupId>
  43 + <groupId>org.springframework.ai</groupId>
44 44 <artifactId>spring-ai-stability-ai-spring-boot-starter</artifactId>
45 45 <version>${spring-ai.version}</version>
46 46 </dependency>
47   -
48   - <!-- 向量化,基于 Redis 存储,Tika 解析内容 -->
49   -
50   - <!-- 暂不做经济型,先注释 TODO 经济型是啥呀? -->
51   - <!-- <dependency>-->
52   - <!-- <groupId>${spring-ai.groupId}</groupId>-->
53   - <!-- <artifactId>spring-ai-transformers-spring-boot-starter</artifactId>-->
54   - <!-- <version>${spring-ai.version}</version>-->
55   - <!-- </dependency>-->
56   - <dependency>
57   - <groupId>${spring-ai.groupId}</groupId>
58   - <artifactId>spring-ai-tika-document-reader</artifactId>
  47 + <dependency>
  48 + <!-- 通义千问 -->
  49 + <groupId>com.alibaba.cloud.ai</groupId>
  50 + <artifactId>spring-ai-alibaba-starter</artifactId>
  51 + <version>${spring-ai.version}.1</version>
  52 + </dependency>
  53 + <dependency>
  54 + <!-- 文心一言 -->
  55 + <groupId>org.springframework.ai</groupId>
  56 + <artifactId>spring-ai-qianfan-spring-boot-starter</artifactId>
59 57 <version>${spring-ai.version}</version>
60   - <!-- TODO 芋艿:boot 项目里,不引入 cloud 依赖!!!另外,这样也是为了解决启动报错的问题! -->
61   - <exclusions>
62   - <exclusion>
63   - <artifactId>spring-cloud-function-context</artifactId>
64   - <groupId>org.springframework.cloud</groupId>
65   - </exclusion>
66   - <exclusion>
67   - <artifactId>spring-cloud-function-core</artifactId>
68   - <groupId>org.springframework.cloud</groupId>
69   - </exclusion>
70   - </exclusions>
71 58 </dependency>
72 59 <dependency>
73   - <groupId>${spring-ai.groupId}</groupId>
74   - <artifactId>spring-ai-redis-store</artifactId>
  60 + <!-- 智谱 GLM -->
  61 + <groupId>org.springframework.ai</groupId>
  62 + <artifactId>spring-ai-zhipuai-spring-boot-starter</artifactId>
  63 + <version>${spring-ai.version}</version>
  64 + </dependency>
  65 + <dependency>
  66 + <groupId>org.springframework.ai</groupId>
  67 + <artifactId>spring-ai-minimax-spring-boot-starter</artifactId>
  68 + <version>${spring-ai.version}</version>
  69 + </dependency>
  70 + <dependency>
  71 + <groupId>org.springframework.ai</groupId>
  72 + <artifactId>spring-ai-moonshot-spring-boot-starter</artifactId>
75 73 <version>${spring-ai.version}</version>
76 74 </dependency>
77 75  
  76 + <!-- 向量存储:https://db-engines.com/en/ranking/vector+dbms -->
78 77 <dependency>
79   - <groupId>cn.iocoder.boot</groupId>
80   - <artifactId>yudao-spring-boot-starter-redis</artifactId>
  78 + <!-- Qdrant:https://qdrant.tech/ -->
  79 + <groupId>org.springframework.ai</groupId>
  80 + <artifactId>spring-ai-qdrant-store</artifactId>
  81 + <version>${spring-ai.version}</version>
81 82 </dependency>
82 83  
83 84 <dependency>
  85 + <!-- Redis:https://redis.io/docs/latest/develop/get-started/vector-database/ -->
  86 + <groupId>org.springframework.ai</groupId>
  87 + <artifactId>spring-ai-redis-store</artifactId>
  88 + <version>${spring-ai.version}</version>
  89 + </dependency>
  90 + <dependency>
84 91 <groupId>cn.iocoder.boot</groupId>
85   - <artifactId>yudao-common</artifactId>
  92 + <artifactId>yudao-spring-boot-starter-redis</artifactId>
86 93 </dependency>
87 94  
88 95 <dependency>
89   - <groupId>${spring-ai.groupId}</groupId>
90   - <artifactId>spring-ai-qianfan-spring-boot-starter</artifactId>
  96 + <!-- Milvus:https://milvus.io/ -->
  97 + <groupId>org.springframework.ai</groupId>
  98 + <artifactId>spring-ai-milvus-store</artifactId>
91 99 <version>${spring-ai.version}</version>
92 100 </dependency>
93 101  
94   - <!-- 阿里云 通义千问 -->
95   - <!-- TODO 芋艿:等 spring cloud alibaba ai 发布最新的时候,可以替换掉这个依赖,并且删除我们直接 cv 的代码 -->
96 102 <dependency>
97   - <groupId>com.alibaba</groupId>
98   - <artifactId>dashscope-sdk-java</artifactId>
99   - <version>2.14.0</version>
  103 + <!-- Tika:负责内容的解析 -->
  104 + <groupId>org.springframework.ai</groupId>
  105 + <artifactId>spring-ai-tika-document-reader</artifactId>
  106 + <version>${spring-ai.version}</version>
  107 + <!-- TODO 芋艿:boot 项目里,不引入 cloud 依赖!!!另外,这样也是为了解决启动报错的问题! -->
100 108 <exclusions>
101 109 <exclusion>
102   - <groupId>org.slf4j</groupId>
103   - <artifactId>slf4j-simple</artifactId>
  110 + <artifactId>spring-cloud-function-context</artifactId>
  111 + <groupId>org.springframework.cloud</groupId>
  112 + </exclusion>
  113 + <exclusion>
  114 + <artifactId>spring-cloud-function-core</artifactId>
  115 + <groupId>org.springframework.cloud</groupId>
104 116 </exclusion>
105 117 </exclusions>
106 118 </dependency>
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java
1 1 package cn.iocoder.yudao.framework.ai.config;
2 2  
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.hutool.extra.spring.SpringUtil;
3 5 import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory;
4 6 import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactoryImpl;
5 7 import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel;
6   -import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatOptions;
  8 +import cn.iocoder.yudao.framework.ai.core.model.doubao.DouBaoChatModel;
  9 +import cn.iocoder.yudao.framework.ai.core.model.hunyuan.HunYuanChatModel;
7 10 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
  11 +import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconFlowChatModel;
8 12 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
9 13 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;
10   -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatOptions;
11   -import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration;
12 14 import lombok.extern.slf4j.Slf4j;
  15 +import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusServiceClientProperties;
  16 +import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreProperties;
  17 +import org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreProperties;
  18 +import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreProperties;
  19 +import org.springframework.ai.embedding.BatchingStrategy;
  20 +import org.springframework.ai.embedding.TokenCountBatchingStrategy;
  21 +import org.springframework.ai.model.tool.ToolCallingManager;
  22 +import org.springframework.ai.openai.OpenAiChatModel;
  23 +import org.springframework.ai.openai.OpenAiChatOptions;
  24 +import org.springframework.ai.openai.api.OpenAiApi;
13 25 import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator;
14 26 import org.springframework.ai.tokenizer.TokenCountEstimator;
15   -import org.springframework.ai.transformer.splitter.TokenTextSplitter;
16 27 import org.springframework.boot.autoconfigure.AutoConfiguration;
17 28 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
18 29 import org.springframework.boot.context.properties.EnableConfigurationProperties;
19 30 import org.springframework.context.annotation.Bean;
20   -import org.springframework.context.annotation.Import;
21   -import org.springframework.context.annotation.Lazy;
22 31  
23 32 /**
24 33 * 芋道 AI 自动配置
... ... @@ -26,9 +35,12 @@ import org.springframework.context.annotation.Lazy;
26 35 * @author fansili
27 36 */
28 37 @AutoConfiguration
29   -@EnableConfigurationProperties(YudaoAiProperties.class)
  38 +@EnableConfigurationProperties({ YudaoAiProperties.class,
  39 + QdrantVectorStoreProperties.class, // 解析 Qdrant 配置
  40 + RedisVectorStoreProperties.class, // 解析 Redis 配置
  41 + MilvusVectorStoreProperties.class, MilvusServiceClientProperties.class // 解析 Milvus 配置
  42 +})
30 43 @Slf4j
31   -@Import(TongYiAutoConfiguration.class)
32 44 public class YudaoAiAutoConfiguration {
33 45  
34 46 @Bean
... ... @@ -36,33 +48,148 @@ public class YudaoAiAutoConfiguration {
36 48 return new AiModelFactoryImpl();
37 49 }
38 50  
39   -
40 51 // ========== 各种 AI Client 创建 ==========
41 52  
42 53 @Bean
43 54 @ConditionalOnProperty(value = "yudao.ai.deepseek.enable", havingValue = "true")
44 55 public DeepSeekChatModel deepSeekChatModel(YudaoAiProperties yudaoAiProperties) {
45   - YudaoAiProperties.DeepSeekProperties properties = yudaoAiProperties.getDeepSeek();
46   - DeepSeekChatOptions options = DeepSeekChatOptions.builder()
47   - .model(properties.getModel())
48   - .temperature(properties.getTemperature())
49   - .maxTokens(properties.getMaxTokens())
50   - .topP(properties.getTopP())
  56 + YudaoAiProperties.DeepSeekProperties properties = yudaoAiProperties.getDeepseek();
  57 + return buildDeepSeekChatModel(properties);
  58 + }
  59 +
  60 + public DeepSeekChatModel buildDeepSeekChatModel(YudaoAiProperties.DeepSeekProperties properties) {
  61 + if (StrUtil.isEmpty(properties.getModel())) {
  62 + properties.setModel(DeepSeekChatModel.MODEL_DEFAULT);
  63 + }
  64 + OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
  65 + .openAiApi(OpenAiApi.builder()
  66 + .baseUrl(DeepSeekChatModel.BASE_URL)
  67 + .apiKey(properties.getApiKey())
  68 + .build())
  69 + .defaultOptions(OpenAiChatOptions.builder()
  70 + .model(properties.getModel())
  71 + .temperature(properties.getTemperature())
  72 + .maxTokens(properties.getMaxTokens())
  73 + .topP(properties.getTopP())
  74 + .build())
  75 + .toolCallingManager(getToolCallingManager())
  76 + .build();
  77 + return new DeepSeekChatModel(openAiChatModel);
  78 + }
  79 +
  80 + @Bean
  81 + @ConditionalOnProperty(value = "yudao.ai.doubao.enable", havingValue = "true")
  82 + public DouBaoChatModel douBaoChatClient(YudaoAiProperties yudaoAiProperties) {
  83 + YudaoAiProperties.DouBaoProperties properties = yudaoAiProperties.getDoubao();
  84 + return buildDouBaoChatClient(properties);
  85 + }
  86 +
  87 + public DouBaoChatModel buildDouBaoChatClient(YudaoAiProperties.DouBaoProperties properties) {
  88 + if (StrUtil.isEmpty(properties.getModel())) {
  89 + properties.setModel(DouBaoChatModel.MODEL_DEFAULT);
  90 + }
  91 + OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
  92 + .openAiApi(OpenAiApi.builder()
  93 + .baseUrl(DouBaoChatModel.BASE_URL)
  94 + .apiKey(properties.getApiKey())
  95 + .build())
  96 + .defaultOptions(OpenAiChatOptions.builder()
  97 + .model(properties.getModel())
  98 + .temperature(properties.getTemperature())
  99 + .maxTokens(properties.getMaxTokens())
  100 + .topP(properties.getTopP())
  101 + .build())
  102 + .toolCallingManager(getToolCallingManager())
  103 + .build();
  104 + return new DouBaoChatModel(openAiChatModel);
  105 + }
  106 +
  107 + @Bean
  108 + @ConditionalOnProperty(value = "yudao.ai.siliconflow.enable", havingValue = "true")
  109 + public SiliconFlowChatModel siliconFlowChatClient(YudaoAiProperties yudaoAiProperties) {
  110 + YudaoAiProperties.SiliconFlowProperties properties = yudaoAiProperties.getSiliconflow();
  111 + return buildSiliconFlowChatClient(properties);
  112 + }
  113 +
  114 + public SiliconFlowChatModel buildSiliconFlowChatClient(YudaoAiProperties.SiliconFlowProperties properties) {
  115 + if (StrUtil.isEmpty(properties.getModel())) {
  116 + properties.setModel(SiliconFlowChatModel.MODEL_DEFAULT);
  117 + }
  118 + OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
  119 + .openAiApi(OpenAiApi.builder()
  120 + .baseUrl(SiliconFlowChatModel.BASE_URL)
  121 + .apiKey(properties.getApiKey())
  122 + .build())
  123 + .defaultOptions(OpenAiChatOptions.builder()
  124 + .model(properties.getModel())
  125 + .temperature(properties.getTemperature())
  126 + .maxTokens(properties.getMaxTokens())
  127 + .topP(properties.getTopP())
  128 + .build())
  129 + .toolCallingManager(getToolCallingManager())
51 130 .build();
52   - return new DeepSeekChatModel(properties.getApiKey(), options);
  131 + return new SiliconFlowChatModel(openAiChatModel);
  132 + }
  133 +
  134 + @Bean
  135 + @ConditionalOnProperty(value = "yudao.ai.hunyuan.enable", havingValue = "true")
  136 + public HunYuanChatModel hunYuanChatClient(YudaoAiProperties yudaoAiProperties) {
  137 + YudaoAiProperties.HunYuanProperties properties = yudaoAiProperties.getHunyuan();
  138 + return buildHunYuanChatClient(properties);
  139 + }
  140 +
  141 + public HunYuanChatModel buildHunYuanChatClient(YudaoAiProperties.HunYuanProperties properties) {
  142 + if (StrUtil.isEmpty(properties.getModel())) {
  143 + properties.setModel(HunYuanChatModel.MODEL_DEFAULT);
  144 + }
  145 + // 特殊:由于混元大模型不提供 deepseek,而是通过知识引擎,所以需要区分下 URL
  146 + if (StrUtil.isEmpty(properties.getBaseUrl())) {
  147 + properties.setBaseUrl(
  148 + StrUtil.startWithIgnoreCase(properties.getModel(), "deepseek") ? HunYuanChatModel.DEEP_SEEK_BASE_URL
  149 + : HunYuanChatModel.BASE_URL);
  150 + }
  151 + // 创建 OpenAiChatModel、HunYuanChatModel 对象
  152 + OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
  153 + .openAiApi(OpenAiApi.builder()
  154 + .baseUrl(properties.getBaseUrl())
  155 + .apiKey(properties.getApiKey())
  156 + .build())
  157 + .defaultOptions(OpenAiChatOptions.builder()
  158 + .model(properties.getModel())
  159 + .temperature(properties.getTemperature())
  160 + .maxTokens(properties.getMaxTokens())
  161 + .topP(properties.getTopP())
  162 + .build())
  163 + .toolCallingManager(getToolCallingManager())
  164 + .build();
  165 + return new HunYuanChatModel(openAiChatModel);
53 166 }
54 167  
55 168 @Bean
56 169 @ConditionalOnProperty(value = "yudao.ai.xinghuo.enable", havingValue = "true")
57 170 public XingHuoChatModel xingHuoChatClient(YudaoAiProperties yudaoAiProperties) {
58 171 YudaoAiProperties.XingHuoProperties properties = yudaoAiProperties.getXinghuo();
59   - XingHuoChatOptions options = XingHuoChatOptions.builder()
60   - .model(properties.getModel())
61   - .temperature(properties.getTemperature())
62   - .maxTokens(properties.getMaxTokens())
63   - .topK(properties.getTopK())
  172 + return buildXingHuoChatClient(properties);
  173 + }
  174 +
  175 + public XingHuoChatModel buildXingHuoChatClient(YudaoAiProperties.XingHuoProperties properties) {
  176 + if (StrUtil.isEmpty(properties.getModel())) {
  177 + properties.setModel(XingHuoChatModel.MODEL_DEFAULT);
  178 + }
  179 + OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
  180 + .openAiApi(OpenAiApi.builder()
  181 + .baseUrl(XingHuoChatModel.BASE_URL)
  182 + .apiKey(properties.getAppKey() + ":" + properties.getSecretKey())
  183 + .build())
  184 + .defaultOptions(OpenAiChatOptions.builder()
  185 + .model(properties.getModel())
  186 + .temperature(properties.getTemperature())
  187 + .maxTokens(properties.getMaxTokens())
  188 + .topP(properties.getTopP())
  189 + .build())
  190 + .toolCallingManager(getToolCallingManager())
64 191 .build();
65   - return new XingHuoChatModel(properties.getAppKey(), properties.getSecretKey(), options);
  192 + return new XingHuoChatModel(openAiChatModel);
66 193 }
67 194  
68 195 @Bean
... ... @@ -78,44 +205,20 @@ public class YudaoAiAutoConfiguration {
78 205 return new SunoApi(yudaoAiProperties.getSuno().getBaseUrl());
79 206 }
80 207  
81   - // ========== rag 相关 ==========
82   - // TODO @xin 免费版本
83   -// @Bean
84   -// @Lazy // TODO 芋艿:临时注释,避免无法启动」
85   -// public TransformersEmbeddingModel transformersEmbeddingClient() {
86   -// return new TransformersEmbeddingModel(MetadataMode.EMBED);
87   -// }
88   -
89   - /**
90   - * TODO @xin 默认版本先不弄,目前都先取对应的 EmbeddingModel
91   - */
92   -// @Bean
93   -// @Lazy // TODO 芋艿:临时注释,避免无法启动
94   -// public RedisVectorStore vectorStore(TransformersEmbeddingModel embeddingModel, RedisVectorStoreProperties properties,
95   -// RedisProperties redisProperties) {
96   -// var config = RedisVectorStore.RedisVectorStoreConfig.builder()
97   -// .withIndexName(properties.getIndex())
98   -// .withPrefix(properties.getPrefix())
99   -// .withMetadataFields(new RedisVectorStore.MetadataField("knowledgeId", Schema.FieldType.NUMERIC))
100   -// .build();
101   -//
102   -// RedisVectorStore redisVectorStore = new RedisVectorStore(config, embeddingModel,
103   -// new JedisPooled(redisProperties.getHost(), redisProperties.getPort()),
104   -// properties.isInitializeSchema());
105   -// redisVectorStore.afterPropertiesSet();
106   -// return redisVectorStore;
107   -// }
108   - @Bean
109   - @Lazy // TODO 芋艿:临时注释,避免无法启动
110   - public TokenTextSplitter tokenTextSplitter() {
111   - //TODO @xin 配置提取
112   - return new TokenTextSplitter(500, 100, 5, 10000, true);
113   - }
  208 + // ========== RAG 相关 ==========
114 209  
115 210 @Bean
116   - @Lazy // TODO 芋艿:临时注释,避免无法启动
117 211 public TokenCountEstimator tokenCountEstimator() {
118 212 return new JTokkitTokenCountEstimator();
119 213 }
120 214  
  215 + @Bean
  216 + public BatchingStrategy batchingStrategy() {
  217 + return new TokenCountBatchingStrategy();
  218 + }
  219 +
  220 + private static ToolCallingManager getToolCallingManager() {
  221 + return SpringUtil.getBean(ToolCallingManager.class);
  222 + }
  223 +
121 224 }
122 225 \ No newline at end of file
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java
... ... @@ -16,11 +16,31 @@ public class YudaoAiProperties {
16 16 /**
17 17 * DeepSeek
18 18 */
19   - private DeepSeekProperties deepSeek;
  19 + @SuppressWarnings("SpellCheckingInspection")
  20 + private DeepSeekProperties deepseek;
  21 +
  22 + /**
  23 + * 字节豆包
  24 + */
  25 + @SuppressWarnings("SpellCheckingInspection")
  26 + private DouBaoProperties doubao;
  27 +
  28 + /**
  29 + * 腾讯混元
  30 + */
  31 + @SuppressWarnings("SpellCheckingInspection")
  32 + private HunYuanProperties hunyuan;
  33 +
  34 + /**
  35 + * 硅基流动
  36 + */
  37 + @SuppressWarnings("SpellCheckingInspection")
  38 + private SiliconFlowProperties siliconflow;
20 39  
21 40 /**
22 41 * 讯飞星火
23 42 */
  43 + @SuppressWarnings("SpellCheckingInspection")
24 44 private XingHuoProperties xinghuo;
25 45  
26 46 /**
... ... @@ -31,33 +51,74 @@ public class YudaoAiProperties {
31 51 /**
32 52 * Suno 音乐
33 53 */
  54 + @SuppressWarnings("SpellCheckingInspection")
34 55 private SunoProperties suno;
35 56  
36 57 @Data
37   - public static class XingHuoProperties {
  58 + public static class DeepSeekProperties {
38 59  
39 60 private String enable;
40   - private String appId;
41   - private String appKey;
42   - private String secretKey;
  61 + private String apiKey;
43 62  
44 63 private String model;
45   - private Float temperature;
  64 + private Double temperature;
46 65 private Integer maxTokens;
47   - private Integer topK;
  66 + private Double topP;
48 67  
49 68 }
50 69  
51 70 @Data
52   - public static class DeepSeekProperties {
  71 + public static class DouBaoProperties {
  72 +
  73 + private String enable;
  74 + private String apiKey;
  75 +
  76 + private String model;
  77 + private Double temperature;
  78 + private Integer maxTokens;
  79 + private Double topP;
  80 +
  81 + }
  82 +
  83 + @Data
  84 + public static class HunYuanProperties {
53 85  
54 86 private String enable;
  87 + private String baseUrl;
55 88 private String apiKey;
56 89  
57 90 private String model;
58   - private Float temperature;
  91 + private Double temperature;
  92 + private Integer maxTokens;
  93 + private Double topP;
  94 +
  95 + }
  96 +
  97 + @Data
  98 + public static class SiliconFlowProperties {
  99 +
  100 + private String enable;
  101 + private String apiKey;
  102 +
  103 + private String model;
  104 + private Double temperature;
  105 + private Integer maxTokens;
  106 + private Double topP;
  107 +
  108 + }
  109 +
  110 + @Data
  111 + public static class XingHuoProperties {
  112 +
  113 + private String enable;
  114 + private String appId;
  115 + private String appKey;
  116 + private String secretKey;
  117 +
  118 + private String model;
  119 + private Double temperature;
59 120 private Integer maxTokens;
60   - private Float topP;
  121 + private Double topP;
61 122  
62 123 }
63 124  
... ...