mirror of
https://gitcode.com/ageerle/ruoyi-ai.git
synced 2026-04-24 09:13:39 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc151e49c5 | ||
|
|
2c6ff66830 | ||
|
|
4f79a66559 | ||
|
|
22883b4334 | ||
|
|
081da6d18d | ||
|
|
74eb5b2530 | ||
|
|
b0328fe0ef | ||
|
|
2ee0aae57e | ||
|
|
d9c3de660a | ||
|
|
c4f7c1f5d0 | ||
|
|
b9097b4989 | ||
|
|
5d14eb20af |
@@ -31,8 +31,8 @@
|
|||||||
|
|
||||||
| 模块 | 现有能力
|
| 模块 | 现有能力
|
||||||
|:----------:|---
|
|:----------:|---
|
||||||
| **模型管理** | 多模型接入(OpenAI/DeepSeek/通义/智谱)、多模态理解、Coze/DIFY/FastGPT平台集成
|
| **模型管理** | 多模型接入(OpenAI/DeepSeek/通义/智谱/MiniMax)、多模态理解、Coze/DIFY/FastGPT平台集成
|
||||||
| **知识管理** | 本地RAG + 向量库(Milvus/Weaviate/Qdrant) + 文档解析
|
| **知识管理** | 本地RAG + 向量库(Milvus/Weaviate/Qdrant) + 文档解析
|
||||||
| **工具管理** | Mcp协议集成、Skills能力 + 可扩展工具生态
|
| **工具管理** | Mcp协议集成、Skills能力 + 可扩展工具生态
|
||||||
| **流程编排** | 可视化工作流设计器、节点拖拽编排、SSE流式执行,目前已经支持模型调用,邮件发送,人工审核等节点
|
| **流程编排** | 可视化工作流设计器、节点拖拽编排、SSE流式执行,目前已经支持模型调用,邮件发送,人工审核等节点
|
||||||
| **多智能体** | 基于Langchain4j的Agent框架、Supervisor模式编排,支持多种决策模型
|
| **多智能体** | 基于Langchain4j的Agent框架、Supervisor模式编排,支持多种决策模型
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
| Module | Current Capabilities |
|
| Module | Current Capabilities |
|
||||||
|:---:|---|
|
|:---:|---|
|
||||||
| **Model Management** | Multi-model integration (OpenAI/DeepSeek/Tongyi/Zhipu), multi-modal understanding, Coze/DIFY/FastGPT platform integration |
|
| **Model Management** | Multi-model integration (OpenAI/DeepSeek/Tongyi/Zhipu/MiniMax), multi-modal understanding, Coze/DIFY/FastGPT platform integration |
|
||||||
| **Knowledge Base** | Local RAG + Vector DB (Milvus/Weaviate/Qdrant) + Document parsing |
|
| **Knowledge Base** | Local RAG + Vector DB (Milvus/Weaviate/Qdrant) + Document parsing |
|
||||||
| **Tool Management** | MCP protocol integration, Skills capability + Extensible tool ecosystem |
|
| **Tool Management** | MCP protocol integration, Skills capability + Extensible tool ecosystem |
|
||||||
| **Workflow Orchestration** | Visual workflow designer, drag-and-drop node orchestration, SSE streaming execution, currently supports model calls, email sending, manual review nodes |
|
| **Workflow Orchestration** | Visual workflow designer, drag-and-drop node orchestration, SSE streaming execution, currently supports model calls, email sending, manual review nodes |
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ services:
|
|||||||
- "28080:8080"
|
- "28080:8080"
|
||||||
environment:
|
environment:
|
||||||
QUERY_DEFAULTS_LIMIT: 25
|
QUERY_DEFAULTS_LIMIT: 25
|
||||||
AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: true
|
AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: "true"
|
||||||
PERSISTENCE_DATA_PATH: /var/lib/weaviate
|
PERSISTENCE_DATA_PATH: /var/lib/weaviate
|
||||||
DEFAULT_VECTORIZER_MODULE: none
|
DEFAULT_VECTORIZER_MODULE: none
|
||||||
ENABLE_MODULES: text2vec-cohere,text2vec-huggingface,text2vec-palm,text2vec-openai,generative-openai,generative-cohere,generative-palm,ref2vec-centroid,reranker-cohere,qna-openai
|
ENABLE_MODULES: text2vec-cohere,text2vec-huggingface,text2vec-palm,text2vec-openai,generative-openai,generative-cohere,generative-palm,ref2vec-centroid,reranker-cohere,qna-openai
|
||||||
|
|||||||
@@ -72,8 +72,9 @@ CREATE TABLE `chat_model` (
|
|||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Records of chat_model
|
-- Records of chat_model
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
INSERT INTO `chat_model` VALUES (2000585866022060033, 'chat', 'deepseek/deepseek-v3.2', 'ppio', 'deepseek', NULL, 'Y', 'https://api.ppinfra.com/openai', 'sk_xx', 103, 1, '2025-12-15 23:16:54', 1, '2026-03-15 19:18:48', 'DeepSeek-V3.2 是一款在高效推理、复杂推理能力与智能体场景中表现突出的领先模型。其基于 DeepSeek Sparse Attention(DSA)稀疏注意力机制,在显著降低计算开销的同时优化长上下文性能;通过可扩展强化学习框架,整体能力达到 GPT-5 同级,高算力版本 V3.2-Speciale 更在推理表现上接近 Gemini-3.0-Pro;同时,模型依托大型智能体任务合成管线,具备更强的工具调用与多步骤决策能力,并在 2025 年 IMO 与 IOI 中取得金牌级表现。作为 MaaS 平台,我们已对 DeepSeek-V3.2 完成深度适配,通过动态调度、批处理加速、低延迟推理与企业级 SLA 保障,进一步增强其在企业生产环境中的稳定性、性价比与可控性,适用于搜索、问答、智能体、代码、数据处理等多类高价值场景。', 0);
|
INSERT INTO `chat_model` VALUES (2000585866022060033, 'chat', 'zai-org/glm-5', 'ppio', 'zai-org/glm-5', NULL, 'Y', 'https://api.ppio.com/openai', 'sk_xx', 103, 1, '2025-12-15 23:16:54', 1, '2026-03-15 19:18:48', 'DeepSeek-V3.2 是一款在高效推理、复杂推理能力与智能体场景中表现突出的领先模型。其基于 DeepSeek Sparse Attention(DSA)稀疏注意力机制,在显著降低计算开销的同时优化长上下文性能;通过可扩展强化学习框架,整体能力达到 GPT-5 同级,高算力版本 V3.2-Speciale 更在推理表现上接近 Gemini-3.0-Pro;同时,模型依托大型智能体任务合成管线,具备更强的工具调用与多步骤决策能力,并在 2025 年 IMO 与 IOI 中取得金牌级表现。作为 MaaS 平台,我们已对 DeepSeek-V3.2 完成深度适配,通过动态调度、批处理加速、低延迟推理与企业级 SLA 保障,进一步增强其在企业生产环境中的稳定性、性价比与可控性,适用于搜索、问答、智能体、代码、数据处理等多类高价值场景。', 0);
|
||||||
INSERT INTO `chat_model` VALUES (2007528268536287233, 'vector', 'baai/bge-m3', 'ppio', 'bge-m3', 1024, 'N', 'https://api.ppinfra.com/openai', 'sk_xx', 103, 1, '2026-01-04 03:03:32', 1, '2026-03-15 19:18:51', 'BGE-M3 是一款具备多维度能力的文本嵌入模型,可同时实现密集检索、多向量检索和稀疏检索三大核心功能。该模型设计上兼容超过100种语言,并支持从短句到长达8192词元的长文本等多种输入形式。在跨语言检索任务中,BGE-M3展现出显著优势,其性能在MIRACL、MKQA等国际基准测试中位居前列。此外,针对长文档检索场景,该模型在MLDR、NarritiveQA等数据集上的表现同样达到行业领先水平。', 0);
|
INSERT INTO `chat_model` VALUES (2007528268536287233, 'vector', 'baai/bge-m3', 'ppio', 'bge-m3', 1024, 'N', 'https://api.ppio.com/openai', 'sk_xx', 103, 1, '2026-01-04 03:03:32', 1, '2026-03-15 19:18:51', 'BGE-M3 是一款具备多维度能力的文本嵌入模型,可同时实现密集检索、多向量检索和稀疏检索三大核心功能。该模型设计上兼容超过100种语言,并支持从短句到长达8192词元的长文本等多种输入形式。在跨语言检索任务中,BGE-M3展现出显著优势,其性能在MIRACL、MKQA等国际基准测试中位居前列。此外,针对长文档检索场景,该模型在MLDR、NarritiveQA等数据集上的表现同样达到行业领先水平。', 0);
|
||||||
|
INSERT INTO `chat_model` VALUES (2045735140488847361, 'chat', 'deepseek-chat', 'custom_api', 'deepseek-chat', NULL, NULL, 'https://api.deepseek.com', 'sk_xx', 103, 1, '2026-04-19 13:24:00', 1, '2026-04-19 13:24:00', 'deepseek对话模型', 0);
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Table structure for chat_provider
|
-- Table structure for chat_provider
|
||||||
@@ -95,22 +96,26 @@ CREATE TABLE `chat_provider` (
|
|||||||
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
|
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
|
||||||
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',
|
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',
|
||||||
`version` int NULL DEFAULT NULL COMMENT '版本',
|
`version` int NULL DEFAULT NULL COMMENT '版本',
|
||||||
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)',
|
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '删除标志',
|
||||||
`update_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '更新IP',
|
`update_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '更新IP',
|
||||||
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户Id',
|
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户Id',
|
||||||
PRIMARY KEY (`id`) USING BTREE,
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
UNIQUE INDEX `unique_provider_code`(`provider_code` ASC, `tenant_id` ASC) USING BTREE,
|
UNIQUE INDEX `unique_provider_code`(`provider_code` ASC, `tenant_id` ASC, `del_flag` ASC) USING BTREE,
|
||||||
INDEX `idx_status`(`status` ASC) USING BTREE
|
INDEX `idx_status`(`status` ASC) USING BTREE
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 2008460994477690882 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '厂商管理表' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB AUTO_INCREMENT = 2045727230803255298 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '厂商管理表' ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Records of chat_provider
|
-- Records of chat_provider
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
INSERT INTO `chat_provider` VALUES (1, 'OpenAI', 'openai', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/01091be272334383a1efd9bc22b73ee6.png', 'OpenAI官方API服务商', 'https://api.openai.com', '0', 1, NULL, '2025-12-14 21:48:11', '1', '1', '2026-02-25 20:46:59', 'OpenAI厂商', NULL, '0', NULL, 0);
|
INSERT INTO `chat_provider` VALUES (1, 'OpenAI', 'openai', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/01091be272334383a1efd9bc22b73ee6.png', 'OpenAI官方API服务商', 'https://api.openai.com', '0', 1, 103, '2025-12-14 21:48:11', '1', '1', '2026-02-25 20:46:59', 'OpenAI厂商', NULL, '0', NULL, 0);
|
||||||
INSERT INTO `chat_provider` VALUES (2, '阿里云百炼', 'qianwen', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/de2aa7e649de44f3ba5c6380ac6acd04.png', '阿里云百炼大模型服务', 'https://dashscope.aliyuncs.com', '0', 2, NULL, '2025-12-14 21:48:11', '1', '1', '2026-02-25 20:49:13', '阿里云厂商', NULL, '0', NULL, 0);
|
INSERT INTO `chat_provider` VALUES (11, '深度求索', 'deepseek', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/04/19/5ba8c30f153246898a4d7dc7b846de8d.png', 'DeepSeek官方API', 'https://api.deepseek.com', '0', 0, 103, '2026-04-19 12:52:34', '1', '1', '2026-04-19 13:13:25', 'DeepSeek官方API', NULL, '0', NULL, 0);
|
||||||
INSERT INTO `chat_provider` VALUES (3, '智谱AI', 'zhipu', 'https://ruoyi-ai-1254149996.cos.ap-guangzhou.myqcloud.com/2025/12/15/a43e98fb7b3b4861b8caa6184e6fa40a.png', '智谱AI大模型服务', 'https://open.bigmodel.cn', '0', 3, NULL, '2025-12-14 21:48:11', '1', '1', '2026-02-06 00:49:14', '智谱AI厂商', NULL, '1', NULL, 0);
|
INSERT INTO `chat_provider` VALUES (12, '智谱AI', 'zhipu', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/04/19/da071783c9284fdd9ed1ce1b57b3c75c.png', '智谱AI大模型服务', 'https://open.bigmodel.cn', '0', 4, 103, '2025-12-14 21:48:11', '1', '1', '2026-04-19 13:14:00', '智谱AI厂商', NULL, '0', NULL, 0);
|
||||||
INSERT INTO `chat_provider` VALUES (5, 'ollama', 'ollama', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/afecabebc8014d80b0f06b4796a74c5d.png', 'ollama大模型', 'http://127.0.0.1:11434', '0', 5, NULL, '2025-12-14 21:48:11', '1', '1', '2026-02-25 20:48:48', 'ollama厂商', NULL, '0', NULL, 0);
|
INSERT INTO `chat_provider` VALUES (13, '小米MIMO', 'xiaomi', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/04/19/18dd39365ce244e3ae5e030da036760e.png', '小米官方API', 'https://api.xiaomimimo.com/anthropic/v1/messages', '0', 3, 103, '2026-04-19 12:48:24', '1', '1', '2026-04-19 13:14:22', '小米官方API', NULL, '0', NULL, 0);
|
||||||
INSERT INTO `chat_provider` VALUES (2000585060904435714, 'PPIO', 'ppio', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/049bb6a507174f73bba4b8d8b9e55b8a.png', 'api聚合厂商', 'https://api.ppinfra.com/openai', '0', 5, 103, '2025-12-15 23:13:42', '1', '1', '2026-02-25 20:49:01', 'api聚合厂商', NULL, '0', NULL, 0);
|
INSERT INTO `chat_provider` VALUES (14, '阿里云百炼', 'qianwen', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/de2aa7e649de44f3ba5c6380ac6acd04.png', '阿里云百炼大模型服务', 'https://dashscope.aliyuncs.com', '0', 2, 103, '2025-12-14 21:48:11', '1', '1', '2026-02-25 20:49:13', '阿里云厂商', NULL, '0', NULL, 0);
|
||||||
|
INSERT INTO `chat_provider` VALUES (15, 'PPIO', 'ppio', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/049bb6a507174f73bba4b8d8b9e55b8a.png', 'api聚合厂商', 'https://api.ppinfra.com/openai', '0', 5, 103, '2025-12-15 23:13:42', '1', '1', '2026-02-25 20:49:01', 'api聚合厂商', NULL, '0', NULL, 0);
|
||||||
|
INSERT INTO `chat_provider` VALUES (16, 'MiniMax', 'minimax', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/04/19/fdc712e90e0e4d78b05862ad230884e5.png', 'MiniMax大模型服务,支持M2.7、M2.5等模型', 'https://api.minimax.io/v1', '0', 6, 103, '2026-04-19 12:50:12', '1', '1', '2026-04-19 13:14:59', 'MiniMax厂商', NULL, '0', NULL, 0);
|
||||||
|
INSERT INTO `chat_provider` VALUES (17, 'ollama', 'ollama', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/afecabebc8014d80b0f06b4796a74c5d.png', 'ollama大模型', 'http://127.0.0.1:11434', '0', 7, 103, '2025-12-14 21:48:11', '1', '1', '2026-02-25 20:48:48', 'ollama厂商', NULL, '0', NULL, 0);
|
||||||
|
INSERT INTO `chat_provider` VALUES (18, '自定义厂商', 'custom_api', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/04/19/c1a8e122510f4e2f90deb36958af710b.png', 'OPENAI兼容格式', '自定义', '0', 8, 103, '2026-04-19 12:35:57', '1', '1', '2026-04-19 13:17:20', 'OPENAI兼容格式', NULL, '0', NULL, 0);
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Table structure for chat_session
|
-- Table structure for chat_session
|
||||||
|
|||||||
92
docs/script/sql/update/updat-0419.sql
Normal file
92
docs/script/sql/update/updat-0419.sql
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
Navicat Premium Dump SQL
|
||||||
|
|
||||||
|
Source Server : localhost-mysql
|
||||||
|
Source Server Type : MySQL
|
||||||
|
Source Server Version : 80045 (8.0.45)
|
||||||
|
Source Host : localhost:3306
|
||||||
|
Source Schema : ruoyi-ai
|
||||||
|
|
||||||
|
Target Server Type : MySQL
|
||||||
|
Target Server Version : 80045 (8.0.45)
|
||||||
|
File Encoding : 65001
|
||||||
|
|
||||||
|
Date: 19/04/2026 13:36:41
|
||||||
|
*/
|
||||||
|
|
||||||
|
SET NAMES utf8mb4;
|
||||||
|
SET FOREIGN_KEY_CHECKS = 0;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for chat_model
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `chat_model`;
|
||||||
|
CREATE TABLE `chat_model` (
|
||||||
|
`id` bigint NOT NULL COMMENT '主键',
|
||||||
|
`category` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '模型分类',
|
||||||
|
`model_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '模型名称',
|
||||||
|
`provider_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '模型供应商',
|
||||||
|
`model_describe` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '模型描述',
|
||||||
|
`model_dimension` int NULL DEFAULT NULL COMMENT '模型维度',
|
||||||
|
`model_show` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '是否显示',
|
||||||
|
`api_host` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '请求地址',
|
||||||
|
`api_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '密钥',
|
||||||
|
`create_dept` bigint NULL DEFAULT NULL COMMENT '创建部门',
|
||||||
|
`create_by` bigint NULL DEFAULT NULL COMMENT '创建者',
|
||||||
|
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
|
||||||
|
`update_by` bigint NULL DEFAULT NULL COMMENT '更新者',
|
||||||
|
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
|
||||||
|
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',
|
||||||
|
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户Id',
|
||||||
|
PRIMARY KEY (`id`) USING BTREE
|
||||||
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '模型管理' ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Records of chat_model
|
||||||
|
-- ----------------------------
|
||||||
|
INSERT INTO `chat_model` VALUES (2000585866022060033, 'chat', 'zai-org/glm-5', 'ppio', 'zai-org/glm-5', NULL, 'Y', 'https://api.ppio.com/openai', 'sk_xx', 103, 1, '2025-12-15 23:16:54', 1, '2026-03-15 19:18:48', 'DeepSeek-V3.2 是一款在高效推理、复杂推理能力与智能体场景中表现突出的领先模型。其基于 DeepSeek Sparse Attention(DSA)稀疏注意力机制,在显著降低计算开销的同时优化长上下文性能;通过可扩展强化学习框架,整体能力达到 GPT-5 同级,高算力版本 V3.2-Speciale 更在推理表现上接近 Gemini-3.0-Pro;同时,模型依托大型智能体任务合成管线,具备更强的工具调用与多步骤决策能力,并在 2025 年 IMO 与 IOI 中取得金牌级表现。作为 MaaS 平台,我们已对 DeepSeek-V3.2 完成深度适配,通过动态调度、批处理加速、低延迟推理与企业级 SLA 保障,进一步增强其在企业生产环境中的稳定性、性价比与可控性,适用于搜索、问答、智能体、代码、数据处理等多类高价值场景。', 0);
|
||||||
|
INSERT INTO `chat_model` VALUES (2007528268536287233, 'vector', 'baai/bge-m3', 'ppio', 'bge-m3', 1024, 'N', 'https://api.ppio.com/openai', 'sk_xx', 103, 1, '2026-01-04 03:03:32', 1, '2026-03-15 19:18:51', 'BGE-M3 是一款具备多维度能力的文本嵌入模型,可同时实现密集检索、多向量检索和稀疏检索三大核心功能。该模型设计上兼容超过100种语言,并支持从短句到长达8192词元的长文本等多种输入形式。在跨语言检索任务中,BGE-M3展现出显著优势,其性能在MIRACL、MKQA等国际基准测试中位居前列。此外,针对长文档检索场景,该模型在MLDR、NarritiveQA等数据集上的表现同样达到行业领先水平。', 0);
|
||||||
|
INSERT INTO `chat_model` VALUES (2045735140488847361, 'chat', 'deepseek-chat', 'custom_api', 'deepseek-chat', NULL, NULL, 'https://api.deepseek.com', 'sk_xx', 103, 1, '2026-04-19 13:24:00', 1, '2026-04-19 13:24:00', 'deepseek对话模型', 0);
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for chat_provider
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `chat_provider`;
|
||||||
|
CREATE TABLE `chat_provider` (
|
||||||
|
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||||
|
`provider_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '厂商名称',
|
||||||
|
`provider_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '厂商编码',
|
||||||
|
`provider_icon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '厂商图标',
|
||||||
|
`provider_desc` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '厂商描述',
|
||||||
|
`api_host` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'API地址',
|
||||||
|
`status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '状态(0正常 1停用)',
|
||||||
|
`sort_order` int NULL DEFAULT 0 COMMENT '排序',
|
||||||
|
`create_dept` bigint NULL DEFAULT NULL COMMENT '创建部门',
|
||||||
|
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
|
||||||
|
`create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '创建者',
|
||||||
|
`update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '更新者',
|
||||||
|
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
|
||||||
|
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',
|
||||||
|
`version` int NULL DEFAULT NULL COMMENT '版本',
|
||||||
|
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '删除标志',
|
||||||
|
`update_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '更新IP',
|
||||||
|
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户Id',
|
||||||
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
UNIQUE INDEX `unique_provider_code`(`provider_code` ASC, `tenant_id` ASC, `del_flag` ASC) USING BTREE,
|
||||||
|
INDEX `idx_status`(`status` ASC) USING BTREE
|
||||||
|
) ENGINE = InnoDB AUTO_INCREMENT = 2045727230803255298 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '厂商管理表' ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Records of chat_provider
|
||||||
|
-- ----------------------------
|
||||||
|
INSERT INTO `chat_provider` VALUES (1, 'OpenAI', 'openai', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/01091be272334383a1efd9bc22b73ee6.png', 'OpenAI官方API服务商', 'https://api.openai.com', '0', 1, 103, '2025-12-14 21:48:11', '1', '1', '2026-02-25 20:46:59', 'OpenAI厂商', NULL, '0', NULL, 0);
|
||||||
|
INSERT INTO `chat_provider` VALUES (11, '深度求索', 'deepseek', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/04/19/5ba8c30f153246898a4d7dc7b846de8d.png', 'DeepSeek官方API', 'https://api.deepseek.com', '0', 0, 103, '2026-04-19 12:52:34', '1', '1', '2026-04-19 13:13:25', 'DeepSeek官方API', NULL, '0', NULL, 0);
|
||||||
|
INSERT INTO `chat_provider` VALUES (12, '智谱AI', 'zhipu', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/04/19/da071783c9284fdd9ed1ce1b57b3c75c.png', '智谱AI大模型服务', 'https://open.bigmodel.cn', '0', 4, 103, '2025-12-14 21:48:11', '1', '1', '2026-04-19 13:14:00', '智谱AI厂商', NULL, '0', NULL, 0);
|
||||||
|
INSERT INTO `chat_provider` VALUES (13, '小米MIMO', 'xiaomi', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/04/19/18dd39365ce244e3ae5e030da036760e.png', '小米官方API', 'https://api.xiaomimimo.com/anthropic/v1/messages', '0', 3, 103, '2026-04-19 12:48:24', '1', '1', '2026-04-19 13:14:22', '小米官方API', NULL, '0', NULL, 0);
|
||||||
|
INSERT INTO `chat_provider` VALUES (14, '阿里云百炼', 'qianwen', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/de2aa7e649de44f3ba5c6380ac6acd04.png', '阿里云百炼大模型服务', 'https://dashscope.aliyuncs.com', '0', 2, 103, '2025-12-14 21:48:11', '1', '1', '2026-02-25 20:49:13', '阿里云厂商', NULL, '0', NULL, 0);
|
||||||
|
INSERT INTO `chat_provider` VALUES (15, 'PPIO', 'ppio', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/049bb6a507174f73bba4b8d8b9e55b8a.png', 'api聚合厂商', 'https://api.ppinfra.com/openai', '0', 5, 103, '2025-12-15 23:13:42', '1', '1', '2026-02-25 20:49:01', 'api聚合厂商', NULL, '0', NULL, 0);
|
||||||
|
INSERT INTO `chat_provider` VALUES (16, 'MiniMax', 'minimax', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/04/19/fdc712e90e0e4d78b05862ad230884e5.png', 'MiniMax大模型服务,支持M2.7、M2.5等模型', 'https://api.minimax.io/v1', '0', 6, 103, '2026-04-19 12:50:12', '1', '1', '2026-04-19 13:14:59', 'MiniMax厂商', NULL, '0', NULL, 0);
|
||||||
|
INSERT INTO `chat_provider` VALUES (17, 'ollama', 'ollama', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/afecabebc8014d80b0f06b4796a74c5d.png', 'ollama大模型', 'http://127.0.0.1:11434', '0', 7, 103, '2025-12-14 21:48:11', '1', '1', '2026-02-25 20:48:48', 'ollama厂商', NULL, '0', NULL, 0);
|
||||||
|
INSERT INTO `chat_provider` VALUES (18, '自定义厂商', 'custom_api', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/04/19/c1a8e122510f4e2f90deb36958af710b.png', 'OPENAI兼容格式', '自定义', '0', 8, 103, '2026-04-19 12:35:57', '1', '1', '2026-04-19 13:17:20', 'OPENAI兼容格式', NULL, '0', NULL, 0);
|
||||||
|
|
||||||
|
SET FOREIGN_KEY_CHECKS = 1;
|
||||||
2
pom.xml
2
pom.xml
@@ -58,7 +58,7 @@
|
|||||||
<langchain4j.community.version>1.13.0-beta23</langchain4j.community.version>
|
<langchain4j.community.version>1.13.0-beta23</langchain4j.community.version>
|
||||||
<langgraph4j.version>1.5.3</langgraph4j.version>
|
<langgraph4j.version>1.5.3</langgraph4j.version>
|
||||||
<weaviate.version>1.19.6</weaviate.version>
|
<weaviate.version>1.19.6</weaviate.version>
|
||||||
<dify.version>1.0.7</dify.version>
|
<dify.version>1.2.6</dify.version>
|
||||||
<!-- gRPC 版本 - 解决 Milvus SDK 依赖冲突 -->
|
<!-- gRPC 版本 - 解决 Milvus SDK 依赖冲突 -->
|
||||||
<grpc.version>1.62.2</grpc.version>
|
<grpc.version>1.62.2</grpc.version>
|
||||||
<!-- Apache Commons Compress - 用于POI处理ZIP格式 -->
|
<!-- Apache Commons Compress - 用于POI处理ZIP格式 -->
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ demo:
|
|||||||
# 是否开启演示模式(开启后所有写操作将被拦截)
|
# 是否开启演示模式(开启后所有写操作将被拦截)
|
||||||
enabled: false
|
enabled: false
|
||||||
# 提示消息
|
# 提示消息
|
||||||
message: "演示模式,不允许进行写操作"
|
message: "演示模式,不允许操作"
|
||||||
# 排除的路径(这些路径不受演示模式限制)
|
# 排除的路径(这些路径不受演示模式限制)
|
||||||
excludes:
|
excludes:
|
||||||
- /login
|
- /login
|
||||||
@@ -276,7 +276,9 @@ demo:
|
|||||||
- /chat/send
|
- /chat/send
|
||||||
- /system/session/**
|
- /system/session/**
|
||||||
- /system/message/**
|
- /system/message/**
|
||||||
|
- /system/attach/**
|
||||||
|
- /system/fragment/**
|
||||||
|
- /system/info/**
|
||||||
--- # warm-flow工作流配置
|
--- # warm-flow工作流配置
|
||||||
warm-flow:
|
warm-flow:
|
||||||
# 是否开启工作流,默认true
|
# 是否开启工作流,默认true
|
||||||
|
|||||||
@@ -174,6 +174,23 @@
|
|||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Test dependencies -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-core</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-junit-jupiter</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import cn.dev33.satoken.annotation.SaCheckPermission;
|
|||||||
import org.ruoyi.common.chat.service.chat.IChatModelService;
|
import org.ruoyi.common.chat.service.chat.IChatModelService;
|
||||||
import org.ruoyi.common.chat.domain.bo.chat.ChatModelBo;
|
import org.ruoyi.common.chat.domain.bo.chat.ChatModelBo;
|
||||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||||
|
import org.ruoyi.enums.ChatModeType;
|
||||||
import org.ruoyi.enums.ModelType;
|
import org.ruoyi.enums.ModelType;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
@@ -23,6 +24,8 @@ import org.ruoyi.common.log.enums.BusinessType;
|
|||||||
import org.ruoyi.common.excel.utils.ExcelUtil;
|
import org.ruoyi.common.excel.utils.ExcelUtil;
|
||||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 模型管理
|
* 模型管理
|
||||||
*
|
*
|
||||||
@@ -55,6 +58,21 @@ public class ChatModelController extends BaseController {
|
|||||||
return R.ok(chatModelService.queryList(bo));
|
return R.ok(chatModelService.queryList(bo));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取模型供应商枚举
|
||||||
|
*/
|
||||||
|
@GetMapping("/providerOptions")
|
||||||
|
public R<List<LinkedHashMap<String, String>>> providerOptions() {
|
||||||
|
List<LinkedHashMap<String, String>> options = new java.util.ArrayList<>();
|
||||||
|
for (ChatModeType type : ChatModeType.values()) {
|
||||||
|
LinkedHashMap<String, String> item = new LinkedHashMap<>();
|
||||||
|
item.put("label", type.getDescription());
|
||||||
|
item.put("value", type.getCode());
|
||||||
|
options.add(item);
|
||||||
|
}
|
||||||
|
return R.ok(options);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导出模型管理列表
|
* 导出模型管理列表
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
package org.ruoyi.controller.knowledge;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import jakarta.validation.constraints.*;
|
|
||||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
|
||||||
import org.ruoyi.domain.bo.knowledge.KnowledgeGraphInstanceBo;
|
|
||||||
import org.ruoyi.domain.vo.knowledge.KnowledgeGraphInstanceVo;
|
|
||||||
import org.ruoyi.service.knowledge.IKnowledgeGraphInstanceService;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
import org.springframework.validation.annotation.Validated;
|
|
||||||
import org.ruoyi.common.idempotent.annotation.RepeatSubmit;
|
|
||||||
import org.ruoyi.common.log.annotation.Log;
|
|
||||||
import org.ruoyi.common.web.core.BaseController;
|
|
||||||
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
|
||||||
import org.ruoyi.common.core.domain.R;
|
|
||||||
import org.ruoyi.common.core.validate.AddGroup;
|
|
||||||
import org.ruoyi.common.core.validate.EditGroup;
|
|
||||||
import org.ruoyi.common.log.enums.BusinessType;
|
|
||||||
import org.ruoyi.common.excel.utils.ExcelUtil;
|
|
||||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 知识图谱实例
|
|
||||||
*
|
|
||||||
* @author ageerle
|
|
||||||
* @date 2025-12-17
|
|
||||||
*/
|
|
||||||
@Validated
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/system/graphInstance")
|
|
||||||
public class KnowledgeGraphInstanceController extends BaseController {
|
|
||||||
|
|
||||||
private final IKnowledgeGraphInstanceService knowledgeGraphInstanceService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询知识图谱实例列表
|
|
||||||
*/
|
|
||||||
@SaCheckPermission("system:graphInstance:list")
|
|
||||||
@GetMapping("/list")
|
|
||||||
public TableDataInfo<KnowledgeGraphInstanceVo> list(KnowledgeGraphInstanceBo bo, PageQuery pageQuery) {
|
|
||||||
return knowledgeGraphInstanceService.queryPageList(bo, pageQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 导出知识图谱实例列表
|
|
||||||
*/
|
|
||||||
@SaCheckPermission("system:graphInstance:export")
|
|
||||||
@Log(title = "知识图谱实例", businessType = BusinessType.EXPORT)
|
|
||||||
@PostMapping("/export")
|
|
||||||
public void export(KnowledgeGraphInstanceBo bo, HttpServletResponse response) {
|
|
||||||
List<KnowledgeGraphInstanceVo> list = knowledgeGraphInstanceService.queryList(bo);
|
|
||||||
ExcelUtil.exportExcel(list, "知识图谱实例", KnowledgeGraphInstanceVo.class, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取知识图谱实例详细信息
|
|
||||||
*
|
|
||||||
* @param id 主键
|
|
||||||
*/
|
|
||||||
@SaCheckPermission("system:graphInstance:query")
|
|
||||||
@GetMapping("/{id}")
|
|
||||||
public R<KnowledgeGraphInstanceVo> getInfo(@NotNull(message = "主键不能为空")
|
|
||||||
@PathVariable Long id) {
|
|
||||||
return R.ok(knowledgeGraphInstanceService.queryById(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 新增知识图谱实例
|
|
||||||
*/
|
|
||||||
@SaCheckPermission("system:graphInstance:add")
|
|
||||||
@Log(title = "知识图谱实例", businessType = BusinessType.INSERT)
|
|
||||||
@RepeatSubmit()
|
|
||||||
@PostMapping()
|
|
||||||
public R<Void> add(@Validated(AddGroup.class) @RequestBody KnowledgeGraphInstanceBo bo) {
|
|
||||||
return toAjax(knowledgeGraphInstanceService.insertByBo(bo));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改知识图谱实例
|
|
||||||
*/
|
|
||||||
@SaCheckPermission("system:graphInstance:edit")
|
|
||||||
@Log(title = "知识图谱实例", businessType = BusinessType.UPDATE)
|
|
||||||
@RepeatSubmit()
|
|
||||||
@PutMapping()
|
|
||||||
public R<Void> edit(@Validated(EditGroup.class) @RequestBody KnowledgeGraphInstanceBo bo) {
|
|
||||||
return toAjax(knowledgeGraphInstanceService.updateByBo(bo));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除知识图谱实例
|
|
||||||
*
|
|
||||||
* @param ids 主键串
|
|
||||||
*/
|
|
||||||
@SaCheckPermission("system:graphInstance:remove")
|
|
||||||
@Log(title = "知识图谱实例", businessType = BusinessType.DELETE)
|
|
||||||
@DeleteMapping("/{ids}")
|
|
||||||
public R<Void> remove(@NotEmpty(message = "主键不能为空")
|
|
||||||
@PathVariable Long[] ids) {
|
|
||||||
return toAjax(knowledgeGraphInstanceService.deleteWithValidByIds(List.of(ids), true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
package org.ruoyi.controller.knowledge;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import jakarta.validation.constraints.*;
|
|
||||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
|
||||||
import org.ruoyi.domain.bo.knowledge.KnowledgeGraphSegmentBo;
|
|
||||||
import org.ruoyi.domain.vo.knowledge.KnowledgeGraphSegmentVo;
|
|
||||||
import org.ruoyi.service.knowledge.IKnowledgeGraphSegmentService;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
import org.springframework.validation.annotation.Validated;
|
|
||||||
import org.ruoyi.common.idempotent.annotation.RepeatSubmit;
|
|
||||||
import org.ruoyi.common.log.annotation.Log;
|
|
||||||
import org.ruoyi.common.web.core.BaseController;
|
|
||||||
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
|
||||||
import org.ruoyi.common.core.domain.R;
|
|
||||||
import org.ruoyi.common.core.validate.AddGroup;
|
|
||||||
import org.ruoyi.common.core.validate.EditGroup;
|
|
||||||
import org.ruoyi.common.log.enums.BusinessType;
|
|
||||||
import org.ruoyi.common.excel.utils.ExcelUtil;
|
|
||||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 知识图谱片段
|
|
||||||
*
|
|
||||||
* @author ageerle
|
|
||||||
* @date 2025-12-17
|
|
||||||
*/
|
|
||||||
@Validated
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/system/graphSegment")
|
|
||||||
public class KnowledgeGraphSegmentController extends BaseController {
|
|
||||||
|
|
||||||
private final IKnowledgeGraphSegmentService knowledgeGraphSegmentService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询知识图谱片段列表
|
|
||||||
*/
|
|
||||||
@SaCheckPermission("system:graphSegment:list")
|
|
||||||
@GetMapping("/list")
|
|
||||||
public TableDataInfo<KnowledgeGraphSegmentVo> list(KnowledgeGraphSegmentBo bo, PageQuery pageQuery) {
|
|
||||||
return knowledgeGraphSegmentService.queryPageList(bo, pageQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 导出知识图谱片段列表
|
|
||||||
*/
|
|
||||||
@SaCheckPermission("system:graphSegment:export")
|
|
||||||
@Log(title = "知识图谱片段", businessType = BusinessType.EXPORT)
|
|
||||||
@PostMapping("/export")
|
|
||||||
public void export(KnowledgeGraphSegmentBo bo, HttpServletResponse response) {
|
|
||||||
List<KnowledgeGraphSegmentVo> list = knowledgeGraphSegmentService.queryList(bo);
|
|
||||||
ExcelUtil.exportExcel(list, "知识图谱片段", KnowledgeGraphSegmentVo.class, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取知识图谱片段详细信息
|
|
||||||
*
|
|
||||||
* @param id 主键
|
|
||||||
*/
|
|
||||||
@SaCheckPermission("system:graphSegment:query")
|
|
||||||
@GetMapping("/{id}")
|
|
||||||
public R<KnowledgeGraphSegmentVo> getInfo(@NotNull(message = "主键不能为空")
|
|
||||||
@PathVariable Long id) {
|
|
||||||
return R.ok(knowledgeGraphSegmentService.queryById(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 新增知识图谱片段
|
|
||||||
*/
|
|
||||||
@SaCheckPermission("system:graphSegment:add")
|
|
||||||
@Log(title = "知识图谱片段", businessType = BusinessType.INSERT)
|
|
||||||
@RepeatSubmit()
|
|
||||||
@PostMapping()
|
|
||||||
public R<Void> add(@Validated(AddGroup.class) @RequestBody KnowledgeGraphSegmentBo bo) {
|
|
||||||
return toAjax(knowledgeGraphSegmentService.insertByBo(bo));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改知识图谱片段
|
|
||||||
*/
|
|
||||||
@SaCheckPermission("system:graphSegment:edit")
|
|
||||||
@Log(title = "知识图谱片段", businessType = BusinessType.UPDATE)
|
|
||||||
@RepeatSubmit()
|
|
||||||
@PutMapping()
|
|
||||||
public R<Void> edit(@Validated(EditGroup.class) @RequestBody KnowledgeGraphSegmentBo bo) {
|
|
||||||
return toAjax(knowledgeGraphSegmentService.updateByBo(bo));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除知识图谱片段
|
|
||||||
*
|
|
||||||
* @param ids 主键串
|
|
||||||
*/
|
|
||||||
@SaCheckPermission("system:graphSegment:remove")
|
|
||||||
@Log(title = "知识图谱片段", businessType = BusinessType.DELETE)
|
|
||||||
@DeleteMapping("/{ids}")
|
|
||||||
public R<Void> remove(@NotEmpty(message = "主键不能为空")
|
|
||||||
@PathVariable Long[] ids) {
|
|
||||||
return toAjax(knowledgeGraphSegmentService.deleteWithValidByIds(List.of(ids), true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,7 +15,11 @@ public enum ChatModeType {
|
|||||||
DEEP_SEEK("deepseek", "深度求索"),
|
DEEP_SEEK("deepseek", "深度求索"),
|
||||||
QIAN_WEN("qianwen", "通义千问"),
|
QIAN_WEN("qianwen", "通义千问"),
|
||||||
OPEN_AI("openai", "openai"),
|
OPEN_AI("openai", "openai"),
|
||||||
PPIO("ppio", "ppio");
|
PPIO("ppio", "ppio"),
|
||||||
|
CUSTOM_API("custom_api", "自定义API"),
|
||||||
|
MINIMAX("minimax", "MiniMax"),
|
||||||
|
XIAOMI("xiaomi", "小米MiMo"),
|
||||||
|
DIFY("dify", "Dify平台");
|
||||||
private final String code;
|
private final String code;
|
||||||
private final String description;
|
private final String description;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
package org.ruoyi.service.chat;
|
package org.ruoyi.service.chat;
|
||||||
|
|
||||||
|
import dev.langchain4j.model.chat.ChatModel;
|
||||||
import dev.langchain4j.model.chat.StreamingChatModel;
|
import dev.langchain4j.model.chat.StreamingChatModel;
|
||||||
|
import dev.langchain4j.model.openai.OpenAiChatModel;
|
||||||
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
|
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
|
||||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 聊天消息Service接口
|
* 聊天消息Service接口
|
||||||
*
|
*
|
||||||
@@ -21,6 +25,23 @@ public interface AbstractChatService {
|
|||||||
*/
|
*/
|
||||||
StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo, ChatRequest chatRequest);
|
StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo, ChatRequest chatRequest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建同步聊天模型(供 Agent/SupervisorAgent 使用)
|
||||||
|
* 默认实现使用 OpenAI 兼容协议,适用于 OpenAI、DeepSeek、PPIO 等兼容接口的 provider。
|
||||||
|
* ZhiPu、QianWen、Ollama 等需覆盖此方法使用各自 SDK。
|
||||||
|
*
|
||||||
|
* @param chatModelVo 模型配置
|
||||||
|
* @return 同步聊天模型实例
|
||||||
|
*/
|
||||||
|
default ChatModel buildChatModel(ChatModelVo chatModelVo) {
|
||||||
|
return OpenAiChatModel.builder()
|
||||||
|
.baseUrl(chatModelVo.getApiHost())
|
||||||
|
.apiKey(chatModelVo.getApiKey())
|
||||||
|
.modelName(chatModelVo.getModelName())
|
||||||
|
.timeout(Duration.ofSeconds(120))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取服务提供商名称
|
* 获取服务提供商名称
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import org.ruoyi.common.sse.core.SseEmitterManager;
|
|||||||
import org.ruoyi.common.sse.utils.SseMessageUtils;
|
import org.ruoyi.common.sse.utils.SseMessageUtils;
|
||||||
import org.ruoyi.domain.bo.vector.QueryVectorBo;
|
import org.ruoyi.domain.bo.vector.QueryVectorBo;
|
||||||
import org.ruoyi.domain.vo.knowledge.KnowledgeInfoVo;
|
import org.ruoyi.domain.vo.knowledge.KnowledgeInfoVo;
|
||||||
|
import org.ruoyi.enums.ChatModeType;
|
||||||
import org.ruoyi.factory.ChatServiceFactory;
|
import org.ruoyi.factory.ChatServiceFactory;
|
||||||
import org.ruoyi.mcp.service.core.ToolProviderFactory;
|
import org.ruoyi.mcp.service.core.ToolProviderFactory;
|
||||||
import org.ruoyi.observability.*;
|
import org.ruoyi.observability.*;
|
||||||
@@ -97,6 +98,8 @@ public class ChatServiceFacade implements IChatService {
|
|||||||
|
|
||||||
private final ToolProviderFactory toolProviderFactory;
|
private final ToolProviderFactory toolProviderFactory;
|
||||||
|
|
||||||
|
private final org.ruoyi.service.chat.impl.provider.DifyWorkflowService difyWorkflowService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 内存实例缓存,避免同一会话重复创建
|
* 内存实例缓存,避免同一会话重复创建
|
||||||
* Key: sessionId, Value: MessageWindowChatMemory实例
|
* Key: sessionId, Value: MessageWindowChatMemory实例
|
||||||
@@ -163,6 +166,14 @@ public class ChatServiceFacade implements IChatService {
|
|||||||
* @return 如果需要提前返回则返回SseEmitter,否则返回null
|
* @return 如果需要提前返回则返回SseEmitter,否则返回null
|
||||||
*/
|
*/
|
||||||
private SseEmitter handleSpecialChatModes(ChatRequest chatRequest) {
|
private SseEmitter handleSpecialChatModes(ChatRequest chatRequest) {
|
||||||
|
// 处理 Dify 工作流对话
|
||||||
|
if (chatRequest.getEnableWorkFlow()
|
||||||
|
&& chatRequest.getChatModelVo() != null
|
||||||
|
&& ChatModeType.DIFY.getCode().equals(chatRequest.getChatModelVo().getProviderCode())) {
|
||||||
|
log.info("处理Dify工作流对话,会话: {}", chatRequest.getSessionId());
|
||||||
|
return difyWorkflowService.streaming(chatRequest.getChatModelVo(), chatRequest);
|
||||||
|
}
|
||||||
|
|
||||||
// 处理工作流对话
|
// 处理工作流对话
|
||||||
if (chatRequest.getEnableWorkFlow()) {
|
if (chatRequest.getEnableWorkFlow()) {
|
||||||
log.info("处理工作流对话,会话: {}", chatRequest.getSessionId());
|
log.info("处理工作流对话,会话: {}", chatRequest.getSessionId());
|
||||||
@@ -430,8 +441,12 @@ public class ChatServiceFacade implements IChatService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dify 自带 RAG 知识库检索,跳过本地向量库查询
|
||||||
|
boolean isDifyProvider = chatRequest.getChatModelVo() != null
|
||||||
|
&& ChatModeType.DIFY.getCode().equals(chatRequest.getChatModelVo().getProviderCode());
|
||||||
|
|
||||||
// 从向量库查询相关历史消息(知识库内容作为上下文)
|
// 从向量库查询相关历史消息(知识库内容作为上下文)
|
||||||
if (chatRequest.getKnowledgeId() != null) {
|
if (chatRequest.getKnowledgeId() != null && !isDifyProvider) {
|
||||||
// 查询知识库信息
|
// 查询知识库信息
|
||||||
KnowledgeInfoVo knowledgeInfoVo = knowledgeInfoService.queryById(Long.valueOf(chatRequest.getKnowledgeId()));
|
KnowledgeInfoVo knowledgeInfoVo = knowledgeInfoService.queryById(Long.valueOf(chatRequest.getKnowledgeId()));
|
||||||
if (knowledgeInfoVo == null) {
|
if (knowledgeInfoVo == null) {
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package org.ruoyi.service.chat.impl.provider;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import dev.langchain4j.model.chat.ChatModel;
|
||||||
|
import dev.langchain4j.model.chat.StreamingChatModel;
|
||||||
|
import dev.langchain4j.model.openai.OpenAiChatModel;
|
||||||
|
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
|
||||||
|
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||||
|
import org.ruoyi.enums.ChatModeType;
|
||||||
|
import org.ruoyi.observability.MyChatModelListener;
|
||||||
|
import org.ruoyi.service.chat.AbstractChatService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义 API 服务调用
|
||||||
|
*
|
||||||
|
* 适用于 OpenAI 兼容接口或仅通过通用 HTTP 协议接入的第三方大模型服务。
|
||||||
|
* 通过模型配置中的 apiHost / apiKey / modelName 即可复用,不需要再写死具体供应商。
|
||||||
|
*
|
||||||
|
* @author better
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class CustomApiServiceImpl implements AbstractChatService {
|
||||||
|
|
||||||
|
private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(180);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo, ChatRequest chatRequest) {
|
||||||
|
return OpenAiStreamingChatModel.builder()
|
||||||
|
.baseUrl(normalizeBaseUrl(chatModelVo.getApiHost()))
|
||||||
|
.apiKey(defaultIfBlank(chatModelVo.getApiKey(), "EMPTY"))
|
||||||
|
.modelName(chatModelVo.getModelName())
|
||||||
|
.timeout(DEFAULT_TIMEOUT)
|
||||||
|
.listeners(List.of(new MyChatModelListener()))
|
||||||
|
.returnThinking(chatRequest.getEnableThinking())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChatModel buildChatModel(ChatModelVo chatModelVo) {
|
||||||
|
return OpenAiChatModel.builder()
|
||||||
|
.baseUrl(normalizeBaseUrl(chatModelVo.getApiHost()))
|
||||||
|
.apiKey(defaultIfBlank(chatModelVo.getApiKey(), "EMPTY"))
|
||||||
|
.modelName(chatModelVo.getModelName())
|
||||||
|
.timeout(DEFAULT_TIMEOUT)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProviderName() {
|
||||||
|
return ChatModeType.CUSTOM_API.getCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeBaseUrl(String baseUrl) {
|
||||||
|
if (StrUtil.isBlank(baseUrl)) {
|
||||||
|
throw new IllegalArgumentException("自定义API的请求地址(apiHost)不能为空");
|
||||||
|
}
|
||||||
|
return baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String defaultIfBlank(String value, String defaultValue) {
|
||||||
|
return StrUtil.isBlank(value) ? defaultValue : value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package org.ruoyi.service.chat.impl.provider;
|
||||||
|
|
||||||
|
import dev.langchain4j.model.chat.ChatModel;
|
||||||
|
import dev.langchain4j.model.chat.StreamingChatModel;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
|
||||||
|
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||||
|
import org.ruoyi.enums.ChatModeType;
|
||||||
|
import org.ruoyi.service.chat.AbstractChatService;
|
||||||
|
import org.ruoyi.service.chat.impl.provider.model.DifyStreamingChatModel;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dify 平台对话服务
|
||||||
|
* <p>
|
||||||
|
* 通过 dify-java-client 接入 Dify 的对话型应用 (Chat App) 和
|
||||||
|
* 工作流编排对话应用 (Chatflow App),支持流式 SSE 响应。
|
||||||
|
*
|
||||||
|
* @author better
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DifyChatServiceImpl implements AbstractChatService {
|
||||||
|
|
||||||
|
private final DifyConversationService difyConversationService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo, ChatRequest chatRequest) {
|
||||||
|
return new DifyStreamingChatModel(chatModelVo, chatRequest, difyConversationService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChatModel buildChatModel(ChatModelVo chatModelVo) {
|
||||||
|
throw new UnsupportedOperationException("Dify 不支持同步 ChatModel,请使用流式模式");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProviderName() {
|
||||||
|
return ChatModeType.DIFY.getCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package org.ruoyi.service.chat.impl.provider;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dify 会话映射管理
|
||||||
|
* <p>
|
||||||
|
* 维护 ruoyi sessionId 与 Dify conversation_id 的映射关系,
|
||||||
|
* 确保多轮对话上下文连续。
|
||||||
|
*
|
||||||
|
* @author better
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class DifyConversationService {
|
||||||
|
|
||||||
|
private final ConcurrentHashMap<Long, String> conversationMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public String getConversationId(Long sessionId) {
|
||||||
|
return conversationMap.get(sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveMapping(Long sessionId, String difyConversationId) {
|
||||||
|
if (sessionId != null && difyConversationId != null) {
|
||||||
|
conversationMap.put(sessionId, difyConversationId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearMapping(Long sessionId) {
|
||||||
|
if (sessionId != null) {
|
||||||
|
conversationMap.remove(sessionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
package org.ruoyi.service.chat.impl.provider;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import io.github.imfangs.dify.client.DifyClientFactory;
|
||||||
|
import io.github.imfangs.dify.client.DifyWorkflowClient;
|
||||||
|
import io.github.imfangs.dify.client.enums.ResponseMode;
|
||||||
|
import io.github.imfangs.dify.client.event.ErrorEvent;
|
||||||
|
import io.github.imfangs.dify.client.event.WorkflowFinishedEvent;
|
||||||
|
import io.github.imfangs.dify.client.event.WorkflowTextChunkEvent;
|
||||||
|
import io.github.imfangs.dify.client.callback.WorkflowStreamCallback;
|
||||||
|
import io.github.imfangs.dify.client.model.workflow.WorkflowRunRequest;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
|
||||||
|
import org.ruoyi.common.chat.domain.dto.request.WorkFlowRunner;
|
||||||
|
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||||
|
import org.ruoyi.common.sse.utils.SseMessageUtils;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dify 工作流执行服务
|
||||||
|
* <p>
|
||||||
|
* 通过 DifyWorkflowClient 调用 Dify 平台上部署的工作流应用,
|
||||||
|
* 并将节点事件通过 SSE 实时推送给前端。
|
||||||
|
*
|
||||||
|
* @author better
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class DifyWorkflowService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流式执行 Dify 工作流
|
||||||
|
*
|
||||||
|
* @param chatModelVo 模型配置(apiHost= Dify 地址, apiKey= Dify 密钥)
|
||||||
|
* @param chatRequest 聊天请求
|
||||||
|
* @return SSE emitter
|
||||||
|
*/
|
||||||
|
public SseEmitter streaming(ChatModelVo chatModelVo, ChatRequest chatRequest) {
|
||||||
|
Long userId = chatRequest.getUserId();
|
||||||
|
String tokenValue = chatRequest.getTokenValue();
|
||||||
|
SseEmitter emitter = chatRequest.getEmitter();
|
||||||
|
|
||||||
|
// 构建 Dify 工作流请求参数
|
||||||
|
Map<String, Object> inputs = convertInputs(chatRequest.getWorkFlowRunner());
|
||||||
|
|
||||||
|
WorkflowRunRequest request = WorkflowRunRequest.builder()
|
||||||
|
.inputs(inputs)
|
||||||
|
.responseMode(ResponseMode.STREAMING)
|
||||||
|
.user(String.valueOf(userId))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
DifyWorkflowClient client = DifyClientFactory.createWorkflowClient(
|
||||||
|
normalizeBaseUrl(chatModelVo.getApiHost()),
|
||||||
|
chatModelVo.getApiKey());
|
||||||
|
|
||||||
|
// 异步执行,避免阻塞请求线程
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
try {
|
||||||
|
client.runWorkflowStream(request, new WorkflowStreamCallback() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWorkflowTextChunk(WorkflowTextChunkEvent event) {
|
||||||
|
String text = event.getData() != null ? event.getData().getText() : null;
|
||||||
|
if (text != null) {
|
||||||
|
SseMessageUtils.sendContent(userId, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWorkflowFinished(WorkflowFinishedEvent event) {
|
||||||
|
// 将最终输出作为内容发送
|
||||||
|
if (event.getData() != null && event.getData().getOutputs() != null) {
|
||||||
|
Map<String, Object> outputs = event.getData().getOutputs();
|
||||||
|
for (Map.Entry<String, Object> entry : outputs.entrySet()) {
|
||||||
|
SseMessageUtils.sendContent(userId,
|
||||||
|
entry.getKey() + ": " + entry.getValue() + "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SseMessageUtils.sendDone(userId);
|
||||||
|
SseMessageUtils.completeConnection(userId, tokenValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorEvent event) {
|
||||||
|
SseMessageUtils.sendError(userId, event.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(Throwable throwable) {
|
||||||
|
log.error("Dify 工作流执行异常", throwable);
|
||||||
|
SseMessageUtils.sendError(userId, throwable.getMessage());
|
||||||
|
SseMessageUtils.completeConnection(userId, tokenValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Dify 工作流执行失败", e);
|
||||||
|
SseMessageUtils.sendError(userId, e.getMessage());
|
||||||
|
SseMessageUtils.completeConnection(userId, tokenValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return emitter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 WorkFlowRunner.inputs (List<ObjectNode>) 转换为 Dify 所需的 Map
|
||||||
|
*/
|
||||||
|
private Map<String, Object> convertInputs(WorkFlowRunner runner) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
if (runner == null || runner.getInputs() == null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
for (ObjectNode node : runner.getInputs()) {
|
||||||
|
Iterator<Map.Entry<String, JsonNode>> fields = node.fields();
|
||||||
|
while (fields.hasNext()) {
|
||||||
|
Map.Entry<String, JsonNode> field = fields.next();
|
||||||
|
result.put(field.getKey(), field.getValue().asText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeBaseUrl(String baseUrl) {
|
||||||
|
if (baseUrl == null || baseUrl.isBlank()) {
|
||||||
|
throw new IllegalArgumentException("Dify API 地址(apiHost)不能为空");
|
||||||
|
}
|
||||||
|
return baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package org.ruoyi.service.chat.impl.provider;
|
||||||
|
|
||||||
|
|
||||||
|
import dev.langchain4j.model.chat.StreamingChatModel;
|
||||||
|
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
|
||||||
|
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||||
|
import org.ruoyi.enums.ChatModeType;
|
||||||
|
import org.ruoyi.observability.MyChatModelListener;
|
||||||
|
import org.ruoyi.service.chat.AbstractChatService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小米MiMo服务调用
|
||||||
|
* <p>
|
||||||
|
* 小米提供OpenAI兼容的API接口,支持MiMo等模型。
|
||||||
|
*
|
||||||
|
* @author ageerle
|
||||||
|
* @date 2026/4/19
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class MiMoServiceImpl implements AbstractChatService {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo, ChatRequest chatRequest) {
|
||||||
|
return OpenAiStreamingChatModel.builder()
|
||||||
|
.baseUrl(chatModelVo.getApiHost())
|
||||||
|
.apiKey(chatModelVo.getApiKey())
|
||||||
|
.modelName(chatModelVo.getModelName())
|
||||||
|
.listeners(List.of(new MyChatModelListener()))
|
||||||
|
.returnThinking(chatRequest.getEnableThinking())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProviderName() {
|
||||||
|
return ChatModeType.XIAOMI.getCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package org.ruoyi.service.chat.impl.provider;
|
||||||
|
|
||||||
|
import dev.langchain4j.model.chat.StreamingChatModel;
|
||||||
|
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
|
||||||
|
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||||
|
import org.ruoyi.enums.ChatModeType;
|
||||||
|
import org.ruoyi.observability.MyChatModelListener;
|
||||||
|
import org.ruoyi.service.chat.AbstractChatService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MiniMax服务调用
|
||||||
|
* <p>
|
||||||
|
* MiniMax提供OpenAI兼容的API接口,支持MiniMax-M2.7、MiniMax-M2.5等模型。
|
||||||
|
* API地址:https://api.minimax.io/v1
|
||||||
|
*
|
||||||
|
* @author octopus
|
||||||
|
* @date 2026/3/21
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class MinimaxServiceImpl implements AbstractChatService {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo, ChatRequest chatRequest) {
|
||||||
|
return OpenAiStreamingChatModel.builder()
|
||||||
|
.baseUrl(chatModelVo.getApiHost())
|
||||||
|
.apiKey(chatModelVo.getApiKey())
|
||||||
|
.modelName(chatModelVo.getModelName())
|
||||||
|
.listeners(List.of(new MyChatModelListener()))
|
||||||
|
.returnThinking(chatRequest.getEnableThinking())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProviderName() {
|
||||||
|
return ChatModeType.MINIMAX.getCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package org.ruoyi.service.chat.impl.provider;
|
package org.ruoyi.service.chat.impl.provider;
|
||||||
|
|
||||||
|
|
||||||
|
import dev.langchain4j.model.chat.ChatModel;
|
||||||
import dev.langchain4j.model.chat.StreamingChatModel;
|
import dev.langchain4j.model.chat.StreamingChatModel;
|
||||||
|
import dev.langchain4j.model.ollama.OllamaChatModel;
|
||||||
import dev.langchain4j.model.ollama.OllamaStreamingChatModel;
|
import dev.langchain4j.model.ollama.OllamaStreamingChatModel;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -37,6 +39,14 @@ public class OllamaServiceImpl implements AbstractChatService {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChatModel buildChatModel(ChatModelVo chatModelVo) {
|
||||||
|
return OllamaChatModel.builder()
|
||||||
|
.baseUrl(chatModelVo.getApiHost())
|
||||||
|
.modelName(chatModelVo.getModelName())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getProviderName() {
|
public String getProviderName() {
|
||||||
return ChatModeType.OLLAMA.getCode();
|
return ChatModeType.OLLAMA.getCode();
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package org.ruoyi.service.chat.impl.provider;
|
package org.ruoyi.service.chat.impl.provider;
|
||||||
|
|
||||||
|
|
||||||
|
import dev.langchain4j.community.model.dashscope.QwenChatModel;
|
||||||
import dev.langchain4j.community.model.dashscope.QwenStreamingChatModel;
|
import dev.langchain4j.community.model.dashscope.QwenStreamingChatModel;
|
||||||
|
import dev.langchain4j.model.chat.ChatModel;
|
||||||
import dev.langchain4j.model.chat.StreamingChatModel;
|
import dev.langchain4j.model.chat.StreamingChatModel;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -38,6 +40,14 @@ public class QianWenChatServiceImpl implements AbstractChatService {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChatModel buildChatModel(ChatModelVo chatModelVo) {
|
||||||
|
return QwenChatModel.builder()
|
||||||
|
.apiKey(chatModelVo.getApiKey())
|
||||||
|
.modelName(chatModelVo.getModelName())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getProviderName() {
|
public String getProviderName() {
|
||||||
return ChatModeType.QIAN_WEN.getCode();
|
return ChatModeType.QIAN_WEN.getCode();
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package org.ruoyi.service.chat.impl.provider;
|
package org.ruoyi.service.chat.impl.provider;
|
||||||
|
|
||||||
|
|
||||||
|
import dev.langchain4j.community.model.zhipu.ZhipuAiChatModel;
|
||||||
import dev.langchain4j.community.model.zhipu.ZhipuAiStreamingChatModel;
|
import dev.langchain4j.community.model.zhipu.ZhipuAiStreamingChatModel;
|
||||||
|
import dev.langchain4j.model.chat.ChatModel;
|
||||||
import dev.langchain4j.model.chat.StreamingChatModel;
|
import dev.langchain4j.model.chat.StreamingChatModel;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -35,6 +37,14 @@ public class ZhiPuChatServiceImpl implements AbstractChatService {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChatModel buildChatModel(ChatModelVo chatModelVo) {
|
||||||
|
return ZhipuAiChatModel.builder()
|
||||||
|
.apiKey(chatModelVo.getApiKey())
|
||||||
|
.model(chatModelVo.getModelName())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getProviderName() {
|
public String getProviderName() {
|
||||||
return ChatModeType.ZHI_PU.getCode();
|
return ChatModeType.ZHI_PU.getCode();
|
||||||
|
|||||||
@@ -0,0 +1,172 @@
|
|||||||
|
package org.ruoyi.service.chat.impl.provider.model;
|
||||||
|
|
||||||
|
import dev.langchain4j.data.message.AiMessage;
|
||||||
|
import dev.langchain4j.data.message.ChatMessage;
|
||||||
|
import dev.langchain4j.data.message.UserMessage;
|
||||||
|
import dev.langchain4j.model.chat.StreamingChatModel;
|
||||||
|
import dev.langchain4j.model.chat.response.ChatResponse;
|
||||||
|
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
|
||||||
|
import io.github.imfangs.dify.client.DifyChatClient;
|
||||||
|
import io.github.imfangs.dify.client.DifyClientFactory;
|
||||||
|
import io.github.imfangs.dify.client.enums.ResponseMode;
|
||||||
|
import io.github.imfangs.dify.client.event.ErrorEvent;
|
||||||
|
import io.github.imfangs.dify.client.event.MessageEndEvent;
|
||||||
|
import io.github.imfangs.dify.client.event.MessageEvent;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
|
||||||
|
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||||
|
import org.ruoyi.service.chat.impl.provider.DifyConversationService;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dify 流式聊天模型适配器
|
||||||
|
* <p>
|
||||||
|
* 将 Dify 的回调式流式响应适配为 langchain4j 的 StreamingChatModel 接口,
|
||||||
|
* 使 ChatServiceFacade 可以像其他 provider 一样统一调用。
|
||||||
|
*
|
||||||
|
* @author better
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class DifyStreamingChatModel implements StreamingChatModel {
|
||||||
|
|
||||||
|
private final ChatModelVo chatModelVo;
|
||||||
|
private final ChatRequest chatRequest;
|
||||||
|
private final DifyConversationService conversationService;
|
||||||
|
|
||||||
|
public DifyStreamingChatModel(ChatModelVo chatModelVo, ChatRequest chatRequest,
|
||||||
|
DifyConversationService conversationService) {
|
||||||
|
this.chatModelVo = chatModelVo;
|
||||||
|
this.chatRequest = chatRequest;
|
||||||
|
this.conversationService = conversationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void chat(List<ChatMessage> messages, StreamingChatResponseHandler handler) {
|
||||||
|
// 1. 从 langchain4j 消息列表中提取最后一条用户消息作为 query
|
||||||
|
String query = extractUserQuery(messages);
|
||||||
|
|
||||||
|
// 2. 获取 Dify conversation_id(多轮对话连续性)
|
||||||
|
String conversationId = null;
|
||||||
|
if (chatRequest.getSessionId() != null) {
|
||||||
|
conversationId = conversationService.getConversationId(chatRequest.getSessionId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 构建 Dify 请求
|
||||||
|
io.github.imfangs.dify.client.model.chat.ChatMessage difyMessage = io.github.imfangs.dify.client.model.chat.ChatMessage.builder()
|
||||||
|
.query(query)
|
||||||
|
.user(String.valueOf(chatRequest.getUserId()))
|
||||||
|
.responseMode(ResponseMode.STREAMING)
|
||||||
|
.conversationId(conversationId)
|
||||||
|
.autoGenerateName(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 4. 创建 Dify 客户端并发送流式请求
|
||||||
|
try {
|
||||||
|
DifyChatClient client = DifyClientFactory.createChatClient(
|
||||||
|
normalizeBaseUrl(chatModelVo.getApiHost()),
|
||||||
|
chatModelVo.getApiKey());
|
||||||
|
|
||||||
|
client.sendChatMessageStream(difyMessage, new DifyChatStreamAdapter(handler));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Dify 流式对话调用失败", e);
|
||||||
|
handler.onError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void chat(String userMessage, StreamingChatResponseHandler handler) {
|
||||||
|
io.github.imfangs.dify.client.model.chat.ChatMessage difyMessage = io.github.imfangs.dify.client.model.chat.ChatMessage.builder()
|
||||||
|
.query(userMessage)
|
||||||
|
.user(String.valueOf(chatRequest.getUserId()))
|
||||||
|
.responseMode(ResponseMode.STREAMING)
|
||||||
|
.conversationId(chatRequest.getSessionId() != null
|
||||||
|
? conversationService.getConversationId(chatRequest.getSessionId()) : null)
|
||||||
|
.autoGenerateName(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try {
|
||||||
|
DifyChatClient client = DifyClientFactory.createChatClient(
|
||||||
|
normalizeBaseUrl(chatModelVo.getApiHost()),
|
||||||
|
chatModelVo.getApiKey());
|
||||||
|
|
||||||
|
client.sendChatMessageStream(difyMessage, new DifyChatStreamAdapter(handler));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Dify 流式对话调用失败", e);
|
||||||
|
handler.onError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 langchain4j 消息列表中提取最后一条用户消息文本
|
||||||
|
*/
|
||||||
|
private String extractUserQuery(List<ChatMessage> messages) {
|
||||||
|
for (int i = messages.size() - 1; i >= 0; i--) {
|
||||||
|
ChatMessage msg = messages.get(i);
|
||||||
|
if (msg instanceof UserMessage) {
|
||||||
|
return ((UserMessage) msg).singleText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeBaseUrl(String baseUrl) {
|
||||||
|
if (baseUrl == null || baseUrl.isBlank()) {
|
||||||
|
throw new IllegalArgumentException("Dify API 地址(apiHost)不能为空");
|
||||||
|
}
|
||||||
|
return baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dify 回调适配器
|
||||||
|
* 将 Dify ChatStreamCallback 事件转发给 langchain4j StreamingChatResponseHandler
|
||||||
|
*/
|
||||||
|
private class DifyChatStreamAdapter implements io.github.imfangs.dify.client.callback.ChatStreamCallback {
|
||||||
|
|
||||||
|
private final StreamingChatResponseHandler handler;
|
||||||
|
private final StringBuilder fullResponse = new StringBuilder();
|
||||||
|
|
||||||
|
DifyChatStreamAdapter(StreamingChatResponseHandler handler) {
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(MessageEvent event) {
|
||||||
|
String answer = event.getAnswer();
|
||||||
|
if (answer != null) {
|
||||||
|
fullResponse.append(answer);
|
||||||
|
handler.onPartialResponse(answer);
|
||||||
|
}
|
||||||
|
// 保存 Dify conversation_id 以维持多轮对话
|
||||||
|
if (event.getConversationId() != null && chatRequest.getSessionId() != null) {
|
||||||
|
conversationService.saveMapping(chatRequest.getSessionId(), event.getConversationId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessageEnd(MessageEndEvent event) {
|
||||||
|
// 保存 conversation_id
|
||||||
|
if (event.getConversationId() != null && chatRequest.getSessionId() != null) {
|
||||||
|
conversationService.saveMapping(chatRequest.getSessionId(), event.getConversationId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建完整的 ChatResponse 交给上层处理
|
||||||
|
AiMessage aiMessage = new AiMessage(fullResponse.toString());
|
||||||
|
ChatResponse response = ChatResponse.builder()
|
||||||
|
.aiMessage(aiMessage)
|
||||||
|
.id(event.getMessageId())
|
||||||
|
.build();
|
||||||
|
handler.onCompleteResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorEvent event) {
|
||||||
|
handler.onError(new RuntimeException(event.getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(Throwable throwable) {
|
||||||
|
handler.onError(throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package org.ruoyi.service.embed.impl;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MiniMax嵌入模型(兼容OpenAI接口)
|
||||||
|
* <p>
|
||||||
|
* 支持embo-01模型,1536维度向量。
|
||||||
|
* API地址:https://api.minimax.io/v1
|
||||||
|
*
|
||||||
|
* @author octopus
|
||||||
|
* @date 2026/3/21
|
||||||
|
*/
|
||||||
|
@Component("minimax")
|
||||||
|
public class MinimaxEmbeddingProvider extends OpenAiEmbeddingProvider {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package org.ruoyi.service.embed.impl;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import dev.langchain4j.community.model.zhipu.ZhipuAiEmbeddingModel;
|
||||||
|
import dev.langchain4j.data.embedding.Embedding;
|
||||||
|
import dev.langchain4j.data.segment.TextSegment;
|
||||||
|
import dev.langchain4j.model.embedding.EmbeddingModel;
|
||||||
|
import dev.langchain4j.model.output.Response;
|
||||||
|
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||||
|
import org.ruoyi.enums.ModalityType;
|
||||||
|
import org.ruoyi.service.embed.BaseEmbedModelService;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author:yang
|
||||||
|
* @Date:
|
||||||
|
* @Description: 智谱AI嵌入模型
|
||||||
|
*/
|
||||||
|
@Component("zhipu")
|
||||||
|
public class ZhipuAiEmbeddingProvider implements BaseEmbedModelService {
|
||||||
|
protected ChatModelVo chatModelVo;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(ChatModelVo config) {
|
||||||
|
this.chatModelVo = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<ModalityType> getSupportedModalities() {
|
||||||
|
return Set.of(ModalityType.TEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<List<Embedding>> embedAll(List<TextSegment> textSegments) {
|
||||||
|
EmbeddingModel model = ZhipuAiEmbeddingModel.builder()
|
||||||
|
.baseUrl(chatModelVo.getApiHost())
|
||||||
|
.apiKey(chatModelVo.getApiKey())
|
||||||
|
.model(chatModelVo.getModelName())
|
||||||
|
.dimensions(chatModelVo.getModelDimension())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return model.embedAll(textSegments);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package org.ruoyi.enums;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for ChatModeType enum
|
||||||
|
*/
|
||||||
|
class ChatModeTypeTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void minimaxEnumExists() {
|
||||||
|
ChatModeType minimax = ChatModeType.MINIMAX;
|
||||||
|
assertNotNull(minimax);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void minimaxCode_isMinimax() {
|
||||||
|
assertEquals("minimax", ChatModeType.MINIMAX.getCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void minimaxDescription_isMiniMax() {
|
||||||
|
assertEquals("MiniMax", ChatModeType.MINIMAX.getDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void allProviders_haveUniqueCode() {
|
||||||
|
ChatModeType[] values = ChatModeType.values();
|
||||||
|
long uniqueCodes = java.util.Arrays.stream(values)
|
||||||
|
.map(ChatModeType::getCode)
|
||||||
|
.distinct()
|
||||||
|
.count();
|
||||||
|
assertEquals(values.length, uniqueCodes, "All providers must have unique codes");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void valueOf_minimax() {
|
||||||
|
assertEquals(ChatModeType.MINIMAX, ChatModeType.valueOf("MINIMAX"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package org.ruoyi.integration;
|
||||||
|
|
||||||
|
import dev.langchain4j.model.chat.StreamingChatModel;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||||
|
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
|
||||||
|
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||||
|
import org.ruoyi.service.chat.impl.provider.MinimaxServiceImpl;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration tests for MiniMax provider.
|
||||||
|
* These tests require a valid MINIMAX_API_KEY environment variable.
|
||||||
|
*/
|
||||||
|
@EnabledIfEnvironmentVariable(named = "MINIMAX_API_KEY", matches = ".+")
|
||||||
|
class MinimaxIntegrationTest {
|
||||||
|
|
||||||
|
private MinimaxServiceImpl minimaxService;
|
||||||
|
private String apiKey;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
minimaxService = new MinimaxServiceImpl();
|
||||||
|
apiKey = System.getenv("MINIMAX_API_KEY");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildStreamingChatModel_withRealApiKey_M27() {
|
||||||
|
ChatModelVo modelVo = new ChatModelVo();
|
||||||
|
modelVo.setApiHost("https://api.minimax.io/v1");
|
||||||
|
modelVo.setApiKey(apiKey);
|
||||||
|
modelVo.setModelName("MiniMax-M2.7");
|
||||||
|
|
||||||
|
ChatRequest request = new ChatRequest();
|
||||||
|
request.setEnableThinking(false);
|
||||||
|
|
||||||
|
StreamingChatModel model = minimaxService.buildStreamingChatModel(modelVo, request);
|
||||||
|
assertNotNull(model, "Should create streaming model with real API key");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildStreamingChatModel_withRealApiKey_M25() {
|
||||||
|
ChatModelVo modelVo = new ChatModelVo();
|
||||||
|
modelVo.setApiHost("https://api.minimax.io/v1");
|
||||||
|
modelVo.setApiKey(apiKey);
|
||||||
|
modelVo.setModelName("MiniMax-M2.5");
|
||||||
|
|
||||||
|
ChatRequest request = new ChatRequest();
|
||||||
|
request.setEnableThinking(false);
|
||||||
|
|
||||||
|
StreamingChatModel model = minimaxService.buildStreamingChatModel(modelVo, request);
|
||||||
|
assertNotNull(model, "Should create streaming model with M2.5");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildStreamingChatModel_withRealApiKey_M25Highspeed() {
|
||||||
|
ChatModelVo modelVo = new ChatModelVo();
|
||||||
|
modelVo.setApiHost("https://api.minimax.io/v1");
|
||||||
|
modelVo.setApiKey(apiKey);
|
||||||
|
modelVo.setModelName("MiniMax-M2.5-highspeed");
|
||||||
|
|
||||||
|
ChatRequest request = new ChatRequest();
|
||||||
|
request.setEnableThinking(false);
|
||||||
|
|
||||||
|
StreamingChatModel model = minimaxService.buildStreamingChatModel(modelVo, request);
|
||||||
|
assertNotNull(model, "Should create streaming model with M2.5-highspeed");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package org.ruoyi.service.chat.impl.provider;
|
||||||
|
|
||||||
|
import dev.langchain4j.model.chat.StreamingChatModel;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
|
||||||
|
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||||
|
import org.ruoyi.enums.ChatModeType;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for MinimaxServiceImpl
|
||||||
|
*/
|
||||||
|
class MinimaxServiceImplTest {
|
||||||
|
|
||||||
|
private MinimaxServiceImpl minimaxService;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
minimaxService = new MinimaxServiceImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getProviderName_returnsMinimaxCode() {
|
||||||
|
assertEquals("minimax", minimaxService.getProviderName());
|
||||||
|
assertEquals(ChatModeType.MINIMAX.getCode(), minimaxService.getProviderName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildStreamingChatModel_returnsNonNull() {
|
||||||
|
ChatModelVo modelVo = new ChatModelVo();
|
||||||
|
modelVo.setApiHost("https://api.minimax.io/v1");
|
||||||
|
modelVo.setApiKey("test-api-key");
|
||||||
|
modelVo.setModelName("MiniMax-M2.7");
|
||||||
|
|
||||||
|
ChatRequest request = new ChatRequest();
|
||||||
|
request.setEnableThinking(false);
|
||||||
|
|
||||||
|
StreamingChatModel model = minimaxService.buildStreamingChatModel(modelVo, request);
|
||||||
|
assertNotNull(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildStreamingChatModel_withThinkingEnabled() {
|
||||||
|
ChatModelVo modelVo = new ChatModelVo();
|
||||||
|
modelVo.setApiHost("https://api.minimax.io/v1");
|
||||||
|
modelVo.setApiKey("test-api-key");
|
||||||
|
modelVo.setModelName("MiniMax-M2.5");
|
||||||
|
|
||||||
|
ChatRequest request = new ChatRequest();
|
||||||
|
request.setEnableThinking(true);
|
||||||
|
|
||||||
|
StreamingChatModel model = minimaxService.buildStreamingChatModel(modelVo, request);
|
||||||
|
assertNotNull(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildStreamingChatModel_withHighspeedModel() {
|
||||||
|
ChatModelVo modelVo = new ChatModelVo();
|
||||||
|
modelVo.setApiHost("https://api.minimax.io/v1");
|
||||||
|
modelVo.setApiKey("test-api-key");
|
||||||
|
modelVo.setModelName("MiniMax-M2.5-highspeed");
|
||||||
|
|
||||||
|
ChatRequest request = new ChatRequest();
|
||||||
|
request.setEnableThinking(false);
|
||||||
|
|
||||||
|
StreamingChatModel model = minimaxService.buildStreamingChatModel(modelVo, request);
|
||||||
|
assertNotNull(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void implementsAbstractChatService() {
|
||||||
|
assertInstanceOf(org.ruoyi.service.chat.AbstractChatService.class, minimaxService);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package org.ruoyi.service.embed.impl;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||||
|
import org.ruoyi.enums.ModalityType;
|
||||||
|
import org.ruoyi.service.embed.BaseEmbedModelService;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for MinimaxEmbeddingProvider
|
||||||
|
*/
|
||||||
|
class MinimaxEmbeddingProviderTest {
|
||||||
|
|
||||||
|
private MinimaxEmbeddingProvider provider;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
provider = new MinimaxEmbeddingProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void implementsBaseEmbedModelService() {
|
||||||
|
assertInstanceOf(BaseEmbedModelService.class, provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void extendsOpenAiEmbeddingProvider() {
|
||||||
|
assertInstanceOf(OpenAiEmbeddingProvider.class, provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getSupportedModalities_returnsText() {
|
||||||
|
Set<ModalityType> modalities = provider.getSupportedModalities();
|
||||||
|
assertNotNull(modalities);
|
||||||
|
assertTrue(modalities.contains(ModalityType.TEXT));
|
||||||
|
assertEquals(1, modalities.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void configure_setsModelConfig() {
|
||||||
|
ChatModelVo config = new ChatModelVo();
|
||||||
|
config.setApiHost("https://api.minimax.io/v1");
|
||||||
|
config.setApiKey("test-api-key");
|
||||||
|
config.setModelName("embo-01");
|
||||||
|
config.setModelDimension(1536);
|
||||||
|
|
||||||
|
provider.configure(config);
|
||||||
|
// configure sets internal state; verify no exception thrown
|
||||||
|
assertNotNull(provider);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user