浏览代码

!19 AI 知识库
Merge pull request !19 from 小新/master-jdk21-ai

芋道源码 8 月之前
父节点
当前提交
261011d1a8
共有 41 个文件被更改,包括 810 次插入228 次删除
  1. 3 18
      yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiChatRoleEnum.java
  2. 3 1
      yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java
  3. 20 6
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeController.java
  4. 54 0
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeDocumentController.java
  5. 51 0
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeSegmentController.java
  6. 13 0
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentPageReqVO.java
  7. 37 0
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentRespVO.java
  8. 23 0
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentUpdateReqVO.java
  9. 1 1
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeCreateMyReqVO.java
  10. 3 5
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeDocumentCreateReqVO.java
  11. 25 0
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeRespVO.java
  12. 2 2
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeUpdateMyReqVO.java
  13. 21 0
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentPageReqVO.java
  14. 33 0
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentRespVO.java
  15. 17 0
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentUpdateReqVO.java
  16. 17 0
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentUpdateStatusReqVO.java
  17. 3 4
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java
  18. 1 1
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDocumentDO.java
  19. 8 2
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeSegmentDO.java
  20. 0 14
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeBaseMapper.java
  21. 10 0
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeDocumentMapper.java
  22. 25 0
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeMapper.java
  23. 11 0
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeSegmentMapper.java
  24. 0 27
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiEmbeddingService.java
  25. 0 35
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiEmbeddingServiceImpl.java
  26. 0 30
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeBaseService.java
  27. 20 1
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentService.java
  28. 84 26
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentServiceImpl.java
  29. 27 0
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentService.java
  30. 27 0
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java
  31. 50 0
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeService.java
  32. 24 25
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java
  33. 18 0
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyService.java
  34. 19 0
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyServiceImpl.java
  35. 37 27
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java
  36. 13 0
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactory.java
  37. 30 2
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java
  38. 27 0
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiVectorStoreFactory.java
  39. 51 0
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiVectorStoreFactoryImpl.java
  40. 2 1
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreAutoConfiguration.java
  41. 二进制
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/webapp/test/Fel.pdf

+ 3 - 18
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiChatRoleEnum.java

@@ -1,11 +1,8 @@
 package cn.iocoder.yudao.module.ai.enums;
 
-import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 
-import java.util.Arrays;
-
 /**
  * AI 内置聊天角色的枚举
  *
@@ -13,16 +10,16 @@ import java.util.Arrays;
  */
 @AllArgsConstructor
 @Getter
-public enum AiChatRoleEnum implements IntArrayValuable {
+public enum AiChatRoleEnum {
 
-    AI_WRITE_ROLE(1, "写作助手", """
+    AI_WRITE_ROLE("写作助手", """
             你是一位出色的写作助手,能够帮助用户生成创意和灵感,并在用户提供场景和提示词时生成对应的回复。你的任务包括:
             1.	撰写建议:根据用户提供的主题或问题,提供详细的写作建议、情节发展方向、角色设定以及背景描写,确保内容结构清晰、有逻辑。
             2.	回复生成:根据用户提供的场景和提示词,生成合适的对话或文字回复,确保语气和风格符合场景需求。
             除此之外不需要除了正文内容外的其他回复,如标题、开头、任何解释性语句或道歉。
             """),
 
-    AI_MIND_MAP_ROLE(2, "导图助手", """
+    AI_MIND_MAP_ROLE("导图助手", """
              你是一位非常优秀的思维导图助手,你会把用户的所有提问都总结成思维导图,然后以 Markdown 格式输出。markdown 只需要输出一级标题,二级标题,三级标题,四级标题,最多输出四级,除此之外不要输出任何其他 markdown 标记。下面是一个合格的例子:
              # Geek-AI 助手
              ## 完整的开源系统
@@ -39,11 +36,6 @@ public enum AiChatRoleEnum implements IntArrayValuable {
             除此之外不要任何解释性语句。
             """);
 
-    // TODO @xin:这个 role 是不是删除掉好点哈。= = 目前主要是没做角色枚举。这里多了 role 反倒容易误解哈
-    /**
-     * 角色
-     */
-    private final Integer role;
     /**
      * 角色名
      */
@@ -54,11 +46,4 @@ public enum AiChatRoleEnum implements IntArrayValuable {
      */
     private final String systemMessage;
 
-    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AiChatRoleEnum::getRole).toArray();
-
-    @Override
-    public int[] array() {
-        return ARRAYS;
-    }
-
 }

+ 3 - 1
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java

@@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
 
 /**
  * AI 错误码枚举类
- *
+ * <p>
  * ai 系统,使用 1-040-000-000 段
  */
 public interface ErrorCodeConstants {
@@ -55,5 +55,7 @@ public interface ErrorCodeConstants {
 
     // ========== API 知识库 1-022-008-000 ==========
     ErrorCode KNOWLEDGE_NOT_EXISTS = new ErrorCode(1_022_008_000, "知识库不存在!");
+    ErrorCode KNOWLEDGE_DOCUMENT_NOT_EXISTS = new ErrorCode(1_022_008_001, "文档不存在!");
+    ErrorCode KNOWLEDGE_SEGMENT_NOT_EXISTS = new ErrorCode(1_022_008_002, "段落不存在!");
 
 }

+ 20 - 6
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeController.java

@@ -1,13 +1,19 @@
 package cn.iocoder.yudao.module.ai.controller.admin.knowledge;
 
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeCreateMyReqVO;
-import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeUpdateMyReqVO;
-import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeBaseService;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeCreateMyReqVO;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeRespVO;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateMyReqVO;
+import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
+import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.annotation.Resource;
 import jakarta.validation.Valid;
+import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -19,19 +25,27 @@ import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUti
 public class AiKnowledgeController {
 
     @Resource
-    private AiKnowledgeBaseService knowledgeBaseService;
+    private AiKnowledgeService knowledgeService;
+
+    @GetMapping("/my-page")
+    @Operation(summary = "获取【我的】知识库分页")
+    public CommonResult<PageResult<AiKnowledgeRespVO>> getKnowledgePageMy(@Validated PageParam pageReqVO) {
+        PageResult<AiKnowledgeDO> pageResult = knowledgeService.getKnowledgePageMy(getLoginUserId(), pageReqVO);
+        return success(BeanUtils.toBean(pageResult, AiKnowledgeRespVO.class));
+    }
+
 
     @PostMapping("/create-my")
     @Operation(summary = "创建【我的】知识库")
     public CommonResult<Long> createKnowledgeMy(@RequestBody @Valid AiKnowledgeCreateMyReqVO createReqVO) {
-        return success(knowledgeBaseService.createKnowledgeMy(createReqVO, getLoginUserId()));
+        return success(knowledgeService.createKnowledgeMy(createReqVO, getLoginUserId()));
     }
 
 
     @PutMapping("/update-my")
     @Operation(summary = "更新【我的】知识库")
     public CommonResult<Boolean> updateKnowledgeMy(@RequestBody @Valid AiKnowledgeUpdateMyReqVO updateReqVO) {
-        knowledgeBaseService.updateKnowledgeMy(updateReqVO, getLoginUserId());
+        knowledgeService.updateKnowledgeMy(updateReqVO, getLoginUserId());
         return success(true);
     }
 

+ 54 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeDocumentController.java

@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.ai.controller.admin.knowledge;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentRespVO;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateReqVO;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeDocumentCreateReqVO;
+import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
+import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+
+@Tag(name = "管理后台 - AI 知识库-文档")
+@RestController
+@RequestMapping("/ai/knowledge/document")
+public class AiKnowledgeDocumentController {
+
+    @Resource
+    private AiKnowledgeDocumentService documentService;
+
+
+    @PostMapping("/create")
+    @Operation(summary = "新建文档")
+    public CommonResult<Long> createKnowledgeDocument(@Validated AiKnowledgeDocumentCreateReqVO reqVO) {
+        Long knowledgeDocumentId = documentService.createKnowledgeDocument(reqVO);
+        return success(knowledgeDocumentId);
+    }
+
+
+    @GetMapping("/page")
+    @Operation(summary = "获取文档分页")
+    public CommonResult<PageResult<AiKnowledgeDocumentRespVO>> getKnowledgeDocumentPageMy(@Validated AiKnowledgeDocumentPageReqVO pageReqVO) {
+        PageResult<AiKnowledgeDocumentDO> pageResult = documentService.getKnowledgeDocumentPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, AiKnowledgeDocumentRespVO.class));
+    }
+
+
+    @PutMapping("/update")
+    @Operation(summary = "更新文档")
+    public CommonResult<Boolean> updateKnowledgeDocument(@Validated @RequestBody AiKnowledgeDocumentUpdateReqVO reqVO) {
+        documentService.updateKnowledgeDocument(reqVO);
+        return success(true);
+    }
+
+
+}

+ 51 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeSegmentController.java

@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.ai.controller.admin.knowledge;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentRespVO;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateReqVO;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO;
+import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
+import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+
+@Tag(name = "管理后台 - AI 知识库-段落")
+@RestController
+@RequestMapping("/ai/knowledge/segment")
+public class AiKnowledgeSegmentController {
+
+    @Resource
+    private AiKnowledgeSegmentService segmentService;
+
+    @GetMapping("/page")
+    @Operation(summary = "获取段落分页")
+    public CommonResult<PageResult<AiKnowledgeSegmentRespVO>> getKnowledgeSegmentPageMy(@Validated AiKnowledgeSegmentPageReqVO pageReqVO) {
+        PageResult<AiKnowledgeSegmentDO> pageResult = segmentService.getKnowledgeSegmentPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, AiKnowledgeSegmentRespVO.class));
+    }
+
+
+    @PutMapping("/update")
+    @Operation(summary = "更新段落内容")
+    public CommonResult<Boolean> updateKnowledgeSegment(@Validated @RequestBody AiKnowledgeSegmentUpdateReqVO reqVO) {
+        segmentService.updateKnowledgeSegment(reqVO);
+        return success(true);
+    }
+
+    @PutMapping("/update-status")
+    @Operation(summary = "启禁用段落内容")
+    public CommonResult<Boolean> updateKnowledgeSegmentStatus(@Validated @RequestBody AiKnowledgeSegmentUpdateStatusReqVO reqVO) {
+        segmentService.updateKnowledgeSegmentStatus(reqVO);
+        return success(true);
+    }
+
+}

