Commit 5f5e77a3923bc39b3ef160195e00df5f3baee1aa

Authored by YunaiV
1 parent ebd93514

【功能新增】AI:新增 document 向量的进度查询

Showing 20 changed files with 385 additions and 61 deletions
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeController.http
@@ -9,7 +9,8 @@ tenant-id: {{adminTenantId}} @@ -9,7 +9,8 @@ tenant-id: {{adminTenantId}}
9 "description": "测试描述", 9 "description": "测试描述",
10 "embeddingModelId": 30, 10 "embeddingModelId": 30,
11 "topK": 3, 11 "topK": 3,
12 - "similarityThreshold": 0.5 12 + "similarityThreshold": 0.5,
  13 + "status": 0
13 } 14 }
14 15
15 ### 更新知识库 16 ### 更新知识库
@@ -24,7 +25,8 @@ tenant-id: {{adminTenantId}} @@ -24,7 +25,8 @@ tenant-id: {{adminTenantId}}
24 "description": "测试描述", 25 "description": "测试描述",
25 "embeddingModelId": 30, 26 "embeddingModelId": 30,
26 "topK": 5, 27 "topK": 5,
27 - "similarityThreshold": 0.6 28 + "similarityThreshold": 0.6,
  29 + "status": 0
28 } 30 }
29 31
30 ### 获取知识库分页 32 ### 获取知识库分页
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeDocumentController.http
@@ -5,7 +5,7 @@ Authorization: Bearer {{token}} @@ -5,7 +5,7 @@ Authorization: Bearer {{token}}
5 tenant-id: {{adminTenantId}} 5 tenant-id: {{adminTenantId}}
6 6
7 { 7 {
8 - "knowledgeId": 1, 8 + "knowledgeId": 2,
9 "name": "测试文档", 9 "name": "测试文档",
10 "url": "https://static.iocoder.cn/README.md", 10 "url": "https://static.iocoder.cn/README.md",
11 "segmentMaxTokens": 800 11 "segmentMaxTokens": 800
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeSegmentController.http
@@ -4,9 +4,14 @@ Content-Type: application/json @@ -4,9 +4,14 @@ Content-Type: application/json
4 Authorization: Bearer {{token}} 4 Authorization: Bearer {{token}}
5 tenant-id: {{adminTenantId}} 5 tenant-id: {{adminTenantId}}
6 6
7 -### 获取文档处理列表  
8 -GET {{baseUrl}}/ai/knowledge/segment/get-process-list?documentIds=1,2,3 7 +### 搜索段落内容
  8 +GET {{baseUrl}}/ai/knowledge/segment/search?knowledgeId=2&content=如何使用这个产品&topK=5&similarityThreshold=0.1
9 Content-Type: application/json 9 Content-Type: application/json
10 Authorization: Bearer {{token}} 10 Authorization: Bearer {{token}}
11 tenant-id: {{adminTenantId}} 11 tenant-id: {{adminTenantId}}
12 12
  13 +### 获取文档处理列表
  14 +GET {{baseUrl}}/ai/knowledge/segment/get-process-list?documentIds=1,2,3
  15 +Content-Type: application/json
  16 +Authorization: Bearer {{token}}
  17 +tenant-id: {{adminTenantId}}
13 \ No newline at end of file 18 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeSegmentController.java
1 package cn.iocoder.yudao.module.ai.controller.admin.knowledge; 1 package cn.iocoder.yudao.module.ai.controller.admin.knowledge;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO;  
7 -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentRespVO;  
8 -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateReqVO;  
9 -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO;  
10 -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentProcessRespVO; 8 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.*;
  9 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
11 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; 10 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
  11 +import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService;
12 import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService; 12 import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService;
  13 +import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchReqBO;
  14 +import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchRespBO;
13 import io.swagger.v3.oas.annotations.Operation; 15 import io.swagger.v3.oas.annotations.Operation;
14 import io.swagger.v3.oas.annotations.Parameter; 16 import io.swagger.v3.oas.annotations.Parameter;
15 import io.swagger.v3.oas.annotations.Parameters; 17 import io.swagger.v3.oas.annotations.Parameters;
@@ -20,9 +22,12 @@ import org.hibernate.validator.constraints.URL; @@ -20,9 +22,12 @@ import org.hibernate.validator.constraints.URL;
20 import org.springframework.validation.annotation.Validated; 22 import org.springframework.validation.annotation.Validated;
21 import org.springframework.web.bind.annotation.*; 23 import org.springframework.web.bind.annotation.*;
22 24
  25 +import java.util.Collections;
23 import java.util.List; 26 import java.util.List;
  27 +import java.util.Map;
24 28
25 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 29 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  30 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
26 31
27 // TODO @芋艿:增加权限标识 32 // TODO @芋艿:增加权限标识
28 @Tag(name = "管理后台 - AI 知识库段落") 33 @Tag(name = "管理后台 - AI 知识库段落")
@@ -34,6 +39,9 @@ public class AiKnowledgeSegmentController { @@ -34,6 +39,9 @@ public class AiKnowledgeSegmentController {
34 @Resource 39 @Resource
35 private AiKnowledgeSegmentService segmentService; 40 private AiKnowledgeSegmentService segmentService;
36 41
  42 + @Resource
  43 + private AiKnowledgeDocumentService documentService;
  44 +
37 @GetMapping("/page") 45 @GetMapping("/page")
38 @Operation(summary = "获取段落分页") 46 @Operation(summary = "获取段落分页")
39 public CommonResult<PageResult<AiKnowledgeSegmentRespVO>> getKnowledgeSegmentPage( 47 public CommonResult<PageResult<AiKnowledgeSegmentRespVO>> getKnowledgeSegmentPage(
@@ -79,4 +87,23 @@ public class AiKnowledgeSegmentController { @@ -79,4 +87,23 @@ public class AiKnowledgeSegmentController {
79 return success(list); 87 return success(list);
80 } 88 }
81 89
  90 + @GetMapping("/search")
  91 + @Operation(summary = "搜索段落内容")
  92 + public CommonResult<List<AiKnowledgeSegmentSearchRespVO>> searchKnowledgeSegment(
  93 + @Valid AiKnowledgeSegmentSearchReqVO reqVO) {
  94 + // 1. 搜索段落
  95 + List<AiKnowledgeSegmentSearchRespBO> segments = segmentService
  96 + .searchKnowledgeSegment(BeanUtils.toBean(reqVO, AiKnowledgeSegmentSearchReqBO.class));
  97 + if (CollUtil.isEmpty(segments)) {
  98 + return success(Collections.emptyList());
  99 + }
  100 +
  101 + // 2. 拼接 VO
  102 + Map<Long, AiKnowledgeDocumentDO> documentMap = documentService.getKnowledgeDocumentMap(convertSet(
  103 + segments, AiKnowledgeSegmentSearchRespBO::getDocumentId));
  104 + return success(BeanUtils.toBean(segments, AiKnowledgeSegmentSearchRespVO.class,
  105 + segment -> MapUtils.findAndThen(documentMap, segment.getDocumentId(),
  106 + document -> segment.setDocumentName(document.getName()))));
  107 + }
  108 +
82 } 109 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentUpdateStatusReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import jakarta.validation.constraints.NotNull;
  7 +import lombok.Data;
  8 +
  9 +@Schema(description = "管理后台 - AI 知识库文档更新状态 Request VO")
  10 +@Data
  11 +public class AiKnowledgeDocumentUpdateStatusReqVO {
  12 +
  13 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583")
  14 + @NotNull(message = "编号不能为空")
  15 + private Long id;
  16 +
  17 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  18 + @NotNull(message = "状态不能为空")
  19 + @InEnum(CommonStatusEnum.class)
  20 + private Integer status;
  21 +
  22 +}
0 \ No newline at end of file 23 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentSearchReqVO.java
@@ -3,15 +3,25 @@ package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment; @@ -3,15 +3,25 @@ package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.Data; 4 import lombok.Data;
5 5
  6 +import jakarta.validation.constraints.NotEmpty;
  7 +import jakarta.validation.constraints.NotNull;
6 8
7 -@Schema(description = "管理后台 - AI 知识库段落召回 Request VO") 9 +@Schema(description = "管理后台 - AI 知识库段落搜索 Request VO")
8 @Data 10 @Data
9 public class AiKnowledgeSegmentSearchReqVO { 11 public class AiKnowledgeSegmentSearchReqVO {
10 12
11 - @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790") 13 + @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  14 + @NotNull(message = "知识库编号不能为空")
12 private Long knowledgeId; 15 private Long knowledgeId;
13 16
14 - @Schema(description = "内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 学习路线") 17 + @Schema(description = "内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "如何使用这个产品")
  18 + @NotEmpty(message = "内容不能为空")
15 private String content; 19 private String content;
16 20
  21 + @Schema(description = "最大返回数量", example = "5")
  22 + private Integer topK;
  23 +
  24 + @Schema(description = "相似度阈值", example = "0.7")
  25 + private Double similarityThreshold;
  26 +
17 } 27 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentSearchRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "管理后台 - AI 知识库段落搜索 Response VO")
  7 +@Data
  8 +public class AiKnowledgeSegmentSearchRespVO extends AiKnowledgeSegmentRespVO {
  9 +
  10 + @Schema(description = "文档名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品使用手册")
  11 + private String documentName;
  12 +
  13 + @Schema(description = "相似度分数", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.95")
  14 + private Double score;
  15 +
  16 +}
0 \ No newline at end of file 17 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatConversationDO.java
@@ -65,6 +65,7 @@ public class AiChatConversationDO extends BaseDO { @@ -65,6 +65,7 @@ public class AiChatConversationDO extends BaseDO {
65 */ 65 */
66 private Long roleId; 66 private Long roleId;
67 67
  68 + // TODO @芋艿:可优化,绑定多个知识库。前提,spring ai 支持 RerankModel 的封装
68 /** 69 /**
69 * 知识库编号 70 * 知识库编号
70 * <p> 71 * <p>
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeDocumentMapper.java
@@ -5,8 +5,11 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; @@ -5,8 +5,11 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO; 6 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO;
7 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO; 7 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
  8 +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
8 import org.apache.ibatis.annotations.Mapper; 9 import org.apache.ibatis.annotations.Mapper;
9 10
  11 +import java.util.Collection;
  12 +
10 /** 13 /**
11 * AI 知识库文档 Mapper 14 * AI 知识库文档 Mapper
12 * 15 *
@@ -22,4 +25,10 @@ public interface AiKnowledgeDocumentMapper extends BaseMapperX&lt;AiKnowledgeDocume @@ -22,4 +25,10 @@ public interface AiKnowledgeDocumentMapper extends BaseMapperX&lt;AiKnowledgeDocume
22 .orderByDesc(AiKnowledgeDocumentDO::getId)); 25 .orderByDesc(AiKnowledgeDocumentDO::getId));
23 } 26 }
24 27
  28 + default void updateRetrievalCountIncr(Collection<Long> ids) {
  29 + update( new LambdaUpdateWrapper<AiKnowledgeDocumentDO>()
  30 + .setSql(" retrieval_count = retrieval_count + 1")
  31 + .in(AiKnowledgeDocumentDO::getId, ids));
  32 + }
  33 +
25 } 34 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeSegmentMapper.java
@@ -7,11 +7,12 @@ import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX; @@ -7,11 +7,12 @@ import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
7 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO; 7 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO;
8 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentProcessRespVO; 8 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentProcessRespVO;
9 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; 9 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
  10 +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
10 import com.github.yulichang.wrapper.MPJLambdaWrapper; 11 import com.github.yulichang.wrapper.MPJLambdaWrapper;
11 import org.apache.ibatis.annotations.Mapper; 12 import org.apache.ibatis.annotations.Mapper;
12 13
13 -import java.util.List;  
14 import java.util.Collection; 14 import java.util.Collection;
  15 +import java.util.List;
15 16
16 /** 17 /**
17 * AI 知识库分片 Mapper 18 * AI 知识库分片 Mapper
@@ -52,4 +53,10 @@ public interface AiKnowledgeSegmentMapper extends BaseMapperX&lt;AiKnowledgeSegment @@ -52,4 +53,10 @@ public interface AiKnowledgeSegmentMapper extends BaseMapperX&lt;AiKnowledgeSegment
52 return selectJoinList(AiKnowledgeSegmentProcessRespVO.class, wrapper); 53 return selectJoinList(AiKnowledgeSegmentProcessRespVO.class, wrapper);
53 } 54 }
54 55
  56 + default void updateRetrievalCountIncrByIds(List<Long> ids) {
  57 + update( new LambdaUpdateWrapper<AiKnowledgeSegmentDO>()
  58 + .setSql(" retrieval_count = retrieval_count + 1")
  59 + .in(AiKnowledgeSegmentDO::getId, ids));
  60 + }
  61 +
55 } 62 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java
@@ -12,7 +12,6 @@ import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; @@ -12,7 +12,6 @@ import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
12 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO; 12 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO;
13 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO; 13 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO;
14 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO; 14 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO;
15 -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentSearchReqVO;  
16 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; 15 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
17 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; 16 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
18 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; 17 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
@@ -133,7 +132,6 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -133,7 +132,6 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
133 Flux<ChatResponse> streamResponse = chatModel.stream(prompt); 132 Flux<ChatResponse> streamResponse = chatModel.stream(prompt);
134 133
135 // 3.4 流式返回 134 // 3.4 流式返回
136 - // TODO 注意:Schedulers.immediate() 目的是,避免默认 Schedulers.parallel() 并发消费 chunk 导致 SSE 响应前端会乱序问题  
137 StringBuffer contentBuffer = new StringBuffer(); 135 StringBuffer contentBuffer = new StringBuffer();
138 return streamResponse.map(chunk -> { 136 return streamResponse.map(chunk -> {
139 String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getText() : null; 137 String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getText() : null;
@@ -159,7 +157,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -159,7 +157,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
159 if (Objects.isNull(knowledgeId)) { 157 if (Objects.isNull(knowledgeId)) {
160 return Collections.emptyList(); 158 return Collections.emptyList();
161 } 159 }
162 - return knowledgeSegmentService.similaritySearch(new AiKnowledgeSegmentSearchReqVO().setKnowledgeId(knowledgeId).setContent(content)); 160 +// return knowledgeSegmentService.similaritySearch(new AiKnowledgeSegmentSearchReqVO().setKnowledgeId(knowledgeId).setContent(content));
  161 + return null;
163 } 162 }
164 163
165 private Prompt buildPrompt(AiChatConversationDO conversation, List<AiChatMessageDO> messages,List<AiKnowledgeSegmentDO> segmentList, 164 private Prompt buildPrompt(AiChatConversationDO conversation, List<AiChatMessageDO> messages,List<AiKnowledgeSegmentDO> segmentList,
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentService.java
1 package cn.iocoder.yudao.module.ai.service.knowledge; 1 package cn.iocoder.yudao.module.ai.service.knowledge;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentCreateListReqVO;
4 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO; 5 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO;
5 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateReqVO; 6 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateReqVO;
6 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateStatusReqVO; 7 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateStatusReqVO;
7 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeDocumentCreateReqVO; 8 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeDocumentCreateReqVO;
8 -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentCreateListReqVO;  
9 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO; 9 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
10 10
  11 +import java.util.Collection;
11 import java.util.List; 12 import java.util.List;
  13 +import java.util.Map;
  14 +
  15 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
12 16
13 /** 17 /**
14 * AI 知识库文档 Service 接口 18 * AI 知识库文档 Service 接口
@@ -64,6 +68,13 @@ public interface AiKnowledgeDocumentService { @@ -64,6 +68,13 @@ public interface AiKnowledgeDocumentService {
64 void updateKnowledgeDocumentStatus(AiKnowledgeDocumentUpdateStatusReqVO reqVO); 68 void updateKnowledgeDocumentStatus(AiKnowledgeDocumentUpdateStatusReqVO reqVO);
65 69
66 /** 70 /**
  71 + * 更新文档检索次数(增加 +1)
  72 + *
  73 + * @param ids 文档编号列表
  74 + */
  75 + void updateKnowledgeDocumentRetrievalCountIncr(Collection<Long> ids);
  76 +
  77 + /**
67 * 校验文档是否存在 78 * 校验文档是否存在
68 * 79 *
69 * @param id 文档编号 80 * @param id 文档编号
@@ -79,4 +90,22 @@ public interface AiKnowledgeDocumentService { @@ -79,4 +90,22 @@ public interface AiKnowledgeDocumentService {
79 */ 90 */
80 String readUrl(String url); 91 String readUrl(String url);
81 92
  93 + /**
  94 + * 获取文档列表
  95 + *
  96 + * @param ids 文档编号列表
  97 + * @return 文档列表
  98 + */
  99 + List<AiKnowledgeDocumentDO> getKnowledgeDocumentList(Collection<Long> ids);
  100 +
  101 + /**
  102 + * 获取文档 Map
  103 + *
  104 + * @param ids 文档编号列表
  105 + * @return 文档 Map
  106 + */
  107 + default Map<Long, AiKnowledgeDocumentDO> getKnowledgeDocumentMap(Collection<Long> ids) {
  108 + return convertMap(getKnowledgeDocumentList(ids), AiKnowledgeDocumentDO::getId);
  109 + }
  110 +
82 } 111 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentServiceImpl.java
@@ -7,10 +7,10 @@ import cn.hutool.http.HttpUtil; @@ -7,10 +7,10 @@ import cn.hutool.http.HttpUtil;
7 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 7 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
8 import cn.iocoder.yudao.framework.common.pojo.PageResult; 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
9 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 9 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  10 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentCreateListReqVO;
10 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO; 11 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO;
11 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateReqVO; 12 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateReqVO;
12 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateStatusReqVO; 13 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateStatusReqVO;
13 -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentCreateListReqVO;  
14 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeDocumentCreateReqVO; 14 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeDocumentCreateReqVO;
15 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO; 15 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
16 import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeDocumentMapper; 16 import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeDocumentMapper;
@@ -25,6 +25,7 @@ import org.springframework.stereotype.Service; @@ -25,6 +25,7 @@ import org.springframework.stereotype.Service;
25 import org.springframework.transaction.annotation.Transactional; 25 import org.springframework.transaction.annotation.Transactional;
26 26
27 import java.util.ArrayList; 27 import java.util.ArrayList;
  28 +import java.util.Collection;
28 import java.util.List; 29 import java.util.List;
29 30
30 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 31 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -149,6 +150,14 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic @@ -149,6 +150,14 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic
149 } 150 }
150 151
151 @Override 152 @Override
  153 + public void updateKnowledgeDocumentRetrievalCountIncr(Collection<Long> ids) {
  154 + if (CollUtil.isEmpty(ids)) {
  155 + return;
  156 + }
  157 + knowledgeDocumentMapper.updateRetrievalCountIncr(ids);
  158 + }
  159 +
  160 + @Override
152 public AiKnowledgeDocumentDO validateKnowledgeDocumentExists(Long id) { 161 public AiKnowledgeDocumentDO validateKnowledgeDocumentExists(Long id) {
153 AiKnowledgeDocumentDO knowledgeDocument = knowledgeDocumentMapper.selectById(id); 162 AiKnowledgeDocumentDO knowledgeDocument = knowledgeDocumentMapper.selectById(id);
154 if (knowledgeDocument == null) { 163 if (knowledgeDocument == null) {
@@ -182,4 +191,12 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic @@ -182,4 +191,12 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic
182 return document.getText(); 191 return document.getText();
183 } 192 }
184 193
  194 + @Override
  195 + public List<AiKnowledgeDocumentDO> getKnowledgeDocumentList(Collection<Long> ids) {
  196 + if (CollUtil.isEmpty(ids)) {
  197 + return new ArrayList<>();
  198 + }
  199 + return knowledgeDocumentMapper.selectByIds(ids);
  200 + }
  201 +
185 } 202 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentService.java
@@ -2,11 +2,12 @@ package cn.iocoder.yudao.module.ai.service.knowledge; @@ -2,11 +2,12 @@ package cn.iocoder.yudao.module.ai.service.knowledge;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO; 4 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO;
5 -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentSearchReqVO; 5 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentProcessRespVO;
6 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateReqVO; 6 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateReqVO;
7 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO; 7 import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO;
8 -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentProcessRespVO;  
9 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; 8 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
  9 +import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchReqBO;
  10 +import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchRespBO;
10 import org.springframework.scheduling.annotation.Async; 11 import org.springframework.scheduling.annotation.Async;
11 12
12 import java.util.List; 13 import java.util.List;
@@ -67,12 +68,12 @@ public interface AiKnowledgeSegmentService { @@ -67,12 +68,12 @@ public interface AiKnowledgeSegmentService {
67 void deleteKnowledgeSegmentByDocumentId(Long documentId); 68 void deleteKnowledgeSegmentByDocumentId(Long documentId);
68 69
69 /** 70 /**
70 - * 召回段落 71 + * 搜索知识库段落,并返回结果
71 * 72 *
72 - * @param reqVO 召回请求信息  
73 - * @return 召回的段落 73 + * @param reqBO 搜索请求信息
  74 + * @return 搜索结果段落列表
74 */ 75 */
75 - List<AiKnowledgeSegmentDO> similaritySearch(AiKnowledgeSegmentSearchReqVO reqVO); 76 + List<AiKnowledgeSegmentSearchRespBO> searchKnowledgeSegment(AiKnowledgeSegmentSearchReqBO reqBO);
76 77
77 /** 78 /**
78 * 根据 URL 内容,切片创建多个段落 79 * 根据 URL 内容,切片创建多个段落
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,6 +2,7 @@ package cn.iocoder.yudao.module.ai.service.knowledge;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.collection.ListUtil; 4 import cn.hutool.core.collection.ListUtil;
  5 +import cn.hutool.core.util.ObjUtil;
5 import cn.hutool.core.util.StrUtil; 6 import cn.hutool.core.util.StrUtil;
6 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 7 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
7 import cn.iocoder.yudao.framework.common.pojo.PageResult; 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
@@ -12,6 +13,8 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO @@ -12,6 +13,8 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO
12 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; 13 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
13 import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeSegmentMapper; 14 import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeSegmentMapper;
14 import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; 15 import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
  16 +import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchReqBO;
  17 +import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchRespBO;
15 import jakarta.annotation.Resource; 18 import jakarta.annotation.Resource;
16 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
17 import org.springframework.ai.document.Document; 20 import org.springframework.ai.document.Document;
@@ -171,25 +174,45 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService @@ -171,25 +174,45 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService
171 } 174 }
172 175
173 @Override 176 @Override
174 - public List<AiKnowledgeSegmentDO> similaritySearch(AiKnowledgeSegmentSearchReqVO reqVO) { 177 + public List<AiKnowledgeSegmentSearchRespBO> searchKnowledgeSegment(AiKnowledgeSegmentSearchReqBO reqBO) {
175 // 1. 校验 178 // 1. 校验
176 - AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(reqVO.getKnowledgeId()); 179 + AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(reqBO.getKnowledgeId());
177 180
178 - // 2. 获取向量存储实例 181 + // 2.1 向量检索
179 VectorStore vectorStore = apiKeyService.getOrCreateVectorStoreByModelId(knowledge.getEmbeddingModelId()); 182 VectorStore vectorStore = apiKeyService.getOrCreateVectorStoreByModelId(knowledge.getEmbeddingModelId());
180 -  
181 - // 3.1 向量检索  
182 List<Document> documents = vectorStore.similaritySearch(SearchRequest.builder() 183 List<Document> documents = vectorStore.similaritySearch(SearchRequest.builder()
183 - .query(reqVO.getContent())  
184 - .topK(knowledge.getTopK()).similarityThreshold(knowledge.getSimilarityThreshold()) 184 + .query(reqBO.getContent())
  185 + .topK(ObjUtil.defaultIfNull(reqBO.getTopK(), knowledge.getTopK()))
  186 + .similarityThreshold(ObjUtil.defaultIfNull(reqBO.getSimilarityThreshold(), knowledge.getSimilarityThreshold()))
185 .filterExpression(new FilterExpressionBuilder() 187 .filterExpression(new FilterExpressionBuilder()
186 - .eq(VECTOR_STORE_METADATA_KNOWLEDGE_ID, reqVO.getKnowledgeId()).build()) 188 + .eq(VECTOR_STORE_METADATA_KNOWLEDGE_ID, reqBO.getKnowledgeId()).build())
187 .build()); 189 .build());
188 if (CollUtil.isEmpty(documents)) { 190 if (CollUtil.isEmpty(documents)) {
189 return ListUtil.empty(); 191 return ListUtil.empty();
190 } 192 }
191 - // 3.2 段落召回  
192 - return segmentMapper.selectListByVectorIds(convertList(documents, Document::getId)); 193 + // 2.2 段落召回
  194 + List<AiKnowledgeSegmentDO> segments = segmentMapper
  195 + .selectListByVectorIds(convertList(documents, Document::getId));
  196 + if (CollUtil.isEmpty(segments)) {
  197 + return ListUtil.empty();
  198 + }
  199 +
  200 + // 3. 增加召回次数
  201 + segmentMapper.updateRetrievalCountIncrByIds(convertList(segments, AiKnowledgeSegmentDO::getId));
  202 +
  203 + // 4. 构建结果
  204 + List<AiKnowledgeSegmentSearchRespBO> result = convertList(segments, segment -> {
  205 + Document document = CollUtil.findOne(documents, // 找到对应的文档
  206 + doc -> Objects.equals(doc.getId(), segment.getVectorId()));
  207 + if (document == null) {
  208 + return null;
  209 + }
  210 + return BeanUtils.toBean(segment, AiKnowledgeSegmentSearchRespBO.class)
  211 + .setScore(document.getScore());
  212 + });
  213 + result.sort((o1, o2)
  214 + -> Double.compare(o2.getScore(), o1.getScore())); // 按照分数降序排序
  215 + return result;
193 } 216 }
194 217
195 @Override 218 @Override
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/bo/AiKnowledgeSegmentSearchReqBO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.service.knowledge.bo;
  2 +
  3 +import lombok.Data;
  4 +
  5 +import javax.validation.constraints.NotNull;
  6 +
  7 +import jakarta.validation.constraints.NotEmpty;
  8 +
  9 +/**
  10 + * AI 知识库段落搜索 Request BO
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +@Data
  15 +public class AiKnowledgeSegmentSearchReqBO {
  16 +
  17 + /**
  18 + * 知识库编号
  19 + */
  20 + @NotNull(message = "知识库编号不能为空")
  21 + private Long knowledgeId;
  22 +
  23 + /**
  24 + * 内容
  25 + */
  26 + @NotEmpty(message = "内容不能为空")
  27 + private String content;
  28 +
  29 + /**
  30 + * 最大返回数量
  31 + */
  32 + private Integer topK;
  33 +
  34 + /**
  35 + * 相似度阈值
  36 + */
  37 + private Double similarityThreshold;
  38 +
  39 +}
0 \ No newline at end of file 40 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/bo/AiKnowledgeSegmentSearchRespBO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.service.knowledge.bo;
  2 +
  3 +import lombok.Data;
  4 +
  5 +/**
  6 + * AI 知识库段落搜索 Response BO
  7 + *
  8 + * @author 芋道源码
  9 + */
  10 +@Data
  11 +public class AiKnowledgeSegmentSearchRespBO {
  12 +
  13 + /**
  14 + * 段落编号
  15 + */
  16 + private Long id;
  17 + /**
  18 + * 文档编号
  19 + */
  20 + private Long documentId;
  21 + /**
  22 + * 知识库编号
  23 + */
  24 + private Long knowledgeId;
  25 +
  26 + /**
  27 + * 内容
  28 + */
  29 + private String content;
  30 + /**
  31 + * 内容长度
  32 + */
  33 + private Integer contentLength;
  34 +
  35 + /**
  36 + * Token 数量
  37 + */
  38 + private Integer tokens;
  39 +
  40 + /**
  41 + * 相似度分数
  42 + */
  43 + private Double score;
  44 +
  45 +}
0 \ No newline at end of file 46 \ 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
@@ -16,6 +16,7 @@ import jakarta.annotation.Resource; @@ -16,6 +16,7 @@ import jakarta.annotation.Resource;
16 import org.springframework.ai.chat.model.ChatModel; 16 import org.springframework.ai.chat.model.ChatModel;
17 import org.springframework.ai.embedding.EmbeddingModel; 17 import org.springframework.ai.embedding.EmbeddingModel;
18 import org.springframework.ai.image.ImageModel; 18 import org.springframework.ai.image.ImageModel;
  19 +import org.springframework.ai.vectorstore.SimpleVectorStore;
19 import org.springframework.ai.vectorstore.VectorStore; 20 import org.springframework.ai.vectorstore.VectorStore;
20 import org.springframework.context.annotation.Lazy; 21 import org.springframework.context.annotation.Lazy;
21 import org.springframework.stereotype.Service; 22 import org.springframework.stereotype.Service;
@@ -154,7 +155,7 @@ public class AiApiKeyServiceImpl implements AiApiKeyService { @@ -154,7 +155,7 @@ public class AiApiKeyServiceImpl implements AiApiKeyService {
154 apiKey.getUrl(), chatModel.getModel()); 155 apiKey.getUrl(), chatModel.getModel());
155 156
156 // 创建或获取 VectorStore 对象 157 // 创建或获取 VectorStore 对象
157 - return modelFactory.getOrCreateVectorStore(embeddingModel); 158 + return modelFactory.getOrCreateVectorStore(SimpleVectorStore.class, embeddingModel);
158 } 159 }
159 160
160 } 161 }
161 \ No newline at end of file 162 \ 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
@@ -99,9 +99,10 @@ public interface AiModelFactory { @@ -99,9 +99,10 @@ public interface AiModelFactory {
99 * <p> 99 * <p>
100 * 如果不存在,则进行创建 100 * 如果不存在,则进行创建
101 * 101 *
  102 + * @param type 向量存储类型
102 * @param embeddingModel 向量模型 103 * @param embeddingModel 向量模型
103 * @return VectorStore 对象 104 * @return VectorStore 对象
104 */ 105 */
105 - VectorStore getOrCreateVectorStore(EmbeddingModel embeddingModel); 106 + VectorStore getOrCreateVectorStore(Class<? extends VectorStore> type, EmbeddingModel embeddingModel);
106 107
107 } 108 }
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java
1 package cn.iocoder.yudao.framework.ai.core.factory; 1 package cn.iocoder.yudao.framework.ai.core.factory;
2 2
  3 +import cn.hutool.core.io.FileUtil;
3 import cn.hutool.core.lang.Assert; 4 import cn.hutool.core.lang.Assert;
4 import cn.hutool.core.lang.Singleton; 5 import cn.hutool.core.lang.Singleton;
5 import cn.hutool.core.lang.func.Func0; 6 import cn.hutool.core.lang.func.Func0;
6 import cn.hutool.core.util.ArrayUtil; 7 import cn.hutool.core.util.ArrayUtil;
  8 +import cn.hutool.core.util.RuntimeUtil;
7 import cn.hutool.core.util.StrUtil; 9 import cn.hutool.core.util.StrUtil;
8 import cn.hutool.extra.spring.SpringUtil; 10 import cn.hutool.extra.spring.SpringUtil;
9 import cn.iocoder.yudao.framework.ai.config.YudaoAiAutoConfiguration; 11 import cn.iocoder.yudao.framework.ai.config.YudaoAiAutoConfiguration;
@@ -24,6 +26,7 @@ import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel; @@ -24,6 +26,7 @@ import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel;
24 import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingOptions; 26 import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingOptions;
25 import com.alibaba.cloud.ai.dashscope.image.DashScopeImageModel; 27 import com.alibaba.cloud.ai.dashscope.image.DashScopeImageModel;
26 import com.azure.ai.openai.OpenAIClientBuilder; 28 import com.azure.ai.openai.OpenAIClientBuilder;
  29 +import lombok.SneakyThrows;
27 import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiAutoConfiguration; 30 import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiAutoConfiguration;
28 import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiChatProperties; 31 import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiChatProperties;
29 import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiConnectionProperties; 32 import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiConnectionProperties;
@@ -60,7 +63,11 @@ import org.springframework.ai.zhipuai.api.ZhiPuAiApi; @@ -60,7 +63,11 @@ import org.springframework.ai.zhipuai.api.ZhiPuAiApi;
60 import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi; 63 import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi;
61 import org.springframework.web.client.RestClient; 64 import org.springframework.web.client.RestClient;
62 65
  66 +import java.io.File;
  67 +import java.time.Duration;
63 import java.util.List; 68 import java.util.List;
  69 +import java.util.Timer;
  70 +import java.util.TimerTask;
64 71
65 /** 72 /**
66 * AI Model 模型工厂的实现类 73 * AI Model 模型工厂的实现类
@@ -73,7 +80,7 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -73,7 +80,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
73 public ChatModel getOrCreateChatModel(AiPlatformEnum platform, String apiKey, String url) { 80 public ChatModel getOrCreateChatModel(AiPlatformEnum platform, String apiKey, String url) {
74 String cacheKey = buildClientCacheKey(ChatModel.class, platform, apiKey, url); 81 String cacheKey = buildClientCacheKey(ChatModel.class, platform, apiKey, url);
75 return Singleton.get(cacheKey, (Func0<ChatModel>) () -> { 82 return Singleton.get(cacheKey, (Func0<ChatModel>) () -> {
76 - //noinspection EnhancedSwitchMigration 83 + // noinspection EnhancedSwitchMigration
77 switch (platform) { 84 switch (platform) {
78 case TONG_YI: 85 case TONG_YI:
79 return buildTongYiChatModel(apiKey); 86 return buildTongYiChatModel(apiKey);
@@ -105,7 +112,7 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -105,7 +112,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
105 112
106 @Override 113 @Override
107 public ChatModel getDefaultChatModel(AiPlatformEnum platform) { 114 public ChatModel getDefaultChatModel(AiPlatformEnum platform) {
108 - //noinspection EnhancedSwitchMigration 115 + // noinspection EnhancedSwitchMigration
109 switch (platform) { 116 switch (platform) {
110 case TONG_YI: 117 case TONG_YI:
111 return SpringUtil.getBean(DashScopeChatModel.class); 118 return SpringUtil.getBean(DashScopeChatModel.class);
@@ -136,7 +143,7 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -136,7 +143,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
136 143
137 @Override 144 @Override
138 public ImageModel getDefaultImageModel(AiPlatformEnum platform) { 145 public ImageModel getDefaultImageModel(AiPlatformEnum platform) {
139 - //noinspection EnhancedSwitchMigration 146 + // noinspection EnhancedSwitchMigration
140 switch (platform) { 147 switch (platform) {
141 case TONG_YI: 148 case TONG_YI:
142 return SpringUtil.getBean(DashScopeImageModel.class); 149 return SpringUtil.getBean(DashScopeImageModel.class);
@@ -155,7 +162,7 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -155,7 +162,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
155 162
156 @Override 163 @Override
157 public ImageModel getOrCreateImageModel(AiPlatformEnum platform, String apiKey, String url) { 164 public ImageModel getOrCreateImageModel(AiPlatformEnum platform, String apiKey, String url) {
158 - //noinspection EnhancedSwitchMigration 165 + // noinspection EnhancedSwitchMigration
159 switch (platform) { 166 switch (platform) {
160 case TONG_YI: 167 case TONG_YI:
161 return buildTongYiImagesModel(apiKey); 168 return buildTongYiImagesModel(apiKey);
@@ -174,9 +181,11 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -174,9 +181,11 @@ public class AiModelFactoryImpl implements AiModelFactory {
174 181
175 @Override 182 @Override
176 public MidjourneyApi getOrCreateMidjourneyApi(String apiKey, String url) { 183 public MidjourneyApi getOrCreateMidjourneyApi(String apiKey, String url) {
177 - String cacheKey = buildClientCacheKey(MidjourneyApi.class, AiPlatformEnum.MIDJOURNEY.getPlatform(), apiKey, url); 184 + String cacheKey = buildClientCacheKey(MidjourneyApi.class, AiPlatformEnum.MIDJOURNEY.getPlatform(), apiKey,
  185 + url);
178 return Singleton.get(cacheKey, (Func0<MidjourneyApi>) () -> { 186 return Singleton.get(cacheKey, (Func0<MidjourneyApi>) () -> {
179 - YudaoAiProperties.MidjourneyProperties properties = SpringUtil.getBean(YudaoAiProperties.class).getMidjourney(); 187 + YudaoAiProperties.MidjourneyProperties properties = SpringUtil.getBean(YudaoAiProperties.class)
  188 + .getMidjourney();
180 return new MidjourneyApi(url, apiKey, properties.getNotifyUrl()); 189 return new MidjourneyApi(url, apiKey, properties.getNotifyUrl());
181 }); 190 });
182 } 191 }
@@ -204,25 +213,31 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -204,25 +213,31 @@ public class AiModelFactoryImpl implements AiModelFactory {
204 } 213 }
205 214
206 @Override 215 @Override
207 - public VectorStore getOrCreateVectorStore(EmbeddingModel embeddingModel) {  
208 -// String cacheKey = buildClientCacheKey(VectorStore.class, platform, apiKey, url);  
209 - String cacheKey = buildClientCacheKey(VectorStore.class, embeddingModel); 216 + public VectorStore getOrCreateVectorStore(Class<? extends VectorStore> type, EmbeddingModel embeddingModel) {
  217 + // String cacheKey = buildClientCacheKey(VectorStore.class, platform, apiKey,
  218 + // url);
  219 + String cacheKey = buildClientCacheKey(VectorStore.class, embeddingModel, type);
210 return Singleton.get(cacheKey, (Func0<VectorStore>) () -> { 220 return Singleton.get(cacheKey, (Func0<VectorStore>) () -> {
  221 + if (type == SimpleVectorStore.class) {
  222 + return buildSimpleVectorStore(embeddingModel);
  223 + }
  224 + throw new IllegalArgumentException(StrUtil.format("未知类型({})", type));
211 // TODO @芋艿:先临时使用 store 225 // TODO @芋艿:先临时使用 store
212 - return SimpleVectorStore.builder(embeddingModel).build();  
213 // TODO @芋艿:@xin:后续看看,是不是切到阿里云之类的 226 // TODO @芋艿:@xin:后续看看,是不是切到阿里云之类的
214 -// String prefix = StrUtil.format("{}#{}:", platform.getPlatform(), apiKey);  
215 -// var config = RedisVectorStore.RedisVectorStoreConfig.builder()  
216 -// .withIndexName(cacheKey)  
217 -// .withPrefix(prefix)  
218 -// .withMetadataFields(new RedisVectorStore.MetadataField("knowledgeId", Schema.FieldType.NUMERIC))  
219 -// .build();  
220 -// RedisProperties redisProperties = SpringUtils.getBean(RedisProperties.class);  
221 -// RedisVectorStore redisVectorStore = new RedisVectorStore(config, embeddingModel,  
222 -// new JedisPooled(redisProperties.getHost(), redisProperties.getPort()),  
223 -// true);  
224 -// redisVectorStore.afterPropertiesSet();  
225 -// return redisVectorStore; 227 + // String prefix = StrUtil.format("{}#{}:", platform.getPlatform(), apiKey);
  228 + // var config = RedisVectorStore.RedisVectorStoreConfig.builder()
  229 + // .withIndexName(cacheKey)
  230 + // .withPrefix(prefix)
  231 + // .withMetadataFields(new RedisVectorStore.MetadataField("knowledgeId",
  232 + // Schema.FieldType.NUMERIC))
  233 + // .build();
  234 + // RedisProperties redisProperties = SpringUtils.getBean(RedisProperties.class);
  235 + // RedisVectorStore redisVectorStore = new RedisVectorStore(config,
  236 + // embeddingModel,
  237 + // new JedisPooled(redisProperties.getHost(), redisProperties.getPort()),
  238 + // true);
  239 + // redisVectorStore.afterPropertiesSet();
  240 + // return redisVectorStore;
226 }); 241 });
227 } 242 }
228 243
@@ -307,7 +322,7 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -307,7 +322,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
307 */ 322 */
308 private ChatModel buildSiliconFlowChatModel(String apiKey) { 323 private ChatModel buildSiliconFlowChatModel(String apiKey) {
309 YudaoAiProperties.SiliconFlowProperties properties = new YudaoAiProperties.SiliconFlowProperties() 324 YudaoAiProperties.SiliconFlowProperties properties = new YudaoAiProperties.SiliconFlowProperties()
310 - .setApiKey(apiKey); 325 + .setApiKey(apiKey);
311 return new YudaoAiAutoConfiguration().buildSiliconFlowChatClient(properties); 326 return new YudaoAiAutoConfiguration().buildSiliconFlowChatClient(properties);
312 } 327 }
313 328
@@ -397,7 +412,8 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -397,7 +412,8 @@ public class AiModelFactoryImpl implements AiModelFactory {
397 */ 412 */
398 private DashScopeEmbeddingModel buildTongYiEmbeddingModel(String apiKey, String model) { 413 private DashScopeEmbeddingModel buildTongYiEmbeddingModel(String apiKey, String model) {
399 DashScopeApi dashScopeApi = new DashScopeApi(apiKey); 414 DashScopeApi dashScopeApi = new DashScopeApi(apiKey);
400 - DashScopeEmbeddingOptions dashScopeEmbeddingOptions = DashScopeEmbeddingOptions.builder().withModel(model).build(); 415 + DashScopeEmbeddingOptions dashScopeEmbeddingOptions = DashScopeEmbeddingOptions.builder().withModel(model)
  416 + .build();
401 return new DashScopeEmbeddingModel(dashScopeApi, MetadataMode.EMBED, dashScopeEmbeddingOptions); 417 return new DashScopeEmbeddingModel(dashScopeApi, MetadataMode.EMBED, dashScopeEmbeddingOptions);
402 } 418 }
403 419
@@ -407,4 +423,58 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -407,4 +423,58 @@ public class AiModelFactoryImpl implements AiModelFactory {
407 return OllamaEmbeddingModel.builder().ollamaApi(ollamaApi).defaultOptions(ollamaOptions).build(); 423 return OllamaEmbeddingModel.builder().ollamaApi(ollamaApi).defaultOptions(ollamaOptions).build();
408 } 424 }
409 425
  426 + // ========== 各种创建 VectorStore 的方法 ==========
  427 +
  428 + /**
  429 + * 注意:仅适合本地测试使用,生产建议还是使用 Qdrant、Milvus 等
  430 + */
  431 + @SneakyThrows
  432 + @SuppressWarnings("ResultOfMethodCallIgnored")
  433 + private SimpleVectorStore buildSimpleVectorStore(EmbeddingModel embeddingModel) {
  434 + SimpleVectorStore vectorStore = SimpleVectorStore.builder(embeddingModel).build();
  435 + // 启动加载
  436 + File file = new File(StrUtil.format("{}/vector_store/simple_{}.json",
  437 + FileUtil.getUserHomePath(), embeddingModel.getClass().getSimpleName()));
  438 + if (!file.exists()) {
  439 + FileUtil.mkParentDirs(file);
  440 + file.createNewFile();
  441 + } else if (file.length() > 0) {
  442 + vectorStore.load(file);
  443 + }
  444 + // 定时持久化,每分钟一次
  445 + Timer timer = new Timer("SimpleVectorStoreTimer-" + file.getAbsolutePath());
  446 + timer.scheduleAtFixedRate(new TimerTask() {
  447 +
  448 + @Override
  449 + public void run() {
  450 + vectorStore.save(file);
  451 + }
  452 +
  453 + }, Duration.ofMinutes(1).toMillis(), Duration.ofMinutes(1).toMillis());
  454 + // 关闭时,进行持久化
  455 + RuntimeUtil.addShutdownHook(() -> vectorStore.save(file));
  456 + return vectorStore;
  457 + }
  458 +
  459 + /**
  460 + * 创建向量存储文件
  461 + *
  462 + * @param embeddingModel 嵌入模型
  463 + * @return 向量存储文件
  464 + */
  465 + private File createVectorStoreFile(EmbeddingModel embeddingModel) {
  466 + // 获取简单类名
  467 + String simpleClassName = embeddingModel.getClass().getSimpleName();
  468 + // 获取用户主目录
  469 + String userHome = FileUtil.getUserHomePath();
  470 + // 创建vector_store目录
  471 + File vectorStoreDir = new File(userHome, "vector_store");
  472 + if (!vectorStoreDir.exists()) {
  473 + vectorStoreDir.mkdirs();
  474 + }
  475 +
  476 + // 创建文件
  477 + return new File(vectorStoreDir, "simple_" + simpleClassName + ".json");
  478 + }
  479 +
410 } 480 }