Commit 0a8c75625a2501a21efcbea14a79784a5b03a10f

Authored by YunaiV
1 parent deca69ad

【代码重构】AI:知识库相关的表结构

Showing 31 changed files with 356 additions and 365 deletions
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java
... ... @@ -21,7 +21,7 @@ public interface ErrorCodeConstants {
21 21 ErrorCode CHAT_MODEL_DISABLE = new ErrorCode(1_040_001_001, "模型({})已禁用!");
22 22 ErrorCode CHAT_MODEL_DEFAULT_NOT_EXISTS = new ErrorCode(1_040_001_002, "操作失败,找不到默认聊天模型");
23 23  
24   - // ========== API 聊天模型 1-040-002-000 ==========
  24 + // ========== API 聊天角色 1-040-002-000 ==========
25 25 ErrorCode CHAT_ROLE_NOT_EXISTS = new ErrorCode(1_040_002_000, "聊天角色不存在");
26 26 ErrorCode CHAT_ROLE_DISABLE = new ErrorCode(1_040_001_001, "聊天角色({})已禁用!");
27 27  
... ... @@ -40,7 +40,6 @@ public interface ErrorCodeConstants {
40 40 ErrorCode IMAGE_NOT_EXISTS = new ErrorCode(1_022_005_000, "图片不存在!");
41 41 ErrorCode IMAGE_MIDJOURNEY_SUBMIT_FAIL = new ErrorCode(1_022_005_001, "Midjourney 提交失败!原因:{}");
42 42 ErrorCode IMAGE_CUSTOM_ID_NOT_EXISTS = new ErrorCode(1_022_005_002, "Midjourney 按钮 customId 不存在! {}");
43   - ErrorCode IMAGE_FAIL = new ErrorCode(1_022_005_002, "图片绘画失败! {}");
44 43  
45 44 // ========== API 音乐 1-040-006-000 ==========
46 45 ErrorCode MUSIC_NOT_EXISTS = new ErrorCode(1_022_006_000, "音乐不存在!");
... ... @@ -54,7 +53,11 @@ public interface ErrorCodeConstants {
54 53  
55 54 // ========== API 知识库 1-022-008-000 ==========
56 55 ErrorCode KNOWLEDGE_NOT_EXISTS = new ErrorCode(1_022_008_000, "知识库不存在!");
57   - ErrorCode KNOWLEDGE_DOCUMENT_NOT_EXISTS = new ErrorCode(1_022_008_001, "文档不存在!");
58   - ErrorCode KNOWLEDGE_SEGMENT_NOT_EXISTS = new ErrorCode(1_022_008_002, "段落不存在!");
  56 +
  57 + ErrorCode KNOWLEDGE_DOCUMENT_NOT_EXISTS = new ErrorCode(1_022_008_101, "文档不存在!");
  58 + ErrorCode KNOWLEDGE_DOCUMENT_FILE_EMPTY = new ErrorCode(1_022_008_102, "文档内容为空!");
  59 + ErrorCode KNOWLEDGE_DOCUMENT_FILE_READ_FAIL = new ErrorCode(1_022_008_102, "文档加载失败!");
  60 +
  61 + ErrorCode KNOWLEDGE_SEGMENT_NOT_EXISTS = new ErrorCode(1_022_008_202, "段落不存在!");
59 62  
60 63 }
... ...
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/knowledge/AiKnowledgeDocumentStatusEnum.java deleted
1   -package cn.iocoder.yudao.module.ai.enums.knowledge;
2   -
3   -import cn.iocoder.yudao.framework.common.core.ArrayValuable;
4   -import lombok.AllArgsConstructor;
5   -import lombok.Getter;
6   -
7   -import java.util.Arrays;
8   -
9   -/**
10   - * AI 知识库-文档状态的枚举
11   - *
12   - * @author xiaoxin
13   - */
14   -@AllArgsConstructor
15   -@Getter
16   -public enum AiKnowledgeDocumentStatusEnum implements ArrayValuable<Integer> {
17   -
18   - IN_PROGRESS(10, "索引中"),
19   - SUCCESS(20, "可用"),
20   - FAIL(30, "失败");
21   -
22   - /**
23   - * 状态
24   - */
25   - private final Integer status;
26   -
27   - /**
28   - * 状态名
29   - */
30   - private final String name;
31   -
32   - public static final Integer[] ARRAYS = Arrays.stream(values()).map(AiKnowledgeDocumentStatusEnum::getStatus).toArray(Integer[]::new);
33   -
34   - @Override
35   - public Integer[] array() {
36   - return ARRAYS;
37   - }
38   -
39   -}
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeController.http 0 → 100644
  1 +### 创建知识库
  2 +POST {{baseUrl}}/ai/knowledge/create
  3 +Content-Type: application/json
  4 +Authorization: {{token}}
  5 +tenant-id: {{adminTenantId}}
  6 +
  7 +{
  8 + "name": "测试标题",
  9 + "description": "测试描述",
  10 + "embeddingModelId": 30,
  11 + "topK": 3,
  12 + "similarityThreshold": 0.5
  13 +}
  14 +
  15 +### 更新知识库
  16 +PUT {{baseUrl}}/ai/knowledge/update
  17 +Content-Type: application/json
  18 +Authorization: {{token}}
  19 +tenant-id: {{adminTenantId}}
  20 +
  21 +{
  22 + "id": 1,
  23 + "name": "测试标题(更新)",
  24 + "description": "测试描述",
  25 + "embeddingModelId": 30,
  26 + "topK": 5,
  27 + "similarityThreshold": 0.6
  28 +}
  29 +
  30 +### 获取知识库分页
  31 +GET {{baseUrl}}/ai/knowledge/page?pageNo=1&pageSize=10
  32 +Authorization: {{token}}
  33 +tenant-id: {{adminTenantId}}
0 34 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeController.java
... ... @@ -3,10 +3,9 @@ package cn.iocoder.yudao.module.ai.controller.admin.knowledge;
3 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6   -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeCreateReqVO;
7 6 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO;
8 7 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeRespVO;
9   -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateReqVO;
  8 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeSaveReqVO;
10 9 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
11 10 import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeService;
12 11 import io.swagger.v3.oas.annotations.Operation;
... ... @@ -17,7 +16,6 @@ import org.springframework.validation.annotation.Validated;
17 16 import org.springframework.web.bind.annotation.*;
18 17  
19 18 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
20   -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
21 19  
22 20 @Tag(name = "管理后台 - AI 知识库")
23 21 @RestController
... ... @@ -31,20 +29,22 @@ public class AiKnowledgeController {
31 29 @GetMapping("/page")
32 30 @Operation(summary = "获取知识库分页")
33 31 public CommonResult<PageResult<AiKnowledgeRespVO>> getKnowledgePage(@Valid AiKnowledgePageReqVO pageReqVO) {
34   - PageResult<AiKnowledgeDO> pageResult = knowledgeService.getKnowledgePage(getLoginUserId(), pageReqVO);
  32 +
  33 + PageResult<AiKnowledgeDO> pageResult = knowledgeService.getKnowledgePage(pageReqVO);
35 34 return success(BeanUtils.toBean(pageResult, AiKnowledgeRespVO.class));
36 35 }
37 36  
38 37 @PostMapping("/create")
39 38 @Operation(summary = "创建知识库")
40   - public CommonResult<Long> createKnowledge(@RequestBody @Valid AiKnowledgeCreateReqVO createReqVO) {
41   - return success(knowledgeService.createKnowledge(createReqVO, getLoginUserId()));
  39 + public CommonResult<Long> createKnowledge(@RequestBody @Valid AiKnowledgeSaveReqVO createReqVO) {
  40 + return success(knowledgeService.createKnowledge(createReqVO));
42 41 }
43 42  
44 43 @PutMapping("/update")
45 44 @Operation(summary = "更新知识库")
46   - public CommonResult<Boolean> updateKnowledge(@RequestBody @Valid AiKnowledgeUpdateReqVO updateReqVO) {
47   - knowledgeService.updateKnowledge(updateReqVO, getLoginUserId());
  45 + public CommonResult<Boolean> updateKnowledge(@RequestBody @Valid AiKnowledgeSaveReqVO updateReqVO) {
  46 + knowledgeService.updateKnowledge(updateReqVO);
48 47 return success(true);
49 48 }
  49 +
50 50 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeDocumentController.http 0 → 100644
  1 +### 创建知识文档
  2 +POST {{baseUrl}}/ai/knowledge/document/create
  3 +Content-Type: application/json
  4 +Authorization: Bearer {{token}}
  5 +tenant-id: {{adminTenantId}}
  6 +
  7 +{
  8 + "knowledgeId": 1,
  9 + "name": "测试文档",
  10 + "url": "https://static.iocoder.cn/README.md",
  11 + "segmentMaxTokens": 800
  12 +}
0 13 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeDocumentController.java
... ... @@ -27,20 +27,21 @@ public class AiKnowledgeDocumentController {
27 27 @Resource
28 28 private AiKnowledgeDocumentService documentService;
29 29  
30   - @PostMapping("/create")
31   - @Operation(summary = "新建文档")
32   - public CommonResult<Long> createKnowledgeDocument(@Valid AiKnowledgeDocumentCreateReqVO reqVO) {
33   - Long knowledgeDocumentId = documentService.createKnowledgeDocument(reqVO);
34   - return success(knowledgeDocumentId);
35   - }
36   -
37 30 @GetMapping("/page")
38 31 @Operation(summary = "获取文档分页")
39   - public CommonResult<PageResult<AiKnowledgeDocumentRespVO>> getKnowledgeDocumentPage(@Valid AiKnowledgeDocumentPageReqVO pageReqVO) {
  32 + public CommonResult<PageResult<AiKnowledgeDocumentRespVO>> getKnowledgeDocumentPage(
  33 + @Valid AiKnowledgeDocumentPageReqVO pageReqVO) {
40 34 PageResult<AiKnowledgeDocumentDO> pageResult = documentService.getKnowledgeDocumentPage(pageReqVO);
41 35 return success(BeanUtils.toBean(pageResult, AiKnowledgeDocumentRespVO.class));
42 36 }
43 37  
  38 + @PostMapping("/create")
  39 + @Operation(summary = "新建文档")
  40 + public CommonResult<Long> createKnowledgeDocument(@RequestBody @Valid AiKnowledgeDocumentCreateReqVO reqVO) {
  41 + Long knowledgeDocumentId = documentService.createKnowledgeDocument(reqVO);
  42 + return success(knowledgeDocumentId);
  43 + }
  44 +
44 45 @PutMapping("/update")
45 46 @Operation(summary = "更新文档")
46 47 public CommonResult<Boolean> updateKnowledgeDocument(@Valid @RequestBody AiKnowledgeDocumentUpdateReqVO reqVO) {
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentUpdateReqVO.java
... ... @@ -11,16 +11,15 @@ import lombok.Data;
11 11 @Data
12 12 public class AiKnowledgeDocumentUpdateReqVO {
13 13  
14   -
15 14 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583")
16 15 @NotNull(message = "编号不能为空")
17 16 private Long id;
18 17  
  18 + @Schema(description = "名称", example = "Java 开发手册")
  19 + private String name;
  20 +
19 21 @Schema(description = "是否启用", example = "1")
20 22 @InEnum(CommonStatusEnum.class)
21 23 private Integer status;
22 24  
23   - @Schema(description = "名称", example = "Java 开发手册")
24   - private String name;
25   -
26 25 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeDocumentCreateReqVO.java
... ... @@ -23,24 +23,8 @@ public class AiKnowledgeDocumentCreateReqVO {
23 23 @URL(message = "文档 URL 格式不正确")
24 24 private String url;
25 25  
26   - @Schema(description = "每个段落的目标 token 数", requiredMode = Schema.RequiredMode.REQUIRED, example = "800")
27   - @NotNull(message = "每个段落的目标 token 数不能为空")
28   - private Integer defaultSegmentTokens;
29   -
30   - @Schema(description = "每个段落的最小字符数", requiredMode = Schema.RequiredMode.REQUIRED, example = "350")
31   - @NotNull(message = "每个段落的最小字符数不能为空")
32   - private Integer minSegmentWordCount;
33   -
34   - @Schema(description = "丢弃阈值:低于此阈值的段落会被丢弃", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
35   - @NotNull(message = "丢弃阈值不能为空")
36   - private Integer minChunkLengthToEmbed;
37   -
38   - @Schema(description = "最大段落数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000")
39   - @NotNull(message = "最大段落数不能为空")
40   - private Integer maxNumSegments;
41   -
42   - @Schema(description = "分块是否保留分隔符", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
43   - @NotNull(message = "分块是否保留分隔符不能为空")
44   - private Boolean keepSeparator;
  26 + @Schema(description = "分段的最大 Token 数", requiredMode = Schema.RequiredMode.REQUIRED, example = "800")
  27 + @NotNull(message = "分段的最大 Token 数不能为空")
  28 + private Integer segmentMaxTokens;
45 29  
46 30 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeCreateReqVO.java renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeSaveReqVO.java
... ... @@ -5,11 +5,12 @@ import jakarta.validation.constraints.NotBlank;
5 5 import jakarta.validation.constraints.NotNull;
6 6 import lombok.Data;
7 7  
8   -import java.util.List;
9   -
10   -@Schema(description = "管理后台 - AI 知识库创建 Request VO")
  8 +@Schema(description = "管理后台 - AI 知识库新增/修改 Request VO")
11 9 @Data
12   -public class AiKnowledgeCreateReqVO {
  10 +public class AiKnowledgeSaveReqVO {
  11 +
  12 + @Schema(description = "对话编号", example = "1204")
  13 + private Long id;
13 14  
14 15 @Schema(description = "知识库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "ruoyi-vue-pro 用户指南")
15 16 @NotBlank(message = "知识库名称不能为空")
... ... @@ -18,19 +19,18 @@ public class AiKnowledgeCreateReqVO {
18 19 @Schema(description = "知识库描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "存储 ruoyi-vue-pro 操作文档")
19 20 private String description;
20 21  
21   - @Schema(description = "可见权限,只能选择哪些人可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1,2,3]")
22   - private List<Long> visibilityPermissions;
  22 + @Schema(description = "向量模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  23 + @NotNull(message = "向量模型不能为空")
  24 + private Long embeddingModelId;
23 25  
24   - @Schema(description = "嵌入模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
25   - @NotNull(message = "嵌入模型不能为空")
26   - private Long modelId;
  26 + @Schema(description = "topK", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
  27 + @NotNull(message = "topK 不能为空")
  28 + private Integer topK;
27 29  
28 30 @Schema(description = "相似性阈值", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.5")
29 31 @NotNull(message = "相似性阈值不能为空")
30 32 private Double similarityThreshold;
31 33  
32   - @Schema(description = "topK", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
33   - @NotNull(message = "topK 不能为空")
34   - private Integer topK;
  34 + // TODO @芋艿:status
35 35  
36 36 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeUpdateReqVO.java deleted
1   -package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
2   -
3   -import io.swagger.v3.oas.annotations.media.Schema;
4   -import jakarta.validation.constraints.NotBlank;
5   -import jakarta.validation.constraints.NotNull;
6   -import lombok.Data;
7   -
8   -import java.util.List;
9   -
10   -@Schema(description = "管理后台 - AI 知识库更新【我的】 Request VO")
11   -@Data
12   -public class AiKnowledgeUpdateReqVO {
13   -
14   - @Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1204")
15   - @NotNull(message = "知识库编号不能为空")
16   - private Long id;
17   -
18   - @Schema(description = "知识库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "")
19   - @NotBlank(message = "知识库名称不能为空")
20   - private String name;
21   -
22   - @Schema(description = "知识库描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "")
23   - private String description;
24   -
25   - @Schema(description = "可见权限,只能选择哪些人可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3")
26   - private List<Long> visibilityPermissions;
27   -
28   - @Schema(description = "嵌入模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
29   - @NotNull(message = "嵌入模型不能为空")
30   - private Long modelId;
31   -
32   -}
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatConversationDO.java
... ... @@ -80,6 +80,8 @@ public class AiChatConversationDO extends BaseDO {
80 80 private Long modelId;
81 81 /**
82 82 * 模型标志
  83 + *
  84 + * 冗余 {@link AiChatModelDO#getModel()} 字段
83 85 */
84 86 private String model;
85 87  
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java
... ... @@ -82,6 +82,8 @@ public class AiChatMessageDO extends BaseDO {
82 82  
83 83 /**
84 84 * 模型标志
  85 + *
  86 + * 冗余 {@link AiChatModelDO#getModel()}
85 87 */
86 88 private String model;
87 89 /**
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/image/AiImageDO.java
... ... @@ -52,6 +52,7 @@ public class AiImageDO extends BaseDO {
52 52 * 枚举 {@link cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum}
53 53 */
54 54 private String platform;
  55 + // TODO @芋艿:modelId?
55 56 /**
56 57 * 模型
57 58 *
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java
... ... @@ -2,15 +2,12 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.knowledge;
2 2  
3 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
5   -import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
  5 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
6 6 import com.baomidou.mybatisplus.annotation.KeySequence;
7   -import com.baomidou.mybatisplus.annotation.TableField;
8 7 import com.baomidou.mybatisplus.annotation.TableId;
9 8 import com.baomidou.mybatisplus.annotation.TableName;
10 9 import lombok.Data;
11 10  
12   -import java.util.List;
13   -
14 11 /**
15 12 * AI 知识库 DO
16 13 *
... ... @@ -27,12 +24,6 @@ public class AiKnowledgeDO extends BaseDO {
27 24 @TableId
28 25 private Long id;
29 26 /**
30   - * 用户编号
31   - * <p>
32   - * 关联 AdminUserDO 的 userId 字段
33   - */
34   - private Long userId;
35   - /**
36 27 * 知识库名称
37 28 */
38 29 private String name;
... ... @@ -42,20 +33,17 @@ public class AiKnowledgeDO extends BaseDO {
42 33 private String description;
43 34  
44 35 /**
45   - * 可见权限,选择哪些人可见
46   - * <p>
47   - * -1 所有人可见,其他为各自用户编号
48   - */
49   - @TableField(typeHandler = LongListTypeHandler.class)
50   - private List<Long> visibilityPermissions;
51   - /**
52   - * 嵌入模型编号
  36 + * 向量模型编号
  37 + *
  38 + * 关联 {@link AiChatModelDO#getId()}
53 39 */
54   - private Long modelId;
  40 + private Long embeddingModelId;
55 41 /**
56 42 * 模型标识
  43 + *
  44 + * 冗余 {@link AiChatModelDO#getModel()}
57 45 */
58   - private String model;
  46 + private String embeddingModel;
59 47  
60 48 /**
61 49 * topK
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDocumentDO.java
... ... @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.knowledge;
2 2  
3 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
5   -import cn.iocoder.yudao.module.ai.enums.knowledge.AiKnowledgeDocumentStatusEnum;
6 5 import com.baomidou.mybatisplus.annotation.KeySequence;
7 6 import com.baomidou.mybatisplus.annotation.TableId;
8 7 import com.baomidou.mybatisplus.annotation.TableName;
... ... @@ -34,53 +33,26 @@ public class AiKnowledgeDocumentDO extends BaseDO {
34 33 */
35 34 private String name;
36 35 /**
37   - * 内容
38   - */
39   - private String content;
40   - /**
41 36 * 文件 URL
42 37 */
43 38 private String url;
44 39 /**
45   - * 文档 token 数量
  40 + * 内容
46 41 */
47   - private Integer tokens;
  42 + private String content;
48 43 /**
49   - * 文档字符数
  44 + * 文档长度
50 45 */
51   - private Integer wordCount;
52   -
  46 + private Integer contentLength;
53 47  
54   - // ========== 自定义分段所用参数 ==========
55   - // TODO @新:3)defaultChunkSize、defaultChunkSize、minChunkSizeChars、maxNumChunks 这几个字段的命名,可能要微信一起讨论下。尽量命名保持风格统一哈。
56   - /**
57   - * 每个文本块的目标 token 数
58   - */
59   - private Integer defaultSegmentTokens;
60 48 /**
61   - * 每个文本块的最小字符数
62   - */
63   - private Integer minSegmentWordCount;
64   - /**
65   - * 低于此值的块会被丢弃
66   - */
67   - private Integer minChunkLengthToEmbed;
68   - /**
69   - * 最大块数
70   - */
71   - private Integer maxNumSegments;
72   - /**
73   - * 分块是否保留分隔符
  49 + * 文档 token 数量
74 50 */
75   - private Boolean keepSeparator;
76   - // ===================================
77   -
  51 + private Integer tokens;
78 52 /**
79   - * 切片状态
80   - * <p>
81   - * 枚举 {@link AiKnowledgeDocumentStatusEnum}
  53 + * 每个文本块的目标 token 数
82 54 */
83   - private Integer sliceStatus;
  55 + private Integer segmentMaxTokens;
84 56  
85 57 /**
86 58 * 状态
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeSegmentDO.java
... ... @@ -17,7 +17,10 @@ import lombok.Data;
17 17 @Data
18 18 public class AiKnowledgeSegmentDO extends BaseDO {
19 19  
20   - public static final String FIELD_KNOWLEDGE_ID = "knowledgeId";
  20 + /**
  21 + * 向量库的编号 - 空值
  22 + */
  23 + public static final String VECTOR_ID_EMPTY = "";
21 24  
22 25 /**
23 26 * 编号
... ... @@ -25,10 +28,6 @@ public class AiKnowledgeSegmentDO extends BaseDO {
25 28 @TableId
26 29 private Long id;
27 30 /**
28   - * 向量库的编号
29   - */
30   - private String vectorId;
31   - /**
32 31 * 知识库编号
33 32 * <p>
34 33 * 关联 {@link AiKnowledgeDO#getId()}
... ... @@ -45,13 +44,19 @@ public class AiKnowledgeSegmentDO extends BaseDO {
45 44 */
46 45 private String content;
47 46 /**
48   - * 字符数
  47 + * 切片内容长度
49 48 */
50   - private Integer wordCount;
  49 + private Integer contentLength;
  50 +
  51 + /**
  52 + * 向量库的编号
  53 + */
  54 + private String vectorId;
51 55 /**
52 56 * token 数量
53 57 */
54 58 private Integer tokens;
  59 +
55 60 /**
56 61 * 状态
57 62 * <p>
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/mindmap/AiMindMapDO.java
... ... @@ -36,6 +36,7 @@ public class AiMindMapDO extends BaseDO {
36 36 * 枚举 {@link AiPlatformEnum}
37 37 */
38 38 private String platform;
  39 + // TODO @芋艿:modelId?
39 40 /**
40 41 * 模型
41 42 */
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatModelDO.java
... ... @@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.annotation.TableId;
8 8 import com.baomidou.mybatisplus.annotation.TableName;
9 9 import lombok.*;
10 10  
  11 +// TODO @芋艿,需要改造,增加 type
11 12 /**
12 13 * AI 聊天模型 DO
13 14 *
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java
... ... @@ -84,6 +84,7 @@ public class AiMusicDO extends BaseDO {
84 84 * 枚举 {@link AiPlatformEnum}
85 85 */
86 86 private String platform;
  87 + // TODO @芋艿:modelId?
87 88 /**
88 89 * 模型
89 90 */
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/write/AiWriteDO.java
... ... @@ -44,6 +44,7 @@ public class AiWriteDO extends BaseDO {
44 44 * 枚举 {@link AiPlatformEnum}
45 45 */
46 46 private String platform;
  47 + // TODO @芋艿:modelId?
47 48 /**
48 49 * 模型
49 50 */
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeMapper.java
... ... @@ -16,11 +16,9 @@ import org.apache.ibatis.annotations.Mapper;
16 16 @Mapper
17 17 public interface AiKnowledgeMapper extends BaseMapperX<AiKnowledgeDO> {
18 18  
19   - default PageResult<AiKnowledgeDO> selectPage(Long userId, AiKnowledgePageReqVO pageReqVO) {
  19 + default PageResult<AiKnowledgeDO> selectPage(AiKnowledgePageReqVO pageReqVO) {
20 20 return selectPage(pageReqVO, new LambdaQueryWrapperX<AiKnowledgeDO>()
21 21 .eq(AiKnowledgeDO::getStatus, CommonStatusEnum.ENABLE.getStatus())
22   - .likeIfPresent(AiKnowledgeDO::getName, pageReqVO.getName())
23   - .and(e -> e.apply("FIND_IN_SET(" + userId + ",visibility_permissions)").or(m -> m.apply("FIND_IN_SET(-1,visibility_permissions)")))
24   - .orderByDesc(AiKnowledgeDO::getId));
  22 + .likeIfPresent(AiKnowledgeDO::getName, pageReqVO.getName()));
25 23 }
26 24 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentService.java
... ... @@ -21,7 +21,6 @@ public interface AiKnowledgeDocumentService {
21 21 */
22 22 Long createKnowledgeDocument(AiKnowledgeDocumentCreateReqVO createReqVO);
23 23  
24   -
25 24 /**
26 25 * 获取文档分页
27 26 *
... ... @@ -36,4 +35,13 @@ public interface AiKnowledgeDocumentService {
36 35 * @param reqVO 更新信息
37 36 */
38 37 void updateKnowledgeDocument(AiKnowledgeDocumentUpdateReqVO reqVO);
  38 +
  39 + /**
  40 + * 校验文档是否存在
  41 + *
  42 + * @param id 文档编号
  43 + * @return 文档信息
  44 + */
  45 + AiKnowledgeDocumentDO validateKnowledgeDocumentExists(Long id);
  46 +
39 47 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentServiceImpl.java
1 1 package cn.iocoder.yudao.module.ai.service.knowledge;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.StrUtil;
4 5 import cn.hutool.http.HttpUtil;
5 6 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
6 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7   -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
8 8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
9 9 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO;
10 10 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateReqVO;
11 11 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeDocumentCreateReqVO;
12 12 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
13   -import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
14 13 import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeDocumentMapper;
15   -import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeSegmentMapper;
16   -import cn.iocoder.yudao.module.ai.enums.knowledge.AiKnowledgeDocumentStatusEnum;
17 14 import jakarta.annotation.Resource;
18 15 import lombok.extern.slf4j.Slf4j;
19 16 import org.springframework.ai.document.Document;
20 17 import org.springframework.ai.reader.tika.TikaDocumentReader;
21 18 import org.springframework.ai.tokenizer.TokenCountEstimator;
22   -import org.springframework.ai.transformer.splitter.TokenTextSplitter;
23   -import org.springframework.ai.vectorstore.VectorStore;
  19 +import org.springframework.context.annotation.Lazy;
24 20 import org.springframework.core.io.ByteArrayResource;
25 21 import org.springframework.stereotype.Service;
26 22 import org.springframework.transaction.annotation.Transactional;
... ... @@ -28,7 +24,7 @@ import org.springframework.transaction.annotation.Transactional;
28 24 import java.util.List;
29 25  
30 26 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
31   -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_DOCUMENT_NOT_EXISTS;
  27 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*;
32 28  
33 29 /**
34 30 * AI 知识库文档 Service 实现类
... ... @@ -40,58 +36,46 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_DOCU
40 36 public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentService {
41 37  
42 38 @Resource
43   - private AiKnowledgeDocumentMapper documentMapper;
44   - @Resource
45   - private AiKnowledgeSegmentMapper segmentMapper;
  39 + private AiKnowledgeDocumentMapper knowledgeDocumentMapper;
46 40  
47 41 @Resource
48 42 private TokenCountEstimator tokenCountEstimator;
  43 +
49 44 @Resource
  45 + private AiKnowledgeSegmentService knowledgeSegmentService;
  46 + @Resource
  47 + @Lazy // 延迟加载,避免循环依赖
50 48 private AiKnowledgeService knowledgeService;
51 49  
52 50 @Override
53 51 @Transactional(rollbackFor = Exception.class)
54 52 public Long createKnowledgeDocument(AiKnowledgeDocumentCreateReqVO createReqVO) {
55   - // 0. 校验并获取向量存储实例
56   - VectorStore vectorStore = knowledgeService.getVectorStoreById(createReqVO.getKnowledgeId());
  53 + // 1. 校验参数
  54 + knowledgeService.validateKnowledgeExists(createReqVO.getKnowledgeId());
57 55  
58   - // 1.1 下载文档
  56 + // 2. 下载文档
59 57 TikaDocumentReader loader = new TikaDocumentReader(downloadFile(createReqVO.getUrl()));
60 58 List<Document> documents = loader.get();
61 59 Document document = CollUtil.getFirst(documents);
62   - // 1.2 文档记录入库
  60 + if (document == null || StrUtil.isEmpty(document.getText())) {
  61 + throw exception(KNOWLEDGE_DOCUMENT_FILE_READ_FAIL);
  62 + }
  63 +
  64 + // 3. 文档记录入库
63 65 String content = document.getText();
64 66 AiKnowledgeDocumentDO documentDO = BeanUtils.toBean(createReqVO, AiKnowledgeDocumentDO.class)
65   - .setTokens(tokenCountEstimator.estimate(content)).setWordCount(content.length())
66   - .setStatus(CommonStatusEnum.ENABLE.getStatus()).setSliceStatus(AiKnowledgeDocumentStatusEnum.SUCCESS.getStatus());
67   - documentMapper.insert(documentDO);
68   - Long documentId = documentDO.getId();
69   - if (CollUtil.isEmpty(documents)) {
70   - return documentId;
71   - }
  67 + .setContent(content).setContentLength(content.length()).setTokens(tokenCountEstimator.estimate(content))
  68 + .setStatus(CommonStatusEnum.ENABLE.getStatus());
  69 + knowledgeDocumentMapper.insert(documentDO);
72 70  
73   - // 2 构造文本分段器
74   - TokenTextSplitter tokenTextSplitter = new TokenTextSplitter(createReqVO.getDefaultSegmentTokens(), createReqVO.getMinSegmentWordCount(), createReqVO.getMinChunkLengthToEmbed(),
75   - createReqVO.getMaxNumSegments(), createReqVO.getKeepSeparator());
76   - // 2.1 文档分段
77   - List<Document> segments = tokenTextSplitter.apply(documents);
78   - // 2.2 分段内容入库
79   - List<AiKnowledgeSegmentDO> segmentDOList = CollectionUtils.convertList(segments,
80   - segment -> new AiKnowledgeSegmentDO().setContent(segment.getText()).setDocumentId(documentId)
81   - .setKnowledgeId(createReqVO.getKnowledgeId()).setVectorId(segment.getId())
82   - .setTokens(tokenCountEstimator.estimate(segment.getText())).setWordCount(segment.getText().length())
83   - .setStatus(CommonStatusEnum.ENABLE.getStatus()));
84   - segmentMapper.insertBatch(segmentDOList);
85   -
86   - // 3. 向量化并存储
87   - segments.forEach(segment -> segment.getMetadata().put(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, createReqVO.getKnowledgeId()));
88   - vectorStore.add(segments);
89   - return documentId;
  71 + // 4. 文档切片入库
  72 + knowledgeSegmentService.createKnowledgeSegmentBySplitContent(documentDO.getId(), document.getText());
  73 + return documentDO.getId();
90 74 }
91 75  
92 76 @Override
93 77 public PageResult<AiKnowledgeDocumentDO> getKnowledgeDocumentPage(AiKnowledgeDocumentPageReqVO pageReqVO) {
94   - return documentMapper.selectPage(pageReqVO);
  78 + return knowledgeDocumentMapper.selectPage(pageReqVO);
95 79 }
96 80  
97 81 @Override
... ... @@ -100,17 +84,13 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic
100 84 validateKnowledgeDocumentExists(reqVO.getId());
101 85 // 2. 更新文档
102 86 AiKnowledgeDocumentDO document = BeanUtils.toBean(reqVO, AiKnowledgeDocumentDO.class);
103   - documentMapper.updateById(document);
  87 + knowledgeDocumentMapper.updateById(document);
  88 + // TODO @芋艿:这里要处理状态的变更
104 89 }
105 90  
106   - /**
107   - * 校验文档是否存在
108   - *
109   - * @param id 文档编号
110   - * @return 文档信息
111   - */
112   - private AiKnowledgeDocumentDO validateKnowledgeDocumentExists(Long id) {
113   - AiKnowledgeDocumentDO knowledgeDocument = documentMapper.selectById(id);
  91 + @Override
  92 + public AiKnowledgeDocumentDO validateKnowledgeDocumentExists(Long id) {
  93 + AiKnowledgeDocumentDO knowledgeDocument = knowledgeDocumentMapper.selectById(id);
114 94 if (knowledgeDocument == null) {
115 95 throw exception(KNOWLEDGE_DOCUMENT_NOT_EXISTS);
116 96 }
... ... @@ -120,6 +100,9 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic
120 100 private org.springframework.core.io.Resource downloadFile(String url) {
121 101 try {
122 102 byte[] bytes = HttpUtil.downloadBytes(url);
  103 + if (bytes.length == 0) {
  104 + throw exception(KNOWLEDGE_DOCUMENT_FILE_EMPTY);
  105 + }
123 106 return new ByteArrayResource(bytes);
124 107 } catch (Exception e) {
125 108 log.error("[downloadFile][url({}) 下载失败]", url, e);
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentService.java
... ... @@ -25,6 +25,14 @@ public interface AiKnowledgeSegmentService {
25 25 PageResult<AiKnowledgeSegmentDO> getKnowledgeSegmentPage(AiKnowledgeSegmentPageReqVO pageReqVO);
26 26  
27 27 /**
  28 + * 基于 content 内容,切片创建多个段落
  29 + *
  30 + * @param documentId 知识库文档编号
  31 + * @param content 文档内容
  32 + */
  33 + void createKnowledgeSegmentBySplitContent(Long documentId, String content);
  34 +
  35 + /**
28 36 * 更新段落的内容
29 37 *
30 38 * @param reqVO 更新内容
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.ai.service.knowledge;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.collection.ListUtil;
  5 +import cn.hutool.core.util.StrUtil;
5 6 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
6 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
... ... @@ -10,23 +11,28 @@ import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowle
10 11 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateReqVO;
11 12 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO;
12 13 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
  14 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
13 15 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
14   -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
15 16 import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeSegmentMapper;
16 17 import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
17   -import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
18 18 import jakarta.annotation.Resource;
19 19 import lombok.extern.slf4j.Slf4j;
20 20 import org.springframework.ai.document.Document;
  21 +import org.springframework.ai.tokenizer.TokenCountEstimator;
  22 +import org.springframework.ai.transformer.splitter.TextSplitter;
  23 +import org.springframework.ai.transformer.splitter.TokenTextSplitter;
21 24 import org.springframework.ai.vectorstore.SearchRequest;
22 25 import org.springframework.ai.vectorstore.VectorStore;
23 26 import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder;
  27 +import org.springframework.context.annotation.Lazy;
24 28 import org.springframework.stereotype.Service;
25 29  
  30 +import java.util.Collections;
26 31 import java.util.List;
27 32 import java.util.Objects;
28 33  
29 34 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  35 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
30 36 import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_SEGMENT_NOT_EXISTS;
31 37  
32 38 /**
... ... @@ -38,85 +44,138 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_SEGM
38 44 @Slf4j
39 45 public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService {
40 46  
  47 + public static final String VECTOR_STORE_METADATA_KNOWLEDGE_ID = "knowledgeId";
  48 + public static final String VECTOR_STORE_METADATA_DOCUMENT_ID = "documentId";
  49 + public static final String VECTOR_STORE_METADATA_SEGMENT_ID = "segmentId";
  50 +
41 51 @Resource
42 52 private AiKnowledgeSegmentMapper segmentMapper;
43 53  
44 54 @Resource
45 55 private AiKnowledgeService knowledgeService;
46 56 @Resource
47   - private AiChatModelService chatModelService;
  57 + @Lazy // 延迟加载,避免循环依赖
  58 + private AiKnowledgeDocumentService knowledgeDocumentService;
48 59 @Resource
49 60 private AiApiKeyService apiKeyService;
50 61  
  62 + @Resource
  63 + private TokenCountEstimator tokenCountEstimator;
  64 +
51 65 @Override
52 66 public PageResult<AiKnowledgeSegmentDO> getKnowledgeSegmentPage(AiKnowledgeSegmentPageReqVO pageReqVO) {
53 67 return segmentMapper.selectPage(pageReqVO);
54 68 }
55 69  
56 70 @Override
  71 + public void createKnowledgeSegmentBySplitContent(Long documentId, String content) {
  72 + // 1. 校验
  73 + AiKnowledgeDocumentDO documentDO = knowledgeDocumentService.validateKnowledgeDocumentExists(documentId);
  74 + AiKnowledgeDO knowledgeDO = knowledgeService.validateKnowledgeExists(documentDO.getKnowledgeId());
  75 + VectorStore vectorStore = getVectorStoreById(knowledgeDO);
  76 +
  77 + // 2. 文档切片
  78 + Document document = new Document(content);
  79 + TextSplitter textSplitter = buildTokenTextSplitter(documentDO.getSegmentMaxTokens());
  80 + List<Document> documentSegments = textSplitter.apply(Collections.singletonList(document));
  81 +
  82 + // 3.1 存储切片
  83 + List<AiKnowledgeSegmentDO> segmentDOs = convertList(documentSegments, segment -> {
  84 + if (StrUtil.isEmpty(segment.getText())) {
  85 + return null;
  86 + }
  87 + return new AiKnowledgeSegmentDO().setKnowledgeId(documentDO.getKnowledgeId()).setDocumentId(documentId)
  88 + .setContent(segment.getText()).setContentLength(segment.getText().length())
  89 + .setVectorId(AiKnowledgeSegmentDO.VECTOR_ID_EMPTY).setTokens(tokenCountEstimator.estimate(segment.getText()))
  90 + .setStatus(CommonStatusEnum.ENABLE.getStatus());
  91 + });
  92 + segmentMapper.insertBatch(segmentDOs);
  93 + // 3.2 切片向量化
  94 + for (int i = 0; i < documentSegments.size(); i++) {
  95 + Document segment = documentSegments.get(i);
  96 + AiKnowledgeSegmentDO segmentDO = segmentDOs.get(i);
  97 + writeVectorStore(vectorStore, segmentDO, segment);
  98 + }
  99 + }
  100 +
  101 + @Override
57 102 public void updateKnowledgeSegment(AiKnowledgeSegmentUpdateReqVO reqVO) {
58 103 // 1. 校验
59   - AiKnowledgeSegmentDO oldKnowledgeSegment = validateKnowledgeSegmentExists(reqVO.getId());
60   -
61   - // 2.1 获取知识库向量实例
62   - VectorStore vectorStore = knowledgeService.getVectorStoreById(oldKnowledgeSegment.getKnowledgeId());
63   - // 2.2 删除原向量
64   - vectorStore.delete(List.of(oldKnowledgeSegment.getVectorId()));
65   - // 2.3 重新向量化
66   - Document document = new Document(reqVO.getContent());
67   - document.getMetadata().put(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, oldKnowledgeSegment.getKnowledgeId());
68   - vectorStore.add(List.of(document));
69   -
70   - // 3. 更新段落内容
71   - AiKnowledgeSegmentDO knowledgeSegment = BeanUtils.toBean(reqVO, AiKnowledgeSegmentDO.class);
72   - knowledgeSegment.setVectorId(document.getId());
73   - segmentMapper.updateById(knowledgeSegment);
  104 + AiKnowledgeSegmentDO segment = validateKnowledgeSegmentExists(reqVO.getId());
  105 +
  106 + // 2. 删除向量
  107 + VectorStore vectorStore = getVectorStoreById(segment.getKnowledgeId());
  108 + deleteVectorStore(vectorStore, segment);
  109 +
  110 + // 3.1 更新切片
  111 + AiKnowledgeSegmentDO segmentDO = BeanUtils.toBean(reqVO, AiKnowledgeSegmentDO.class);
  112 + segmentMapper.updateById(segmentDO);
  113 + // 3.2 重新向量化
  114 + writeVectorStore(vectorStore, segmentDO, new Document(segmentDO.getContent()));
74 115 }
75 116  
76 117 @Override
77 118 public void updateKnowledgeSegmentStatus(AiKnowledgeSegmentUpdateStatusReqVO reqVO) {
78   - // 0 校验
79   - AiKnowledgeSegmentDO oldKnowledgeSegment = validateKnowledgeSegmentExists(reqVO.getId());
80   - // 1 获取知识库向量实例
81   - VectorStore vectorStore = knowledgeService.getVectorStoreById(oldKnowledgeSegment.getKnowledgeId());
82   - AiKnowledgeSegmentDO knowledgeSegment = BeanUtils.toBean(reqVO, AiKnowledgeSegmentDO.class);
  119 + // 1. 校验
  120 + AiKnowledgeSegmentDO segment = validateKnowledgeSegmentExists(reqVO.getId());
  121 +
  122 + // 2. 获取知识库向量实例
  123 + VectorStore vectorStore = getVectorStoreById(segment.getKnowledgeId());
  124 +
  125 + // 3. 更新状态
  126 + segmentMapper.updateById(new AiKnowledgeSegmentDO().setId(reqVO.getId()).setStatus(reqVO.getStatus()));
83 127  
  128 + // 4. 更新向量
84 129 if (Objects.equals(reqVO.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
85   - // 2.1 启用重新向量化
86   - Document document = new Document(oldKnowledgeSegment.getContent());
87   - document.getMetadata().put(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, oldKnowledgeSegment.getKnowledgeId());
88   - vectorStore.add(List.of(document));
89   - knowledgeSegment.setVectorId(document.getId());
  130 + writeVectorStore(vectorStore, segment, new Document(segment.getContent()));
90 131 } else {
91   - // 2.2 禁用删除向量
92   - vectorStore.delete(List.of(oldKnowledgeSegment.getVectorId()));
93   - knowledgeSegment.setVectorId("");
  132 + deleteVectorStore(vectorStore, segment);
94 133 }
95   - // 3 更新段落状态
96   - segmentMapper.updateById(knowledgeSegment);
  134 + }
  135 +
  136 + private void writeVectorStore(VectorStore vectorStore, AiKnowledgeSegmentDO segmentDO, Document segment) {
  137 + // 1. 向量存储
  138 + segment.getMetadata().put(VECTOR_STORE_METADATA_KNOWLEDGE_ID, segmentDO.getKnowledgeId());
  139 + segment.getMetadata().put(VECTOR_STORE_METADATA_DOCUMENT_ID, segmentDO.getDocumentId());
  140 + segment.getMetadata().put(VECTOR_STORE_METADATA_SEGMENT_ID, segmentDO.getId());
  141 + vectorStore.add(List.of(segment));
  142 +
  143 + // 2. 更新向量 ID
  144 + segmentMapper.updateById(new AiKnowledgeSegmentDO().setId(segmentDO.getId()).setVectorId(segment.getId()));
  145 + }
  146 +
  147 + private void deleteVectorStore(VectorStore vectorStore, AiKnowledgeSegmentDO segmentDO) {
  148 + // 1. 更新向量 ID
  149 + if (StrUtil.isEmpty(segmentDO.getVectorId())) {
  150 + return;
  151 + }
  152 + segmentMapper.updateById(new AiKnowledgeSegmentDO().setId(segmentDO.getId())
  153 + .setVectorId(AiKnowledgeSegmentDO.VECTOR_ID_EMPTY));
  154 +
  155 + // 2. 删除向量
  156 + vectorStore.delete(List.of(segmentDO.getVectorId()));
97 157 }
98 158  
99 159 @Override
100 160 public List<AiKnowledgeSegmentDO> similaritySearch(AiKnowledgeSegmentSearchReqVO reqVO) {
101 161 // 1. 校验
102 162 AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(reqVO.getKnowledgeId());
103   - AiChatModelDO model = chatModelService.validateChatModel(knowledge.getModelId());
104 163  
105 164 // 2. 获取向量存储实例
106   - VectorStore vectorStore = apiKeyService.getOrCreateVectorStore(model.getKeyId());
  165 + VectorStore vectorStore = apiKeyService.getOrCreateVectorStoreByModelId(knowledge.getEmbeddingModelId());
107 166  
108 167 // 3.1 向量检索
109   - List<Document> documentList = vectorStore.similaritySearch(SearchRequest.builder()
  168 + List<Document> documents = vectorStore.similaritySearch(SearchRequest.builder()
110 169 .query(reqVO.getContent())
111   - .topK(knowledge.getTopK())
112   - .similarityThreshold(knowledge.getSimilarityThreshold())
113   - .filterExpression(new FilterExpressionBuilder().eq(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, reqVO.getKnowledgeId()).build())
  170 + .topK(knowledge.getTopK()).similarityThreshold(knowledge.getSimilarityThreshold())
  171 + .filterExpression(new FilterExpressionBuilder()
  172 + .eq(VECTOR_STORE_METADATA_KNOWLEDGE_ID, reqVO.getKnowledgeId()).build())
114 173 .build());
115   - if (CollUtil.isEmpty(documentList)) {
  174 + if (CollUtil.isEmpty(documents)) {
116 175 return ListUtil.empty();
117 176 }
118 177 // 3.2 段落召回
119   - return segmentMapper.selectListByVectorIds(CollUtil.getFieldValues(documentList, "id", String.class));
  178 + return segmentMapper.selectListByVectorIds(convertList(documents, Document::getId));
120 179 }
121 180  
122 181 /**
... ... @@ -133,4 +192,22 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService
133 192 return knowledgeSegment;
134 193 }
135 194  
  195 + private VectorStore getVectorStoreById(AiKnowledgeDO knowledge) {
  196 + return apiKeyService.getOrCreateVectorStoreByModelId(knowledge.getEmbeddingModelId());
  197 + }
  198 +
  199 + private VectorStore getVectorStoreById(Long knowledgeId) {
  200 + return getVectorStoreById(knowledgeService.validateKnowledgeExists(knowledgeId));
  201 + }
  202 +
  203 + private static TextSplitter buildTokenTextSplitter(Integer segmentMaxTokens) {
  204 + return TokenTextSplitter.builder()
  205 + .withChunkSize(segmentMaxTokens)
  206 + .withMinChunkSizeChars(Integer.MAX_VALUE) // 忽略字符的截断
  207 + .withMinChunkLengthToEmbed(1) // 允许的最小有效分段长度
  208 + .withMaxNumChunks(Integer.MAX_VALUE)
  209 + .withKeepSeparator(true) // 保留分隔符
  210 + .build();
  211 + }
  212 +
136 213 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeService.java
1 1 package cn.iocoder.yudao.module.ai.service.knowledge;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeCreateReqVO;
5 4 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO;
6   -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateReqVO;
  5 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeSaveReqVO;
7 6 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
8   -import org.springframework.ai.vectorstore.VectorStore;
9 7  
10 8 /**
11 9 * AI 知识库-基础信息 Service 接口
... ... @@ -18,18 +16,16 @@ public interface AiKnowledgeService {
18 16 * 创建知识库
19 17 *
20 18 * @param createReqVO 创建信息
21   - * @param userId 用户编号
22 19 * @return 编号
23 20 */
24   - Long createKnowledge(AiKnowledgeCreateReqVO createReqVO, Long userId);
  21 + Long createKnowledge(AiKnowledgeSaveReqVO createReqVO);
25 22  
26 23 /**
27 24 * 更新知识库
28 25 *
29 26 * @param updateReqVO 更新信息
30   - * @param userId 用户编号
31 27 */
32   - void updateKnowledge(AiKnowledgeUpdateReqVO updateReqVO, Long userId);
  28 + void updateKnowledge(AiKnowledgeSaveReqVO updateReqVO);
33 29  
34 30 /**
35 31 * 校验知识库是否存在
... ... @@ -41,18 +37,9 @@ public interface AiKnowledgeService {
41 37 /**
42 38 * 获得知识库分页
43 39 *
44   - * @param userId 用户编号
45 40 * @param pageReqVO 分页查询
46 41 * @return 知识库分页
47 42 */
48   - PageResult<AiKnowledgeDO> getKnowledgePage(Long userId, AiKnowledgePageReqVO pageReqVO);
49   -
50   - /**
51   - * 根据知识库编号获取向量存储实例
52   - *
53   - * @param id 知识库编号
54   - * @return 向量存储实例
55   - */
56   - VectorStore getVectorStoreById(Long id);
  43 + PageResult<AiKnowledgeDO> getKnowledgePage(AiKnowledgePageReqVO pageReqVO);
57 44  
58 45 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java
1 1 package cn.iocoder.yudao.module.ai.service.knowledge;
2 2  
3   -import cn.hutool.core.util.ObjUtil;
4 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7   -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeCreateReqVO;
8 6 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO;
9   -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateReqVO;
  7 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeSaveReqVO;
10 8 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
11 9 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
12 10 import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeMapper;
... ... @@ -34,35 +32,30 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService {
34 32  
35 33 @Resource
36 34 private AiChatModelService chatModelService;
37   - @Resource
38   - private AiApiKeyService apiKeyService;
39 35  
40 36 @Override
41   - public Long createKnowledge(AiKnowledgeCreateReqVO createReqVO, Long userId) {
  37 + public Long createKnowledge(AiKnowledgeSaveReqVO createReqVO) {
42 38 // 1. 校验模型配置
43   - AiChatModelDO model = chatModelService.validateChatModel(createReqVO.getModelId());
  39 + AiChatModelDO model = chatModelService.validateChatModel(createReqVO.getEmbeddingModelId());
44 40  
45 41 // 2. 插入知识库
46   - AiKnowledgeDO knowledgeBase = BeanUtils.toBean(createReqVO, AiKnowledgeDO.class)
47   - .setModel(model.getModel()).setUserId(userId).setStatus(CommonStatusEnum.ENABLE.getStatus());
48   - knowledgeMapper.insert(knowledgeBase);
49   - return knowledgeBase.getId();
  42 + AiKnowledgeDO knowledge = BeanUtils.toBean(createReqVO, AiKnowledgeDO.class)
  43 + .setEmbeddingModel(model.getModel()).setStatus(CommonStatusEnum.ENABLE.getStatus());
  44 + knowledgeMapper.insert(knowledge);
  45 + return knowledge.getId();
50 46 }
51 47  
52 48 @Override
53   - public void updateKnowledge(AiKnowledgeUpdateReqVO updateReqVO, Long userId) {
  49 + public void updateKnowledge(AiKnowledgeSaveReqVO updateReqVO) {
54 50 // 1.1 校验知识库存在
55   - AiKnowledgeDO knowledgeBaseDO = validateKnowledgeExists(updateReqVO.getId());
56   - if (ObjUtil.notEqual(knowledgeBaseDO.getUserId(), userId)) {
57   - throw exception(KNOWLEDGE_NOT_EXISTS);
58   - }
  51 + validateKnowledgeExists(updateReqVO.getId());
59 52 // 1.2 校验模型配置
60   - AiChatModelDO model = chatModelService.validateChatModel(updateReqVO.getModelId());
  53 + AiChatModelDO model = chatModelService.validateChatModel(updateReqVO.getEmbeddingModelId());
61 54  
62 55 // 2. 更新知识库
63   - AiKnowledgeDO updateDO = BeanUtils.toBean(updateReqVO, AiKnowledgeDO.class);
64   - updateDO.setModel(model.getModel());
65   - knowledgeMapper.updateById(updateDO);
  56 + AiKnowledgeDO updateObj = BeanUtils.toBean(updateReqVO, AiKnowledgeDO.class)
  57 + .setEmbeddingModel(model.getModel());
  58 + knowledgeMapper.updateById(updateObj);
66 59 }
67 60  
68 61 @Override
... ... @@ -75,16 +68,8 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService {
75 68 }
76 69  
77 70 @Override
78   - public PageResult<AiKnowledgeDO> getKnowledgePage(Long userId, AiKnowledgePageReqVO pageReqVO) {
79   - return knowledgeMapper.selectPage(userId, pageReqVO);
80   - }
81   -
82   - @Override
83   - public VectorStore getVectorStoreById(Long id) {
84   - AiKnowledgeDO knowledge = validateKnowledgeExists(id);
85   - AiChatModelDO model = chatModelService.validateChatModel(knowledge.getModelId());
86   - // 创建或获取 VectorStore 对象
87   - return apiKeyService.getOrCreateVectorStore(model.getKeyId());
  71 + public PageResult<AiKnowledgeDO> getKnowledgePage(AiKnowledgePageReqVO pageReqVO) {
  72 + return knowledgeMapper.selectPage(pageReqVO);
88 73 }
89 74  
90 75 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyService.java
... ... @@ -9,7 +9,6 @@ import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveR
9 9 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
10 10 import jakarta.validation.Valid;
11 11 import org.springframework.ai.chat.model.ChatModel;
12   -import org.springframework.ai.embedding.EmbeddingModel;
13 12 import org.springframework.ai.image.ImageModel;
14 13 import org.springframework.ai.vectorstore.VectorStore;
15 14  
... ... @@ -114,19 +113,11 @@ public interface AiApiKeyService {
114 113 SunoApi getSunoApi();
115 114  
116 115 /**
117   - * 获得 EmbeddingModel 对象
118   - *
119   - * @param id 编号
120   - * @return EmbeddingModel 对象
121   - */
122   - EmbeddingModel getEmbeddingModel(Long id);
123   -
124   - /**
125 116 * 获得 VectorStore 对象
126 117 *
127   - * @param id 编号
  118 + * @param modelId 编号
128 119 * @return VectorStore 对象
129 120 */
130   - VectorStore getOrCreateVectorStore(Long id);
  121 + VectorStore getOrCreateVectorStoreByModelId(Long modelId);
131 122  
132 123 }
133 124 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyServiceImpl.java
... ... @@ -10,12 +10,14 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
10 10 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyPageReqVO;
11 11 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveReqVO;
12 12 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
  13 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
13 14 import cn.iocoder.yudao.module.ai.dal.mysql.model.AiApiKeyMapper;
14 15 import jakarta.annotation.Resource;
15 16 import org.springframework.ai.chat.model.ChatModel;
16 17 import org.springframework.ai.embedding.EmbeddingModel;
17 18 import org.springframework.ai.image.ImageModel;
18 19 import org.springframework.ai.vectorstore.VectorStore;
  20 +import org.springframework.context.annotation.Lazy;
19 21 import org.springframework.stereotype.Service;
20 22 import org.springframework.validation.annotation.Validated;
21 23  
... ... @@ -36,6 +38,11 @@ public class AiApiKeyServiceImpl implements AiApiKeyService {
36 38 @Resource
37 39 private AiApiKeyMapper apiKeyMapper;
38 40  
  41 + // TODO @芋艿:后续要不要改?
  42 + @Resource
  43 + @Lazy // 延迟加载,解决渲染依赖
  44 + private AiChatModelService chatModelService;
  45 +
39 46 @Resource
40 47 private AiModelFactory modelFactory;
41 48  
... ... @@ -136,18 +143,18 @@ public class AiApiKeyServiceImpl implements AiApiKeyService {
136 143 }
137 144  
138 145 @Override
139   - public EmbeddingModel getEmbeddingModel(Long id) {
140   - AiApiKeyDO apiKey = validateApiKey(id);
  146 + public VectorStore getOrCreateVectorStoreByModelId(Long modelId) {
  147 + // 获取模型 + 密钥
  148 + AiChatModelDO chatModel = chatModelService.validateChatModel(modelId);
  149 + AiApiKeyDO apiKey = validateApiKey(chatModel.getKeyId());
141 150 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform());
142   - return modelFactory.getOrCreateEmbeddingModel(platform, apiKey.getApiKey(), apiKey.getUrl());
143   - }
144 151  
145   - @Override
146   - public VectorStore getOrCreateVectorStore(Long id) {
147   - AiApiKeyDO apiKey = validateApiKey(id);
148   - AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform());
  152 + // 创建或获取 EmbeddingModel 对象
  153 + EmbeddingModel embeddingModel = modelFactory.getOrCreateEmbeddingModel(platform, apiKey.getApiKey(),
  154 + apiKey.getUrl(), chatModel.getModel());
  155 +
149 156 // 创建或获取 VectorStore 对象
150   - return modelFactory.getOrCreateVectorStore(getEmbeddingModel(id), platform, apiKey.getApiKey(), apiKey.getUrl());
  157 + return modelFactory.getOrCreateVectorStore(embeddingModel);
151 158 }
152 159  
153 160 }
154 161 \ No newline at end of file
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactory.java
... ... @@ -89,21 +89,19 @@ public interface AiModelFactory {
89 89 * @param platform 平台
90 90 * @param apiKey API KEY
91 91 * @param url API URL
  92 + * @param model 模型
92 93 * @return ChatModel 对象
93 94 */
94   - EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String apiKey, String url);
  95 + EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String apiKey, String url, String model);
95 96  
96 97 /**
97 98 * 基于指定配置,获得 VectorStore 对象
98 99 * <p>
99 100 * 如果不存在,则进行创建
100 101 *
101   - * @param embeddingModel 嵌入模型
102   - * @param platform 平台
103   - * @param apiKey API KEY
104   - * @param url API URL
  102 + * @param embeddingModel 向量模型
105 103 * @return VectorStore 对象
106 104 */
107   - VectorStore getOrCreateVectorStore(EmbeddingModel embeddingModel, AiPlatformEnum platform, String apiKey, String url);
  105 + VectorStore getOrCreateVectorStore(EmbeddingModel embeddingModel);
108 106  
109 107 }
... ...
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.dashscope.api.DashScopeApi;
21 21 import com.alibaba.cloud.ai.dashscope.api.DashScopeImageApi;
22 22 import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
23 23 import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel;
  24 +import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingOptions;
24 25 import com.alibaba.cloud.ai.dashscope.image.DashScopeImageModel;
25 26 import com.azure.ai.openai.OpenAIClientBuilder;
26 27 import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiAutoConfiguration;
... ... @@ -33,10 +34,13 @@ import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiAutoConfiguration;
33 34 import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiConnectionProperties;
34 35 import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
35 36 import org.springframework.ai.chat.model.ChatModel;
  37 +import org.springframework.ai.document.MetadataMode;
36 38 import org.springframework.ai.embedding.EmbeddingModel;
37 39 import org.springframework.ai.image.ImageModel;
38 40 import org.springframework.ai.ollama.OllamaChatModel;
  41 +import org.springframework.ai.ollama.OllamaEmbeddingModel;
39 42 import org.springframework.ai.ollama.api.OllamaApi;
  43 +import org.springframework.ai.ollama.api.OllamaOptions;
40 44 import org.springframework.ai.openai.OpenAiChatModel;
41 45 import org.springframework.ai.openai.OpenAiImageModel;
42 46 import org.springframework.ai.openai.api.OpenAiApi;
... ... @@ -184,13 +188,15 @@ public class AiModelFactoryImpl implements AiModelFactory {
184 188 }
185 189  
186 190 @Override
187   - public EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String apiKey, String url) {
188   - String cacheKey = buildClientCacheKey(EmbeddingModel.class, platform, apiKey, url);
  191 + public EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String apiKey, String url, String model) {
  192 + String cacheKey = buildClientCacheKey(EmbeddingModel.class, platform, apiKey, url, model);
189 193 return Singleton.get(cacheKey, (Func0<EmbeddingModel>) () -> {
190   - // TODO @xin 先测试一个
191 194 switch (platform) {
192 195 case TONG_YI:
193   - return buildTongYiEmbeddingModel(apiKey);
  196 + return buildTongYiEmbeddingModel(apiKey, model);
  197 + case OLLAMA:
  198 + return buildOllamaEmbeddingModel(url, model);
  199 + // TODO @芋艿:各个平台的向量化能力;
194 200 default:
195 201 throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
196 202 }
... ... @@ -198,13 +204,14 @@ public class AiModelFactoryImpl implements AiModelFactory {
198 204 }
199 205  
200 206 @Override
201   - public VectorStore getOrCreateVectorStore(EmbeddingModel embeddingModel, AiPlatformEnum platform, String apiKey, String url) {
202   - String cacheKey = buildClientCacheKey(VectorStore.class, platform, apiKey, url);
  207 + public VectorStore getOrCreateVectorStore(EmbeddingModel embeddingModel) {
  208 +// String cacheKey = buildClientCacheKey(VectorStore.class, platform, apiKey, url);
  209 + String cacheKey = buildClientCacheKey(VectorStore.class, embeddingModel);
203 210 return Singleton.get(cacheKey, (Func0<VectorStore>) () -> {
204   - String prefix = StrUtil.format("{}#{}:", platform.getPlatform(), apiKey);
205 211 // TODO @芋艿:先临时使用 store
206 212 return SimpleVectorStore.builder(embeddingModel).build();
207 213 // TODO @芋艿:@xin:后续看看,是不是切到阿里云之类的
  214 +// String prefix = StrUtil.format("{}#{}:", platform.getPlatform(), apiKey);
208 215 // var config = RedisVectorStore.RedisVectorStoreConfig.builder()
209 216 // .withIndexName(cacheKey)
210 217 // .withPrefix(prefix)
... ... @@ -388,9 +395,16 @@ public class AiModelFactoryImpl implements AiModelFactory {
388 395 /**
389 396 * 可参考 {@link DashScopeAutoConfiguration} 的 dashscopeEmbeddingModel 方法
390 397 */
391   - private EmbeddingModel buildTongYiEmbeddingModel(String apiKey) {
  398 + private DashScopeEmbeddingModel buildTongYiEmbeddingModel(String apiKey, String model) {
392 399 DashScopeApi dashScopeApi = new DashScopeApi(apiKey);
393   - return new DashScopeEmbeddingModel(dashScopeApi);
  400 + DashScopeEmbeddingOptions dashScopeEmbeddingOptions = DashScopeEmbeddingOptions.builder().withModel(model).build();
  401 + return new DashScopeEmbeddingModel(dashScopeApi, MetadataMode.EMBED, dashScopeEmbeddingOptions);
  402 + }
  403 +
  404 + private OllamaEmbeddingModel buildOllamaEmbeddingModel(String url, String model) {
  405 + OllamaApi ollamaApi = new OllamaApi(url);
  406 + OllamaOptions ollamaOptions = OllamaOptions.builder().model(model).build();
  407 + return OllamaEmbeddingModel.builder().ollamaApi(ollamaApi).defaultOptions(ollamaOptions).build();
394 408 }
395 409  
396 410 }
... ...