+ 13 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentPageReqVO.java

@@ -0,0 +1,13 @@
+package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - AI 知识库-文档 分页 Request VO")
+@Data
+public class AiKnowledgeDocumentPageReqVO extends PageParam {
+
+    @Schema(description = "文档名称", example = "Java 开发手册")
+    private String name;
+}

+ 37 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentRespVO.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - AI 知识库-文档 Response VO")
+@Data
+public class AiKnowledgeDocumentRespVO extends PageParam {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
+    private Long id;
+
+    @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
+    private Long knowledgeId;
+
+    @Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
+    private String name;
+
+    @Schema(description = "内容", example = "Java 是一门面向对象的语言.....")
+    private String content;
+
+    @Schema(description = "文档 url", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://doc.iocoder.cn")
+    private String url;
+
+    @Schema(description = "token 数量", example = "1024")
+    private Integer tokens;
+
+    @Schema(description = "字符数", example = "1008")
+    private Integer wordCount;
+
+    @Schema(description = "切片状态", example = "1")
+    private Integer sliceStatus;
+
+    @Schema(description = "文档状态", example = "1")
+    private Integer status;
+}

+ 23 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentUpdateReqVO.java

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+
+@Schema(description = "管理后台 - AI 更新 知识库-文档 Request VO")
+@Data
+public class AiKnowledgeDocumentUpdateReqVO {
+
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583")
+    @NotNull(message = "编号不能为空")
+    private Long id;
+
+    @Schema(description = "是否启用", example = "1")
+    private Integer status;
+
+    @Schema(description = "名称", example = "Java 开发手册")
+    private String name;
+
+}

+ 1 - 1
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/AiKnowledgeCreateMyReqVO.java → yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeCreateMyReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo;
+package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.constraints.NotBlank;

+ 3 - 5
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/AiKnowledgeDocumentCreateReqVO.java → yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeDocumentCreateReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo;
+package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.constraints.NotBlank;
@@ -6,10 +6,8 @@ import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 import org.hibernate.validator.constraints.URL;
 
-/**
- * @author xiaoxin
- */
-@Schema(description = "管理后台 - AI 知识库【创建文档】 Request VO")
+
+@Schema(description = "管理后台 - AI 知识库创建【文档】 Request VO")
 @Data
 public class AiKnowledgeDocumentCreateReqVO {
 

+ 25 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeRespVO.java

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+
+@Schema(description = "管理后台 - AI 知识库 Response VO")
+@Data
+public class AiKnowledgeRespVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
+    private Long id;
+
+    @Schema(description = "知识库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "ruoyi-vue-pro 用户指南")
+    private String name;
+
+    @Schema(description = "知识库描述", example = "ruoyi-vue-pro 用户指南")
+    private String description;
+
+    @Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "14")
+    private Long modelId;
+
+    @Schema(description = "模型标识", example = "qwen-72b-chat")
+    private String model;
+}

+ 2 - 2
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/AiKnowledgeUpdateMyReqVO.java → yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeUpdateMyReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo;
+package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.constraints.NotBlank;
@@ -7,7 +7,7 @@ import lombok.Data;
 
 import java.util.List;
 
-@Schema(description = "管理后台 - AI 知识库创建【我的】 Request VO")
+@Schema(description = "管理后台 - AI 知识库更新【我的】 Request VO")
 @Data
 public class AiKnowledgeUpdateMyReqVO {
 

+ 21 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentPageReqVO.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - AI 知识库分页 Request VO")
+@Data
+public class AiKnowledgeSegmentPageReqVO extends PageParam {
+
+
+    @Schema(description = "分段状态", example = "1")
+    private Integer status;
+
+    @Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer documentId;
+
+    @Schema(description = "分段内容关键字", example = "Java 开发")
+    private String keyword;
+
+}

+ 33 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentRespVO.java

@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - AI 知识库-文档 Response VO")
+@Data
+public class AiKnowledgeSegmentRespVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
+    private Long id;
+
+    @Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
+    private Long documentId;
+
+    @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
+    private Long knowledgeId;
+
+    @Schema(description = "向量库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1858496a-1dde-4edf-a43e-0aed08f37f8c")
+    private String vectorId;
+
+    @Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
+    private String content;
+
+    @Schema(description = "token 数量", example = "1024")
+    private Integer tokens;
+
+    @Schema(description = "字符数", example = "1008")
+    private Integer wordCount;
+
+    @Schema(description = "文档状态", example = "1")
+    private Integer status;
+}

+ 17 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentUpdateReqVO.java

@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+
+@Schema(description = "管理后台 - AI 更新 知识库-段落 request VO")
+@Data
+public class AiKnowledgeSegmentUpdateReqVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
+    private Long id;
+
+    @Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
+    private String content;
+
+}

