> generateWriteContent(@RequestBody @Valid AiWriteGenerateReqVO generateReqVO) {
return writeService.generateWriteContent(generateReqVO, getLoginUserId());
}
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/vo/AiWriteGenerateReqVO.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/vo/AiWriteGenerateReqVO.java
similarity index 100%
rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/vo/AiWriteGenerateReqVO.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/vo/AiWriteGenerateReqVO.java
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/vo/AiWritePageReqVO.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/vo/AiWritePageReqVO.java
similarity index 93%
rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/vo/AiWritePageReqVO.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/vo/AiWritePageReqVO.java
index 047380e422..04f99ae13c 100644
--- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/vo/AiWritePageReqVO.java
+++ b/yudao-module-ai/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_
@Schema(description = "管理后台 - AI 写作分页 Request VO")
@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
public class AiWritePageReqVO extends PageParam {
@Schema(description = "用户编号", example = "28404")
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/vo/AiWriteRespVO.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/vo/AiWriteRespVO.java
similarity index 100%
rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/vo/AiWriteRespVO.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/vo/AiWriteRespVO.java
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/app/package-info.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/app/package-info.java
similarity index 100%
rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/app/package-info.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/app/package-info.java
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/package-info.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/package-info.java
similarity index 100%
rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/package-info.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/package-info.java
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatConversationDO.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatConversationDO.java
similarity index 86%
rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatConversationDO.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatConversationDO.java
index 7d9625f58f..23aec276db 100644
--- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatConversationDO.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatConversationDO.java
@@ -1,9 +1,8 @@
package cn.iocoder.yudao.module.ai.dal.dataobject.chat;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
-import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
-import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
+import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@@ -22,7 +21,6 @@ import java.time.LocalDateTime;
@TableName("ai_chat_conversation")
@KeySequence("ai_chat_conversation_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
-@EqualsAndHashCode(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
@@ -65,21 +63,16 @@ public class AiChatConversationDO extends BaseDO {
*/
private Long roleId;
- /**
- * 知识库编号
- *
- * 关联 {@link AiKnowledgeDO#getId()}
- */
- private Long knowledgeId;
-
/**
* 模型编号
*
- * 关联 {@link AiChatModelDO#getId()} 字段
+ * 关联 {@link AiModelDO#getId()} 字段
*/
private Long modelId;
/**
* 模型标志
+ *
+ * 冗余 {@link AiModelDO#getModel()} 字段
*/
private String model;
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java
similarity index 84%
rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java
index ecd10609f5..2364d750cb 100644
--- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java
@@ -1,14 +1,14 @@
package cn.iocoder.yudao.module.ai.dal.dataobject.chat;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
-import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
+import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
-import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.*;
import org.springframework.ai.chat.messages.MessageType;
@@ -20,10 +20,9 @@ import java.util.List;
* @since 2024/4/14 17:35
* @since 2024/4/14 17:35
*/
-@TableName("ai_chat_message")
+@TableName(value = "ai_chat_message", autoResultMap = true)
@KeySequence("ai_chat_conversation_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
-@EqualsAndHashCode(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
@@ -71,23 +70,16 @@ public class AiChatMessageDO extends BaseDO {
*/
private Long roleId;
-
- /**
- * 段落编号数组
- *
- * 关联 {@link AiKnowledgeSegmentDO#getId()} 字段
- */
- @TableField(typeHandler = JacksonTypeHandler.class)
- private List segmentIds;
-
/**
* 模型标志
+ *
+ * 冗余 {@link AiModelDO#getModel()}
*/
private String model;
/**
* 模型编号
*
- * 关联 {@link AiChatModelDO#getId()} 字段
+ * 关联 {@link AiModelDO#getId()} 字段
*/
private Long modelId;
@@ -101,4 +93,12 @@ public class AiChatMessageDO extends BaseDO {
*/
private Boolean useContext;
+ /**
+ * 知识库段落编号数组
+ *
+ * 关联 {@link AiKnowledgeSegmentDO#getId()} 字段
+ */
+ @TableField(typeHandler = LongListTypeHandler.class)
+ private List segmentIds;
+
}
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/image/AiImageDO.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/image/AiImageDO.java
similarity index 85%
rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/image/AiImageDO.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/image/AiImageDO.java
index 56749a1d00..72acf72df5 100644
--- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/image/AiImageDO.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/image/AiImageDO.java
@@ -1,8 +1,9 @@
package cn.iocoder.yudao.module.ai.dal.dataobject.image;
-import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
+import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
+import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
-import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
+import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.baomidou.mybatisplus.annotation.KeySequence;
@@ -49,13 +50,19 @@ public class AiImageDO extends BaseDO {
/**
* 平台
*
- * 枚举 {@link cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum}
+ * 枚举 {@link AiPlatformEnum}
*/
private String platform;
/**
- * 模型
+ * 模型编号
*
- * 冗余 {@link AiChatModelDO#getModel()}
+ * 关联 {@link AiModelDO#getId()}
+ */
+ private Long modelId;
+ /**
+ * 模型标识
+ *
+ * 冗余 {@link AiModelDO#getModel()}
*/
private String model;
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java
similarity index 67%
rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java
index 638a8ba50b..e1327a50ef 100644
--- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java
+++ b/yudao-module-ai/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;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
-import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
+import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
-import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
-import java.util.List;
-
/**
* AI 知识库 DO
*
@@ -26,12 +23,6 @@ public class AiKnowledgeDO extends BaseDO {
*/
@TableId
private Long id;
- /**
- * 用户编号
- *
- * 关联 AdminUserDO 的 userId 字段
- */
- private Long userId;
/**
* 知识库名称
*/
@@ -42,20 +33,17 @@ public class AiKnowledgeDO extends BaseDO {
private String description;
/**
- * 可见权限,选择哪些人可见
- *
- * -1 所有人可见,其他为各自用户编号
+ * 向量模型编号
+ *
+ * 关联 {@link AiModelDO#getId()}
*/
- @TableField(typeHandler = LongListTypeHandler.class)
- private List visibilityPermissions;
- /**
- * 嵌入模型编号
- */
- private Long modelId;
+ private Long embeddingModelId;
/**
* 模型标识
+ *
+ * 冗余 {@link AiModelDO#getModel()}
*/
- private String model;
+ private String embeddingModel;
/**
* topK
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDocumentDO.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDocumentDO.java
similarity index 56%
rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDocumentDO.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDocumentDO.java
index ee8bfd5aab..ac014e926b 100644
--- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDocumentDO.java
+++ b/yudao-module-ai/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;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
-import cn.iocoder.yudao.module.ai.enums.knowledge.AiKnowledgeDocumentStatusEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@@ -30,57 +29,35 @@ public class AiKnowledgeDocumentDO extends BaseDO {
*/
private Long knowledgeId;
/**
- * 文件名称
+ * 文档名称
*/
private String name;
- /**
- * 内容
- */
- private String content;
/**
* 文件 URL
*/
private String url;
+ /**
+ * 内容
+ */
+ private String content;
+ /**
+ * 文档长度
+ */
+ private Integer contentLength;
+
/**
* 文档 token 数量
*/
private Integer tokens;
/**
- * 文档字符数
+ * 分片最大 Token 数
*/
- private Integer wordCount;
-
-
- // ========== 自定义分段所用参数 ==========
- // TODO @新:3)defaultChunkSize、defaultChunkSize、minChunkSizeChars、maxNumChunks 这几个字段的命名,可能要微信一起讨论下。尽量命名保持风格统一哈。
- /**
- * 每个文本块的目标 token 数
- */
- private Integer defaultSegmentTokens;
- /**
- * 每个文本块的最小字符数
- */
- private Integer minSegmentWordCount;
- /**
- * 低于此值的块会被丢弃
- */
- private Integer minChunkLengthToEmbed;
- /**
- * 最大块数
- */
- private Integer maxNumSegments;
- /**
- * 分块是否保留分隔符
- */
- private Boolean keepSeparator;
- // ===================================
+ private Integer segmentMaxTokens;
/**
- * 切片状态
- *
- * 枚举 {@link AiKnowledgeDocumentStatusEnum}
+ * 召回次数
*/
- private Integer sliceStatus;
+ private Integer retrievalCount;
/**
* 状态
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeSegmentDO.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeSegmentDO.java
similarity index 84%
rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeSegmentDO.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeSegmentDO.java
index b08e960d14..cccbd6846b 100644
--- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeSegmentDO.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeSegmentDO.java
@@ -17,17 +17,16 @@ import lombok.Data;
@Data
public class AiKnowledgeSegmentDO extends BaseDO {
- public static final String FIELD_KNOWLEDGE_ID = "knowledgeId";
+ /**
+ * 向量库的编号 - 空值
+ */
+ public static final String VECTOR_ID_EMPTY = "";
/**
* 编号
*/
@TableId
private Long id;
- /**
- * 向量库的编号
- */
- private String vectorId;
/**
* 知识库编号
*
@@ -45,13 +44,24 @@ public class AiKnowledgeSegmentDO extends BaseDO {
*/
private String content;
/**
- * 字符数
+ * 切片内容长度
*/
- private Integer wordCount;
+ private Integer contentLength;
+
+ /**
+ * 向量库的编号
+ */
+ private String vectorId;
/**
* token 数量
*/
private Integer tokens;
+
+ /**
+ * 召回次数
+ */
+ private Integer retrievalCount;
+
/**
* 状态
*
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/mindmap/AiMindMapDO.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/mindmap/AiMindMapDO.java
similarity index 83%
rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/mindmap/AiMindMapDO.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/mindmap/AiMindMapDO.java
index b9768529f1..db788b7e83 100644
--- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/mindmap/AiMindMapDO.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/mindmap/AiMindMapDO.java
@@ -1,7 +1,8 @@
package cn.iocoder.yudao.module.ai.dal.dataobject.mindmap;
-import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
+import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@@ -36,6 +37,12 @@ public class AiMindMapDO extends BaseDO {
* 枚举 {@link AiPlatformEnum}
*/
private String platform;
+ /**
+ * 模型编号
+ *
+ * 关联 {@link AiModelDO#getId()}
+ */
+ private Long modelId;
/**
* 模型
*/
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiApiKeyDO.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiApiKeyDO.java
similarity index 91%
rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiApiKeyDO.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiApiKeyDO.java
index e251d55c85..f2c683a503 100644
--- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiApiKeyDO.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiApiKeyDO.java
@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.ai.dal.dataobject.model;
-import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
+import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
@@ -16,7 +16,6 @@ import lombok.*;
@TableName("ai_api_key")
@KeySequence("ai_chat_conversation_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
-@EqualsAndHashCode(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatRoleDO.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatRoleDO.java
similarity index 71%
rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatRoleDO.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatRoleDO.java
index f5ed533a92..bb6a3ca48d 100644
--- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatRoleDO.java
+++ b/yudao-module-ai/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;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
+import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
+import java.util.List;
+
/**
* AI 聊天角色 DO
*
@@ -16,7 +21,6 @@ import lombok.*;
@TableName(value = "ai_chat_role", autoResultMap = true)
@KeySequence("ai_chat_role_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
-@EqualsAndHashCode(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
@@ -58,10 +62,25 @@ public class AiChatRoleDO extends BaseDO {
/**
* 模型编号
*
- * 关联 {@link AiChatModelDO#getId()} 字段
+ * 关联 {@link AiModelDO#getId()} 字段
*/
private Long modelId;
+ /**
+ * 引用的知识库编号列表
+ *
+ * 关联 {@link AiKnowledgeDO#getId()} 字段
+ */
+ @TableField(typeHandler = LongListTypeHandler.class)
+ private List knowledgeIds;
+ /**
+ * 引用的工具编号列表
+ *
+ * 关联 {@link AiToolDO#getId()} 字段
+ */
+ @TableField(typeHandler = LongListTypeHandler.class)
+ private List toolIds;
+
/**
* 是否公开
*
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatModelDO.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiModelDO.java
similarity index 73%
rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatModelDO.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiModelDO.java
index 7197f8b58f..9e54f94c5a 100644
--- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatModelDO.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiModelDO.java
@@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.ai.dal.dataobject.model;
-import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
+import cn.iocoder.yudao.module.ai.enums.model.AiModelTypeEnum;
+import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
@@ -9,21 +10,20 @@ import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
- * AI 聊天模型 DO
+ * AI 模型 DO
*
- * 默认聊天模型:{@link #status} 为开启,并且 {@link #sort} 排序第一
+ * 默认模型:{@link #status} 为开启,并且 {@link #sort} 排序第一
*
* @author fansili
* @since 2024/4/24 19:39
*/
-@TableName("ai_chat_model")
-@KeySequence("ai_chat_model_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@TableName("ai_model")
+@KeySequence("ai_model_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
-@EqualsAndHashCode(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
-public class AiChatModelDO extends BaseDO {
+public class AiModelDO extends BaseDO {
/**
* 编号
@@ -50,6 +50,12 @@ public class AiChatModelDO extends BaseDO {
* 枚举 {@link AiPlatformEnum}
*/
private String platform;
+ /**
+ * 类型
+ *
+ * 枚举 {@link AiModelTypeEnum}
+ */
+ private Integer type;
/**
* 排序值
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiToolDO.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiToolDO.java
new file mode 100644
index 0000000000..7773e978cc
--- /dev/null
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiToolDO.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.ai.dal.dataobject.model;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.ai.service.model.tool.DirectoryListToolFunction;
+import cn.iocoder.yudao.module.ai.service.model.tool.WeatherQueryToolFunction;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * AI 工具 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("ai_tool")
+@KeySequence("ai_tool_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class AiToolDO extends BaseDO {
+
+ /**
+ * 工具编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 工具名称
+ *
+ * 对应 Bean 的名字,例如说:
+ * 1. {@link DirectoryListToolFunction} 的 Bean 名字是 directory_list
+ * 2. {@link WeatherQueryToolFunction} 的 Bean 名字是 weather_query
+ */
+ private String name;
+ /**
+ * 工具描述
+ */
+ private String description;
+ /**
+ * 状态
+ *
+ * 枚举 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
+ */
+ private Integer status;
+
+}
\ No newline at end of file
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java
similarity index 95%
rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java
index e03d62c162..cc16133a70 100644
--- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java
@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.ai.dal.dataobject.music;
-import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
+import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.ai.enums.music.AiMusicGenerateModeEnum;
import cn.iocoder.yudao.module.ai.enums.music.AiMusicStatusEnum;
@@ -84,6 +84,7 @@ public class AiMusicDO extends BaseDO {
* 枚举 {@link AiPlatformEnum}
*/
private String platform;
+ // TODO @芋艿:modelId?
/**
* 模型
*/
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/workflow/AiWorkflowDO.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/workflow/AiWorkflowDO.java
new file mode 100644
index 0000000000..d844f7da2e
--- /dev/null
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/workflow/AiWorkflowDO.java
@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.ai.dal.dataobject.workflow;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+/**
+ * AI 工作流 DO
+ *
+ * @author lesan
+ */
+@TableName(value = "ai_workflow", autoResultMap = true)
+@KeySequence("ai_workflow") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+public class AiWorkflowDO extends BaseDO {
+
+ /**
+ * 编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 工作流名称
+ */
+ private String name;
+ /**
+ * 工作流标识
+ */
+ private String code;
+
+ /**
+ * 工作流模型 JSON 数据
+ */
+ private String graph;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+ /**
+ * 状态
+ *
+ * 枚举 {@link CommonStatusEnum}
+ */
+ private Integer status;
+
+}
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/write/AiWriteDO.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/write/AiWriteDO.java
similarity index 75%
rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/write/AiWriteDO.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/write/AiWriteDO.java
index 0d6f9c5e64..75e1e29cef 100644
--- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/write/AiWriteDO.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/write/AiWriteDO.java
@@ -1,7 +1,9 @@
package cn.iocoder.yudao.module.ai.dal.dataobject.write;
-import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
+import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
+import cn.iocoder.yudao.module.ai.enums.DictTypeConstants;
import cn.iocoder.yudao.module.ai.enums.write.AiWriteTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -44,6 +46,12 @@ public class AiWriteDO extends BaseDO {
* 枚举 {@link AiPlatformEnum}
*/
private String platform;
+ /**
+ * 模型编号
+ *
+ * 关联 {@link AiModelDO#getId()}
+ */
+ private Long modelId;
/**
* 模型
*/
@@ -66,25 +74,25 @@ public class AiWriteDO extends BaseDO {
/**
* 长度提示词
*
- * 字典:{@link cn.iocoder.yudao.module.ai.enums.DictTypeConstants#AI_WRITE_LENGTH}
+ * 字典:{@link DictTypeConstants#AI_WRITE_LENGTH}
*/
private Integer length;
/**
* 格式提示词
*
- * 字典:{@link cn.iocoder.yudao.module.ai.enums.DictTypeConstants#AI_WRITE_FORMAT}
+ * 字典:{@link DictTypeConstants#AI_WRITE_FORMAT}
*/
private Integer format;
/**
* 语气提示词
*
- * 字典:{@link cn.iocoder.yudao.module.ai.enums.DictTypeConstants#AI_WRITE_TONE}
+ * 字典:{@link DictTypeConstants#AI_WRITE_TONE}
*/
private Integer tone;
/**
* 语言提示词
*
- * 字典:{@link cn.iocoder.yudao.module.ai.enums.DictTypeConstants#AI_WRITE_LANGUAGE}
+ * 字典:{@link DictTypeConstants#AI_WRITE_LANGUAGE}
*/
private Integer language;
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatConversationMapper.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatConversationMapper.java
similarity index 100%
rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatConversationMapper.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatConversationMapper.java
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatMessageMapper.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatMessageMapper.java
similarity index 100%
rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatMessageMapper.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatMessageMapper.java
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/image/AiImageMapper.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/image/AiImageMapper.java
similarity index 100%
rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/image/AiImageMapper.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/image/AiImageMapper.java
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeDocumentMapper.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeDocumentMapper.java
similarity index 53%
rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeDocumentMapper.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeDocumentMapper.java
index 7692d1cede..55f04bb328 100644
--- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeDocumentMapper.java
+++ b/yudao-module-ai/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;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO;
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
+import java.util.Collection;
+import java.util.List;
+
/**
- * AI 知识库-文档 Mapper
+ * AI 知识库文档 Mapper
*
* @author xiaoxin
*/
@@ -17,8 +21,23 @@ public interface AiKnowledgeDocumentMapper extends BaseMapperX selectPage(AiKnowledgeDocumentPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX()
+ .eqIfPresent(AiKnowledgeDocumentDO::getKnowledgeId, reqVO.getKnowledgeId())
.likeIfPresent(AiKnowledgeDocumentDO::getName, reqVO.getName())
.orderByDesc(AiKnowledgeDocumentDO::getId));
}
+ default void updateRetrievalCountIncr(Collection ids) {
+ update(new LambdaUpdateWrapper()
+ .setSql(" retrieval_count = retrieval_count + 1")
+ .in(AiKnowledgeDocumentDO::getId, ids));
+ }
+
+ default List selectListByStatus(Integer status) {
+ return selectList(AiKnowledgeDocumentDO::getStatus, status);
+ }
+
+ default List selectListByKnowledgeId(Long knowledgeId) {
+ return selectList(AiKnowledgeDocumentDO::getKnowledgeId, knowledgeId);
+ }
+
}
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeMapper.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeMapper.java
similarity index 64%
rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeMapper.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeMapper.java
index f07a9a2afa..3433c0b973 100644
--- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeMapper.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeMapper.java
@@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.ai.dal.mysql.knowledge;
-import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
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
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
import org.apache.ibatis.annotations.Mapper;
+import java.util.List;
+
/**
- * AI 知识库基础信息 Mapper
+ * AI 知识库 Mapper
*
* @author xiaoxin
*/
@Mapper
public interface AiKnowledgeMapper extends BaseMapperX {
- default PageResult selectPage(Long userId, AiKnowledgePageReqVO pageReqVO) {
+ default PageResult selectPage(AiKnowledgePageReqVO pageReqVO) {
return selectPage(pageReqVO, new LambdaQueryWrapperX()
- .eq(AiKnowledgeDO::getStatus, CommonStatusEnum.ENABLE.getStatus())
.likeIfPresent(AiKnowledgeDO::getName, pageReqVO.getName())
- .and(e -> e.apply("FIND_IN_SET(" + userId + ",visibility_permissions)").or(m -> m.apply("FIND_IN_SET(-1,visibility_permissions)")))
+ .eqIfPresent(AiKnowledgeDO::getStatus, pageReqVO.getStatus())
+ .betweenIfPresent(AiKnowledgeDO::getCreateTime, pageReqVO.getCreateTime())
.orderByDesc(AiKnowledgeDO::getId));
}
+
+ default List selectListByStatus(Integer status) {
+ return selectList(AiKnowledgeDO::getStatus, status);
+ }
+
}
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeSegmentMapper.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeSegmentMapper.java
new file mode 100644
index 0000000000..1b9ca867f5
--- /dev/null
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeSegmentMapper.java
@@ -0,0 +1,67 @@
+package cn.iocoder.yudao.module.ai.dal.mysql.knowledge;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentProcessRespVO;
+import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * AI 知识库分片 Mapper
+ *
+ * @author xiaoxin
+ */
+@Mapper
+public interface AiKnowledgeSegmentMapper extends BaseMapperX {
+
+ default PageResult selectPage(AiKnowledgeSegmentPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .eq(AiKnowledgeSegmentDO::getDocumentId, reqVO.getDocumentId())
+ .likeIfPresent(AiKnowledgeSegmentDO::getContent, reqVO.getContent())
+ .eqIfPresent(AiKnowledgeSegmentDO::getStatus, reqVO.getStatus())
+ .orderByDesc(AiKnowledgeSegmentDO::getId));
+ }
+
+ default List selectListByVectorIds(List vectorIds) {
+ return selectList(new LambdaQueryWrapperX()
+ .in(AiKnowledgeSegmentDO::getVectorId, vectorIds)
+ .orderByDesc(AiKnowledgeSegmentDO::getId));
+ }
+
+ default List selectListByDocumentId(Long documentId) {
+ return selectList(new LambdaQueryWrapperX()
+ .eq(AiKnowledgeSegmentDO::getDocumentId, documentId)
+ .orderByDesc(AiKnowledgeSegmentDO::getId));
+ }
+
+ default List selectListByKnowledgeIdAndStatus(Long knowledgeId, Integer status) {
+ return selectList(AiKnowledgeSegmentDO::getKnowledgeId, knowledgeId,
+ AiKnowledgeSegmentDO::getStatus, status);
+ }
+
+ default List selectProcessList(Collection documentIds) {
+ MPJLambdaWrapper wrapper = new MPJLambdaWrapperX()
+ .selectAs(AiKnowledgeSegmentDO::getDocumentId, AiKnowledgeSegmentProcessRespVO::getDocumentId)
+ .selectCount(AiKnowledgeSegmentDO::getId, "count")
+ .select("COUNT(CASE WHEN vector_id > '" + AiKnowledgeSegmentDO.VECTOR_ID_EMPTY
+ + "' THEN 1 ELSE NULL END) AS embeddingCount")
+ .in(AiKnowledgeSegmentDO::getDocumentId, documentIds)
+ .groupBy(AiKnowledgeSegmentDO::getDocumentId);
+ return selectJoinList(AiKnowledgeSegmentProcessRespVO.class, wrapper);
+ }
+
+ default void updateRetrievalCountIncrByIds(List ids) {
+ update(new LambdaUpdateWrapper()
+ .setSql(" retrieval_count = retrieval_count + 1")
+ .in(AiKnowledgeSegmentDO::getId, ids));
+ }
+
+}
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/mindmap/AiMindMapMapper.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/mindmap/AiMindMapMapper.java
similarity index 100%
rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/mindmap/AiMindMapMapper.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/mindmap/AiMindMapMapper.java
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiApiKeyMapper.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiApiKeyMapper.java
similarity index 100%
rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiApiKeyMapper.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiApiKeyMapper.java
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiChatMapper.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiChatMapper.java
new file mode 100644
index 0000000000..bfe2caf52a
--- /dev/null
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiChatMapper.java
@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.ai.dal.mysql.model;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
+import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelPageReqVO;
+import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import javax.annotation.Nullable;
+import java.util.List;
+
+/**
+ * API 模型 Mapper
+ *
+ * @author fansili
+ */
+@Mapper
+public interface AiChatMapper extends BaseMapperX {
+
+ default AiModelDO selectFirstByStatus(Integer type, Integer status) {
+ return selectOne(new QueryWrapperX()
+ .eq("type", type)
+ .eq("status", status)
+ .limitN(1)
+ .orderByAsc("sort"));
+ }
+
+ default PageResult selectPage(AiModelPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .likeIfPresent(AiModelDO::getName, reqVO.getName())
+ .eqIfPresent(AiModelDO::getModel, reqVO.getModel())
+ .eqIfPresent(AiModelDO::getPlatform, reqVO.getPlatform())
+ .orderByAsc(AiModelDO::getSort));
+ }
+
+ default List selectListByStatusAndType(Integer status, Integer type,
+ @Nullable String platform) {
+ return selectList(new LambdaQueryWrapperX()
+ .eq(AiModelDO::getStatus, status)
+ .eq(AiModelDO::getType, type)
+ .eqIfPresent(AiModelDO::getPlatform, platform)
+ .orderByAsc(AiModelDO::getSort));
+ }
+
+}
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiChatRoleMapper.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiChatRoleMapper.java
similarity index 100%
rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiChatRoleMapper.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiChatRoleMapper.java
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiToolMapper.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiToolMapper.java
new file mode 100644
index 0000000000..d5d296692a
--- /dev/null
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiToolMapper.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.ai.dal.mysql.model;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool.AiToolPageReqVO;
+import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiToolDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * AI 工具 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface AiToolMapper extends BaseMapperX {
+
+ default PageResult selectPage(AiToolPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .likeIfPresent(AiToolDO::getName, reqVO.getName())
+ .eqIfPresent(AiToolDO::getDescription, reqVO.getDescription())
+ .eqIfPresent(AiToolDO::getStatus, reqVO.getStatus())
+ .betweenIfPresent(AiToolDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(AiToolDO::getId));
+ }
+
+ default List selectListByStatus(Integer status) {
+ return selectList(new LambdaQueryWrapperX()
+ .eq(AiToolDO::getStatus, status)
+ .orderByDesc(AiToolDO::getId));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/music/AiMusicMapper.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/music/AiMusicMapper.java
similarity index 100%
rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/music/AiMusicMapper.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/music/AiMusicMapper.java
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/workflow/AiWorkflowMapper.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/workflow/AiWorkflowMapper.java
new file mode 100644
index 0000000000..3770dbf0b5
--- /dev/null
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/workflow/AiWorkflowMapper.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.ai.dal.mysql.workflow;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.ai.controller.admin.workflow.vo.AiWorkflowPageReqVO;
+import cn.iocoder.yudao.module.ai.dal.dataobject.workflow.AiWorkflowDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * AI 工作流 Mapper
+ *
+ * @author lesan
+ */
+@Mapper
+public interface AiWorkflowMapper extends BaseMapperX {
+
+ default AiWorkflowDO selectByCode(String code) {
+ return selectOne(AiWorkflowDO::getCode, code);
+ }
+
+ default PageResult selectPage(AiWorkflowPageReqVO pageReqVO) {
+ return selectPage(pageReqVO, new LambdaQueryWrapperX()
+ .eqIfPresent(AiWorkflowDO::getStatus, pageReqVO.getStatus())
+ .likeIfPresent(AiWorkflowDO::getName, pageReqVO.getName())
+ .likeIfPresent(AiWorkflowDO::getCode, pageReqVO.getCode())
+ .betweenIfPresent(AiWorkflowDO::getCreateTime, pageReqVO.getCreateTime()));
+ }
+
+}
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/write/AiWriteMapper.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/write/AiWriteMapper.java
similarity index 100%
rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/write/AiWriteMapper.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/write/AiWriteMapper.java
diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiChatRoleEnum.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/AiChatRoleEnum.java
similarity index 88%
rename from yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiChatRoleEnum.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/AiChatRoleEnum.java
index 6cb98c5629..1479274959 100644
--- a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiChatRoleEnum.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/AiChatRoleEnum.java
@@ -35,11 +35,7 @@ public enum AiChatRoleEnum {
### 微信
除此之外不要任何解释性语句。
"""),
-
- AI_KNOWLEDGE_ROLE("知识库助手", """
- 给你提供一些数据参考:{info},请回答我的问题。
- 请你跟进数据参考与工具返回结果回复用户的请求。
- """);
+ ;
/**
* 角色名
diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/DictTypeConstants.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/DictTypeConstants.java
similarity index 100%
rename from yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/DictTypeConstants.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/DictTypeConstants.java
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java
new file mode 100644
index 0000000000..8a8a388324
--- /dev/null
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java
@@ -0,0 +1,68 @@
+package cn.iocoder.yudao.module.ai.enums;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+
+/**
+ * AI 错误码枚举类
+ *
+ * ai 系统,使用 1-040-000-000 段
+ */
+public interface ErrorCodeConstants {
+
+ // ========== API 密钥 1-040-000-000 ==========
+ ErrorCode API_KEY_NOT_EXISTS = new ErrorCode(1_040_000_000, "API 密钥不存在");
+ ErrorCode API_KEY_DISABLE = new ErrorCode(1_040_000_001, "API 密钥已禁用!");
+
+ // ========== API 模型 1-040-001-000 ==========
+ ErrorCode MODEL_NOT_EXISTS = new ErrorCode(1_040_001_000, "模型不存在!");
+ ErrorCode MODEL_DISABLE = new ErrorCode(1_040_001_001, "模型({})已禁用!");
+ ErrorCode MODEL_DEFAULT_NOT_EXISTS = new ErrorCode(1_040_001_002, "操作失败,找不到默认模型");
+ ErrorCode MODEL_USE_TYPE_ERROR = new ErrorCode(1_040_001_003, "操作失败,该模型的模型类型不正确");
+
+ // ========== API 聊天角色 1-040-002-000 ==========
+ ErrorCode CHAT_ROLE_NOT_EXISTS = new ErrorCode(1_040_002_000, "聊天角色不存在");
+ ErrorCode CHAT_ROLE_DISABLE = new ErrorCode(1_040_001_001, "聊天角色({})已禁用!");
+
+ // ========== API 聊天会话 1-040-003-000 ==========
+ ErrorCode CHAT_CONVERSATION_NOT_EXISTS = new ErrorCode(1_040_003_000, "对话不存在!");
+ ErrorCode CHAT_CONVERSATION_MODEL_ERROR = new ErrorCode(1_040_003_001, "操作失败,该聊天模型的配置不完整");
+
+ // ========== API 聊天消息 1-040-004-000 ==========
+ ErrorCode CHAT_MESSAGE_NOT_EXIST = new ErrorCode(1_040_004_000, "消息不存在!");
+ ErrorCode CHAT_STREAM_ERROR = new ErrorCode(1_040_004_001, "对话生成异常!");
+
+ // ========== API 绘画 1-040-005-000 ==========
+ ErrorCode IMAGE_NOT_EXISTS = new ErrorCode(1_040_005_000, "图片不存在!");
+ ErrorCode IMAGE_MIDJOURNEY_SUBMIT_FAIL = new ErrorCode(1_040_005_001, "Midjourney 提交失败!原因:{}");
+ ErrorCode IMAGE_CUSTOM_ID_NOT_EXISTS = new ErrorCode(1_040_005_002, "Midjourney 按钮 customId 不存在! {}");
+
+ // ========== API 音乐 1-040-006-000 ==========
+ ErrorCode MUSIC_NOT_EXISTS = new ErrorCode(1_040_006_000, "音乐不存在!");
+
+ // ========== API 写作 1-040-007-000 ==========
+ ErrorCode WRITE_NOT_EXISTS = new ErrorCode(1_040_007_000, "作文不存在!");
+ ErrorCode WRITE_STREAM_ERROR = new ErrorCode(1_040_07_001, "写作生成异常!");
+
+ // ========== API 思维导图 1-040-008-000 ==========
+ ErrorCode MIND_MAP_NOT_EXISTS = new ErrorCode(1_040_008_000, "思维导图不存在!");
+
+ // ========== API 知识库 1-040-009-000 ==========
+ ErrorCode KNOWLEDGE_NOT_EXISTS = new ErrorCode(1_040_009_000, "知识库不存在!");
+
+ ErrorCode KNOWLEDGE_DOCUMENT_NOT_EXISTS = new ErrorCode(1_040_009_101, "文档不存在!");
+ ErrorCode KNOWLEDGE_DOCUMENT_FILE_EMPTY = new ErrorCode(1_040_009_102, "文档内容为空!");
+ ErrorCode KNOWLEDGE_DOCUMENT_FILE_DOWNLOAD_FAIL = new ErrorCode(1_040_009_102, "文件下载失败!");
+ ErrorCode KNOWLEDGE_DOCUMENT_FILE_READ_FAIL = new ErrorCode(1_040_009_102, "文档加载失败!");
+
+ ErrorCode KNOWLEDGE_SEGMENT_NOT_EXISTS = new ErrorCode(1_040_009_202, "段落不存在!");
+ ErrorCode KNOWLEDGE_SEGMENT_CONTENT_TOO_LONG = new ErrorCode(1_040_009_203, "内容 Token 数为 {},超过最大限制 {}");
+
+ // ========== AI 工具 1-040-010-000 ==========
+ ErrorCode TOOL_NOT_EXISTS = new ErrorCode(1_040_010_000, "工具不存在");
+ ErrorCode TOOL_NAME_NOT_EXISTS = new ErrorCode(1_040_010_001, "工具({})找不到 Bean");
+
+ // ========== AI 工作流 1-040-011-000 ==========
+ ErrorCode WORKFLOW_NOT_EXISTS = new ErrorCode(1_040_011_000, "工作流不存在");
+ ErrorCode WORKFLOW_CODE_EXISTS = new ErrorCode(1_040_011_001, "工作流标识已存在");
+
+}
diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/image/AiImageStatusEnum.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/image/AiImageStatusEnum.java
similarity index 100%
rename from yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/image/AiImageStatusEnum.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/image/AiImageStatusEnum.java
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiModelTypeEnum.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiModelTypeEnum.java
new file mode 100644
index 0000000000..bdba3e8915
--- /dev/null
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiModelTypeEnum.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.ai.enums.model;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * AI 模型类型的枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@RequiredArgsConstructor
+public enum AiModelTypeEnum implements ArrayValuable {
+
+ CHAT(1, "对话"),
+ IMAGE(2, "图片"),
+ VOICE(3, "语音"),
+ VIDEO(4, "视频"),
+ EMBEDDING(5, "向量"),
+ RERANK(6, "重排序");
+
+ /**
+ * 类型
+ */
+ private final Integer type;
+ /**
+ * 类型名
+ */
+ private final String name;
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(AiModelTypeEnum::getType).toArray(Integer[]::new);
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiPlatformEnum.java
similarity index 64%
rename from yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiPlatformEnum.java
index 1922e9a2cf..cebe0b9568 100644
--- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiPlatformEnum.java
@@ -1,8 +1,11 @@
-package cn.iocoder.yudao.framework.ai.core.enums;
+package cn.iocoder.yudao.module.ai.enums.model;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
+import java.util.Arrays;
+
/**
* AI 模型平台
*
@@ -10,7 +13,7 @@ import lombok.Getter;
*/
@Getter
@AllArgsConstructor
-public enum AiPlatformEnum {
+public enum AiPlatformEnum implements ArrayValuable {
// ========== 国内平台 ==========
@@ -19,6 +22,12 @@ public enum AiPlatformEnum {
DEEP_SEEK("DeepSeek", "DeepSeek"), // DeepSeek
ZHI_PU("ZhiPu", "智谱"), // 智谱 AI
XING_HUO("XingHuo", "星火"), // 讯飞
+ DOU_BAO("DouBao", "豆包"), // 字节
+ HUN_YUAN("HunYuan", "混元"), // 腾讯
+ SILICON_FLOW("SiliconFlow", "硅基流动"), // 硅基流动
+ MINI_MAX("MiniMax", "MiniMax"), // 稀宇科技
+ MOONSHOT("Moonshot", "月之暗灭"), // KIMI
+ BAI_CHUAN("BaiChuan", "百川智能"), // 百川智能
// ========== 国外平台 ==========
@@ -41,6 +50,8 @@ public enum AiPlatformEnum {
*/
private final String name;
+ public static final String[] ARRAYS = Arrays.stream(values()).map(AiPlatformEnum::getPlatform).toArray(String[]::new);
+
public static AiPlatformEnum validatePlatform(String platform) {
for (AiPlatformEnum platformEnum : AiPlatformEnum.values()) {
if (platformEnum.getPlatform().equals(platform)) {
@@ -50,4 +61,9 @@ public enum AiPlatformEnum {
throw new IllegalArgumentException("非法平台: " + platform);
}
+ @Override
+ public String[] array() {
+ return ARRAYS;
+ }
+
}
diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicGenerateModeEnum.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicGenerateModeEnum.java
similarity index 100%
rename from yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicGenerateModeEnum.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicGenerateModeEnum.java
diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicStatusEnum.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicStatusEnum.java
similarity index 100%
rename from yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicStatusEnum.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicStatusEnum.java
diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/write/AiWriteTypeEnum.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/write/AiWriteTypeEnum.java
similarity index 100%
rename from yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/write/AiWriteTypeEnum.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/write/AiWriteTypeEnum.java
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/AiAutoConfiguration.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/AiAutoConfiguration.java
new file mode 100644
index 0000000000..a28d726b90
--- /dev/null
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/AiAutoConfiguration.java
@@ -0,0 +1,253 @@
+package cn.iocoder.yudao.module.ai.framework.ai.config;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import cn.iocoder.yudao.module.ai.framework.ai.core.AiModelFactory;
+import cn.iocoder.yudao.module.ai.framework.ai.core.AiModelFactoryImpl;
+import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel;
+import cn.iocoder.yudao.module.ai.framework.ai.core.model.deepseek.DeepSeekChatModel;
+import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel;
+import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel;
+import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi;
+import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants;
+import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowChatModel;
+import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi;
+import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusServiceClientProperties;
+import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreProperties;
+import org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreProperties;
+import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreProperties;
+import org.springframework.ai.embedding.BatchingStrategy;
+import org.springframework.ai.embedding.TokenCountBatchingStrategy;
+import org.springframework.ai.model.tool.ToolCallingManager;
+import org.springframework.ai.openai.OpenAiChatModel;
+import org.springframework.ai.openai.OpenAiChatOptions;
+import org.springframework.ai.openai.api.OpenAiApi;
+import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator;
+import org.springframework.ai.tokenizer.TokenCountEstimator;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 芋道 AI 自动配置
+ *
+ * @author fansili
+ */
+@Configuration
+@EnableConfigurationProperties({ YudaoAiProperties.class,
+ QdrantVectorStoreProperties.class, // 解析 Qdrant 配置
+ RedisVectorStoreProperties.class, // 解析 Redis 配置
+ MilvusVectorStoreProperties.class, MilvusServiceClientProperties.class // 解析 Milvus 配置
+})
+@Slf4j
+public class AiAutoConfiguration {
+
+ @Bean
+ public AiModelFactory aiModelFactory() {
+ return new AiModelFactoryImpl();
+ }
+
+ // ========== 各种 AI Client 创建 ==========
+
+ @Bean
+ @ConditionalOnProperty(value = "yudao.ai.deepseek.enable", havingValue = "true")
+ public DeepSeekChatModel deepSeekChatModel(YudaoAiProperties yudaoAiProperties) {
+ YudaoAiProperties.DeepSeekProperties properties = yudaoAiProperties.getDeepseek();
+ return buildDeepSeekChatModel(properties);
+ }
+
+ public DeepSeekChatModel buildDeepSeekChatModel(YudaoAiProperties.DeepSeekProperties properties) {
+ if (StrUtil.isEmpty(properties.getModel())) {
+ properties.setModel(DeepSeekChatModel.MODEL_DEFAULT);
+ }
+ OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
+ .openAiApi(OpenAiApi.builder()
+ .baseUrl(DeepSeekChatModel.BASE_URL)
+ .apiKey(properties.getApiKey())
+ .build())
+ .defaultOptions(OpenAiChatOptions.builder()
+ .model(properties.getModel())
+ .temperature(properties.getTemperature())
+ .maxTokens(properties.getMaxTokens())
+ .topP(properties.getTopP())
+ .build())
+ .toolCallingManager(getToolCallingManager())
+ .build();
+ return new DeepSeekChatModel(openAiChatModel);
+ }
+
+ @Bean
+ @ConditionalOnProperty(value = "yudao.ai.doubao.enable", havingValue = "true")
+ public DouBaoChatModel douBaoChatClient(YudaoAiProperties yudaoAiProperties) {
+ YudaoAiProperties.DouBaoProperties properties = yudaoAiProperties.getDoubao();
+ return buildDouBaoChatClient(properties);
+ }
+
+ public DouBaoChatModel buildDouBaoChatClient(YudaoAiProperties.DouBaoProperties properties) {
+ if (StrUtil.isEmpty(properties.getModel())) {
+ properties.setModel(DouBaoChatModel.MODEL_DEFAULT);
+ }
+ OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
+ .openAiApi(OpenAiApi.builder()
+ .baseUrl(DouBaoChatModel.BASE_URL)
+ .apiKey(properties.getApiKey())
+ .build())
+ .defaultOptions(OpenAiChatOptions.builder()
+ .model(properties.getModel())
+ .temperature(properties.getTemperature())
+ .maxTokens(properties.getMaxTokens())
+ .topP(properties.getTopP())
+ .build())
+ .toolCallingManager(getToolCallingManager())
+ .build();
+ return new DouBaoChatModel(openAiChatModel);
+ }
+
+ @Bean
+ @ConditionalOnProperty(value = "yudao.ai.siliconflow.enable", havingValue = "true")
+ public SiliconFlowChatModel siliconFlowChatClient(YudaoAiProperties yudaoAiProperties) {
+ YudaoAiProperties.SiliconFlowProperties properties = yudaoAiProperties.getSiliconflow();
+ return buildSiliconFlowChatClient(properties);
+ }
+
+ public SiliconFlowChatModel buildSiliconFlowChatClient(YudaoAiProperties.SiliconFlowProperties properties) {
+ if (StrUtil.isEmpty(properties.getModel())) {
+ properties.setModel(SiliconFlowApiConstants.MODEL_DEFAULT);
+ }
+ OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
+ .openAiApi(OpenAiApi.builder()
+ .baseUrl(SiliconFlowApiConstants.DEFAULT_BASE_URL)
+ .apiKey(properties.getApiKey())
+ .build())
+ .defaultOptions(OpenAiChatOptions.builder()
+ .model(properties.getModel())
+ .temperature(properties.getTemperature())
+ .maxTokens(properties.getMaxTokens())
+ .topP(properties.getTopP())
+ .build())
+ .toolCallingManager(getToolCallingManager())
+ .build();
+ return new SiliconFlowChatModel(openAiChatModel);
+ }
+
+ @Bean
+ @ConditionalOnProperty(value = "yudao.ai.hunyuan.enable", havingValue = "true")
+ public HunYuanChatModel hunYuanChatClient(YudaoAiProperties yudaoAiProperties) {
+ YudaoAiProperties.HunYuanProperties properties = yudaoAiProperties.getHunyuan();
+ return buildHunYuanChatClient(properties);
+ }
+
+ public HunYuanChatModel buildHunYuanChatClient(YudaoAiProperties.HunYuanProperties properties) {
+ if (StrUtil.isEmpty(properties.getModel())) {
+ properties.setModel(HunYuanChatModel.MODEL_DEFAULT);
+ }
+ // 特殊:由于混元大模型不提供 deepseek,而是通过知识引擎,所以需要区分下 URL
+ if (StrUtil.isEmpty(properties.getBaseUrl())) {
+ properties.setBaseUrl(
+ StrUtil.startWithIgnoreCase(properties.getModel(), "deepseek") ? HunYuanChatModel.DEEP_SEEK_BASE_URL
+ : HunYuanChatModel.BASE_URL);
+ }
+ // 创建 OpenAiChatModel、HunYuanChatModel 对象
+ OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
+ .openAiApi(OpenAiApi.builder()
+ .baseUrl(properties.getBaseUrl())
+ .apiKey(properties.getApiKey())
+ .build())
+ .defaultOptions(OpenAiChatOptions.builder()
+ .model(properties.getModel())
+ .temperature(properties.getTemperature())
+ .maxTokens(properties.getMaxTokens())
+ .topP(properties.getTopP())
+ .build())
+ .toolCallingManager(getToolCallingManager())
+ .build();
+ return new HunYuanChatModel(openAiChatModel);
+ }
+
+ @Bean
+ @ConditionalOnProperty(value = "yudao.ai.xinghuo.enable", havingValue = "true")
+ public XingHuoChatModel xingHuoChatClient(YudaoAiProperties yudaoAiProperties) {
+ YudaoAiProperties.XingHuoProperties properties = yudaoAiProperties.getXinghuo();
+ return buildXingHuoChatClient(properties);
+ }
+
+ public XingHuoChatModel buildXingHuoChatClient(YudaoAiProperties.XingHuoProperties properties) {
+ if (StrUtil.isEmpty(properties.getModel())) {
+ properties.setModel(XingHuoChatModel.MODEL_DEFAULT);
+ }
+ OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
+ .openAiApi(OpenAiApi.builder()
+ .baseUrl(XingHuoChatModel.BASE_URL)
+ .apiKey(properties.getAppKey() + ":" + properties.getSecretKey())
+ .build())
+ .defaultOptions(OpenAiChatOptions.builder()
+ .model(properties.getModel())
+ .temperature(properties.getTemperature())
+ .maxTokens(properties.getMaxTokens())
+ .topP(properties.getTopP())
+ .build())
+ .toolCallingManager(getToolCallingManager())
+ .build();
+ return new XingHuoChatModel(openAiChatModel);
+ }
+
+ @Bean
+ @ConditionalOnProperty(value = "yudao.ai.baichuan.enable", havingValue = "true")
+ public BaiChuanChatModel baiChuanChatClient(YudaoAiProperties yudaoAiProperties) {
+ YudaoAiProperties.BaiChuanProperties properties = yudaoAiProperties.getBaichuan();
+ return buildBaiChuanChatClient(properties);
+ }
+
+ public BaiChuanChatModel buildBaiChuanChatClient(YudaoAiProperties.BaiChuanProperties properties) {
+ if (StrUtil.isEmpty(properties.getModel())) {
+ properties.setModel(BaiChuanChatModel.MODEL_DEFAULT);
+ }
+ OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
+ .openAiApi(OpenAiApi.builder()
+ .baseUrl(BaiChuanChatModel.BASE_URL)
+ .apiKey(properties.getApiKey())
+ .build())
+ .defaultOptions(OpenAiChatOptions.builder()
+ .model(properties.getModel())
+ .temperature(properties.getTemperature())
+ .maxTokens(properties.getMaxTokens())
+ .topP(properties.getTopP())
+ .build())
+ .toolCallingManager(getToolCallingManager())
+ .build();
+ return new BaiChuanChatModel(openAiChatModel);
+ }
+
+ @Bean
+ @ConditionalOnProperty(value = "yudao.ai.midjourney.enable", havingValue = "true")
+ public MidjourneyApi midjourneyApi(YudaoAiProperties yudaoAiProperties) {
+ YudaoAiProperties.MidjourneyProperties config = yudaoAiProperties.getMidjourney();
+ return new MidjourneyApi(config.getBaseUrl(), config.getApiKey(), config.getNotifyUrl());
+ }
+
+ @Bean
+ @ConditionalOnProperty(value = "yudao.ai.suno.enable", havingValue = "true")
+ public SunoApi sunoApi(YudaoAiProperties yudaoAiProperties) {
+ return new SunoApi(yudaoAiProperties.getSuno().getBaseUrl());
+ }
+
+ // ========== RAG 相关 ==========
+
+ @Bean
+ public TokenCountEstimator tokenCountEstimator() {
+ return new JTokkitTokenCountEstimator();
+ }
+
+ @Bean
+ public BatchingStrategy batchingStrategy() {
+ return new TokenCountBatchingStrategy();
+ }
+
+ private static ToolCallingManager getToolCallingManager() {
+ return SpringUtil.getBean(ToolCallingManager.class);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/YudaoAiProperties.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/YudaoAiProperties.java
new file mode 100644
index 0000000000..7f8046768a
--- /dev/null
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/YudaoAiProperties.java
@@ -0,0 +1,164 @@
+package cn.iocoder.yudao.module.ai.framework.ai.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * 芋道 AI 配置类
+ *
+ * @author fansili
+ * @since 1.0
+ */
+@ConfigurationProperties(prefix = "yudao.ai")
+@Data
+public class YudaoAiProperties {
+
+ /**
+ * DeepSeek
+ */
+ @SuppressWarnings("SpellCheckingInspection")
+ private DeepSeekProperties deepseek;
+
+ /**
+ * 字节豆包
+ */
+ @SuppressWarnings("SpellCheckingInspection")
+ private DouBaoProperties doubao;
+
+ /**
+ * 腾讯混元
+ */
+ @SuppressWarnings("SpellCheckingInspection")
+ private HunYuanProperties hunyuan;
+
+ /**
+ * 硅基流动
+ */
+ @SuppressWarnings("SpellCheckingInspection")
+ private SiliconFlowProperties siliconflow;
+
+ /**
+ * 讯飞星火
+ */
+ @SuppressWarnings("SpellCheckingInspection")
+ private XingHuoProperties xinghuo;
+
+ /**
+ * 百川
+ */
+ @SuppressWarnings("SpellCheckingInspection")
+ private BaiChuanProperties baichuan;
+
+ /**
+ * Midjourney 绘图
+ */
+ private MidjourneyProperties midjourney;
+
+ /**
+ * Suno 音乐
+ */
+ @SuppressWarnings("SpellCheckingInspection")
+ private SunoProperties suno;
+
+ @Data
+ public static class DeepSeekProperties {
+
+ private String enable;
+ private String apiKey;
+
+ private String model;
+ private Double temperature;
+ private Integer maxTokens;
+ private Double topP;
+
+ }
+
+ @Data
+ public static class DouBaoProperties {
+
+ private String enable;
+ private String apiKey;
+
+ private String model;
+ private Double temperature;
+ private Integer maxTokens;
+ private Double topP;
+
+ }
+
+ @Data
+ public static class HunYuanProperties {
+
+ private String enable;
+ private String baseUrl;
+ private String apiKey;
+
+ private String model;
+ private Double temperature;
+ private Integer maxTokens;
+ private Double topP;
+
+ }
+
+ @Data
+ public static class SiliconFlowProperties {
+
+ private String enable;
+ private String apiKey;
+
+ private String model;
+ private Double temperature;
+ private Integer maxTokens;
+ private Double topP;
+
+ }
+
+ @Data
+ public static class XingHuoProperties {
+
+ private String enable;
+ private String appId;
+ private String appKey;
+ private String secretKey;
+
+ private String model;
+ private Double temperature;
+ private Integer maxTokens;
+ private Double topP;
+
+ }
+
+ @Data
+ public static class BaiChuanProperties {
+
+ private String enable;
+ private String apiKey;
+
+ private String model;
+ private Double temperature;
+ private Integer maxTokens;
+ private Double topP;
+
+ }
+
+ @Data
+ public static class MidjourneyProperties {
+
+ private String enable;
+ private String baseUrl;
+
+ private String apiKey;
+ private String notifyUrl;
+
+ }
+
+ @Data
+ public static class SunoProperties {
+
+ private boolean enable = false;
+
+ private String baseUrl;
+
+ }
+
+}
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactory.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/AiModelFactory.java
similarity index 77%
rename from yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactory.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/AiModelFactory.java
index 243c4ae4bc..659fa1f92b 100644
--- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactory.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/AiModelFactory.java
@@ -1,13 +1,15 @@
-package cn.iocoder.yudao.framework.ai.core.factory;
+package cn.iocoder.yudao.module.ai.framework.ai.core;
-import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
-import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
-import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
+import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
+import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi;
+import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.image.ImageModel;
import org.springframework.ai.vectorstore.VectorStore;
+import java.util.Map;
+
/**
* AI Model 模型工厂的接口类
*
@@ -89,21 +91,23 @@ public interface AiModelFactory {
* @param platform 平台
* @param apiKey API KEY
* @param url API URL
+ * @param model 模型
* @return ChatModel 对象
*/
- EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String apiKey, String url);
+ EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String apiKey, String url, String model);
/**
* 基于指定配置,获得 VectorStore 对象
- *
+ *
* 如果不存在,则进行创建
*
- * @param embeddingModel 嵌入模型
- * @param platform 平台
- * @param apiKey API KEY
- * @param url API URL
+ * @param type 向量存储类型
+ * @param embeddingModel 向量模型
+ * @param metadataFields 元数据字段
* @return VectorStore 对象
*/
- VectorStore getOrCreateVectorStore(EmbeddingModel embeddingModel, AiPlatformEnum platform, String apiKey, String url);
+ VectorStore getOrCreateVectorStore(Class extends VectorStore> type,
+ EmbeddingModel embeddingModel,
+ Map> metadataFields);
}
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/AiModelFactoryImpl.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/AiModelFactoryImpl.java
new file mode 100644
index 0000000000..f258ffaf1b
--- /dev/null
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/AiModelFactoryImpl.java
@@ -0,0 +1,752 @@
+package cn.iocoder.yudao.module.ai.framework.ai.core;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.lang.Singleton;
+import cn.hutool.core.lang.func.Func0;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.RuntimeUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import cn.iocoder.yudao.module.ai.framework.ai.config.AiAutoConfiguration;
+import cn.iocoder.yudao.module.ai.framework.ai.config.YudaoAiProperties;
+import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
+import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel;
+import cn.iocoder.yudao.module.ai.framework.ai.core.model.deepseek.DeepSeekChatModel;
+import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel;
+import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel;
+import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi;
+import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants;
+import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowChatModel;
+import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageApi;
+import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageModel;
+import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi;
+import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel;
+import cn.iocoder.yudao.framework.common.util.spring.SpringUtils;
+import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeAutoConfiguration;
+import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
+import com.alibaba.cloud.ai.dashscope.api.DashScopeImageApi;
+import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
+import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
+import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel;
+import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingOptions;
+import com.alibaba.cloud.ai.dashscope.image.DashScopeImageModel;
+import com.azure.ai.openai.OpenAIClientBuilder;
+import io.micrometer.observation.ObservationRegistry;
+import io.milvus.client.MilvusServiceClient;
+import io.qdrant.client.QdrantClient;
+import io.qdrant.client.QdrantGrpcClient;
+import lombok.SneakyThrows;
+import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiAutoConfiguration;
+import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiChatProperties;
+import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiConnectionProperties;
+import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiEmbeddingProperties;
+import org.springframework.ai.autoconfigure.minimax.MiniMaxAutoConfiguration;
+import org.springframework.ai.autoconfigure.moonshot.MoonshotAutoConfiguration;
+import org.springframework.ai.autoconfigure.ollama.OllamaAutoConfiguration;
+import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration;
+import org.springframework.ai.autoconfigure.qianfan.QianFanAutoConfiguration;
+import org.springframework.ai.autoconfigure.stabilityai.StabilityAiImageAutoConfiguration;
+import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusServiceClientConnectionDetails;
+import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusServiceClientProperties;
+import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreAutoConfiguration;
+import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreProperties;
+import org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreAutoConfiguration;
+import org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreProperties;
+import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreAutoConfiguration;
+import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreProperties;
+import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiAutoConfiguration;
+import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
+import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel;
+import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.ai.document.MetadataMode;
+import org.springframework.ai.embedding.BatchingStrategy;
+import org.springframework.ai.embedding.EmbeddingModel;
+import org.springframework.ai.image.ImageModel;
+import org.springframework.ai.minimax.MiniMaxChatModel;
+import org.springframework.ai.minimax.MiniMaxChatOptions;
+import org.springframework.ai.minimax.MiniMaxEmbeddingModel;
+import org.springframework.ai.minimax.MiniMaxEmbeddingOptions;
+import org.springframework.ai.minimax.api.MiniMaxApi;
+import org.springframework.ai.model.function.FunctionCallbackResolver;
+import org.springframework.ai.model.tool.ToolCallingManager;
+import org.springframework.ai.moonshot.MoonshotChatModel;
+import org.springframework.ai.moonshot.MoonshotChatOptions;
+import org.springframework.ai.moonshot.api.MoonshotApi;
+import org.springframework.ai.ollama.OllamaChatModel;
+import org.springframework.ai.ollama.OllamaEmbeddingModel;
+import org.springframework.ai.ollama.api.OllamaApi;
+import org.springframework.ai.ollama.api.OllamaOptions;
+import org.springframework.ai.openai.OpenAiChatModel;
+import org.springframework.ai.openai.OpenAiEmbeddingModel;
+import org.springframework.ai.openai.OpenAiEmbeddingOptions;
+import org.springframework.ai.openai.OpenAiImageModel;
+import org.springframework.ai.openai.api.OpenAiApi;
+import org.springframework.ai.openai.api.OpenAiImageApi;
+import org.springframework.ai.openai.api.common.OpenAiApiConstants;
+import org.springframework.ai.qianfan.QianFanChatModel;
+import org.springframework.ai.qianfan.QianFanEmbeddingModel;
+import org.springframework.ai.qianfan.QianFanEmbeddingOptions;
+import org.springframework.ai.qianfan.QianFanImageModel;
+import org.springframework.ai.qianfan.api.QianFanApi;
+import org.springframework.ai.qianfan.api.QianFanImageApi;
+import org.springframework.ai.stabilityai.StabilityAiImageModel;
+import org.springframework.ai.stabilityai.api.StabilityAiApi;
+import org.springframework.ai.vectorstore.SimpleVectorStore;
+import org.springframework.ai.vectorstore.VectorStore;
+import org.springframework.ai.vectorstore.milvus.MilvusVectorStore;
+import org.springframework.ai.vectorstore.observation.DefaultVectorStoreObservationConvention;
+import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention;
+import org.springframework.ai.vectorstore.qdrant.QdrantVectorStore;
+import org.springframework.ai.vectorstore.redis.RedisVectorStore;
+import org.springframework.ai.zhipuai.*;
+import org.springframework.ai.zhipuai.api.ZhiPuAiApi;
+import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
+import org.springframework.web.client.RestClient;
+import redis.clients.jedis.JedisPooled;
+
+import java.io.File;
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static org.springframework.ai.retry.RetryUtils.DEFAULT_RETRY_TEMPLATE;
+
+/**
+ * AI Model 模型工厂的实现类
+ *
+ * @author 芋道源码
+ */
+public class AiModelFactoryImpl implements AiModelFactory {
+
+ @Override
+ public ChatModel getOrCreateChatModel(AiPlatformEnum platform, String apiKey, String url) {
+ String cacheKey = buildClientCacheKey(ChatModel.class, platform, apiKey, url);
+ return Singleton.get(cacheKey, (Func0) () -> {
+ // noinspection EnhancedSwitchMigration
+ switch (platform) {
+ case TONG_YI:
+ return buildTongYiChatModel(apiKey);
+ case YI_YAN:
+ return buildYiYanChatModel(apiKey);
+ case DEEP_SEEK:
+ return buildDeepSeekChatModel(apiKey);
+ case DOU_BAO:
+ return buildDouBaoChatModel(apiKey);
+ case HUN_YUAN:
+ return buildHunYuanChatModel(apiKey, url);
+ case SILICON_FLOW:
+ return buildSiliconFlowChatModel(apiKey);
+ case ZHI_PU:
+ return buildZhiPuChatModel(apiKey, url);
+ case MINI_MAX:
+ return buildMiniMaxChatModel(apiKey, url);
+ case MOONSHOT:
+ return buildMoonshotChatModel(apiKey, url);
+ case XING_HUO:
+ return buildXingHuoChatModel(apiKey);
+ case BAI_CHUAN:
+ return buildBaiChuanChatModel(apiKey);
+ case OPENAI:
+ return buildOpenAiChatModel(apiKey, url);
+ case AZURE_OPENAI:
+ return buildAzureOpenAiChatModel(apiKey, url);
+ case OLLAMA:
+ return buildOllamaChatModel(url);
+ default:
+ throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
+ }
+ });
+ }
+
+ @Override
+ public ChatModel getDefaultChatModel(AiPlatformEnum platform) {
+ // noinspection EnhancedSwitchMigration
+ switch (platform) {
+ case TONG_YI:
+ return SpringUtil.getBean(DashScopeChatModel.class);
+ case YI_YAN:
+ return SpringUtil.getBean(QianFanChatModel.class);
+ case DEEP_SEEK:
+ return SpringUtil.getBean(DeepSeekChatModel.class);
+ case DOU_BAO:
+ return SpringUtil.getBean(DouBaoChatModel.class);
+ case HUN_YUAN:
+ return SpringUtil.getBean(HunYuanChatModel.class);
+ case SILICON_FLOW:
+ return SpringUtil.getBean(SiliconFlowChatModel.class);
+ case ZHI_PU:
+ return SpringUtil.getBean(ZhiPuAiChatModel.class);
+ case MINI_MAX:
+ return SpringUtil.getBean(MiniMaxChatModel.class);
+ case MOONSHOT:
+ return SpringUtil.getBean(MoonshotChatModel.class);
+ case XING_HUO:
+ return SpringUtil.getBean(XingHuoChatModel.class);
+ case BAI_CHUAN:
+ return SpringUtil.getBean(AzureOpenAiChatModel.class);
+ case OPENAI:
+ return SpringUtil.getBean(OpenAiChatModel.class);
+ case AZURE_OPENAI:
+ return SpringUtil.getBean(AzureOpenAiChatModel.class);
+ case OLLAMA:
+ return SpringUtil.getBean(OllamaChatModel.class);
+ default:
+ throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
+ }
+ }
+
+ @Override
+ public ImageModel getDefaultImageModel(AiPlatformEnum platform) {
+ // noinspection EnhancedSwitchMigration
+ switch (platform) {
+ case TONG_YI:
+ return SpringUtil.getBean(DashScopeImageModel.class);
+ case YI_YAN:
+ return SpringUtil.getBean(QianFanImageModel.class);
+ case ZHI_PU:
+ return SpringUtil.getBean(ZhiPuAiImageModel.class);
+ case SILICON_FLOW:
+ return SpringUtil.getBean(SiliconFlowImageModel.class);
+ case OPENAI:
+ return SpringUtil.getBean(OpenAiImageModel.class);
+ case STABLE_DIFFUSION:
+ return SpringUtil.getBean(StabilityAiImageModel.class);
+ default:
+ throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
+ }
+ }
+
+ @Override
+ public ImageModel getOrCreateImageModel(AiPlatformEnum platform, String apiKey, String url) {
+ // noinspection EnhancedSwitchMigration
+ switch (platform) {
+ case TONG_YI:
+ return buildTongYiImagesModel(apiKey);
+ case YI_YAN:
+ return buildQianFanImageModel(apiKey);
+ case ZHI_PU:
+ return buildZhiPuAiImageModel(apiKey, url);
+ case OPENAI:
+ return buildOpenAiImageModel(apiKey, url);
+ case SILICON_FLOW:
+ return buildSiliconFlowImageModel(apiKey,url);
+ case STABLE_DIFFUSION:
+ return buildStabilityAiImageModel(apiKey, url);
+ default:
+ throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
+ }
+ }
+
+ @Override
+ public MidjourneyApi getOrCreateMidjourneyApi(String apiKey, String url) {
+ String cacheKey = buildClientCacheKey(MidjourneyApi.class, AiPlatformEnum.MIDJOURNEY.getPlatform(), apiKey,
+ url);
+ return Singleton.get(cacheKey, (Func0) () -> {
+ YudaoAiProperties.MidjourneyProperties properties = SpringUtil.getBean(YudaoAiProperties.class)
+ .getMidjourney();
+ return new MidjourneyApi(url, apiKey, properties.getNotifyUrl());
+ });
+ }
+
+ @Override
+ public SunoApi getOrCreateSunoApi(String apiKey, String url) {
+ String cacheKey = buildClientCacheKey(SunoApi.class, AiPlatformEnum.SUNO.getPlatform(), apiKey, url);
+ return Singleton.get(cacheKey, (Func0) () -> new SunoApi(url));
+ }
+
+ @Override
+ @SuppressWarnings("EnhancedSwitchMigration")
+ public EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String apiKey, String url, String model) {
+ String cacheKey = buildClientCacheKey(EmbeddingModel.class, platform, apiKey, url, model);
+ return Singleton.get(cacheKey, (Func0) () -> {
+ switch (platform) {
+ case TONG_YI:
+ return buildTongYiEmbeddingModel(apiKey, model);
+ case YI_YAN:
+ return buildYiYanEmbeddingModel(apiKey, model);
+ case ZHI_PU:
+ return buildZhiPuEmbeddingModel(apiKey, url, model);
+ case MINI_MAX:
+ return buildMiniMaxEmbeddingModel(apiKey, url, model);
+ case OPENAI:
+ return buildOpenAiEmbeddingModel(apiKey, url, model);
+ case AZURE_OPENAI:
+ return buildAzureOpenAiEmbeddingModel(apiKey, url, model);
+ case OLLAMA:
+ return buildOllamaEmbeddingModel(url, model);
+ default:
+ throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
+ }
+ });
+ }
+
+ @Override
+ public VectorStore getOrCreateVectorStore(Class extends VectorStore> type,
+ EmbeddingModel embeddingModel,
+ Map> metadataFields) {
+ String cacheKey = buildClientCacheKey(VectorStore.class, embeddingModel, type);
+ return Singleton.get(cacheKey, (Func0) () -> {
+ if (type == SimpleVectorStore.class) {
+ return buildSimpleVectorStore(embeddingModel);
+ }
+ if (type == QdrantVectorStore.class) {
+ return buildQdrantVectorStore(embeddingModel);
+ }
+ if (type == RedisVectorStore.class) {
+ return buildRedisVectorStore(embeddingModel, metadataFields);
+ }
+ if (type == MilvusVectorStore.class) {
+ return buildMilvusVectorStore(embeddingModel);
+ }
+ throw new IllegalArgumentException(StrUtil.format("未知类型({})", type));
+ });
+ }
+
+ private static String buildClientCacheKey(Class> clazz, Object... params) {
+ if (ArrayUtil.isEmpty(params)) {
+ return clazz.getName();
+ }
+ return StrUtil.format("{}#{}", clazz.getName(), ArrayUtil.join(params, "_"));
+ }
+
+ // ========== 各种创建 spring-ai 客户端的方法 ==========
+
+ /**
+ * 可参考 {@link DashScopeAutoConfiguration} 的 dashscopeChatModel 方法
+ */
+ private static DashScopeChatModel buildTongYiChatModel(String key) {
+ DashScopeApi dashScopeApi = new DashScopeApi(key);
+ DashScopeChatOptions options = DashScopeChatOptions.builder().withModel(DashScopeApi.DEFAULT_CHAT_MODEL)
+ .withTemperature(0.7).build();
+ return new DashScopeChatModel(dashScopeApi, options, getFunctionCallbackResolver(), DEFAULT_RETRY_TEMPLATE);
+ }
+
+ /**
+ * 可参考 {@link DashScopeAutoConfiguration} 的 dashScopeImageModel 方法
+ */
+ private static DashScopeImageModel buildTongYiImagesModel(String key) {
+ DashScopeImageApi dashScopeImageApi = new DashScopeImageApi(key);
+ return new DashScopeImageModel(dashScopeImageApi);
+ }
+
+ /**
+ * 可参考 {@link QianFanAutoConfiguration} 的 qianFanChatModel 方法
+ */
+ private static QianFanChatModel buildYiYanChatModel(String key) {
+ List keys = StrUtil.split(key, '|');
+ Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");
+ String appKey = keys.get(0);
+ String secretKey = keys.get(1);
+ QianFanApi qianFanApi = new QianFanApi(appKey, secretKey);
+ return new QianFanChatModel(qianFanApi);
+ }
+
+ /**
+ * 可参考 {@link QianFanAutoConfiguration} 的 qianFanImageModel 方法
+ */
+ private QianFanImageModel buildQianFanImageModel(String key) {
+ List keys = StrUtil.split(key, '|');
+ Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");
+ String appKey = keys.get(0);
+ String secretKey = keys.get(1);
+ QianFanImageApi qianFanApi = new QianFanImageApi(appKey, secretKey);
+ return new QianFanImageModel(qianFanApi);
+ }
+
+ /**
+ * 可参考 {@link AiAutoConfiguration#deepSeekChatModel(YudaoAiProperties)}
+ */
+ private static DeepSeekChatModel buildDeepSeekChatModel(String apiKey) {
+ YudaoAiProperties.DeepSeekProperties properties = new YudaoAiProperties.DeepSeekProperties()
+ .setApiKey(apiKey);
+ return new AiAutoConfiguration().buildDeepSeekChatModel(properties);
+ }
+
+ /**
+ * 可参考 {@link AiAutoConfiguration#douBaoChatClient(YudaoAiProperties)}
+ */
+ private ChatModel buildDouBaoChatModel(String apiKey) {
+ YudaoAiProperties.DouBaoProperties properties = new YudaoAiProperties.DouBaoProperties()
+ .setApiKey(apiKey);
+ return new AiAutoConfiguration().buildDouBaoChatClient(properties);
+ }
+
+ /**
+ * 可参考 {@link AiAutoConfiguration#hunYuanChatClient(YudaoAiProperties)}
+ */
+ private ChatModel buildHunYuanChatModel(String apiKey, String url) {
+ YudaoAiProperties.HunYuanProperties properties = new YudaoAiProperties.HunYuanProperties()
+ .setBaseUrl(url).setApiKey(apiKey);
+ return new AiAutoConfiguration().buildHunYuanChatClient(properties);
+ }
+
+ /**
+ * 可参考 {@link AiAutoConfiguration#siliconFlowChatClient(YudaoAiProperties)}
+ */
+ private ChatModel buildSiliconFlowChatModel(String apiKey) {
+ YudaoAiProperties.SiliconFlowProperties properties = new YudaoAiProperties.SiliconFlowProperties()
+ .setApiKey(apiKey);
+ return new AiAutoConfiguration().buildSiliconFlowChatClient(properties);
+ }
+
+ /**
+ * 可参考 {@link ZhiPuAiAutoConfiguration} 的 zhiPuAiChatModel 方法
+ */
+ private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) {
+ ZhiPuAiApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiApi(apiKey)
+ : new ZhiPuAiApi(url, apiKey);
+ ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder().model(ZhiPuAiApi.DEFAULT_CHAT_MODEL).temperature(0.7).build();
+ return new ZhiPuAiChatModel(zhiPuAiApi, options, getFunctionCallbackResolver(), DEFAULT_RETRY_TEMPLATE);
+ }
+
+ /**
+ * 可参考 {@link ZhiPuAiAutoConfiguration} 的 zhiPuAiImageModel 方法
+ */
+ private ZhiPuAiImageModel buildZhiPuAiImageModel(String apiKey, String url) {
+ ZhiPuAiImageApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiImageApi(apiKey)
+ : new ZhiPuAiImageApi(url, apiKey, RestClient.builder());
+ return new ZhiPuAiImageModel(zhiPuAiApi);
+ }
+
+ /**
+ * 可参考 {@link MiniMaxAutoConfiguration} 的 miniMaxChatModel 方法
+ */
+ private MiniMaxChatModel buildMiniMaxChatModel(String apiKey, String url) {
+ MiniMaxApi miniMaxApi = StrUtil.isEmpty(url) ? new MiniMaxApi(apiKey)
+ : new MiniMaxApi(url, apiKey);
+ MiniMaxChatOptions options = MiniMaxChatOptions.builder().model(MiniMaxApi.DEFAULT_CHAT_MODEL).temperature(0.7).build();
+ return new MiniMaxChatModel(miniMaxApi, options, getFunctionCallbackResolver(), DEFAULT_RETRY_TEMPLATE);
+ }
+
+ /**
+ * 可参考 {@link MoonshotAutoConfiguration} 的 moonshotChatModel 方法
+ */
+ private MoonshotChatModel buildMoonshotChatModel(String apiKey, String url) {
+ MoonshotApi moonshotApi = StrUtil.isEmpty(url)? new MoonshotApi(apiKey)
+ : new MoonshotApi(url, apiKey);
+ MoonshotChatOptions options = MoonshotChatOptions.builder().model(MoonshotApi.DEFAULT_CHAT_MODEL).build();
+ return new MoonshotChatModel(moonshotApi, options, getFunctionCallbackResolver(), DEFAULT_RETRY_TEMPLATE);
+ }
+
+ /**
+ * 可参考 {@link AiAutoConfiguration#xingHuoChatClient(YudaoAiProperties)}
+ */
+ private static XingHuoChatModel buildXingHuoChatModel(String key) {
+ List keys = StrUtil.split(key, '|');
+ Assert.equals(keys.size(), 2, "XingHuoChatClient 的密钥需要 (appKey|secretKey) 格式");
+ YudaoAiProperties.XingHuoProperties properties = new YudaoAiProperties.XingHuoProperties()
+ .setAppKey(keys.get(0)).setSecretKey(keys.get(1));
+ return new AiAutoConfiguration().buildXingHuoChatClient(properties);
+ }
+
+ /**
+ * 可参考 {@link AiAutoConfiguration#baiChuanChatClient(YudaoAiProperties)}
+ */
+ private BaiChuanChatModel buildBaiChuanChatModel(String apiKey) {
+ YudaoAiProperties.BaiChuanProperties properties = new YudaoAiProperties.BaiChuanProperties()
+ .setApiKey(apiKey);
+ return new AiAutoConfiguration().buildBaiChuanChatClient(properties);
+ }
+
+ /**
+ * 可参考 {@link OpenAiAutoConfiguration} 的 openAiChatModel 方法
+ */
+ private static OpenAiChatModel buildOpenAiChatModel(String openAiToken, String url) {
+ url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL);
+ OpenAiApi openAiApi = OpenAiApi.builder().baseUrl(url).apiKey(openAiToken).build();
+ return OpenAiChatModel.builder().openAiApi(openAiApi).toolCallingManager(getToolCallingManager()).build();
+ }
+
+ // TODO @芋艿:手头暂时没密钥,使用建议再测试下
+ /**
+ * 可参考 {@link AzureOpenAiAutoConfiguration}
+ */
+ private static AzureOpenAiChatModel buildAzureOpenAiChatModel(String apiKey, String url) {
+ AzureOpenAiAutoConfiguration azureOpenAiAutoConfiguration = new AzureOpenAiAutoConfiguration();
+ // 创建 OpenAIClient 对象
+ AzureOpenAiConnectionProperties connectionProperties = new AzureOpenAiConnectionProperties();
+ connectionProperties.setApiKey(apiKey);
+ connectionProperties.setEndpoint(url);
+ OpenAIClientBuilder openAIClient = azureOpenAiAutoConfiguration.openAIClientBuilder(connectionProperties, null);
+ // 获取 AzureOpenAiChatProperties 对象
+ AzureOpenAiChatProperties chatProperties = SpringUtil.getBean(AzureOpenAiChatProperties.class);
+ return azureOpenAiAutoConfiguration.azureOpenAiChatModel(openAIClient, chatProperties,
+ getToolCallingManager(), null, null);
+ }
+
+ /**
+ * 可参考 {@link OpenAiAutoConfiguration} 的 openAiImageModel 方法
+ */
+ private OpenAiImageModel buildOpenAiImageModel(String openAiToken, String url) {
+ url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL);
+ OpenAiImageApi openAiApi = OpenAiImageApi.builder().baseUrl(url).apiKey(openAiToken).build();
+ return new OpenAiImageModel(openAiApi);
+ }
+
+ /**
+ * 创建 SiliconFlowImageModel 对象
+ */
+ private SiliconFlowImageModel buildSiliconFlowImageModel(String apiToken, String url) {
+ url = StrUtil.blankToDefault(url, SiliconFlowApiConstants.DEFAULT_BASE_URL);
+ SiliconFlowImageApi openAiApi = new SiliconFlowImageApi(url, apiToken);
+ return new SiliconFlowImageModel(openAiApi);
+ }
+
+ /**
+ * 可参考 {@link OllamaAutoConfiguration} 的 ollamaApi 方法
+ */
+ private static OllamaChatModel buildOllamaChatModel(String url) {
+ OllamaApi ollamaApi = new OllamaApi(url);
+ return OllamaChatModel.builder().ollamaApi(ollamaApi).toolCallingManager(getToolCallingManager()).build();
+ }
+
+ /**
+ * 可参考 {@link StabilityAiImageAutoConfiguration} 的 stabilityAiImageModel 方法
+ */
+ private StabilityAiImageModel buildStabilityAiImageModel(String apiKey, String url) {
+ url = StrUtil.blankToDefault(url, StabilityAiApi.DEFAULT_BASE_URL);
+ StabilityAiApi stabilityAiApi = new StabilityAiApi(apiKey, StabilityAiApi.DEFAULT_IMAGE_MODEL, url);
+ return new StabilityAiImageModel(stabilityAiApi);
+ }
+
+ // ========== 各种创建 EmbeddingModel 的方法 ==========
+
+ /**
+ * 可参考 {@link DashScopeAutoConfiguration} 的 dashscopeEmbeddingModel 方法
+ */
+ private DashScopeEmbeddingModel buildTongYiEmbeddingModel(String apiKey, String model) {
+ DashScopeApi dashScopeApi = new DashScopeApi(apiKey);
+ DashScopeEmbeddingOptions dashScopeEmbeddingOptions = DashScopeEmbeddingOptions.builder().withModel(model).build();
+ return new DashScopeEmbeddingModel(dashScopeApi, MetadataMode.EMBED, dashScopeEmbeddingOptions);
+ }
+
+ /**
+ * 可参考 {@link ZhiPuAiAutoConfiguration} 的 zhiPuAiEmbeddingModel 方法
+ */
+ private ZhiPuAiEmbeddingModel buildZhiPuEmbeddingModel(String apiKey, String url, String model) {
+ ZhiPuAiApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiApi(apiKey)
+ : new ZhiPuAiApi(url, apiKey);
+ ZhiPuAiEmbeddingOptions zhiPuAiEmbeddingOptions = ZhiPuAiEmbeddingOptions.builder().model(model).build();
+ return new ZhiPuAiEmbeddingModel(zhiPuAiApi, MetadataMode.EMBED, zhiPuAiEmbeddingOptions);
+ }
+
+ /**
+ * 可参考 {@link MiniMaxAutoConfiguration} 的 miniMaxEmbeddingModel 方法
+ */
+ private EmbeddingModel buildMiniMaxEmbeddingModel(String apiKey, String url, String model) {
+ MiniMaxApi miniMaxApi = StrUtil.isEmpty(url)? new MiniMaxApi(apiKey)
+ : new MiniMaxApi(url, apiKey);
+ MiniMaxEmbeddingOptions miniMaxEmbeddingOptions = MiniMaxEmbeddingOptions.builder().model(model).build();
+ return new MiniMaxEmbeddingModel(miniMaxApi, MetadataMode.EMBED, miniMaxEmbeddingOptions);
+ }
+
+ /**
+ * 可参考 {@link QianFanAutoConfiguration} 的 qianFanEmbeddingModel 方法
+ */
+ private QianFanEmbeddingModel buildYiYanEmbeddingModel(String key, String model) {
+ List keys = StrUtil.split(key, '|');
+ Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");
+ String appKey = keys.get(0);
+ String secretKey = keys.get(1);
+ QianFanApi qianFanApi = new QianFanApi(appKey, secretKey);
+ QianFanEmbeddingOptions qianFanEmbeddingOptions = QianFanEmbeddingOptions.builder().model(model).build();
+ return new QianFanEmbeddingModel(qianFanApi, MetadataMode.EMBED, qianFanEmbeddingOptions);
+ }
+
+ private OllamaEmbeddingModel buildOllamaEmbeddingModel(String url, String model) {
+ OllamaApi ollamaApi = new OllamaApi(url);
+ OllamaOptions ollamaOptions = OllamaOptions.builder().model(model).build();
+ return OllamaEmbeddingModel.builder().ollamaApi(ollamaApi).defaultOptions(ollamaOptions).build();
+ }
+
+ /**
+ * 可参考 {@link OpenAiAutoConfiguration} 的 openAiEmbeddingModel 方法
+ */
+ private OpenAiEmbeddingModel buildOpenAiEmbeddingModel(String openAiToken, String url, String model) {
+ url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL);
+ OpenAiApi openAiApi = OpenAiApi.builder().baseUrl(url).apiKey(openAiToken).build();
+ OpenAiEmbeddingOptions openAiEmbeddingProperties = OpenAiEmbeddingOptions.builder().model(model).build();
+ return new OpenAiEmbeddingModel(openAiApi, MetadataMode.EMBED, openAiEmbeddingProperties);
+ }
+
+ // TODO @芋艿:手头暂时没密钥,使用建议再测试下
+ /**
+ * 可参考 {@link AzureOpenAiAutoConfiguration} 的 azureOpenAiEmbeddingModel 方法
+ */
+ private AzureOpenAiEmbeddingModel buildAzureOpenAiEmbeddingModel(String apiKey, String url, String model) {
+ AzureOpenAiAutoConfiguration azureOpenAiAutoConfiguration = new AzureOpenAiAutoConfiguration();
+ // 创建 OpenAIClient 对象
+ AzureOpenAiConnectionProperties connectionProperties = new AzureOpenAiConnectionProperties();
+ connectionProperties.setApiKey(apiKey);
+ connectionProperties.setEndpoint(url);
+ OpenAIClientBuilder openAIClient = azureOpenAiAutoConfiguration.openAIClientBuilder(connectionProperties, null);
+ // 获取 AzureOpenAiChatProperties 对象
+ AzureOpenAiEmbeddingProperties embeddingProperties = SpringUtil.getBean(AzureOpenAiEmbeddingProperties.class);
+ return azureOpenAiAutoConfiguration.azureOpenAiEmbeddingModel(openAIClient, embeddingProperties,
+ null, null);
+ }
+
+ // ========== 各种创建 VectorStore 的方法 ==========
+
+ /**
+ * 注意:仅适合本地测试使用,生产建议还是使用 Qdrant、Milvus 等
+ */
+ @SneakyThrows
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ private SimpleVectorStore buildSimpleVectorStore(EmbeddingModel embeddingModel) {
+ SimpleVectorStore vectorStore = SimpleVectorStore.builder(embeddingModel).build();
+ // 启动加载
+ File file = new File(StrUtil.format("{}/vector_store/simple_{}.json",
+ FileUtil.getUserHomePath(), embeddingModel.getClass().getSimpleName()));
+ if (!file.exists()) {
+ FileUtil.mkParentDirs(file);
+ file.createNewFile();
+ } else if (file.length() > 0) {
+ vectorStore.load(file);
+ }
+ // 定时持久化,每分钟一次
+ Timer timer = new Timer("SimpleVectorStoreTimer-" + file.getAbsolutePath());
+ timer.scheduleAtFixedRate(new TimerTask() {
+
+ @Override
+ public void run() {
+ vectorStore.save(file);
+ }
+
+ }, Duration.ofMinutes(1).toMillis(), Duration.ofMinutes(1).toMillis());
+ // 关闭时,进行持久化
+ RuntimeUtil.addShutdownHook(() -> vectorStore.save(file));
+ return vectorStore;
+ }
+
+ /**
+ * 参考 {@link QdrantVectorStoreAutoConfiguration} 的 vectorStore 方法
+ */
+ @SneakyThrows
+ private QdrantVectorStore buildQdrantVectorStore(EmbeddingModel embeddingModel) {
+ QdrantVectorStoreAutoConfiguration configuration = new QdrantVectorStoreAutoConfiguration();
+ QdrantVectorStoreProperties properties = SpringUtil.getBean(QdrantVectorStoreProperties.class);
+ // 参考 QdrantVectorStoreAutoConfiguration 实现,创建 QdrantClient 对象
+ QdrantGrpcClient.Builder grpcClientBuilder = QdrantGrpcClient.newBuilder(
+ properties.getHost(), properties.getPort(), properties.isUseTls());
+ if (StrUtil.isNotEmpty(properties.getApiKey())) {
+ grpcClientBuilder.withApiKey(properties.getApiKey());
+ }
+ QdrantClient qdrantClient = new QdrantClient(grpcClientBuilder.build());
+ // 创建 QdrantVectorStore 对象
+ QdrantVectorStore vectorStore = configuration.vectorStore(embeddingModel, properties, qdrantClient,
+ getObservationRegistry(), getCustomObservationConvention(), getBatchingStrategy());
+ // 初始化索引
+ vectorStore.afterPropertiesSet();
+ return vectorStore;
+ }
+
+ /**
+ * 参考 {@link RedisVectorStoreAutoConfiguration} 的 vectorStore 方法
+ */
+ private RedisVectorStore buildRedisVectorStore(EmbeddingModel embeddingModel,
+ Map> metadataFields) {
+ // 创建 JedisPooled 对象
+ RedisProperties redisProperties = SpringUtils.getBean(RedisProperties.class);
+ JedisPooled jedisPooled = new JedisPooled(redisProperties.getHost(), redisProperties.getPort());
+ // 创建 RedisVectorStoreProperties 对象
+ RedisVectorStoreAutoConfiguration configuration = new RedisVectorStoreAutoConfiguration();
+ RedisVectorStoreProperties properties = SpringUtil.getBean(RedisVectorStoreProperties.class);
+ RedisVectorStore redisVectorStore = RedisVectorStore.builder(jedisPooled, embeddingModel)
+ .indexName(properties.getIndex()).prefix(properties.getPrefix())
+ .initializeSchema(properties.isInitializeSchema())
+ .metadataFields(convertList(metadataFields.entrySet(), entry -> {
+ String fieldName = entry.getKey();
+ Class> fieldType = entry.getValue();
+ if (Number.class.isAssignableFrom(fieldType)) {
+ return RedisVectorStore.MetadataField.numeric(fieldName);
+ }
+ if (Boolean.class.isAssignableFrom(fieldType)) {
+ return RedisVectorStore.MetadataField.tag(fieldName);
+ }
+ return RedisVectorStore.MetadataField.text(fieldName);
+ }))
+ .observationRegistry(getObservationRegistry().getObject())
+ .customObservationConvention(getCustomObservationConvention().getObject())
+ .batchingStrategy(getBatchingStrategy())
+ .build();
+ // 初始化索引
+ redisVectorStore.afterPropertiesSet();
+ return redisVectorStore;
+ }
+
+ /**
+ * 参考 {@link MilvusVectorStoreAutoConfiguration} 的 vectorStore 方法
+ */
+ @SneakyThrows
+ private MilvusVectorStore buildMilvusVectorStore(EmbeddingModel embeddingModel) {
+ MilvusVectorStoreAutoConfiguration configuration = new MilvusVectorStoreAutoConfiguration();
+ // 获取配置属性
+ MilvusVectorStoreProperties serverProperties = SpringUtil.getBean(MilvusVectorStoreProperties.class);
+ MilvusServiceClientProperties clientProperties = SpringUtil.getBean(MilvusServiceClientProperties.class);
+
+ // 创建 MilvusServiceClient 对象
+ MilvusServiceClient milvusClient = configuration.milvusClient(serverProperties, clientProperties,
+ new MilvusServiceClientConnectionDetails() {
+
+ @Override
+ public String getHost() {
+ return clientProperties.getHost();
+ }
+
+ @Override
+ public int getPort() {
+ return clientProperties.getPort();
+ }
+
+ }
+ );
+ // 创建 MilvusVectorStore 对象
+ MilvusVectorStore vectorStore = configuration.vectorStore(milvusClient, embeddingModel, serverProperties,
+ getBatchingStrategy(), getObservationRegistry(), getCustomObservationConvention());
+
+ // 初始化索引
+ vectorStore.afterPropertiesSet();
+ return vectorStore;
+ }
+
+ private static ObjectProvider getObservationRegistry() {
+ return new ObjectProvider<>() {
+
+ @Override
+ public ObservationRegistry getObject() throws BeansException {
+ return SpringUtil.getBean(ObservationRegistry.class);
+ }
+
+ };
+ }
+
+ private static ObjectProvider getCustomObservationConvention() {
+ return new ObjectProvider<>() {
+ @Override
+ public VectorStoreObservationConvention getObject() throws BeansException {
+ return new DefaultVectorStoreObservationConvention();
+ }
+ };
+ }
+
+ private static BatchingStrategy getBatchingStrategy() {
+ return SpringUtil.getBean(BatchingStrategy.class);
+ }
+
+ private static ToolCallingManager getToolCallingManager() {
+ return SpringUtil.getBean(ToolCallingManager.class);
+ }
+
+ private static FunctionCallbackResolver getFunctionCallbackResolver() {
+ return SpringUtil.getBean(FunctionCallbackResolver.class);
+ }
+
+}
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/baichuan/BaiChuanChatModel.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/baichuan/BaiChuanChatModel.java
new file mode 100644
index 0000000000..5fb71c942c
--- /dev/null
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/baichuan/BaiChuanChatModel.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.ai.chat.model.ChatResponse;
+import org.springframework.ai.chat.prompt.ChatOptions;
+import org.springframework.ai.chat.prompt.Prompt;
+import org.springframework.ai.openai.OpenAiChatModel;
+import reactor.core.publisher.Flux;
+
+/**
+ * 百川 {@link ChatModel} 实现类
+ *
+ * @author 芋道源码
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class BaiChuanChatModel implements ChatModel {
+
+ public static final String BASE_URL = "https://api.baichuan-ai.com";
+
+ public static final String MODEL_DEFAULT = "Baichuan4-Turbo";
+
+ /**
+ * 兼容 OpenAI 接口,进行复用
+ */
+ private final OpenAiChatModel openAiChatModel;
+
+ @Override
+ public ChatResponse call(Prompt prompt) {
+ return openAiChatModel.call(prompt);
+ }
+
+ @Override
+ public Flux stream(Prompt prompt) {
+ return openAiChatModel.stream(prompt);
+ }
+
+ @Override
+ public ChatOptions getDefaultOptions() {
+ return openAiChatModel.getDefaultOptions();
+ }
+
+}
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/deepseek/DeepSeekChatModel.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/deepseek/DeepSeekChatModel.java
new file mode 100644
index 0000000000..d603abf6b0
--- /dev/null
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/deepseek/DeepSeekChatModel.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.ai.framework.ai.core.model.deepseek;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.ai.chat.model.ChatResponse;
+import org.springframework.ai.chat.prompt.ChatOptions;
+import org.springframework.ai.chat.prompt.Prompt;
+import org.springframework.ai.openai.OpenAiChatModel;
+import reactor.core.publisher.Flux;
+
+/**
+ * DeepSeek {@link ChatModel} 实现类
+ *
+ * @author fansili
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class DeepSeekChatModel implements ChatModel {
+
+ public static final String BASE_URL = "https://api.deepseek.com";
+
+ public static final String MODEL_DEFAULT = "deepseek-chat";
+
+ /**
+ * 兼容 OpenAI 接口,进行复用
+ */
+ private final OpenAiChatModel openAiChatModel;
+
+ @Override
+ public ChatResponse call(Prompt prompt) {
+ return openAiChatModel.call(prompt);
+ }
+
+ @Override
+ public Flux stream(Prompt prompt) {
+ return openAiChatModel.stream(prompt);
+ }
+
+ @Override
+ public ChatOptions getDefaultOptions() {
+ return openAiChatModel.getDefaultOptions();
+ }
+
+}
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/doubao/DouBaoChatModel.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/doubao/DouBaoChatModel.java
new file mode 100644
index 0000000000..6e2bfda499
--- /dev/null
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/doubao/DouBaoChatModel.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.ai.chat.model.ChatResponse;
+import org.springframework.ai.chat.prompt.ChatOptions;
+import org.springframework.ai.chat.prompt.Prompt;
+import org.springframework.ai.openai.OpenAiChatModel;
+import reactor.core.publisher.Flux;
+
+/**
+ * 字节豆包 {@link ChatModel} 实现类
+ *
+ * @author fansili
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class DouBaoChatModel implements ChatModel {
+
+ public static final String BASE_URL = "https://ark.cn-beijing.volces.com/api";
+
+ public static final String MODEL_DEFAULT = "doubao-1-5-lite-32k-250115";
+
+ /**
+ * 兼容 OpenAI 接口,进行复用
+ */
+ private final OpenAiChatModel openAiChatModel;
+
+ @Override
+ public ChatResponse call(Prompt prompt) {
+ return openAiChatModel.call(prompt);
+ }
+
+ @Override
+ public Flux stream(Prompt prompt) {
+ return openAiChatModel.stream(prompt);
+ }
+
+ @Override
+ public ChatOptions getDefaultOptions() {
+ return openAiChatModel.getDefaultOptions();
+ }
+
+}
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/hunyuan/HunYuanChatModel.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/hunyuan/HunYuanChatModel.java
new file mode 100644
index 0000000000..debd0a4a90
--- /dev/null
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/hunyuan/HunYuanChatModel.java
@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.ai.chat.model.ChatResponse;
+import org.springframework.ai.chat.prompt.ChatOptions;
+import org.springframework.ai.chat.prompt.Prompt;
+import org.springframework.ai.openai.OpenAiChatModel;
+import reactor.core.publisher.Flux;
+
+/**
+ * 腾云混元 {@link ChatModel} 实现类
+ *
+ * 1. 混元大模型:基于 知识引擎原子能力 实现
+ * 2. 知识引擎原子能力:基于 知识引擎原子能力 实现
+ *
+ * @author fansili
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class HunYuanChatModel implements ChatModel {
+
+ public static final String BASE_URL = "https://api.hunyuan.cloud.tencent.com";
+
+ public static final String MODEL_DEFAULT = "hunyuan-turbo";
+
+ public static final String DEEP_SEEK_BASE_URL = "https://api.lkeap.cloud.tencent.com";
+
+ public static final String DEEP_SEEK_MODEL_DEFAULT = "deepseek-v3";
+
+ /**
+ * 兼容 OpenAI 接口,进行复用
+ */
+ private final OpenAiChatModel openAiChatModel;
+
+ @Override
+ public ChatResponse call(Prompt prompt) {
+ return openAiChatModel.call(prompt);
+ }
+
+ @Override
+ public Flux stream(Prompt prompt) {
+ return openAiChatModel.stream(prompt);
+ }
+
+ @Override
+ public ChatOptions getDefaultOptions() {
+ return openAiChatModel.getDefaultOptions();
+ }
+
+}
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/api/MidjourneyApi.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/midjourney/api/MidjourneyApi.java
similarity index 96%
rename from yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/api/MidjourneyApi.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/midjourney/api/MidjourneyApi.java
index 55091c78d4..051ef31851 100644
--- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/api/MidjourneyApi.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/midjourney/api/MidjourneyApi.java
@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.framework.ai.core.model.midjourney.api;
+package cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
@@ -8,9 +8,9 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.ai.openai.api.ApiUtils;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatusCode;
+import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
@@ -50,7 +50,10 @@ public class MidjourneyApi {
public MidjourneyApi(String baseUrl, String apiKey, String notifyUrl) {
this.webClient = WebClient.builder()
.baseUrl(baseUrl)
- .defaultHeaders(ApiUtils.getJsonContentHeaders(apiKey))
+ .defaultHeaders(httpHeaders -> {
+ httpHeaders.setContentType(MediaType.APPLICATION_JSON);
+ httpHeaders.setBearerAuth(apiKey);
+ })
.build();
this.notifyUrl = notifyUrl;
}
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/com/alibaba/cloud/ai/tongyi/common/exception/TongYiImagesException.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/siliconflow/SiliconFlowApiConstants.java
similarity index 58%
rename from yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/com/alibaba/cloud/ai/tongyi/common/exception/TongYiImagesException.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/siliconflow/SiliconFlowApiConstants.java
index 5e2e577e97..24a00acbec 100644
--- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/com/alibaba/cloud/ai/tongyi/common/exception/TongYiImagesException.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/siliconflow/SiliconFlowApiConstants.java
@@ -14,26 +14,21 @@
* limitations under the License.
*/
-package com.alibaba.cloud.ai.tongyi.common.exception;
+package cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow;
/**
- * TongYi models images exception.
+ * SiliconFlow API 枚举类
*
- * @author yuluo
- * @author yuluo
- * @since 2023.0.1.0
+ * @author zzt
*/
+public final class SiliconFlowApiConstants {
-public class TongYiImagesException extends TongYiException {
+ public static final String DEFAULT_BASE_URL = "https://api.siliconflow.cn";
- public TongYiImagesException(String message) {
+ public static final String MODEL_DEFAULT = "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B";
- super(message);
- }
+ public static final String DEFAULT_IMAGE_MODEL = "Kwai-Kolors/Kolors";
- public TongYiImagesException(String message, Throwable cause) {
-
- super(message, cause);
- }
+ public static final String PROVIDER_NAME = "Siiconflow";
}
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/siliconflow/SiliconFlowChatModel.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/siliconflow/SiliconFlowChatModel.java
new file mode 100644
index 0000000000..631b3455ec
--- /dev/null
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/siliconflow/SiliconFlowChatModel.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.ai.chat.model.ChatResponse;
+import org.springframework.ai.chat.prompt.ChatOptions;
+import org.springframework.ai.chat.prompt.Prompt;
+import org.springframework.ai.openai.OpenAiChatModel;
+import reactor.core.publisher.Flux;
+
+/**
+ * 硅基流动 {@link ChatModel} 实现类
+ *
+ * 1. API 文档:API 文档
+ *
+ * @author fansili
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class SiliconFlowChatModel implements ChatModel {
+
+ /**
+ * 兼容 OpenAI 接口,进行复用
+ */
+ private final OpenAiChatModel openAiChatModel;
+
+ @Override
+ public ChatResponse call(Prompt prompt) {
+ return openAiChatModel.call(prompt);
+ }
+
+ @Override
+ public Flux stream(Prompt prompt) {
+ return openAiChatModel.stream(prompt);
+ }
+
+ @Override
+ public ChatOptions getDefaultOptions() {
+ return openAiChatModel.getDefaultOptions();
+ }
+
+}
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/siliconflow/SiliconFlowImageApi.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/siliconflow/SiliconFlowImageApi.java
new file mode 100644
index 0000000000..f9cd81cb3e
--- /dev/null
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/siliconflow/SiliconFlowImageApi.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2023-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.springframework.ai.model.ApiKey;
+import org.springframework.ai.model.NoopApiKey;
+import org.springframework.ai.model.SimpleApiKey;
+import org.springframework.ai.openai.api.OpenAiImageApi;
+import org.springframework.ai.retry.RetryUtils;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.ResponseErrorHandler;
+import org.springframework.web.client.RestClient;
+
+import java.util.Map;
+
+/**
+ * 硅基流动 Image API
+ *
+ * @see Images
+ *
+ * @author zzt
+ */
+public class SiliconFlowImageApi {
+
+ private final RestClient restClient;
+
+ public SiliconFlowImageApi(String aiToken) {
+ this(SiliconFlowApiConstants.DEFAULT_BASE_URL, aiToken, RestClient.builder());
+ }
+
+ public SiliconFlowImageApi(String baseUrl, String openAiToken) {
+ this(baseUrl, openAiToken, RestClient.builder());
+ }
+
+ public SiliconFlowImageApi(String baseUrl, String openAiToken, RestClient.Builder restClientBuilder) {
+ this(baseUrl, openAiToken, restClientBuilder, RetryUtils.DEFAULT_RESPONSE_ERROR_HANDLER);
+ }
+
+ public SiliconFlowImageApi(String baseUrl, String apiKey, RestClient.Builder restClientBuilder,
+ ResponseErrorHandler responseErrorHandler) {
+ this(baseUrl, apiKey, CollectionUtils.toMultiValueMap(Map.of()), restClientBuilder, responseErrorHandler);
+ }
+
+ public SiliconFlowImageApi(String baseUrl, String apiKey, MultiValueMap headers,
+ RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) {
+ this(baseUrl, new SimpleApiKey(apiKey), headers, restClientBuilder, responseErrorHandler);
+ }
+
+ public SiliconFlowImageApi(String baseUrl, ApiKey apiKey, MultiValueMap headers,
+ RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) {
+
+ // @formatter:off
+ this.restClient = restClientBuilder.baseUrl(baseUrl)
+ .defaultHeaders(h -> {
+ if(!(apiKey instanceof NoopApiKey)) {
+ h.setBearerAuth(apiKey.getValue());
+ }
+ h.setContentType(MediaType.APPLICATION_JSON);
+ h.addAll(headers);
+ })
+ .defaultStatusHandler(responseErrorHandler)
+ .build();
+ // @formatter:on
+ }
+
+ public ResponseEntity createImage(SiliconflowImageRequest siliconflowImageRequest) {
+ Assert.notNull(siliconflowImageRequest, "Image request cannot be null.");
+ Assert.hasLength(siliconflowImageRequest.prompt(), "Prompt cannot be empty.");
+
+ return this.restClient.post()
+ .uri("v1/images/generations")
+ .body(siliconflowImageRequest)
+ .retrieve()
+ .toEntity(OpenAiImageApi.OpenAiImageResponse.class);
+ }
+
+
+ // @formatter:off
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ public record SiliconflowImageRequest (
+ @JsonProperty("prompt") String prompt,
+ @JsonProperty("model") String model,
+ @JsonProperty("batch_size") Integer batchSize,
+ @JsonProperty("negative_prompt") String negativePrompt,
+ @JsonProperty("seed") Integer seed,
+ @JsonProperty("num_inference_steps") Integer numInferenceSteps,
+ @JsonProperty("guidance_scale") Float guidanceScale,
+ @JsonProperty("image") String image) {
+
+ public SiliconflowImageRequest(String prompt, String model) {
+ this(prompt, model, null, null, null, null, null, null);
+ }
+ }
+
+}
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/siliconflow/SiliconFlowImageModel.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/siliconflow/SiliconFlowImageModel.java
new file mode 100644
index 0000000000..43f8ad2168
--- /dev/null
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/siliconflow/SiliconFlowImageModel.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2023-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow;
+
+import io.micrometer.observation.ObservationRegistry;
+import lombok.Setter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.ai.image.*;
+import org.springframework.ai.image.observation.DefaultImageModelObservationConvention;
+import org.springframework.ai.image.observation.ImageModelObservationContext;
+import org.springframework.ai.image.observation.ImageModelObservationConvention;
+import org.springframework.ai.image.observation.ImageModelObservationDocumentation;
+import org.springframework.ai.model.ModelOptionsUtils;
+import org.springframework.ai.openai.OpenAiImageModel;
+import org.springframework.ai.openai.api.OpenAiImageApi;
+import org.springframework.ai.openai.metadata.OpenAiImageGenerationMetadata;
+import org.springframework.ai.retry.RetryUtils;
+import org.springframework.http.ResponseEntity;
+import org.springframework.lang.Nullable;
+import org.springframework.retry.support.RetryTemplate;
+import org.springframework.util.Assert;
+
+import java.util.List;
+
+/**
+ * 硅基流动 {@link ImageModel} 实现类
+ *
+ * 参考 {@link OpenAiImageModel} 实现
+ *
+ * @author zzt
+ */
+public class SiliconFlowImageModel implements ImageModel {
+
+ private static final Logger logger = LoggerFactory.getLogger(SiliconFlowImageModel.class);
+
+ private static final ImageModelObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultImageModelObservationConvention();
+
+ private final SiliconFlowImageOptions defaultOptions;
+
+ private final RetryTemplate retryTemplate;
+
+ private final SiliconFlowImageApi siliconFlowImageApi;
+
+ private final ObservationRegistry observationRegistry;
+
+ @Setter
+ private ImageModelObservationConvention observationConvention = DEFAULT_OBSERVATION_CONVENTION;
+
+ public SiliconFlowImageModel(SiliconFlowImageApi siliconFlowImageApi) {
+ this(siliconFlowImageApi, SiliconFlowImageOptions.builder().build(), RetryUtils.DEFAULT_RETRY_TEMPLATE);
+ }
+
+ public SiliconFlowImageModel(SiliconFlowImageApi siliconFlowImageApi, SiliconFlowImageOptions options, RetryTemplate retryTemplate) {
+ this(siliconFlowImageApi, options, retryTemplate, ObservationRegistry.NOOP);
+ }
+
+ public SiliconFlowImageModel(SiliconFlowImageApi siliconFlowImageApi, SiliconFlowImageOptions options, RetryTemplate retryTemplate,
+ ObservationRegistry observationRegistry) {
+ Assert.notNull(siliconFlowImageApi, "OpenAiImageApi must not be null");
+ Assert.notNull(options, "options must not be null");
+ Assert.notNull(retryTemplate, "retryTemplate must not be null");
+ Assert.notNull(observationRegistry, "observationRegistry must not be null");
+ this.siliconFlowImageApi = siliconFlowImageApi;
+ this.defaultOptions = options;
+ this.retryTemplate = retryTemplate;
+ this.observationRegistry = observationRegistry;
+ }
+
+ @Override
+ public ImageResponse call(ImagePrompt imagePrompt) {
+ SiliconFlowImageOptions requestImageOptions = mergeOptions(imagePrompt.getOptions(), this.defaultOptions);
+ SiliconFlowImageApi.SiliconflowImageRequest imageRequest = createRequest(imagePrompt, requestImageOptions);
+
+ var observationContext = ImageModelObservationContext.builder()
+ .imagePrompt(imagePrompt)
+ .provider(SiliconFlowApiConstants.PROVIDER_NAME)
+ .requestOptions(imagePrompt.getOptions())
+ .build();
+
+ return ImageModelObservationDocumentation.IMAGE_MODEL_OPERATION
+ .observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext,
+ this.observationRegistry)
+ .observe(() -> {
+ ResponseEntity imageResponseEntity = this.retryTemplate
+ .execute(ctx -> this.siliconFlowImageApi.createImage(imageRequest));
+
+ ImageResponse imageResponse = convertResponse(imageResponseEntity, imageRequest);
+
+ observationContext.setResponse(imageResponse);
+
+ return imageResponse;
+ });
+ }
+
+ private SiliconFlowImageApi.SiliconflowImageRequest createRequest(ImagePrompt imagePrompt,
+ SiliconFlowImageOptions requestImageOptions) {
+ String instructions = imagePrompt.getInstructions().get(0).getText();
+
+ SiliconFlowImageApi.SiliconflowImageRequest imageRequest = new SiliconFlowImageApi.SiliconflowImageRequest(instructions,
+ SiliconFlowApiConstants.DEFAULT_IMAGE_MODEL);
+
+ return ModelOptionsUtils.merge(requestImageOptions, imageRequest, SiliconFlowImageApi.SiliconflowImageRequest.class);
+ }
+
+ private ImageResponse convertResponse(ResponseEntity imageResponseEntity,
+ SiliconFlowImageApi.SiliconflowImageRequest siliconflowImageRequest) {
+ OpenAiImageApi.OpenAiImageResponse imageApiResponse = imageResponseEntity.getBody();
+ if (imageApiResponse == null) {
+ logger.warn("No image response returned for request: {}", siliconflowImageRequest);
+ return new ImageResponse(List.of());
+ }
+
+ List imageGenerationList = imageApiResponse.data()
+ .stream()
+ .map(entry -> new ImageGeneration(new Image(entry.url(), entry.b64Json()),
+ new OpenAiImageGenerationMetadata(entry.revisedPrompt())))
+ .toList();
+
+ ImageResponseMetadata openAiImageResponseMetadata = new ImageResponseMetadata(imageApiResponse.created());
+ return new ImageResponse(imageGenerationList, openAiImageResponseMetadata);
+ }
+
+ private SiliconFlowImageOptions mergeOptions(@Nullable ImageOptions runtimeOptions, SiliconFlowImageOptions defaultOptions) {
+ var runtimeOptionsForProvider = ModelOptionsUtils.copyToTarget(runtimeOptions, ImageOptions.class,
+ SiliconFlowImageOptions.class);
+
+ if (runtimeOptionsForProvider == null) {
+ return defaultOptions;
+ }
+
+ return SiliconFlowImageOptions.builder()
+ // Handle portable image options
+ .model(ModelOptionsUtils.mergeOption(runtimeOptionsForProvider.getModel(), defaultOptions.getModel()))
+ .batchSize(ModelOptionsUtils.mergeOption(runtimeOptionsForProvider.getN(), defaultOptions.getN()))
+ .width(ModelOptionsUtils.mergeOption(runtimeOptionsForProvider.getWidth(), defaultOptions.getWidth()))
+ .height(ModelOptionsUtils.mergeOption(runtimeOptionsForProvider.getHeight(), defaultOptions.getHeight()))
+ // Handle SiliconFlow specific image options
+ .negativePrompt(ModelOptionsUtils.mergeOption(runtimeOptionsForProvider.getNegativePrompt(), defaultOptions.getNegativePrompt()))
+ .numInferenceSteps(ModelOptionsUtils.mergeOption(runtimeOptionsForProvider.getNumInferenceSteps(), defaultOptions.getNumInferenceSteps()))
+ .guidanceScale(ModelOptionsUtils.mergeOption(runtimeOptionsForProvider.getGuidanceScale(), defaultOptions.getGuidanceScale()))
+ .seed(ModelOptionsUtils.mergeOption(runtimeOptionsForProvider.getSeed(), defaultOptions.getSeed()))
+ .build();
+ }
+}
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/siliconflow/SiliconFlowImageOptions.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/siliconflow/SiliconFlowImageOptions.java
new file mode 100644
index 0000000000..6b8dd9f114
--- /dev/null
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/siliconflow/SiliconFlowImageOptions.java
@@ -0,0 +1,105 @@
+package cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.ai.image.ImageOptions;
+
+/**
+ * 硅基流动 {@link ImageOptions}
+ *
+ * @author zzt
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class SiliconFlowImageOptions implements ImageOptions {
+
+ @JsonProperty("model")
+ private String model;
+
+ @JsonProperty("negative_prompt")
+ private String negativePrompt;
+
+ /**
+ * The number of images to generate. Must be between 1 and 4.
+ */
+ @JsonProperty("image_size")
+ private String imageSize;
+
+ /**
+ * The number of images to generate. Must be between 1 and 4.
+ */
+ @JsonProperty("batch_size")
+ private Integer batchSize = 1;
+
+ /**
+ * number of inference steps
+ */
+ @JsonProperty("num_inference_steps")
+ private Integer numInferenceSteps = 25;
+
+ /**
+ * This value is used to control the degree of match between the generated image and the given prompt. The higher the value, the more the generated image will tend to strictly match the text prompt. The lower the value, the more creative and diverse the generated image will be, potentially containing more unexpected elements.
+ *
+ * Required range: 0 <= x <= 20
+ */
+ @JsonProperty("guidance_scale")
+ private Float guidanceScale = 0.75F;
+
+ /**
+ * 如果想要每次都生成固定的图片,可以把 seed 设置为固定值
+ *
+ */
+ @JsonProperty("seed")
+ private Integer seed = (int)(Math.random() * 1_000_000_000);
+
+ /**
+ * The image that needs to be uploaded should be converted into base64 format.
+ */
+ @JsonProperty("image")
+ private String image;
+
+ /**
+ * 宽
+ */
+ private Integer width;
+
+ /**
+ * 高
+ */
+ private Integer height;
+
+ public void setHeight(Integer height) {
+ this.height = height;
+ if (this.width != null && this.height != null) {
+ this.imageSize = this.width + "x" + this.height;
+ }
+ }
+
+ public void setWidth(Integer width) {
+ this.width = width;
+ if (this.width != null && this.height != null) {
+ this.imageSize = this.width + "x" + this.height;
+ }
+ }
+
+ @Override
+ public Integer getN() {
+ return batchSize;
+ }
+
+ @Override
+ public String getResponseFormat() {
+ return "url";
+ }
+
+ @Override
+ public String getStyle() {
+ return null;
+ }
+
+}
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/api/SunoApi.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/suno/api/SunoApi.java
similarity index 99%
rename from yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/api/SunoApi.java
rename to yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/suno/api/SunoApi.java
index 81cc654fb6..87e685e4ce 100644
--- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/api/SunoApi.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/suno/api/SunoApi.java
@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.framework.ai.core.model.suno.api;
+package cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.StrPool;
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/wenduoduo/api/WenDuoDuoPptApi.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/wenduoduo/api/WenDuoDuoPptApi.java
new file mode 100644
index 0000000000..69b8ec8e93
--- /dev/null
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/wenduoduo/api/WenDuoDuoPptApi.java
@@ -0,0 +1,381 @@
+package cn.iocoder.yudao.module.ai.framework.ai.core.model.wenduoduo.api;
+
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpRequest;
+import org.springframework.http.HttpStatusCode;
+import org.springframework.http.MediaType;
+import org.springframework.util.Assert;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.reactive.function.BodyInserters;
+import org.springframework.web.reactive.function.client.ClientResponse;
+import org.springframework.web.reactive.function.client.WebClient;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+/**
+ * 文多多 API
+ *
+ * @author xiaoxin
+ * @see PPT 生成 API
+ */
+@Slf4j
+public class WenDuoDuoPptApi {
+
+ public static final String BASE_URL = "https://docmee.cn";
+ public static final String TOKEN_NAME = "token";
+
+ private final WebClient webClient;
+
+ private final Predicate STATUS_PREDICATE = status -> !status.is2xxSuccessful();
+
+ private final Function