+ 17 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentUpdateStatusReqVO.java

@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+
+@Schema(description = "管理后台 - AI 更新 知识库-段落 request VO")
+@Data
+public class AiKnowledgeSegmentUpdateStatusReqVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
+    private Long id;
+
+    @Schema(description = "是否启用", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer status;
+
+}

+ 3 - 4
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeBaseDO.java → yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java

@@ -10,15 +10,14 @@ import lombok.Data;
 
 import java.util.List;
 
-// TODO @xin:要不把 AiKnowledgeBaseDO 改成 AiKnowledgeDO。感觉 base 后缀,感觉有点奇怪(让人以为是基类)。然后,我们很多地方的外键编号,都是 knowledgeId
 /**
  * AI 知识库 DO
  *
  * @author xiaoxin
  */
-@TableName(value = "ai_knowledge_base", autoResultMap = true)
+@TableName(value = "ai_knowledge", autoResultMap = true)
 @Data
-public class AiKnowledgeBaseDO extends BaseDO {
+public class AiKnowledgeDO extends BaseDO {
 
     /**
      * 编号
@@ -46,7 +45,7 @@ public class AiKnowledgeBaseDO extends BaseDO {
     @TableField(typeHandler = JacksonTypeHandler.class)
     private List<Long> visibilityPermissions;
     /**
-     * 嵌入模型编号,高质量模式时维护
+     * 嵌入模型编号
      */
     private Long modelId;
     /**

+ 1 - 1
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDocumentDO.java

@@ -24,7 +24,7 @@ public class AiKnowledgeDocumentDO extends BaseDO {
     /**
      * 知识库编号
      *
-     * 关联 {@link AiKnowledgeBaseDO#getId()}
+     * 关联 {@link AiKnowledgeDO#getId()}
      */
     private Long knowledgeId;
     /**

+ 8 - 2
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeSegmentDO.java

@@ -15,6 +15,8 @@ import lombok.Data;
 @Data
 public class AiKnowledgeSegmentDO extends BaseDO {
 
+    public static final String FIELD_KNOWLEDGE_ID = "knowledgeId";
+
     /**
      * 编号
      */
@@ -24,10 +26,14 @@ public class AiKnowledgeSegmentDO extends BaseDO {
      * 向量库的编号
      */
     private String vectorId;
-    // TODO @新:knowledgeId 加个,会方便点
+    /**
+     * 知识库编号
+     * 关联 {@link AiKnowledgeDO#getId()}
+     */
+    private Long knowledgeId;
     /**
      * 文档编号
-     *
+     * <p>
      * 关联 {@link AiKnowledgeDocumentDO#getId()}
      */
     private Long documentId;

+ 0 - 14
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeBaseMapper.java

@@ -1,14 +0,0 @@
-package cn.iocoder.yudao.module.ai.dal.mysql.knowledge;
-
-import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
-import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeBaseDO;
-import org.apache.ibatis.annotations.Mapper;
-
-/**
- * AI 知识库基础信息 Mapper
- *
- * @author xiaoxin
- */
-@Mapper
-public interface AiKnowledgeBaseMapper extends BaseMapperX<AiKnowledgeBaseDO> {
-}

+ 10 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeDocumentMapper.java

@@ -1,6 +1,9 @@
 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.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO;
 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
 import org.apache.ibatis.annotations.Mapper;
 
@@ -11,4 +14,11 @@ import org.apache.ibatis.annotations.Mapper;
  */
 @Mapper
 public interface AiKnowledgeDocumentMapper extends BaseMapperX<AiKnowledgeDocumentDO> {
+
+    default PageResult<AiKnowledgeDocumentDO> selectPage(AiKnowledgeDocumentPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<AiKnowledgeDocumentDO>()
+                .likeIfPresent(AiKnowledgeDocumentDO::getName, reqVO.getName())
+                .orderByDesc(AiKnowledgeDocumentDO::getId));
+    }
+
 }

+ 25 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeMapper.java

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.ai.dal.mysql.knowledge;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+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.dal.dataobject.knowledge.AiKnowledgeDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * AI 知识库基础信息 Mapper
+ *
+ * @author xiaoxin
+ */
+@Mapper
+public interface AiKnowledgeMapper extends BaseMapperX<AiKnowledgeDO> {
+
+    default PageResult<AiKnowledgeDO> selectPageByMy(Long userId, PageParam pageReqVO) {
+        return selectPage(pageReqVO, new LambdaQueryWrapperX<AiKnowledgeDO>()
+                .eq(AiKnowledgeDO::getUserId, userId)
+                .eq(AiKnowledgeDO::getStatus, CommonStatusEnum.ENABLE.getStatus())
+                .orderByDesc(AiKnowledgeDO::getId));
+    }
+}

+ 11 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeSegmentMapper.java

@@ -1,6 +1,9 @@
 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.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO;
 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
 import org.apache.ibatis.annotations.Mapper;
 
@@ -11,4 +14,12 @@ import org.apache.ibatis.annotations.Mapper;
  */
 @Mapper
 public interface AiKnowledgeSegmentMapper extends BaseMapperX<AiKnowledgeSegmentDO> {
+
+    default PageResult<AiKnowledgeSegmentDO> selectPage(AiKnowledgeSegmentPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<AiKnowledgeSegmentDO>()
+                .eq(AiKnowledgeSegmentDO::getDocumentId, reqVO.getDocumentId())
+                .eqIfPresent(AiKnowledgeSegmentDO::getStatus, reqVO.getStatus())
+                .likeIfPresent(AiKnowledgeSegmentDO::getContent, reqVO.getKeyword())
+                .orderByDesc(AiKnowledgeSegmentDO::getId));
+    }
 }

+ 0 - 27
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiEmbeddingService.java

@@ -1,27 +0,0 @@
-package cn.iocoder.yudao.module.ai.service.knowledge;
-
-import org.springframework.ai.document.Document;
-import org.springframework.ai.vectorstore.SearchRequest;
-
-import java.util.List;
-
-/**
- * AI 嵌入 Service 接口
- *
- * @author xiaoxin
- */
-public interface AiEmbeddingService {
-
-    /**
-     * 向量化文档并存储
-     */
-    void add(List<Document> documents);
-
-    /**
-     * 相似查询
-     *
-     * @param request 查询实体
-     */
-    List<Document> similaritySearch(SearchRequest request);
-
-}

+ 0 - 35
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiEmbeddingServiceImpl.java

@@ -1,35 +0,0 @@
-package cn.iocoder.yudao.module.ai.service.knowledge;
-
-import jakarta.annotation.Resource;
-import org.springframework.ai.document.Document;
-import org.springframework.ai.vectorstore.RedisVectorStore;
-import org.springframework.ai.vectorstore.SearchRequest;
-import org.springframework.stereotype.Service;
-
-import java.util.List;
-
-// TODO @xin:是不是不用 AiEmbeddingServiceImpl,直接 vectorStore 注入到需要的地方就好啦。通过 KnowledgeDocumentService 返回就好。
-/**
- * AI 嵌入 Service 实现类
- *
- * @author xiaoxin
- */
-@Service
-public class AiEmbeddingServiceImpl implements AiEmbeddingService {
-
-    @Resource
-    private RedisVectorStore vectorStore;
-
-    @Override
-//    @Async
-    // TODO xiaoxin 报错先注释
-    public void add(List<Document> documents) {
-        vectorStore.add(documents);
-    }
-
-    @Override
-    public List<Document> similaritySearch(SearchRequest request) {
-        return vectorStore.similaritySearch(request);
-    }
-
-}

+ 0 - 30
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeBaseService.java

@@ -1,30 +0,0 @@
-package cn.iocoder.yudao.module.ai.service.knowledge;
-import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeCreateMyReqVO;
-import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeUpdateMyReqVO;
-
-/**
- * AI 知识库-基础信息 Service 接口
- *
- * @author xiaoxin
- */
-public interface AiKnowledgeBaseService {
-
-    /**
-     * 创建【我的】知识库
-     *
-     * @param createReqVO 创建信息
-     * @param userId 用户编号
-     * @return 编号
-     */
-    Long createKnowledgeMy(AiKnowledgeCreateMyReqVO createReqVO, Long userId);
-
-
-    /**
-     * 创建【我的】知识库
-     *
-     * @param updateReqVO 更新信息
-     * @param userId 用户编号
-     */
-    void updateKnowledgeMy(AiKnowledgeUpdateMyReqVO updateReqVO, Long userId);
-
-}

+ 20 - 1
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentService.java

@@ -1,6 +1,10 @@
 package cn.iocoder.yudao.module.ai.service.knowledge;
 
-import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeDocumentCreateReqVO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateReqVO;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeDocumentCreateReqVO;
+import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
 
 /**
  * AI 知识库-文档 Service 接口
@@ -17,4 +21,19 @@ public interface AiKnowledgeDocumentService {
      */
     Long createKnowledgeDocument(AiKnowledgeDocumentCreateReqVO createReqVO);
 
+
+    /**
+     * 获取文档分页
+     *
+     * @param pageReqVO 分页参数
+     * @return 文档分页
+     */
+    PageResult<AiKnowledgeDocumentDO> getKnowledgeDocumentPage(AiKnowledgeDocumentPageReqVO pageReqVO);
+
+    /**
+     * 更新文档
+     *
+     * @param reqVO 更新信息
+     */
+    void updateKnowledgeDocument(AiKnowledgeDocumentUpdateReqVO reqVO);
 }

+ 84 - 26
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentServiceImpl.java

@@ -1,27 +1,39 @@
 package cn.iocoder.yudao.module.ai.service.knowledge;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.http.HttpUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
-import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeDocumentCreateReqVO;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateReqVO;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeDocumentCreateReqVO;
+import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
 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.mysql.knowledge.AiKnowledgeDocumentMapper;
 import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeSegmentMapper;
 import cn.iocoder.yudao.module.ai.enums.knowledge.AiKnowledgeDocumentStatusEnum;
+import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
+import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.ai.document.Document;
 import org.springframework.ai.reader.tika.TikaDocumentReader;
-import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator;
+import org.springframework.ai.tokenizer.TokenCountEstimator;
 import org.springframework.ai.transformer.splitter.TokenTextSplitter;
-import org.springframework.beans.factory.annotation.Value;
+import org.springframework.ai.vectorstore.VectorStore;
+import org.springframework.core.io.ByteArrayResource;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.util.List;
-import java.util.Objects;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_DOCUMENT_NOT_EXISTS;
 
 /**
  * AI 知识库-文档 Service 实现类
@@ -39,53 +51,99 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic
 
     @Resource
     private TokenTextSplitter tokenTextSplitter;
-
     @Resource
-    private AiEmbeddingService embeddingService;
+    private TokenCountEstimator tokenCountEstimator;
 
-    // TODO @xin:@Resource 注入
-    private static final JTokkitTokenCountEstimator TOKEN_COUNT_ESTIMATOR = new JTokkitTokenCountEstimator();
+    @Resource
+    private AiApiKeyService apiKeyService;
+    @Resource
+    private AiKnowledgeService knowledgeService;
+    @Resource
+    private AiChatModelService chatModelService;
 
-    // TODO xiaoxin 临时测试用,后续删
-    @Value("classpath:/webapp/test/Fel.pdf")
-    private org.springframework.core.io.Resource data;
 
     // TODO 芋艿:需要 review 下,代码格式;
-    // TODO @xin:最好有 1、/2、/3 这种,让代码更有层次感
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Long createKnowledgeDocument(AiKnowledgeDocumentCreateReqVO createReqVO) {
-        // TODO xiaoxin 后续从 url 加载
-        TikaDocumentReader loader = new TikaDocumentReader(data);
-        // 加载文档
+        // 1.1 下载文档
+        String url = createReqVO.getUrl();
+        // 1.2 加载文档
+        TikaDocumentReader loader = new TikaDocumentReader(downloadFile(url));
         List<Document> documents = loader.get();
         Document document = CollUtil.getFirst(documents);
-        // TODO @xin:是不是不存在,就抛出异常呀;厚泽 return 呀;
-        // TODO 芋艿 文档层面有没有可能会比较大,这两个字段是否可以从分段表计算得出?回复:先直接算;
-        Integer tokens = Objects.nonNull(document) ? TOKEN_COUNT_ESTIMATOR.estimate(document.getContent()) : 0;
-        Integer wordCount = Objects.nonNull(document) ? document.getContent().length() : 0;
+        String content = document.getContent();
+        Integer tokens = tokenCountEstimator.estimate(content);
+        Integer wordCount = content.length();
 
+        // 1.3 文档记录入库
         AiKnowledgeDocumentDO documentDO = BeanUtils.toBean(createReqVO, AiKnowledgeDocumentDO.class)
                 .setTokens(tokens).setWordCount(wordCount)
                 .setStatus(CommonStatusEnum.ENABLE.getStatus()).setSliceStatus(AiKnowledgeDocumentStatusEnum.SUCCESS.getStatus());
-        // 文档记录入库
         documentMapper.insert(documentDO);
         Long documentId = documentDO.getId();
         if (CollUtil.isEmpty(documents)) {
             return documentId;
         }
 
-        // 文档分段
+        // 2.1 文档分段
         List<Document> segments = tokenTextSplitter.apply(documents);
-        // 分段内容入库
+        // 2.2 分段内容入库
         List<AiKnowledgeSegmentDO> segmentDOList = CollectionUtils.convertList(segments,
-                segment -> new AiKnowledgeSegmentDO().setContent(segment.getContent()).setDocumentId(documentId)
-                        .setTokens(TOKEN_COUNT_ESTIMATOR.estimate(segment.getContent())).setWordCount(segment.getContent().length())
+                segment -> new AiKnowledgeSegmentDO().setContent(segment.getContent()).setDocumentId(documentId).setKnowledgeId(createReqVO.getKnowledgeId()).setVectorId(segment.getId())
+                        .setTokens(tokenCountEstimator.estimate(segment.getContent())).setWordCount(segment.getContent().length())
                         .setStatus(CommonStatusEnum.ENABLE.getStatus()));
         segmentMapper.insertBatch(segmentDOList);
-        // 向量化并存储
-        embeddingService.add(segments);
+
+        // 3.1 document 补充源数据
+        segments.forEach(segment -> {
+            Map<String, Object> metadata = segment.getMetadata();
+            metadata.put(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, createReqVO.getKnowledgeId());
+        });
+
+        AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(createReqVO.getKnowledgeId());
+        AiChatModelDO model = chatModelService.validateChatModel(knowledge.getModelId());
+        // 3.2 获取向量存储实例
+        VectorStore vectorStore = apiKeyService.getOrCreateVectorStore(model.getKeyId());
+        // 3.3 向量化并存储
+        vectorStore.add(segments);
         return documentId;
     }
 
+    @Override
+    public PageResult<AiKnowledgeDocumentDO> getKnowledgeDocumentPage(AiKnowledgeDocumentPageReqVO pageReqVO) {
+        return documentMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public void updateKnowledgeDocument(AiKnowledgeDocumentUpdateReqVO reqVO) {
+        validateKnowledgeDocumentExists(reqVO.getId());
+        AiKnowledgeDocumentDO document = BeanUtils.toBean(reqVO, AiKnowledgeDocumentDO.class);
+        documentMapper.updateById(document);
+    }
+
+    /**
+     * 校验文档是否存在
+     *
+     * @param id 文档编号
+     * @return 文档信息
+     */
+    private AiKnowledgeDocumentDO validateKnowledgeDocumentExists(Long id) {
+        AiKnowledgeDocumentDO knowledgeDocument = documentMapper.selectById(id);
+        if (knowledgeDocument == null) {
+            throw exception(KNOWLEDGE_DOCUMENT_NOT_EXISTS);
+        }
+        return knowledgeDocument;
+    }
+
+    private org.springframework.core.io.Resource downloadFile(String url) {
+        try {
+            byte[] bytes = HttpUtil.downloadBytes(url);
+            return new ByteArrayResource(bytes);
+        } catch (Exception e) {
+            log.error("[downloadFile][url({}) 下载失败]", url, e);
+            throw new RuntimeException(e);
+        }
+    }
+
 }

+ 27 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentService.java

@@ -1,5 +1,11 @@
 package cn.iocoder.yudao.module.ai.service.knowledge;
 
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateReqVO;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO;
+import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
+
 /**
  * AI 知识库分片 Service 接口
  *
@@ -7,4 +13,25 @@ package cn.iocoder.yudao.module.ai.service.knowledge;
  */
 public interface AiKnowledgeSegmentService {
 
+    /**
+     * 获取段落分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 文档分页
+     */
+    PageResult<AiKnowledgeSegmentDO> getKnowledgeSegmentPage(AiKnowledgeSegmentPageReqVO pageReqVO);
+
+    /**
+     * 更新段落内容
+     *
+     * @param reqVO 更新内容
+     */
+    void updateKnowledgeSegment(AiKnowledgeSegmentUpdateReqVO reqVO);
+
+    /**
+     * 更新状态
+     *
+     * @param reqVO 更新内容
+     */
+    void updateKnowledgeSegmentStatus(AiKnowledgeSegmentUpdateStatusReqVO reqVO);
 }

+ 27 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java

@@ -1,5 +1,13 @@
 package cn.iocoder.yudao.module.ai.service.knowledge;
 
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateReqVO;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO;
+import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
+import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeSegmentMapper;
+import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
@@ -12,4 +20,23 @@ import org.springframework.stereotype.Service;
 @Slf4j
 public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService {
 
+    @Resource
+    private AiKnowledgeSegmentMapper segmentMapper;
+
+    @Override
+    public PageResult<AiKnowledgeSegmentDO> getKnowledgeSegmentPage(AiKnowledgeSegmentPageReqVO pageReqVO) {
+        return segmentMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public void updateKnowledgeSegment(AiKnowledgeSegmentUpdateReqVO reqVO) {
+        segmentMapper.updateById(BeanUtils.toBean(reqVO, AiKnowledgeSegmentDO.class));
+        // TODO @xin 重新向量化
+    }
+
+    @Override
+    public void updateKnowledgeSegmentStatus(AiKnowledgeSegmentUpdateStatusReqVO reqVO) {
+        segmentMapper.updateById(BeanUtils.toBean(reqVO, AiKnowledgeSegmentDO.class));
+        // TODO @xin 1.禁用删除向量 2.启用重新向量化
+    }
 }

+ 50 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeService.java

@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.ai.service.knowledge;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeCreateMyReqVO;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateMyReqVO;
+import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
+
+/**
+ * AI 知识库-基础信息 Service 接口
+ *
+ * @author xiaoxin
+ */
+public interface AiKnowledgeService {
+
+    /**
+     * 创建【我的】知识库
+     *
+     * @param createReqVO 创建信息
+     * @param userId      用户编号
+     * @return 编号
+     */
+    Long createKnowledgeMy(AiKnowledgeCreateMyReqVO createReqVO, Long userId);
+
+
+    /**
+     * 创建【我的】知识库
+     *
+     * @param updateReqVO 更新信息
+     * @param userId      用户编号
+     */
+    void updateKnowledgeMy(AiKnowledgeUpdateMyReqVO updateReqVO, Long userId);
+
+
+    /**
+     * 校验知识库是否存在
+     *
+     * @param id 记录编号
+     */
+    AiKnowledgeDO validateKnowledgeExists(Long id);
+
+    /**
+     * 获得【我的】知识库分页
+     *
+     * @param userId 用户编号
+     * @param pageReqVO   分页查询
+     * @return 知识库分页
+     */
+    PageResult<AiKnowledgeDO> getKnowledgePageMy(Long userId, PageParam pageReqVO);
+}

+ 24 - 25
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeBaseServiceImpl.java → yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java

@@ -1,14 +1,15 @@
 package cn.iocoder.yudao.module.ai.service.knowledge;
 
-import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.ObjUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
-import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeCreateMyReqVO;
-import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeUpdateMyReqVO;
-import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeBaseDO;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeCreateMyReqVO;
+import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateMyReqVO;
+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.mysql.knowledge.AiKnowledgeBaseMapper;
+import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeMapper;
 import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
@@ -24,56 +25,54 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_NOT_
  */
 @Service
 @Slf4j
-public class AiKnowledgeBaseServiceImpl implements AiKnowledgeBaseService {
+public class AiKnowledgeServiceImpl implements AiKnowledgeService {
 
     @Resource
     private AiChatModelService chatModalService;
 
     @Resource
-    private AiKnowledgeBaseMapper knowledgeBaseMapper;
+    private AiKnowledgeMapper knowledgeMapper;
 
     @Override
     public Long createKnowledgeMy(AiKnowledgeCreateMyReqVO createReqVO, Long userId) {
-        // TODO @xin:貌似直接调用 chatModalService.validateChatModel(id) 完事,不用搞个方法
         // 1. 校验模型配置
-        AiChatModelDO model = validateChatModel(createReqVO.getModelId());
+        AiChatModelDO model = chatModalService.validateChatModel(createReqVO.getModelId());
 
         // 2. 插入知识库
-        // TODO @xin:不用 DO 结尾
-        AiKnowledgeBaseDO knowledgeBaseDO = BeanUtils.toBean(createReqVO, AiKnowledgeBaseDO.class)
+        AiKnowledgeDO knowledgeBase = BeanUtils.toBean(createReqVO, AiKnowledgeDO.class)
                 .setModel(model.getModel()).setUserId(userId).setStatus(CommonStatusEnum.ENABLE.getStatus());
-        knowledgeBaseMapper.insert(knowledgeBaseDO);
-        return knowledgeBaseDO.getId();
+        knowledgeMapper.insert(knowledgeBase);
+        return knowledgeBase.getId();
     }
 
     @Override
     public void updateKnowledgeMy(AiKnowledgeUpdateMyReqVO updateReqVO, Long userId) {
         // 1.1 校验知识库存在
-        AiKnowledgeBaseDO knowledgeBaseDO = validateKnowledgeExists(updateReqVO.getId());
+        AiKnowledgeDO knowledgeBaseDO = validateKnowledgeExists(updateReqVO.getId());
         if (ObjUtil.notEqual(knowledgeBaseDO.getUserId(), userId)) {
             throw exception(KNOWLEDGE_NOT_EXISTS);
         }
         // 1.2 校验模型配置
-        AiChatModelDO model = validateChatModel(updateReqVO.getModelId());
+        AiChatModelDO model = chatModalService.validateChatModel(updateReqVO.getModelId());
 
         // 2. 更新知识库
-        AiKnowledgeBaseDO updateDO = BeanUtils.toBean(updateReqVO, AiKnowledgeBaseDO.class);
+        AiKnowledgeDO updateDO = BeanUtils.toBean(updateReqVO, AiKnowledgeDO.class);
         updateDO.setModel(model.getModel());
-        knowledgeBaseMapper.updateById(updateDO);
+        knowledgeMapper.updateById(updateDO);
     }
 
-    private AiChatModelDO validateChatModel(Long id) {
-        AiChatModelDO model = chatModalService.validateChatModel(id);
-        Assert.notNull(model, "未找到对应嵌入模型");
-        return model;
-    }
-
-    public AiKnowledgeBaseDO validateKnowledgeExists(Long id) {
-        AiKnowledgeBaseDO knowledgeBase = knowledgeBaseMapper.selectById(id);
+    @Override
+    public AiKnowledgeDO validateKnowledgeExists(Long id) {
+        AiKnowledgeDO knowledgeBase = knowledgeMapper.selectById(id);
         if (knowledgeBase == null) {
             throw exception(KNOWLEDGE_NOT_EXISTS);
         }
         return knowledgeBase;
     }
 
+    @Override
+    public PageResult<AiKnowledgeDO> getKnowledgePageMy(Long userId, PageParam pageReqVO) {
+        return knowledgeMapper.selectPageByMy(userId, pageReqVO);
+    }
+
 }

+ 18 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyService.java

@@ -9,7 +9,9 @@ import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveR
 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
 import jakarta.validation.Valid;
 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.List;
 
@@ -83,6 +85,14 @@ public interface AiApiKeyService {
      */
     ChatModel getChatModel(Long id);
 
+    /**
+     * 获得 EmbeddingModel 对象
+     *
+     * @param id 编号
+     * @return EmbeddingModel 对象
+     */
+    EmbeddingModel getEmbeddingModel(Long id);
+
     /**
      * 获得 ImageModel 对象
      *
@@ -111,4 +121,12 @@ public interface AiApiKeyService {
      */
     SunoApi getSunoApi();
 
+    /**
+     * 获得 vector 对象
+     *
+     * @param id 编号
+     * @return VectorStore 对象
+     */
+    VectorStore getOrCreateVectorStore(Long id);
+
 }

+ 19 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyServiceImpl.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.ai.service.model;
 
 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
 import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory;
+import cn.iocoder.yudao.framework.ai.core.factory.AiVectorStoreFactory;
 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.framework.common.enums.CommonStatusEnum;
@@ -13,7 +14,9 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
 import cn.iocoder.yudao.module.ai.dal.mysql.model.AiApiKeyMapper;
 import jakarta.annotation.Resource;
 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 org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
@@ -36,6 +39,8 @@ public class AiApiKeyServiceImpl implements AiApiKeyService {
 
     @Resource
     private AiModelFactory modelFactory;
+    @Resource
+    private AiVectorStoreFactory vectorFactory;
 
     @Override
     public Long createApiKey(AiApiKeySaveReqVO createReqVO) {
@@ -104,6 +109,13 @@ public class AiApiKeyServiceImpl implements AiApiKeyService {
         return modelFactory.getOrCreateChatModel(platform, apiKey.getApiKey(), apiKey.getUrl());
     }
 
+    @Override
+    public EmbeddingModel getEmbeddingModel(Long id) {
+        AiApiKeyDO apiKey = validateApiKey(id);
+        AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform());
+        return modelFactory.getOrCreateEmbeddingModel(platform, apiKey.getApiKey(), apiKey.getUrl());
+    }
+
     @Override
     public ImageModel getImageModel(AiPlatformEnum platform) {
         AiApiKeyDO apiKey = apiKeyMapper.selectFirstByPlatformAndStatus(platform.getPlatform(), CommonStatusEnum.ENABLE.getStatus());
@@ -132,4 +144,11 @@ public class AiApiKeyServiceImpl implements AiApiKeyService {
         }
         return modelFactory.getOrCreateSunoApi(apiKey.getApiKey(), apiKey.getUrl());
     }
+
+    @Override
+    public VectorStore getOrCreateVectorStore(Long id) {
+        AiApiKeyDO apiKey = validateApiKey(id);
+        AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform());
+        return vectorFactory.getOrCreateVectorStore(getEmbeddingModel(id), platform, apiKey.getApiKey(), apiKey.getUrl());
+    }
 }

+ 37 - 27
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java

@@ -2,6 +2,8 @@ package cn.iocoder.yudao.framework.ai.config;
 
 import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory;
 import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactoryImpl;
+import cn.iocoder.yudao.framework.ai.core.factory.AiVectorStoreFactory;
+import cn.iocoder.yudao.framework.ai.core.factory.AiVectorStoreFactoryImpl;
 import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel;
 import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatOptions;
 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
@@ -10,20 +12,15 @@ import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;
 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatOptions;
 import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreProperties;
-import org.springframework.ai.document.MetadataMode;
-import org.springframework.ai.embedding.EmbeddingModel;
+import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator;
+import org.springframework.ai.tokenizer.TokenCountEstimator;
 import org.springframework.ai.transformer.splitter.TokenTextSplitter;
-import org.springframework.ai.transformers.TransformersEmbeddingModel;
-import org.springframework.ai.vectorstore.RedisVectorStore;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Import;
 import org.springframework.context.annotation.Lazy;
-import redis.clients.jedis.JedisPooled;
 
 /**
  * 芋道 AI 自动配置
@@ -41,6 +38,12 @@ public class YudaoAiAutoConfiguration {
         return new AiModelFactoryImpl();
     }
 
+    @Bean
+    public AiVectorStoreFactory aiVectorFactory() {
+        return new AiVectorStoreFactoryImpl();
+    }
+
+
     // ========== 各种 AI Client 创建 ==========
 
     @Bean
@@ -83,35 +86,42 @@ public class YudaoAiAutoConfiguration {
     }
 
     // ========== rag 相关 ==========
-    @Bean
-    @Lazy // TODO 芋艿:临时注释,避免无法启动
-    public EmbeddingModel transformersEmbeddingClient() {
-        return new TransformersEmbeddingModel(MetadataMode.EMBED);
-    }
+    // TODO @xin 免费版本
+//    @Bean
+//    @Lazy // TODO 芋艿:临时注释,避免无法启动」
+//    public EmbeddingModel transformersEmbeddingClient() {
+//        return new TransformersEmbeddingModel(MetadataMode.EMBED);
+//    }
 
     /**
-     * 我们启动有加载很多 Embedding 模型,不晓得取哪个好,先 new 个 TransformersEmbeddingModel 跑
+     * TODO @xin 默认版本先不弄,目前都先取对应的 EmbeddingModel
      */
+//    @Bean
+//    @Lazy // TODO 芋艿:临时注释,避免无法启动
+//    public RedisVectorStore vectorStore(TongYiTextEmbeddingModel tongYiTextEmbeddingModel, RedisVectorStoreProperties properties,
+//                                        RedisProperties redisProperties) {
+//        var config = RedisVectorStore.RedisVectorStoreConfig.builder()
+//                .withIndexName(properties.getIndex())
+//                .withPrefix(properties.getPrefix())
+//                .build();
+//
+//        RedisVectorStore redisVectorStore = new RedisVectorStore(config, tongYiTextEmbeddingModel,
+//                new JedisPooled(redisProperties.getHost(), redisProperties.getPort()),
+//                properties.isInitializeSchema());
+//        redisVectorStore.afterPropertiesSet();
+//        return redisVectorStore;
+//    }
+
     @Bean
     @Lazy // TODO 芋艿:临时注释,避免无法启动
-    public RedisVectorStore vectorStore(TransformersEmbeddingModel transformersEmbeddingModel, RedisVectorStoreProperties properties,
-                                        RedisProperties redisProperties) {
-        var config = RedisVectorStore.RedisVectorStoreConfig.builder()
-                .withIndexName(properties.getIndex())
-                .withPrefix(properties.getPrefix())
-                .build();
-
-        RedisVectorStore redisVectorStore = new RedisVectorStore(config, transformersEmbeddingModel,
-                new JedisPooled(redisProperties.getHost(), redisProperties.getPort()),
-                properties.isInitializeSchema());
-        redisVectorStore.afterPropertiesSet();
-        return redisVectorStore;
+    public TokenTextSplitter tokenTextSplitter() {
+        return new TokenTextSplitter(500, 100, 5, 10000, true);
     }
 
     @Bean
     @Lazy // TODO 芋艿:临时注释,避免无法启动
-    public TokenTextSplitter tokenTextSplitter() {
-        return new TokenTextSplitter(500, 100, 5, 10000, true);
+    public TokenCountEstimator tokenCountEstimator() {
+        return new JTokkitTokenCountEstimator();
     }
 
 }

+ 13 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactory.java

@@ -4,6 +4,7 @@ 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 org.springframework.ai.chat.model.ChatModel;
+import org.springframework.ai.embedding.EmbeddingModel;
 import org.springframework.ai.image.ImageModel;
 
 /**
@@ -25,6 +26,18 @@ public interface AiModelFactory {
      */
     ChatModel getOrCreateChatModel(AiPlatformEnum platform, String apiKey, String url);
 
+    /**
+     * 基于指定配置,获得 EmbeddingModel 对象
+     * <p>
+     * 如果不存在,则进行创建
+     *
+     * @param platform 平台
+     * @param apiKey   API KEY
+     * @param url      API URL
+     * @return ChatModel 对象
+     */
+    EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String apiKey, String url);
+
     /**
      * 基于默认配置,获得 ChatModel 对象
      *

+ 30 - 2
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java

@@ -21,6 +21,7 @@ import com.alibaba.cloud.ai.tongyi.image.TongYiImagesModel;
 import com.alibaba.cloud.ai.tongyi.image.TongYiImagesProperties;
 import com.alibaba.dashscope.aigc.generation.Generation;
 import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis;
+import com.alibaba.dashscope.embeddings.TextEmbedding;
 import com.azure.ai.openai.OpenAIClient;
 import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiAutoConfiguration;
 import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiChatProperties;
@@ -37,6 +38,7 @@ import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiConnectionProperties;
 import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiImageProperties;
 import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
 import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.ai.embedding.EmbeddingModel;
 import org.springframework.ai.image.ImageModel;
 import org.springframework.ai.model.function.FunctionCallbackContext;
 import org.springframework.ai.ollama.OllamaChatModel;
@@ -97,6 +99,21 @@ public class AiModelFactoryImpl implements AiModelFactory {
         });
     }
 
+    @Override
+    public EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String apiKey, String url) {
+        String cacheKey = buildClientCacheKey(EmbeddingModel.class, platform, apiKey, url);
+        return Singleton.get(cacheKey, (Func0<EmbeddingModel>) () -> {
+            // TODO @xin 先测试一个
+            switch (platform) {
+                case TONG_YI:
+                    return buildTongYiEmbeddingModel(apiKey);
+                default:
+                    throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
+            }
+        });
+    }
+
+
     @Override
     public ChatModel getDefaultChatModel(AiPlatformEnum platform) {
         //noinspection EnhancedSwitchMigration
@@ -239,7 +256,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
 
     /**
      * 可参考 {@link ZhiPuAiAutoConfiguration#zhiPuAiChatModel(
-     * ZhiPuAiConnectionProperties, ZhiPuAiChatProperties, RestClient.Builder, List, FunctionCallbackContext, RetryTemplate, ResponseErrorHandler)}
+     *ZhiPuAiConnectionProperties, ZhiPuAiChatProperties, RestClient.Builder, List, FunctionCallbackContext, RetryTemplate, ResponseErrorHandler)}
      */
     private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) {
         url = StrUtil.blankToDefault(url, ZhiPuAiConnectionProperties.DEFAULT_BASE_URL);
@@ -249,7 +266,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
 
     /**
      * 可参考 {@link ZhiPuAiAutoConfiguration#zhiPuAiImageModel(
-     * ZhiPuAiConnectionProperties, ZhiPuAiImageProperties, RestClient.Builder, RetryTemplate, ResponseErrorHandler)}
+     *ZhiPuAiConnectionProperties, ZhiPuAiImageProperties, RestClient.Builder, RetryTemplate, ResponseErrorHandler)}
      */
     private ZhiPuAiImageModel buildZhiPuAiImageModel(String apiKey, String url) {
         url = StrUtil.blankToDefault(url, ZhiPuAiConnectionProperties.DEFAULT_BASE_URL);
@@ -315,4 +332,15 @@ public class AiModelFactoryImpl implements AiModelFactory {
         return new StabilityAiImageModel(stabilityAiApi);
     }
 
+    // ========== 各种创建 EmbeddingModel 的方法 ==========
+
+    /**
+     * 可参考 {@link TongYiAutoConfiguration#tongYiTextEmbeddingClient(TextEmbedding, TongYiConnectionProperties)}
+     */
+    private EmbeddingModel buildTongYiEmbeddingModel(String apiKey) {
+        TongYiConnectionProperties connectionProperties = new TongYiConnectionProperties();
+        connectionProperties.setApiKey(apiKey);
+        return new TongYiAutoConfiguration().tongYiTextEmbeddingClient(SpringUtil.getBean(TextEmbedding.class), connectionProperties);
+    }
+
 }

+ 27 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiVectorStoreFactory.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.framework.ai.core.factory;
+
+import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
+import org.springframework.ai.embedding.EmbeddingModel;
+import org.springframework.ai.vectorstore.VectorStore;
+
+/**
+ * AI Vector 模型工厂的接口类
+ * @author xiaoxin
+ */
+public interface AiVectorStoreFactory {
+
+
+    /**
+     * 基于指定配置,获得 VectorStore 对象
+     * <p>
+     * 如果不存在,则进行创建
+     *
+     * @param embeddingModel 嵌入模型
+     * @param platform       平台
+     * @param apiKey         API KEY
+     * @param url            API URL
+     * @return VectorStore 对象
+     */
+    VectorStore getOrCreateVectorStore(EmbeddingModel embeddingModel, AiPlatformEnum platform, String apiKey, String url);
+
+}

+ 51 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiVectorStoreFactoryImpl.java

@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.framework.ai.core.factory;
+
+import cn.hutool.core.lang.Singleton;
+import cn.hutool.core.lang.func.Func0;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
+import cn.iocoder.yudao.framework.common.util.spring.SpringUtils;
+import org.springframework.ai.embedding.EmbeddingModel;
+import org.springframework.ai.vectorstore.RedisVectorStore;
+import org.springframework.ai.vectorstore.VectorStore;
+import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
+import redis.clients.jedis.JedisPooled;
+
+/**
+ * AI Vector 模型工厂的实现类
+ * 使用 redisVectorStore 实现 VectorStore
+ *
+ * @author xiaoxin
+ */
+public class AiVectorStoreFactoryImpl implements AiVectorStoreFactory {
+
+    @Override
+    public VectorStore getOrCreateVectorStore(EmbeddingModel embeddingModel, AiPlatformEnum platform, String apiKey, String url) {
+        String cacheKey = buildClientCacheKey(VectorStore.class, platform, apiKey, url);
+        return Singleton.get(cacheKey, (Func0<VectorStore>) () -> {
+            // TODO 芋艿 @xin 这两个配置取哪好呢
+            // TODO 不同模型的向量维度可能会不一样,目前看貌似是以 index 来做区分的,维度不一样存不到一个 index 上
+            String index = "default-index";
+            String prefix = "default:";
+            var config = RedisVectorStore.RedisVectorStoreConfig.builder()
+                    .withIndexName(index)
+                    .withPrefix(prefix)
+                    .build();
+            RedisProperties redisProperties = SpringUtils.getBean(RedisProperties.class);
+            RedisVectorStore redisVectorStore = new RedisVectorStore(config, embeddingModel,
+                    new JedisPooled(redisProperties.getHost(), redisProperties.getPort()),
+                    true);
+            redisVectorStore.afterPropertiesSet();
+            return redisVectorStore;
+        });
+    }
+
+
+    private static String buildClientCacheKey(Class<?> clazz, Object... params) {
+        if (ArrayUtil.isEmpty(params)) {
+            return clazz.getName();
+        }
+        return StrUtil.format("{}#{}", clazz.getName(), ArrayUtil.join(params, "_"));
+    }
+}

+ 2 - 1
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreAutoConfiguration.java

@@ -19,6 +19,7 @@ import org.springframework.ai.embedding.EmbeddingModel;
 import org.springframework.ai.vectorstore.RedisVectorStore;
 import org.springframework.ai.vectorstore.RedisVectorStore.RedisVectorStoreConfig;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
@@ -38,7 +39,7 @@ import redis.clients.jedis.JedisPooled;
  */
 @AutoConfiguration(after = RedisAutoConfiguration.class)
 @ConditionalOnClass({JedisPooled.class, JedisConnectionFactory.class, RedisVectorStore.class, EmbeddingModel.class})
-//@ConditionalOnBean(JedisConnectionFactory.class)
+@ConditionalOnBean(JedisConnectionFactory.class)
 @EnableConfigurationProperties(RedisVectorStoreProperties.class)
 public class RedisVectorStoreAutoConfiguration {
 

二进制
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/webapp/test/Fel.pdf