From 8e012b10bb28d16cdde68c715babbcddb88e17b7 Mon Sep 17 00:00:00 2001 From: xiaoxin <718949661@qq.com> Date: Tue, 3 Sep 2024 16:50:42 +0800 Subject: [PATCH 01/10] =?UTF-8?q?=E3=80=90=E4=BC=98=E5=8C=96=E3=80=91AI=20?= =?UTF-8?q?=E7=9F=A5=E8=AF=86=E5=BA=93:=20VectorStore=20=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=20=E6=8A=BD=E5=88=B0=20AiModelFactory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai/service/model/AiApiKeyServiceImpl.java | 5 +- .../ai/config/YudaoAiAutoConfiguration.java | 7 --- .../ai/core/factory/AiModelFactory.java | 14 +++++ .../ai/core/factory/AiModelFactoryImpl.java | 27 ++++++++++ .../ai/core/factory/AiVectorStoreFactory.java | 28 ---------- .../factory/AiVectorStoreFactoryImpl.java | 52 ------------------- 6 files changed, 42 insertions(+), 91 deletions(-) delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiVectorStoreFactory.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiVectorStoreFactoryImpl.java diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyServiceImpl.java index bf11ec218..b2807905a 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyServiceImpl.java @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.ai.service.model; import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory; -import cn.iocoder.yudao.framework.ai.core.factory.AiVectorStoreFactory; import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; @@ -39,8 +38,6 @@ public class AiApiKeyServiceImpl implements AiApiKeyService { @Resource private AiModelFactory modelFactory; - @Resource - private AiVectorStoreFactory vectorFactory; @Override public Long createApiKey(AiApiKeySaveReqVO createReqVO) { @@ -149,7 +146,7 @@ public class AiApiKeyServiceImpl implements AiApiKeyService { public VectorStore getOrCreateVectorStore(Long id) { AiApiKeyDO apiKey = validateApiKey(id); AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform()); - return vectorFactory.getOrCreateVectorStore(getEmbeddingModel(id), platform, apiKey.getApiKey(), apiKey.getUrl()); + return modelFactory.getOrCreateVectorStore(getEmbeddingModel(id), platform, apiKey.getApiKey(), apiKey.getUrl()); } } \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java index 79a1f345b..cd5cfc58b 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java @@ -2,8 +2,6 @@ package cn.iocoder.yudao.framework.ai.config; import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory; import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactoryImpl; -import cn.iocoder.yudao.framework.ai.core.factory.AiVectorStoreFactory; -import cn.iocoder.yudao.framework.ai.core.factory.AiVectorStoreFactoryImpl; import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel; import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatOptions; import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; @@ -38,11 +36,6 @@ public class YudaoAiAutoConfiguration { return new AiModelFactoryImpl(); } - @Bean - public AiVectorStoreFactory aiVectorFactory() { - return new AiVectorStoreFactoryImpl(); - } - // ========== 各种 AI Client 创建 ========== diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactory.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactory.java index 7e8465375..243c4ae4b 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactory.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactory.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.embedding.EmbeddingModel; import org.springframework.ai.image.ImageModel; +import org.springframework.ai.vectorstore.VectorStore; /** * AI Model 模型工厂的接口类 @@ -92,4 +93,17 @@ public interface AiModelFactory { */ EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String apiKey, String url); + /** + * 基于指定配置,获得 VectorStore 对象 + *

+ * 如果不存在,则进行创建 + * + * @param embeddingModel 嵌入模型 + * @param platform 平台 + * @param apiKey API KEY + * @param url API URL + * @return VectorStore 对象 + */ + VectorStore getOrCreateVectorStore(EmbeddingModel embeddingModel, AiPlatformEnum platform, String apiKey, String url); + } diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java index aa46c45f2..dfe029796 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java @@ -13,6 +13,7 @@ import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel; import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; +import cn.iocoder.yudao.framework.common.util.spring.SpringUtils; import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration; import com.alibaba.cloud.ai.tongyi.TongYiConnectionProperties; import com.alibaba.cloud.ai.tongyi.chat.TongYiChatModel; @@ -54,13 +55,17 @@ import org.springframework.ai.qianfan.api.QianFanApi; import org.springframework.ai.qianfan.api.QianFanImageApi; import org.springframework.ai.stabilityai.StabilityAiImageModel; import org.springframework.ai.stabilityai.api.StabilityAiApi; +import org.springframework.ai.vectorstore.RedisVectorStore; +import org.springframework.ai.vectorstore.VectorStore; import org.springframework.ai.zhipuai.ZhiPuAiChatModel; import org.springframework.ai.zhipuai.ZhiPuAiImageModel; import org.springframework.ai.zhipuai.api.ZhiPuAiApi; import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi; +import org.springframework.boot.autoconfigure.data.redis.RedisProperties; import org.springframework.retry.support.RetryTemplate; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; +import redis.clients.jedis.JedisPooled; import java.util.List; @@ -191,6 +196,28 @@ public class AiModelFactoryImpl implements AiModelFactory { }); } + @Override + public VectorStore getOrCreateVectorStore(EmbeddingModel embeddingModel, AiPlatformEnum platform, String apiKey, String url) { + String cacheKey = buildClientCacheKey(VectorStore.class, platform, apiKey, url); + return Singleton.get(cacheKey, (Func0) () -> { + // TODO 芋艿 @xin 这两个配置取哪好呢 + // TODO 不同模型的向量维度可能会不一样,目前看貌似是以 index 来做区分的,维度不一样存不到一个 index 上 + // TODO 回复:好的哈 + String index = "default-index"; + String prefix = "default:"; + var config = RedisVectorStore.RedisVectorStoreConfig.builder() + .withIndexName(index) + .withPrefix(prefix) + .build(); + RedisProperties redisProperties = SpringUtils.getBean(RedisProperties.class); + RedisVectorStore redisVectorStore = new RedisVectorStore(config, embeddingModel, + new JedisPooled(redisProperties.getHost(), redisProperties.getPort()), + true); + redisVectorStore.afterPropertiesSet(); + return redisVectorStore; + }); + } + private static String buildClientCacheKey(Class clazz, Object... params) { if (ArrayUtil.isEmpty(params)) { return clazz.getName(); diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiVectorStoreFactory.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiVectorStoreFactory.java deleted file mode 100644 index dad58a2c0..000000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiVectorStoreFactory.java +++ /dev/null @@ -1,28 +0,0 @@ -package cn.iocoder.yudao.framework.ai.core.factory; - -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; -import org.springframework.ai.embedding.EmbeddingModel; -import org.springframework.ai.vectorstore.VectorStore; - -// TODO @xin:也放到 AiModelFactory 里面好了,后续改成 AiFactory -/** - * AI Vector 模型工厂的接口类 - * - * @author xiaoxin - */ -public interface AiVectorStoreFactory { - - /** - * 基于指定配置,获得 VectorStore 对象 - *

- * 如果不存在,则进行创建 - * - * @param embeddingModel 嵌入模型 - * @param platform 平台 - * @param apiKey API KEY - * @param url API URL - * @return VectorStore 对象 - */ - VectorStore getOrCreateVectorStore(EmbeddingModel embeddingModel, AiPlatformEnum platform, String apiKey, String url); - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiVectorStoreFactoryImpl.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiVectorStoreFactoryImpl.java deleted file mode 100644 index ec04c5e88..000000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiVectorStoreFactoryImpl.java +++ /dev/null @@ -1,52 +0,0 @@ -package cn.iocoder.yudao.framework.ai.core.factory; - -import cn.hutool.core.lang.Singleton; -import cn.hutool.core.lang.func.Func0; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; -import cn.iocoder.yudao.framework.common.util.spring.SpringUtils; -import org.springframework.ai.embedding.EmbeddingModel; -import org.springframework.ai.vectorstore.RedisVectorStore; -import org.springframework.ai.vectorstore.VectorStore; -import org.springframework.boot.autoconfigure.data.redis.RedisProperties; -import redis.clients.jedis.JedisPooled; - -/** - * AI Vector 模型工厂的实现类 - * 使用 redisVectorStore 实现 VectorStore - * - * @author xiaoxin - */ -public class AiVectorStoreFactoryImpl implements AiVectorStoreFactory { - - @Override - public VectorStore getOrCreateVectorStore(EmbeddingModel embeddingModel, AiPlatformEnum platform, String apiKey, String url) { - String cacheKey = buildClientCacheKey(VectorStore.class, platform, apiKey, url); - return Singleton.get(cacheKey, (Func0) () -> { - // TODO 芋艿 @xin 这两个配置取哪好呢 - // TODO 不同模型的向量维度可能会不一样,目前看貌似是以 index 来做区分的,维度不一样存不到一个 index 上 - // TODO 回复:好的哈 - String index = "default-index"; - String prefix = "default:"; - var config = RedisVectorStore.RedisVectorStoreConfig.builder() - .withIndexName(index) - .withPrefix(prefix) - .build(); - RedisProperties redisProperties = SpringUtils.getBean(RedisProperties.class); - RedisVectorStore redisVectorStore = new RedisVectorStore(config, embeddingModel, - new JedisPooled(redisProperties.getHost(), redisProperties.getPort()), - true); - redisVectorStore.afterPropertiesSet(); - return redisVectorStore; - }); - } - - private static String buildClientCacheKey(Class clazz, Object... params) { - if (ArrayUtil.isEmpty(params)) { - return clazz.getName(); - } - return StrUtil.format("{}#{}", clazz.getName(), ArrayUtil.join(params, "_")); - } - -} From c26cecaed6fa237987e1ca562ac05f0a5803dd1f Mon Sep 17 00:00:00 2001 From: xiaoxin <718949661@qq.com> Date: Tue, 3 Sep 2024 16:54:51 +0800 Subject: [PATCH 02/10] =?UTF-8?q?=E3=80=90=E4=BC=98=E5=8C=96=E3=80=91AI=20?= =?UTF-8?q?=E7=9F=A5=E8=AF=86=E5=BA=93:=20=E4=B8=8D=E5=81=9A=E7=BB=8F?= =?UTF-8?q?=E6=B5=8E=E5=9E=8B=20=E5=85=88=E6=B3=A8=E9=87=8A=20spring-ai-tr?= =?UTF-8?q?ansformers-spring-boot-starter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml b/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml index 85996cb82..27c762a72 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml @@ -46,11 +46,13 @@ - - ${spring-ai.groupId} - spring-ai-transformers-spring-boot-starter - ${spring-ai.version} - + + + + + + + ${spring-ai.groupId} spring-ai-tika-document-reader From 92d32b652e05639b6b9407930e9218b75cdbb8f7 Mon Sep 17 00:00:00 2001 From: xiaoxin <718949661@qq.com> Date: Thu, 5 Sep 2024 13:32:16 +0800 Subject: [PATCH 03/10] =?UTF-8?q?=E3=80=90=E6=96=B0=E5=A2=9E=E3=80=91AI=20?= =?UTF-8?q?=E7=9F=A5=E8=AF=86=E5=BA=93:=20=E6=AE=B5=E8=90=BD=E5=8F=AC?= =?UTF-8?q?=E5=9B=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AiKnowledgeSegmentSearchReqVO.java | 17 ++++++++ .../knowledge/AiKnowledgeSegmentMapper.java | 8 ++++ .../knowledge/AiKnowledgeSegmentService.java | 12 ++++++ .../AiKnowledgeSegmentServiceImpl.java | 42 +++++++++++++++++++ .../ai/config/YudaoAiAutoConfiguration.java | 9 ++-- .../ai/core/factory/AiModelFactoryImpl.java | 10 ++--- 6 files changed, 88 insertions(+), 10 deletions(-) create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentSearchReqVO.java diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentSearchReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentSearchReqVO.java new file mode 100644 index 000000000..75349df62 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentSearchReqVO.java @@ -0,0 +1,17 @@ +package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + + +@Schema(description = "管理后台 - AI 知识库段落召回 Request VO") +@Data +public class AiKnowledgeSegmentSearchReqVO { + + @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790") + private Long knowledgeId; + + @Schema(description = "内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 学习路线") + private String content; + +} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeSegmentMapper.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeSegmentMapper.java index 912d18cbc..ea238b826 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeSegmentMapper.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeSegmentMapper.java @@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowle import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; import org.apache.ibatis.annotations.Mapper; +import java.util.List; + /** * AI 知识库-分片 Mapper * @@ -22,4 +24,10 @@ public interface AiKnowledgeSegmentMapper extends BaseMapperX selectList(List vectorIdList) { + return selectList(new LambdaQueryWrapperX() + .in(AiKnowledgeSegmentDO::getVectorId, vectorIdList) + .orderByDesc(AiKnowledgeSegmentDO::getId)); + } } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentService.java index 8ecb2d24a..49ed67135 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentService.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentService.java @@ -2,10 +2,13 @@ package cn.iocoder.yudao.module.ai.service.knowledge; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentSearchReqVO; import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateReqVO; import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO; import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; +import java.util.List; + /** * AI 知识库段落 Service 接口 * @@ -35,4 +38,13 @@ public interface AiKnowledgeSegmentService { */ void updateKnowledgeSegmentStatus(AiKnowledgeSegmentUpdateStatusReqVO reqVO); + + /** + * 段落召回 + * + * @param reqVO 召回请求信息 + * @return 召回的段落 + */ + List similaritySearch(AiKnowledgeSegmentSearchReqVO reqVO); + } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java index 7f751b176..3bcf5d692 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java @@ -1,16 +1,29 @@ package cn.iocoder.yudao.module.ai.service.knowledge; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.ListUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentSearchReqVO; import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateReqVO; import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO; +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeSegmentMapper; +import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; +import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.document.Document; +import org.springframework.ai.vectorstore.SearchRequest; +import org.springframework.ai.vectorstore.VectorStore; +import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder; import org.springframework.stereotype.Service; +import java.util.List; + /** * AI 知识库分片 Service 实现类 * @@ -23,6 +36,13 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService @Resource private AiKnowledgeSegmentMapper segmentMapper; + @Resource + private AiKnowledgeService knowledgeService; + @Resource + private AiChatModelService chatModelService; + @Resource + private AiApiKeyService apiKeyService; + @Override public PageResult getKnowledgeSegmentPage(AiKnowledgeSegmentPageReqVO pageReqVO) { return segmentMapper.selectPage(pageReqVO); @@ -39,4 +59,26 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService segmentMapper.updateById(BeanUtils.toBean(reqVO, AiKnowledgeSegmentDO.class)); // TODO @xin 1.禁用删除向量 2.启用重新向量化 } + + @Override + public List similaritySearch(AiKnowledgeSegmentSearchReqVO reqVO) { + // 0. 校验 + AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(reqVO.getKnowledgeId()); + AiChatModelDO model = chatModelService.validateChatModel(knowledge.getModelId()); + + // 1.1 获取向量存储实例 + VectorStore vectorStore = apiKeyService.getOrCreateVectorStore(model.getKeyId()); + + // 1.2 向量检索 + List documentList = vectorStore.similaritySearch(SearchRequest.query(reqVO.getContent()) + //TODO @xin 配置提取 + .withTopK(5) + .withSimilarityThreshold(0.5d) + .withFilterExpression(new FilterExpressionBuilder().eq(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, reqVO.getKnowledgeId()).build())); + if (CollUtil.isEmpty(documentList)) { + return ListUtil.empty(); + } + // 2.1 段落召回 + return segmentMapper.selectList(CollUtil.getFieldValues(documentList, "id", String.class)); + } } diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java index cd5cfc58b..0d2620b0c 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java @@ -82,7 +82,7 @@ public class YudaoAiAutoConfiguration { // TODO @xin 免费版本 // @Bean // @Lazy // TODO 芋艿:临时注释,避免无法启动」 -// public EmbeddingModel transformersEmbeddingClient() { +// public TransformersEmbeddingModel transformersEmbeddingClient() { // return new TransformersEmbeddingModel(MetadataMode.EMBED); // } @@ -91,23 +91,24 @@ public class YudaoAiAutoConfiguration { */ // @Bean // @Lazy // TODO 芋艿:临时注释,避免无法启动 -// public RedisVectorStore vectorStore(TongYiTextEmbeddingModel tongYiTextEmbeddingModel, RedisVectorStoreProperties properties, +// public RedisVectorStore vectorStore(TransformersEmbeddingModel embeddingModel, RedisVectorStoreProperties properties, // RedisProperties redisProperties) { // var config = RedisVectorStore.RedisVectorStoreConfig.builder() // .withIndexName(properties.getIndex()) // .withPrefix(properties.getPrefix()) +// .withMetadataFields(new RedisVectorStore.MetadataField("knowledgeId", Schema.FieldType.NUMERIC)) // .build(); // -// RedisVectorStore redisVectorStore = new RedisVectorStore(config, tongYiTextEmbeddingModel, +// RedisVectorStore redisVectorStore = new RedisVectorStore(config, embeddingModel, // new JedisPooled(redisProperties.getHost(), redisProperties.getPort()), // properties.isInitializeSchema()); // redisVectorStore.afterPropertiesSet(); // return redisVectorStore; // } - @Bean @Lazy // TODO 芋艿:临时注释,避免无法启动 public TokenTextSplitter tokenTextSplitter() { + //TODO @xin 配置提取 return new TokenTextSplitter(500, 100, 5, 10000, true); } diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java index dfe029796..7acd24769 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java @@ -66,6 +66,7 @@ import org.springframework.retry.support.RetryTemplate; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; import redis.clients.jedis.JedisPooled; +import redis.clients.jedis.search.Schema; import java.util.List; @@ -200,14 +201,11 @@ public class AiModelFactoryImpl implements AiModelFactory { public VectorStore getOrCreateVectorStore(EmbeddingModel embeddingModel, AiPlatformEnum platform, String apiKey, String url) { String cacheKey = buildClientCacheKey(VectorStore.class, platform, apiKey, url); return Singleton.get(cacheKey, (Func0) () -> { - // TODO 芋艿 @xin 这两个配置取哪好呢 - // TODO 不同模型的向量维度可能会不一样,目前看貌似是以 index 来做区分的,维度不一样存不到一个 index 上 - // TODO 回复:好的哈 - String index = "default-index"; - String prefix = "default:"; + String prefix = StrUtil.format("{}#{}:", platform.getPlatform(), apiKey); var config = RedisVectorStore.RedisVectorStoreConfig.builder() - .withIndexName(index) + .withIndexName(cacheKey) .withPrefix(prefix) + .withMetadataFields(new RedisVectorStore.MetadataField("knowledgeId", Schema.FieldType.NUMERIC)) .build(); RedisProperties redisProperties = SpringUtils.getBean(RedisProperties.class); RedisVectorStore redisVectorStore = new RedisVectorStore(config, embeddingModel, From 9b8136ef303568302131a1fd3914b47a19268d4f Mon Sep 17 00:00:00 2001 From: xiaoxin <718949661@qq.com> Date: Thu, 5 Sep 2024 17:11:56 +0800 Subject: [PATCH 04/10] =?UTF-8?q?=E3=80=90=E6=96=B0=E5=A2=9E=E3=80=91AI=20?= =?UTF-8?q?=E7=9F=A5=E8=AF=86=E5=BA=93:=20=E9=85=8D=E7=BD=AE=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E3=80=81=E6=AE=B5=E8=90=BD=E5=90=AF=E7=A6=81?= =?UTF-8?q?=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../knowledge/AiKnowledgeCreateMyReqVO.java | 7 ++ .../AiKnowledgeDocumentCreateReqVO.java | 19 ++++++ .../dataobject/knowledge/AiKnowledgeDO.java | 12 ++++ .../knowledge/AiKnowledgeDocumentDO.java | 22 ++++++- .../knowledge/AiKnowledgeSegmentDO.java | 9 ++- .../AiKnowledgeDocumentServiceImpl.java | 21 ++---- .../AiKnowledgeSegmentServiceImpl.java | 64 +++++++++++++++++-- .../service/knowledge/AiKnowledgeService.java | 9 +++ .../knowledge/AiKnowledgeServiceImpl.java | 13 ++++ 9 files changed, 149 insertions(+), 27 deletions(-) diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeCreateMyReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeCreateMyReqVO.java index 44a5e87ee..58a89caee 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeCreateMyReqVO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeCreateMyReqVO.java @@ -25,4 +25,11 @@ public class AiKnowledgeCreateMyReqVO { @NotNull(message = "嵌入模型不能为空") private Long modelId; + @Schema(description = "相似性阈值", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.5") + @NotNull(message = "相似性阈值不能为空") + private Double similarityThreshold; + + @Schema(description = "topK", requiredMode = Schema.RequiredMode.REQUIRED, example = "3") + @NotNull(message = "topK 不能为空") + private Integer topK; } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeDocumentCreateReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeDocumentCreateReqVO.java index 9cc5290ab..651bdc0f7 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeDocumentCreateReqVO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeDocumentCreateReqVO.java @@ -23,4 +23,23 @@ public class AiKnowledgeDocumentCreateReqVO { @URL(message = "文档 URL 格式不正确") private String url; + @Schema(description = "每个文本块的目标 token 数", requiredMode = Schema.RequiredMode.REQUIRED, example = "800") + @NotNull(message = "每个文本块的目标 token 数不能为空") + private Integer defaultChunkSize; + + @Schema(description = "每个文本块的最小字符数", requiredMode = Schema.RequiredMode.REQUIRED, example = "350") + @NotNull(message = "每个文本块的最小字符数不能为空") + private Integer minChunkSizeChars; + + @Schema(description = "丢弃阈值", requiredMode = Schema.RequiredMode.REQUIRED, example = "5") + @NotNull(message = "丢弃阈值不能为空") + private Integer minChunkLengthToEmbed; + + @Schema(description = "最大块数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000") + @NotNull(message = "最大块数不能为空") + private Integer maxNumChunks; + + @Schema(description = "分块是否保留分隔符", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @NotNull(message = "分块是否保留分隔符不能为空") + private Boolean keepSeparator; } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java index 756d8cdb3..5db631dd4 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java @@ -52,6 +52,18 @@ public class AiKnowledgeDO extends BaseDO { * 模型标识 */ private String model; + + /** + * topK + */ + private Integer topK; + + /** + * 相似度阈值 + */ + private Double similarityThreshold; + + /** * 状态 *

diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDocumentDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDocumentDO.java index c5e526cce..18fa46c3a 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDocumentDO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDocumentDO.java @@ -23,7 +23,7 @@ public class AiKnowledgeDocumentDO extends BaseDO { private Long id; /** * 知识库编号 - * + *

* 关联 {@link AiKnowledgeDO#getId()} */ private Long knowledgeId; @@ -47,6 +47,26 @@ public class AiKnowledgeDocumentDO extends BaseDO { * 字符数 */ private Integer wordCount; + /** + * 每个文本块的目标 token 数 + */ + private Integer defaultChunkSize; + /** + * 每个文本块的最小字符数 + */ + private Integer minChunkSizeChars; + /** + * 低于此值的块会被丢弃 + */ + private Integer minChunkLengthToEmbed; + /** + * 最大块数 + */ + private Integer maxNumChunks; + /** + * 分块是否保留分隔符 + */ + private Boolean keepSeparator; /** * 切片状态 *

diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeSegmentDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeSegmentDO.java index 84f7de654..be57265e1 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeSegmentDO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeSegmentDO.java @@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.knowledge; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; @@ -25,16 +27,17 @@ public class AiKnowledgeSegmentDO extends BaseDO { /** * 向量库的编号 */ + @TableField(updateStrategy = FieldStrategy.ALWAYS) private String vectorId; /** * 知识库编号 - * + *

* 关联 {@link AiKnowledgeDO#getId()} */ private Long knowledgeId; /** * 文档编号 - * + *

* 关联 {@link AiKnowledgeDocumentDO#getId()} */ private Long documentId; @@ -52,7 +55,7 @@ public class AiKnowledgeSegmentDO extends BaseDO { private Integer tokens; /** * 状态 - * + *

* 枚举 {@link CommonStatusEnum} */ private Integer status; diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentServiceImpl.java index 99f0621c8..05a9dce22 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentServiceImpl.java @@ -9,15 +9,11 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO; import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateReqVO; import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeDocumentCreateReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO; import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeDocumentMapper; import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeSegmentMapper; import cn.iocoder.yudao.module.ai.enums.knowledge.AiKnowledgeDocumentStatusEnum; -import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; -import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.document.Document; @@ -48,24 +44,16 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic @Resource private AiKnowledgeSegmentMapper segmentMapper; - @Resource - private TokenTextSplitter tokenTextSplitter; @Resource private TokenCountEstimator tokenCountEstimator; - - @Resource - private AiApiKeyService apiKeyService; @Resource private AiKnowledgeService knowledgeService; - @Resource - private AiChatModelService chatModelService; @Override @Transactional(rollbackFor = Exception.class) public Long createKnowledgeDocument(AiKnowledgeDocumentCreateReqVO createReqVO) { - // 0. 校验 - AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(createReqVO.getKnowledgeId()); - AiChatModelDO model = chatModelService.validateChatModel(knowledge.getModelId()); + // 0. 校验并获取向量存储实例 + VectorStore vectorStore = knowledgeService.getVectorStoreById(createReqVO.getKnowledgeId()); // 1.1 下载文档 TikaDocumentReader loader = new TikaDocumentReader(downloadFile(createReqVO.getUrl())); @@ -82,6 +70,9 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic return documentId; } + // 2 构造文本分段器 + TokenTextSplitter tokenTextSplitter = new TokenTextSplitter(createReqVO.getDefaultChunkSize(), createReqVO.getMinChunkSizeChars(), createReqVO.getMinChunkLengthToEmbed(), + createReqVO.getMaxNumChunks(), createReqVO.getKeepSeparator()); // 2.1 文档分段 List segments = tokenTextSplitter.apply(documents); // 2.2 分段内容入库 @@ -92,8 +83,6 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic .setStatus(CommonStatusEnum.ENABLE.getStatus())); segmentMapper.insertBatch(segmentDOList); - // 3.1 获取向量存储实例 - VectorStore vectorStore = apiKeyService.getOrCreateVectorStore(model.getKeyId()); // 3.2 向量化并存储 segments.forEach(segment -> segment.getMetadata().put(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, createReqVO.getKnowledgeId())); vectorStore.add(segments); diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java index 3bcf5d692..1813d0b48 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java +++ b/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; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO; @@ -23,6 +24,10 @@ import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder; import org.springframework.stereotype.Service; import java.util.List; +import java.util.Objects; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_SEGMENT_NOT_EXISTS; /** * AI 知识库分片 Service 实现类 @@ -50,14 +55,45 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService @Override public void updateKnowledgeSegment(AiKnowledgeSegmentUpdateReqVO reqVO) { - segmentMapper.updateById(BeanUtils.toBean(reqVO, AiKnowledgeSegmentDO.class)); - // TODO @xin 重新向量化 + // 0 校验 + AiKnowledgeSegmentDO oldKnowledgeSegment = validateKnowledgeSegmentExists(reqVO.getId()); + // 2.1 获取知识库向量实例 + VectorStore vectorStore = knowledgeService.getVectorStoreById(oldKnowledgeSegment.getKnowledgeId()); + // 2.2 删除原向量 + vectorStore.delete(List.of(oldKnowledgeSegment.getVectorId())); + + // 2.3 重新向量化 + Document document = new Document(reqVO.getContent()); + document.getMetadata().put(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, oldKnowledgeSegment.getKnowledgeId()); + vectorStore.add(List.of(document)); + + // 2.1 更新段落内容 + AiKnowledgeSegmentDO knowledgeSegment = BeanUtils.toBean(reqVO, AiKnowledgeSegmentDO.class); + knowledgeSegment.setVectorId(document.getId()); + segmentMapper.updateById(knowledgeSegment); } @Override public void updateKnowledgeSegmentStatus(AiKnowledgeSegmentUpdateStatusReqVO reqVO) { - segmentMapper.updateById(BeanUtils.toBean(reqVO, AiKnowledgeSegmentDO.class)); - // TODO @xin 1.禁用删除向量 2.启用重新向量化 + // 0 校验 + AiKnowledgeSegmentDO oldKnowledgeSegment = validateKnowledgeSegmentExists(reqVO.getId()); + // 1 获取知识库向量实例 + VectorStore vectorStore = knowledgeService.getVectorStoreById(oldKnowledgeSegment.getKnowledgeId()); + AiKnowledgeSegmentDO knowledgeSegment = BeanUtils.toBean(reqVO, AiKnowledgeSegmentDO.class); + + if (Objects.equals(reqVO.getStatus(), CommonStatusEnum.ENABLE.getStatus())) { + // 2.1 启用重新向量化 + Document document = new Document(oldKnowledgeSegment.getContent()); + document.getMetadata().put(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, oldKnowledgeSegment.getKnowledgeId()); + vectorStore.add(List.of(document)); + knowledgeSegment.setVectorId(document.getId()); + } else { + // 2.2 禁用删除向量 + vectorStore.delete(List.of(oldKnowledgeSegment.getVectorId())); + knowledgeSegment.setVectorId(null); + } + // 3 更新段落状态 + segmentMapper.updateById(knowledgeSegment); } @Override @@ -71,9 +107,8 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService // 1.2 向量检索 List documentList = vectorStore.similaritySearch(SearchRequest.query(reqVO.getContent()) - //TODO @xin 配置提取 - .withTopK(5) - .withSimilarityThreshold(0.5d) + .withTopK(knowledge.getTopK()) + .withSimilarityThreshold(knowledge.getSimilarityThreshold()) .withFilterExpression(new FilterExpressionBuilder().eq(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, reqVO.getKnowledgeId()).build())); if (CollUtil.isEmpty(documentList)) { return ListUtil.empty(); @@ -81,4 +116,19 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService // 2.1 段落召回 return segmentMapper.selectList(CollUtil.getFieldValues(documentList, "id", String.class)); } + + + /** + * 校验段落是否存在 + * + * @param id 文档编号 + * @return 段落信息 + */ + private AiKnowledgeSegmentDO validateKnowledgeSegmentExists(Long id) { + AiKnowledgeSegmentDO knowledgeSegment = segmentMapper.selectById(id); + if (knowledgeSegment == null) { + throw exception(KNOWLEDGE_SEGMENT_NOT_EXISTS); + } + return knowledgeSegment; + } } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeService.java index 9f43c5328..d9770f452 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeService.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeService.java @@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeCreateMyReqVO; import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateMyReqVO; import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; +import org.springframework.ai.vectorstore.VectorStore; /** * AI 知识库-基础信息 Service 接口 @@ -47,4 +48,12 @@ public interface AiKnowledgeService { * @return 知识库分页 */ PageResult getKnowledgePageMy(Long userId, PageParam pageReqVO); + + /** + * 根据知识库编号获取向量存储实例 + * + * @param knowledgeId 知识库编号 + * @return 向量存储实例 + */ + VectorStore getVectorStoreById(Long knowledgeId); } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java index 1948bb00e..7a145d734 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java @@ -10,9 +10,11 @@ import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnow import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeMapper; +import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.vectorstore.VectorStore; import org.springframework.stereotype.Service; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; @@ -32,6 +34,10 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService { @Resource private AiKnowledgeMapper knowledgeMapper; + @Resource + private AiChatModelService chatModelService; + @Resource + private AiApiKeyService apiKeyService; @Override public Long createKnowledgeMy(AiKnowledgeCreateMyReqVO createReqVO, Long userId) { @@ -75,4 +81,11 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService { return knowledgeMapper.selectPageByMy(userId, pageReqVO); } + @Override + public VectorStore getVectorStoreById(Long knowledgeId) { + AiKnowledgeDO knowledge = validateKnowledgeExists(knowledgeId); + AiChatModelDO model = chatModelService.validateChatModel(knowledge.getModelId()); + return apiKeyService.getOrCreateVectorStore(model.getKeyId()); + } + } From 8e56b81a3a73de6e6d2d4d2e8da9b1285ef69df8 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 7 Sep 2024 20:12:37 +0800 Subject: [PATCH 05/10] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91AI=20=E5=A4=A7=E6=A8=A1=E5=9E=8B=EF=BC=9A?= =?UTF-8?q?=E7=9F=A5=E8=AF=86=E5=BA=93=E7=9A=84=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vo/knowledge/AiKnowledgeCreateMyReqVO.java | 1 + .../AiKnowledgeDocumentCreateReqVO.java | 1 + .../dal/dataobject/knowledge/AiKnowledgeDO.java | 3 +-- .../knowledge/AiKnowledgeDocumentDO.java | 2 ++ .../knowledge/AiKnowledgeSegmentDO.java | 2 +- .../knowledge/AiKnowledgeSegmentMapper.java | 2 ++ .../AiKnowledgeDocumentServiceImpl.java | 2 +- .../knowledge/AiKnowledgeSegmentService.java | 3 +-- .../knowledge/AiKnowledgeSegmentServiceImpl.java | 16 ++++++++-------- .../ai/service/knowledge/AiKnowledgeService.java | 4 ++-- .../knowledge/AiKnowledgeServiceImpl.java | 2 ++ .../ai/service/model/AiApiKeyServiceImpl.java | 1 + .../yudao-spring-boot-starter-ai/pom.xml | 2 +- .../ai/core/factory/AiModelFactoryImpl.java | 1 + 14 files changed, 25 insertions(+), 17 deletions(-) diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeCreateMyReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeCreateMyReqVO.java index 58a89caee..a1e23ca64 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeCreateMyReqVO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeCreateMyReqVO.java @@ -32,4 +32,5 @@ public class AiKnowledgeCreateMyReqVO { @Schema(description = "topK", requiredMode = Schema.RequiredMode.REQUIRED, example = "3") @NotNull(message = "topK 不能为空") private Integer topK; + } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeDocumentCreateReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeDocumentCreateReqVO.java index 651bdc0f7..d393fb672 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeDocumentCreateReqVO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeDocumentCreateReqVO.java @@ -42,4 +42,5 @@ public class AiKnowledgeDocumentCreateReqVO { @Schema(description = "分块是否保留分隔符", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") @NotNull(message = "分块是否保留分隔符不能为空") private Boolean keepSeparator; + } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java index 5db631dd4..5d7556235 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java @@ -57,17 +57,16 @@ public class AiKnowledgeDO extends BaseDO { * topK */ private Integer topK; - /** * 相似度阈值 */ private Double similarityThreshold; - /** * 状态 *

* 枚举 {@link CommonStatusEnum} */ private Integer status; + } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDocumentDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDocumentDO.java index 18fa46c3a..8b82a55db 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDocumentDO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDocumentDO.java @@ -47,10 +47,12 @@ public class AiKnowledgeDocumentDO extends BaseDO { * 字符数 */ private Integer wordCount; + // TODO @新:chunk 1)是不是 segment,这样命名保持一致会好点哈?2)Size 是不是改成 Tokens 会统一点;3)defaultChunkSize、defaultChunkSize、minChunkSizeChars、maxNumChunks 这几个字段的命名,可能要微信一起讨论下。尽量命名保持风格统一哈。 /** * 每个文本块的目标 token 数 */ private Integer defaultChunkSize; + // TODO @xin:SizeChars 和 wordCount 好像是一个意思,是不是也要统一哈。 /** * 每个文本块的最小字符数 */ diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeSegmentDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeSegmentDO.java index be57265e1..70d85dc60 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeSegmentDO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeSegmentDO.java @@ -27,7 +27,7 @@ public class AiKnowledgeSegmentDO extends BaseDO { /** * 向量库的编号 */ - @TableField(updateStrategy = FieldStrategy.ALWAYS) + @TableField(updateStrategy = FieldStrategy.ALWAYS) // TODO @新:尽量规避要这个注解。万一后面加个 status 单独更新,可能会踩坑。 private String vectorId; /** * 知识库编号 diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeSegmentMapper.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeSegmentMapper.java index ea238b826..bda05989b 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeSegmentMapper.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeSegmentMapper.java @@ -25,9 +25,11 @@ public interface AiKnowledgeSegmentMapper extends BaseMapperX selectList(List vectorIdList) { return selectList(new LambdaQueryWrapperX() .in(AiKnowledgeSegmentDO::getVectorId, vectorIdList) .orderByDesc(AiKnowledgeSegmentDO::getId)); } + } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentServiceImpl.java index 05a9dce22..807509d2b 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentServiceImpl.java @@ -83,7 +83,7 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic .setStatus(CommonStatusEnum.ENABLE.getStatus())); segmentMapper.insertBatch(segmentDOList); - // 3.2 向量化并存储 + // 3. 向量化并存储 segments.forEach(segment -> segment.getMetadata().put(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, createReqVO.getKnowledgeId())); vectorStore.add(segments); return documentId; diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentService.java index 49ed67135..91bffc276 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentService.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentService.java @@ -38,9 +38,8 @@ public interface AiKnowledgeSegmentService { */ void updateKnowledgeSegmentStatus(AiKnowledgeSegmentUpdateStatusReqVO reqVO); - /** - * 段落召回 + * 召回段落 * * @param reqVO 召回请求信息 * @return 召回的段落 diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java index 1813d0b48..e1386c936 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java @@ -55,19 +55,19 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService @Override public void updateKnowledgeSegment(AiKnowledgeSegmentUpdateReqVO reqVO) { - // 0 校验 + // 1. 校验 AiKnowledgeSegmentDO oldKnowledgeSegment = validateKnowledgeSegmentExists(reqVO.getId()); + // 2.1 获取知识库向量实例 VectorStore vectorStore = knowledgeService.getVectorStoreById(oldKnowledgeSegment.getKnowledgeId()); // 2.2 删除原向量 vectorStore.delete(List.of(oldKnowledgeSegment.getVectorId())); - // 2.3 重新向量化 Document document = new Document(reqVO.getContent()); document.getMetadata().put(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, oldKnowledgeSegment.getKnowledgeId()); vectorStore.add(List.of(document)); - // 2.1 更新段落内容 + // 3. 更新段落内容 AiKnowledgeSegmentDO knowledgeSegment = BeanUtils.toBean(reqVO, AiKnowledgeSegmentDO.class); knowledgeSegment.setVectorId(document.getId()); segmentMapper.updateById(knowledgeSegment); @@ -98,14 +98,14 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService @Override public List similaritySearch(AiKnowledgeSegmentSearchReqVO reqVO) { - // 0. 校验 + // 1. 校验 AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(reqVO.getKnowledgeId()); AiChatModelDO model = chatModelService.validateChatModel(knowledge.getModelId()); - // 1.1 获取向量存储实例 + // 2. 获取向量存储实例 VectorStore vectorStore = apiKeyService.getOrCreateVectorStore(model.getKeyId()); - // 1.2 向量检索 + // 3.1 向量检索 List documentList = vectorStore.similaritySearch(SearchRequest.query(reqVO.getContent()) .withTopK(knowledge.getTopK()) .withSimilarityThreshold(knowledge.getSimilarityThreshold()) @@ -113,11 +113,10 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService if (CollUtil.isEmpty(documentList)) { return ListUtil.empty(); } - // 2.1 段落召回 + // 3.2 段落召回 return segmentMapper.selectList(CollUtil.getFieldValues(documentList, "id", String.class)); } - /** * 校验段落是否存在 * @@ -131,4 +130,5 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService } return knowledgeSegment; } + } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeService.java index d9770f452..8cf07d91a 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeService.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeService.java @@ -23,7 +23,6 @@ public interface AiKnowledgeService { */ Long createKnowledgeMy(AiKnowledgeCreateMyReqVO createReqVO, Long userId); - /** * 创建【我的】知识库 * @@ -32,7 +31,6 @@ public interface AiKnowledgeService { */ void updateKnowledgeMy(AiKnowledgeUpdateMyReqVO updateReqVO, Long userId); - /** * 校验知识库是否存在 * @@ -49,6 +47,7 @@ public interface AiKnowledgeService { */ PageResult getKnowledgePageMy(Long userId, PageParam pageReqVO); + // TODO @新:knowledgeId 和 validateKnowledgeExists 的 id 是同一个么?如果是的话,建议变量也用 id 哈,然后两边的 id 注释,保持一致 /** * 根据知识库编号获取向量存储实例 * @@ -56,4 +55,5 @@ public interface AiKnowledgeService { * @return 向量存储实例 */ VectorStore getVectorStoreById(Long knowledgeId); + } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java index 7a145d734..7b0c37b5f 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java @@ -38,6 +38,7 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService { private AiChatModelService chatModelService; @Resource private AiApiKeyService apiKeyService; + // TODO @新:chatModelService 和 apiKeyService 可以放到 33 行的 chatModalService 后面。尽量保持,想通类型的变量在一块。例如说,Service 一块,Mapper 一块。 @Override public Long createKnowledgeMy(AiKnowledgeCreateMyReqVO createReqVO, Long userId) { @@ -85,6 +86,7 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService { public VectorStore getVectorStoreById(Long knowledgeId) { AiKnowledgeDO knowledge = validateKnowledgeExists(knowledgeId); AiChatModelDO model = chatModelService.validateChatModel(knowledge.getModelId()); + // 创建或获取 VectorStore 对象 return apiKeyService.getOrCreateVectorStore(model.getKeyId()); } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyServiceImpl.java index b2807905a..50e1fbd7a 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyServiceImpl.java @@ -146,6 +146,7 @@ public class AiApiKeyServiceImpl implements AiApiKeyService { public VectorStore getOrCreateVectorStore(Long id) { AiApiKeyDO apiKey = validateApiKey(id); AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform()); + // 创建或获取 VectorStore 对象 return modelFactory.getOrCreateVectorStore(getEmbeddingModel(id), platform, apiKey.getApiKey(), apiKey.getUrl()); } diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml b/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml index 27c762a72..7de6534d7 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml @@ -47,7 +47,7 @@ - + diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java index 7acd24769..a7f7aa2f6 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java @@ -197,6 +197,7 @@ public class AiModelFactoryImpl implements AiModelFactory { }); } + // TODO @新:貌似可以创建一个大的 VectorStore。然后搜的时候,通过 Filter.Expression 过滤对应的数据。 @Override public VectorStore getOrCreateVectorStore(EmbeddingModel embeddingModel, AiPlatformEnum platform, String apiKey, String url) { String cacheKey = buildClientCacheKey(VectorStore.class, platform, apiKey, url); From 5cd870748df348ef43b99b19de358d10aaac3b3c Mon Sep 17 00:00:00 2001 From: xiaoxin <718949661@qq.com> Date: Sun, 22 Sep 2024 15:20:55 +0800 Subject: [PATCH 06/10] =?UTF-8?q?=E3=80=90=E8=A7=A3=E5=86=B3todo=E3=80=91A?= =?UTF-8?q?I=20=E7=9F=A5=E8=AF=86=E5=BA=93:=20=E5=AD=97=E6=AE=B5=E5=91=BD?= =?UTF-8?q?=E5=90=8D=E7=BB=9F=E4=B8=80=20=E8=A1=A5=E5=85=85=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AiKnowledgeSegmentController.java | 2 +- .../AiKnowledgeDocumentCreateReqVO.java | 20 +++++++++---------- .../dataobject/knowledge/AiKnowledgeDO.java | 6 ++++-- .../knowledge/AiKnowledgeDocumentDO.java | 18 ++++++++++------- .../knowledge/AiKnowledgeSegmentDO.java | 3 --- .../knowledge/AiKnowledgeSegmentMapper.java | 3 +-- .../AiKnowledgeDocumentServiceImpl.java | 4 ++-- .../AiKnowledgeSegmentServiceImpl.java | 4 ++-- .../service/knowledge/AiKnowledgeService.java | 5 ++--- .../knowledge/AiKnowledgeServiceImpl.java | 13 +++++------- .../ai/core/factory/AiModelFactoryImpl.java | 1 - 11 files changed, 38 insertions(+), 41 deletions(-) diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeSegmentController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeSegmentController.java index a0d0952a8..d4ca7ca49 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeSegmentController.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeSegmentController.java @@ -29,7 +29,7 @@ public class AiKnowledgeSegmentController { @GetMapping("/page") @Operation(summary = "获取段落分页") - public CommonResult> getKnowledgeSegmentPageMy(@Valid AiKnowledgeSegmentPageReqVO pageReqVO) { + public CommonResult> getKnowledgeSegmentPage(@Valid AiKnowledgeSegmentPageReqVO pageReqVO) { PageResult pageResult = segmentService.getKnowledgeSegmentPage(pageReqVO); return success(BeanUtils.toBean(pageResult, AiKnowledgeSegmentRespVO.class)); } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeDocumentCreateReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeDocumentCreateReqVO.java index d393fb672..df6b6821d 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeDocumentCreateReqVO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeDocumentCreateReqVO.java @@ -23,21 +23,21 @@ public class AiKnowledgeDocumentCreateReqVO { @URL(message = "文档 URL 格式不正确") private String url; - @Schema(description = "每个文本块的目标 token 数", requiredMode = Schema.RequiredMode.REQUIRED, example = "800") - @NotNull(message = "每个文本块的目标 token 数不能为空") - private Integer defaultChunkSize; + @Schema(description = "每个段落的目标 token 数", requiredMode = Schema.RequiredMode.REQUIRED, example = "800") + @NotNull(message = "每个段落的目标 token 数不能为空") + private Integer defaultSegmentTokens; - @Schema(description = "每个文本块的最小字符数", requiredMode = Schema.RequiredMode.REQUIRED, example = "350") - @NotNull(message = "每个文本块的最小字符数不能为空") - private Integer minChunkSizeChars; + @Schema(description = "每个段落的最小字符数", requiredMode = Schema.RequiredMode.REQUIRED, example = "350") + @NotNull(message = "每个段落的最小字符数不能为空") + private Integer minSegmentWordCount; - @Schema(description = "丢弃阈值", requiredMode = Schema.RequiredMode.REQUIRED, example = "5") + @Schema(description = "丢弃阈值:低于此阈值的段落会被丢弃", requiredMode = Schema.RequiredMode.REQUIRED, example = "5") @NotNull(message = "丢弃阈值不能为空") private Integer minChunkLengthToEmbed; - @Schema(description = "最大块数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000") - @NotNull(message = "最大块数不能为空") - private Integer maxNumChunks; + @Schema(description = "最大段落数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000") + @NotNull(message = "最大段落数不能为空") + private Integer maxNumSegments; @Schema(description = "分块是否保留分隔符", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") @NotNull(message = "分块是否保留分隔符不能为空") diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java index 5d7556235..1551b8ac8 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java @@ -38,9 +38,11 @@ public class AiKnowledgeDO extends BaseDO { * 知识库描述 */ private String description; - // TODO @新:如果全部可见,需要怎么设置? + /** - * 可见权限,只能选择哪些人可见 + * 可见权限,选择哪些人可见 + *

+ * -1 所有人可见,其他为各自用户编号 */ @TableField(typeHandler = JacksonTypeHandler.class) private List visibilityPermissions; diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDocumentDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDocumentDO.java index 8b82a55db..297944611 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDocumentDO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDocumentDO.java @@ -40,23 +40,25 @@ public class AiKnowledgeDocumentDO extends BaseDO { */ private String url; /** - * token 数量 + * 文档 token 数量 */ private Integer tokens; /** - * 字符数 + * 文档字符数 */ private Integer wordCount; - // TODO @新:chunk 1)是不是 segment,这样命名保持一致会好点哈?2)Size 是不是改成 Tokens 会统一点;3)defaultChunkSize、defaultChunkSize、minChunkSizeChars、maxNumChunks 这几个字段的命名,可能要微信一起讨论下。尽量命名保持风格统一哈。 + + + // ========== 自定义分段所用参数 ========== + // TODO @新:3)defaultChunkSize、defaultChunkSize、minChunkSizeChars、maxNumChunks 这几个字段的命名,可能要微信一起讨论下。尽量命名保持风格统一哈。 /** * 每个文本块的目标 token 数 */ - private Integer defaultChunkSize; - // TODO @xin:SizeChars 和 wordCount 好像是一个意思,是不是也要统一哈。 + private Integer defaultSegmentTokens; /** * 每个文本块的最小字符数 */ - private Integer minChunkSizeChars; + private Integer minSegmentWordCount; /** * 低于此值的块会被丢弃 */ @@ -64,11 +66,13 @@ public class AiKnowledgeDocumentDO extends BaseDO { /** * 最大块数 */ - private Integer maxNumChunks; + private Integer maxNumSegments; /** * 分块是否保留分隔符 */ private Boolean keepSeparator; + // =================================== + /** * 切片状态 *

diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeSegmentDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeSegmentDO.java index 70d85dc60..9bb3d3338 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeSegmentDO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeSegmentDO.java @@ -2,8 +2,6 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.knowledge; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import com.baomidou.mybatisplus.annotation.FieldStrategy; -import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; @@ -27,7 +25,6 @@ public class AiKnowledgeSegmentDO extends BaseDO { /** * 向量库的编号 */ - @TableField(updateStrategy = FieldStrategy.ALWAYS) // TODO @新:尽量规避要这个注解。万一后面加个 status 单独更新,可能会踩坑。 private String vectorId; /** * 知识库编号 diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeSegmentMapper.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeSegmentMapper.java index bda05989b..094f19b52 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeSegmentMapper.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeSegmentMapper.java @@ -25,8 +25,7 @@ public interface AiKnowledgeSegmentMapper extends BaseMapperX selectList(List vectorIdList) { + default List selectListByVectorIds(List vectorIdList) { return selectList(new LambdaQueryWrapperX() .in(AiKnowledgeSegmentDO::getVectorId, vectorIdList) .orderByDesc(AiKnowledgeSegmentDO::getId)); diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentServiceImpl.java index 807509d2b..ff475f92c 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentServiceImpl.java @@ -71,8 +71,8 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic } // 2 构造文本分段器 - TokenTextSplitter tokenTextSplitter = new TokenTextSplitter(createReqVO.getDefaultChunkSize(), createReqVO.getMinChunkSizeChars(), createReqVO.getMinChunkLengthToEmbed(), - createReqVO.getMaxNumChunks(), createReqVO.getKeepSeparator()); + TokenTextSplitter tokenTextSplitter = new TokenTextSplitter(createReqVO.getDefaultSegmentTokens(), createReqVO.getMinSegmentWordCount(), createReqVO.getMinChunkLengthToEmbed(), + createReqVO.getMaxNumSegments(), createReqVO.getKeepSeparator()); // 2.1 文档分段 List segments = tokenTextSplitter.apply(documents); // 2.2 分段内容入库 diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java index e1386c936..5523fe278 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java @@ -90,7 +90,7 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService } else { // 2.2 禁用删除向量 vectorStore.delete(List.of(oldKnowledgeSegment.getVectorId())); - knowledgeSegment.setVectorId(null); + knowledgeSegment.setVectorId(""); } // 3 更新段落状态 segmentMapper.updateById(knowledgeSegment); @@ -114,7 +114,7 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService return ListUtil.empty(); } // 3.2 段落召回 - return segmentMapper.selectList(CollUtil.getFieldValues(documentList, "id", String.class)); + return segmentMapper.selectListByVectorIds(CollUtil.getFieldValues(documentList, "id", String.class)); } /** diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeService.java index 8cf07d91a..6ff878d19 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeService.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeService.java @@ -47,13 +47,12 @@ public interface AiKnowledgeService { */ PageResult getKnowledgePageMy(Long userId, PageParam pageReqVO); - // TODO @新:knowledgeId 和 validateKnowledgeExists 的 id 是同一个么?如果是的话,建议变量也用 id 哈,然后两边的 id 注释,保持一致 /** * 根据知识库编号获取向量存储实例 * - * @param knowledgeId 知识库编号 + * @param id 知识库编号 * @return 向量存储实例 */ - VectorStore getVectorStoreById(Long knowledgeId); + VectorStore getVectorStoreById(Long id); } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java index 7b0c37b5f..9269582ac 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java @@ -29,21 +29,18 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_NOT_ @Slf4j public class AiKnowledgeServiceImpl implements AiKnowledgeService { - @Resource - private AiChatModelService chatModalService; - @Resource private AiKnowledgeMapper knowledgeMapper; + @Resource private AiChatModelService chatModelService; @Resource private AiApiKeyService apiKeyService; - // TODO @新:chatModelService 和 apiKeyService 可以放到 33 行的 chatModalService 后面。尽量保持,想通类型的变量在一块。例如说,Service 一块,Mapper 一块。 @Override public Long createKnowledgeMy(AiKnowledgeCreateMyReqVO createReqVO, Long userId) { // 1. 校验模型配置 - AiChatModelDO model = chatModalService.validateChatModel(createReqVO.getModelId()); + AiChatModelDO model = chatModelService.validateChatModel(createReqVO.getModelId()); // 2. 插入知识库 AiKnowledgeDO knowledgeBase = BeanUtils.toBean(createReqVO, AiKnowledgeDO.class) @@ -60,7 +57,7 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService { throw exception(KNOWLEDGE_NOT_EXISTS); } // 1.2 校验模型配置 - AiChatModelDO model = chatModalService.validateChatModel(updateReqVO.getModelId()); + AiChatModelDO model = chatModelService.validateChatModel(updateReqVO.getModelId()); // 2. 更新知识库 AiKnowledgeDO updateDO = BeanUtils.toBean(updateReqVO, AiKnowledgeDO.class); @@ -83,8 +80,8 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService { } @Override - public VectorStore getVectorStoreById(Long knowledgeId) { - AiKnowledgeDO knowledge = validateKnowledgeExists(knowledgeId); + public VectorStore getVectorStoreById(Long id) { + AiKnowledgeDO knowledge = validateKnowledgeExists(id); AiChatModelDO model = chatModelService.validateChatModel(knowledge.getModelId()); // 创建或获取 VectorStore 对象 return apiKeyService.getOrCreateVectorStore(model.getKeyId()); diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java index a7f7aa2f6..7acd24769 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java @@ -197,7 +197,6 @@ public class AiModelFactoryImpl implements AiModelFactory { }); } - // TODO @新:貌似可以创建一个大的 VectorStore。然后搜的时候,通过 Filter.Expression 过滤对应的数据。 @Override public VectorStore getOrCreateVectorStore(EmbeddingModel embeddingModel, AiPlatformEnum platform, String apiKey, String url) { String cacheKey = buildClientCacheKey(VectorStore.class, platform, apiKey, url); From 0700c3f15eb38e4f6b39d07e7632a75641def31a Mon Sep 17 00:00:00 2001 From: xiaoxin <718949661@qq.com> Date: Sun, 22 Sep 2024 18:13:21 +0800 Subject: [PATCH 07/10] =?UTF-8?q?=E3=80=90=E6=96=B0=E5=A2=9E=E3=80=91AI?= =?UTF-8?q?=EF=BC=9A=E8=81=8A=E5=A4=A9=E6=8E=A5=E5=85=A5=E7=9F=A5=E8=AF=86?= =?UTF-8?q?=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/module/ai/enums/AiChatRoleEnum.java | 7 +++- .../AiChatConversationCreateMyReqVO.java | 3 ++ .../AiChatConversationUpdateMyReqVO.java | 3 ++ .../dataobject/chat/AiChatConversationDO.java | 8 ++++ .../chat/AiChatConversationServiceImpl.java | 16 +++++++- .../chat/AiChatMessageServiceImpl.java | 39 +++++++++++++++---- 6 files changed, 66 insertions(+), 10 deletions(-) diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiChatRoleEnum.java b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiChatRoleEnum.java index 029961bf3..6cb98c562 100644 --- a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiChatRoleEnum.java +++ b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiChatRoleEnum.java @@ -34,7 +34,12 @@ public enum AiChatRoleEnum { ### 支付宝 ### 微信 除此之外不要任何解释性语句。 - """); + """), + + AI_KNOWLEDGE_ROLE("知识库助手", """ + 给你提供一些数据参考:{info},请回答我的问题。 + 请你跟进数据参考与工具返回结果回复用户的请求。 + """); /** * 角色名 diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationCreateMyReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationCreateMyReqVO.java index c13200b6a..84595bea2 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationCreateMyReqVO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationCreateMyReqVO.java @@ -10,4 +10,7 @@ public class AiChatConversationCreateMyReqVO { @Schema(description = "聊天角色编号", example = "666") private Long roleId; + @Schema(description = "知识库编号", example = "1204") + private Long knowledgeId; + } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationUpdateMyReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationUpdateMyReqVO.java index f9ce64bae..2b57572c4 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationUpdateMyReqVO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationUpdateMyReqVO.java @@ -21,6 +21,9 @@ public class AiChatConversationUpdateMyReqVO { @Schema(description = "模型编号", example = "1") private Long modelId; + @Schema(description = "知识库编号", example = "1") + private Long knowledgeId; + @Schema(description = "角色设定", example = "一个快乐的程序员") private String systemMessage; diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatConversationDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatConversationDO.java index 0b7eb0233..7d9625f58 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatConversationDO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatConversationDO.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.chat; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; import com.baomidou.mybatisplus.annotation.KeySequence; @@ -64,6 +65,13 @@ public class AiChatConversationDO extends BaseDO { */ private Long roleId; + /** + * 知识库编号 + *

+ * 关联 {@link AiKnowledgeDO#getId()} + */ + private Long knowledgeId; + /** * 模型编号 * diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationServiceImpl.java index 83dcd8dff..8f094087f 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationServiceImpl.java @@ -13,6 +13,7 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatConversationMapper; +import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeService; import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService; import jakarta.annotation.Resource; @@ -22,6 +23,7 @@ import org.springframework.validation.annotation.Validated; import java.time.LocalDateTime; import java.util.List; +import java.util.Objects; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; @@ -45,6 +47,8 @@ public class AiChatConversationServiceImpl implements AiChatConversationService private AiChatModelService chatModalService; @Resource private AiChatRoleService chatRoleService; + @Resource + private AiKnowledgeService knowledgeService; @Override public Long createChatConversationMy(AiChatConversationCreateMyReqVO createReqVO, Long userId) { @@ -56,9 +60,14 @@ public class AiChatConversationServiceImpl implements AiChatConversationService Assert.notNull(model, "必须找到默认模型"); validateChatModel(model); + // 1.3 校验知识库 + if (Objects.nonNull(createReqVO.getKnowledgeId())) { + knowledgeService.validateKnowledgeExists(createReqVO.getKnowledgeId()); + } + // 2. 创建 AiChatConversationDO 聊天对话 AiChatConversationDO conversation = new AiChatConversationDO().setUserId(userId).setPinned(false) - .setModelId(model.getId()).setModel(model.getModel()) + .setModelId(model.getId()).setModel(model.getModel()).setKnowledgeId(createReqVO.getKnowledgeId()) .setTemperature(model.getTemperature()).setMaxTokens(model.getMaxTokens()).setMaxContexts(model.getMaxContexts()); if (role != null) { conversation.setTitle(role.getName()).setRoleId(role.getId()).setSystemMessage(role.getSystemMessage()); @@ -82,6 +91,11 @@ public class AiChatConversationServiceImpl implements AiChatConversationService model = chatModalService.validateChatModel(updateReqVO.getModelId()); } + // 1.3 校验知识库是否存在 + if (updateReqVO.getKnowledgeId() != null) { + knowledgeService.validateKnowledgeExists(updateReqVO.getKnowledgeId()); + } + // 2. 更新对话信息 AiChatConversationDO updateObj = BeanUtils.toBean(updateReqVO, AiChatConversationDO.class); if (Boolean.TRUE.equals(updateReqVO.getPinned())) { diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java index 72fa06a79..4ef5af8ee 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java @@ -12,21 +12,29 @@ import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO; import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO; import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO; +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentSearchReqVO; import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper; +import cn.iocoder.yudao.module.ai.enums.AiChatRoleEnum; import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants; +import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService; import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; -import org.springframework.ai.chat.messages.*; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.MessageType; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.StreamingChatModel; import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.chat.prompt.PromptTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import reactor.core.publisher.Flux; @@ -59,6 +67,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { private AiChatModelService chatModalService; @Resource private AiApiKeyService apiKeyService; + @Resource + private AiKnowledgeSegmentService knowledgeSegmentService; @Transactional(rollbackFor = Exception.class) public AiChatMessageSendRespVO sendMessage(AiChatMessageSendReqVO sendReqVO, Long userId) { @@ -141,14 +151,27 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { AiChatModelDO model, AiChatMessageSendReqVO sendReqVO) { // 1. 构建 Prompt Message 列表 List chatMessages = new ArrayList<>(); - // 1.1 system context 角色设定 + + // 1.1 知识库召回 + if (Objects.nonNull(conversation.getKnowledgeId())) { + List segmentList = knowledgeSegmentService.similaritySearch(new AiKnowledgeSegmentSearchReqVO().setKnowledgeId(conversation.getKnowledgeId()).setContent(sendReqVO.getContent())); + if (CollUtil.isNotEmpty(segmentList)) { + PromptTemplate promptTemplate = new PromptTemplate(AiChatRoleEnum.AI_KNOWLEDGE_ROLE.getSystemMessage()); + StringBuilder infoBuilder = StrUtil.builder(); + segmentList.forEach(segment -> infoBuilder.append(System.lineSeparator()).append(segment.getContent())); + Message message = promptTemplate.createMessage(Map.of("info", infoBuilder.toString())); + chatMessages.add(message); + } + } + + // 1.2 system context 角色设定 if (StrUtil.isNotBlank(conversation.getSystemMessage())) { chatMessages.add(new SystemMessage(conversation.getSystemMessage())); } - // 1.2 history message 历史消息 + // 1.3 history message 历史消息 List contextMessages = filterContextMessages(messages, conversation, sendReqVO); contextMessages.forEach(message -> chatMessages.add(AiUtils.buildMessage(message.getType(), message.getContent()))); - // 1.3 user message 新发送消息 + // 1.4 user message 新发送消息 chatMessages.add(new UserMessage(sendReqVO.getContent())); // 2. 构建 ChatOptions 对象 @@ -160,12 +183,12 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { /** * 从历史消息中,获得倒序的 n 组消息作为消息上下文 - * + *

* n 组:指的是 user + assistant 形成一组 * - * @param messages 消息列表 + * @param messages 消息列表 * @param conversation 对话 - * @param sendReqVO 发送请求 + * @param sendReqVO 发送请求 * @return 消息上下文 */ private List filterContextMessages(List messages, @@ -182,7 +205,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { } AiChatMessageDO userMessage = CollUtil.get(messages, i - 1); if (userMessage == null || ObjUtil.notEqual(assistantMessage.getReplyId(), userMessage.getId()) - || StrUtil.isEmpty(assistantMessage.getContent())) { + || StrUtil.isEmpty(assistantMessage.getContent())) { continue; } // 由于后续要 reverse 反转,所以先添加 assistantMessage From 6b651baeed316f9963d420b1eb72161b809e1a1c Mon Sep 17 00:00:00 2001 From: xiaoxin <718949661@qq.com> Date: Wed, 25 Sep 2024 16:06:35 +0800 Subject: [PATCH 08/10] =?UTF-8?q?=E3=80=90=E6=96=B0=E5=A2=9E=E3=80=91AI?= =?UTF-8?q?=EF=BC=9A=E7=9F=A5=E8=AF=86=E5=BA=93=E5=8F=AF=E8=A7=81=E6=9D=83?= =?UTF-8?q?=E9=99=90=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../knowledge/AiKnowledgeController.java | 31 +++++++++---------- .../AiKnowledgeDocumentController.java | 2 +- ...ReqVO.java => AiKnowledgeCreateReqVO.java} | 10 +++--- .../vo/knowledge/AiKnowledgePageReqVO.java | 14 +++++++++ ...ReqVO.java => AiKnowledgeUpdateReqVO.java} | 8 ++--- .../dataobject/knowledge/AiKnowledgeDO.java | 7 +---- .../mysql/knowledge/AiKnowledgeMapper.java | 7 +++-- .../service/knowledge/AiKnowledgeService.java | 22 ++++++------- .../knowledge/AiKnowledgeServiceImpl.java | 14 ++++----- 9 files changed, 60 insertions(+), 55 deletions(-) rename yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/{AiKnowledgeCreateMyReqVO.java => AiKnowledgeCreateReqVO.java} (86%) create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgePageReqVO.java rename yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/{AiKnowledgeUpdateMyReqVO.java => AiKnowledgeUpdateReqVO.java} (90%) diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeController.java index dc2c8e3ae..3ffea5e80 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeController.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeController.java @@ -1,12 +1,12 @@ package cn.iocoder.yudao.module.ai.controller.admin.knowledge; import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeCreateMyReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeCreateReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO; import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeRespVO; -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateMyReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateReqVO; import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeService; import io.swagger.v3.oas.annotations.Operation; @@ -28,24 +28,23 @@ public class AiKnowledgeController { @Resource private AiKnowledgeService knowledgeService; - @GetMapping("/my-page") - @Operation(summary = "获取【我的】知识库分页") - public CommonResult> getKnowledgePageMy(@Validated PageParam pageReqVO) { - PageResult pageResult = knowledgeService.getKnowledgePageMy(getLoginUserId(), pageReqVO); + @GetMapping("/page") + @Operation(summary = "获取知识库分页") + public CommonResult> getKnowledgePage(@Valid AiKnowledgePageReqVO pageReqVO) { + PageResult pageResult = knowledgeService.getKnowledgePage(getLoginUserId(), pageReqVO); return success(BeanUtils.toBean(pageResult, AiKnowledgeRespVO.class)); } - @PostMapping("/create-my") - @Operation(summary = "创建【我的】知识库") - public CommonResult createKnowledgeMy(@RequestBody @Valid AiKnowledgeCreateMyReqVO createReqVO) { - return success(knowledgeService.createKnowledgeMy(createReqVO, getLoginUserId())); + @PostMapping("/create") + @Operation(summary = "创建知识库") + public CommonResult createKnowledge(@RequestBody @Valid AiKnowledgeCreateReqVO createReqVO) { + return success(knowledgeService.createKnowledge(createReqVO, getLoginUserId())); } - @PutMapping("/update-my") - @Operation(summary = "更新【我的】知识库") - public CommonResult updateKnowledgeMy(@RequestBody @Valid AiKnowledgeUpdateMyReqVO updateReqVO) { - knowledgeService.updateKnowledgeMy(updateReqVO, getLoginUserId()); + @PutMapping("/update") + @Operation(summary = "更新知识库") + public CommonResult updateKnowledge(@RequestBody @Valid AiKnowledgeUpdateReqVO updateReqVO) { + knowledgeService.updateKnowledge(updateReqVO, getLoginUserId()); return success(true); } - } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeDocumentController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeDocumentController.java index d86210556..75c4d805b 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeDocumentController.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeDocumentController.java @@ -36,7 +36,7 @@ public class AiKnowledgeDocumentController { @GetMapping("/page") @Operation(summary = "获取文档分页") - public CommonResult> getKnowledgeDocumentPageMy(@Valid AiKnowledgeDocumentPageReqVO pageReqVO) { + public CommonResult> getKnowledgeDocumentPage(@Valid AiKnowledgeDocumentPageReqVO pageReqVO) { PageResult pageResult = documentService.getKnowledgeDocumentPage(pageReqVO); return success(BeanUtils.toBean(pageResult, AiKnowledgeDocumentRespVO.class)); } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeCreateMyReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeCreateReqVO.java similarity index 86% rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeCreateMyReqVO.java rename to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeCreateReqVO.java index a1e23ca64..bae20d936 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeCreateMyReqVO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeCreateReqVO.java @@ -5,11 +5,9 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Data; -import java.util.List; - -@Schema(description = "管理后台 - AI 知识库创建【我的】 Request VO") +@Schema(description = "管理后台 - AI 知识库创建 Request VO") @Data -public class AiKnowledgeCreateMyReqVO { +public class AiKnowledgeCreateReqVO { @Schema(description = "知识库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "ruoyi-vue-pro 用户指南") @NotBlank(message = "知识库名称不能为空") @@ -18,8 +16,8 @@ public class AiKnowledgeCreateMyReqVO { @Schema(description = "知识库描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "存储 ruoyi-vue-pro 操作文档") private String description; - @Schema(description = "可见权限,只能选择哪些人可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1]") - private List visibilityPermissions; + @Schema(description = "可见权限,只能选择哪些人可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3") + private String visibilityPermissions; @Schema(description = "嵌入模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @NotNull(message = "嵌入模型不能为空") diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgePageReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgePageReqVO.java new file mode 100644 index 000000000..941732f1a --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgePageReqVO.java @@ -0,0 +1,14 @@ +package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - AI 知识库的分页 Request VO") +@Data +public class AiKnowledgePageReqVO extends PageParam { + + @Schema(description = "知识库名称", example = "Java 开发手册") + private String name; + +} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeUpdateMyReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeUpdateReqVO.java similarity index 90% rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeUpdateMyReqVO.java rename to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeUpdateReqVO.java index 987c9bf4a..91925f53a 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeUpdateMyReqVO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeUpdateReqVO.java @@ -5,11 +5,9 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Data; -import java.util.List; - @Schema(description = "管理后台 - AI 知识库更新【我的】 Request VO") @Data -public class AiKnowledgeUpdateMyReqVO { +public class AiKnowledgeUpdateReqVO { @Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1204") @NotNull(message = "知识库编号不能为空") @@ -22,8 +20,8 @@ public class AiKnowledgeUpdateMyReqVO { @Schema(description = "知识库描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "") private String description; - @Schema(description = "可见权限,只能选择哪些人可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1]") - private List visibilityPermissions; + @Schema(description = "可见权限,只能选择哪些人可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3") + private String visibilityPermissions; @Schema(description = "嵌入模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @NotNull(message = "嵌入模型不能为空") diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java index 1551b8ac8..5ad2dd05c 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java @@ -2,14 +2,10 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.knowledge; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import lombok.Data; -import java.util.List; - /** * AI 知识库 DO * @@ -44,8 +40,7 @@ public class AiKnowledgeDO extends BaseDO { *

* -1 所有人可见,其他为各自用户编号 */ - @TableField(typeHandler = JacksonTypeHandler.class) - private List visibilityPermissions; + private String visibilityPermissions; /** * 嵌入模型编号 */ diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeMapper.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeMapper.java index 2bf23411a..f07a9a2af 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeMapper.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/knowledge/AiKnowledgeMapper.java @@ -1,10 +1,10 @@ package cn.iocoder.yudao.module.ai.dal.mysql.knowledge; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO; import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; import org.apache.ibatis.annotations.Mapper; @@ -16,10 +16,11 @@ import org.apache.ibatis.annotations.Mapper; @Mapper public interface AiKnowledgeMapper extends BaseMapperX { - default PageResult selectPageByMy(Long userId, PageParam pageReqVO) { + default PageResult selectPage(Long userId, AiKnowledgePageReqVO pageReqVO) { return selectPage(pageReqVO, new LambdaQueryWrapperX() - .eq(AiKnowledgeDO::getUserId, userId) .eq(AiKnowledgeDO::getStatus, CommonStatusEnum.ENABLE.getStatus()) + .likeIfPresent(AiKnowledgeDO::getName, pageReqVO.getName()) + .and(e -> e.apply("FIND_IN_SET(" + userId + ",visibility_permissions)").or(m -> m.apply("FIND_IN_SET(-1,visibility_permissions)"))) .orderByDesc(AiKnowledgeDO::getId)); } } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeService.java index 6ff878d19..7060076a4 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeService.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeService.java @@ -1,9 +1,9 @@ package cn.iocoder.yudao.module.ai.service.knowledge; -import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeCreateMyReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateMyReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeCreateReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateReqVO; import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; import org.springframework.ai.vectorstore.VectorStore; @@ -15,21 +15,21 @@ import org.springframework.ai.vectorstore.VectorStore; public interface AiKnowledgeService { /** - * 创建【我的】知识库 + * 创建知识库 * * @param createReqVO 创建信息 * @param userId 用户编号 * @return 编号 */ - Long createKnowledgeMy(AiKnowledgeCreateMyReqVO createReqVO, Long userId); + Long createKnowledge(AiKnowledgeCreateReqVO createReqVO, Long userId); /** - * 创建【我的】知识库 + * 更新知识库 * * @param updateReqVO 更新信息 * @param userId 用户编号 */ - void updateKnowledgeMy(AiKnowledgeUpdateMyReqVO updateReqVO, Long userId); + void updateKnowledge(AiKnowledgeUpdateReqVO updateReqVO, Long userId); /** * 校验知识库是否存在 @@ -39,13 +39,13 @@ public interface AiKnowledgeService { AiKnowledgeDO validateKnowledgeExists(Long id); /** - * 获得【我的】知识库分页 + * 获得知识库分页 * - * @param userId 用户编号 - * @param pageReqVO 分页查询 + * @param userId 用户编号 + * @param pageReqVO 分页查询 * @return 知识库分页 */ - PageResult getKnowledgePageMy(Long userId, PageParam pageReqVO); + PageResult getKnowledgePage(Long userId, AiKnowledgePageReqVO pageReqVO); /** * 根据知识库编号获取向量存储实例 diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java index 9269582ac..1a000c19d 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java @@ -2,11 +2,11 @@ package cn.iocoder.yudao.module.ai.service.knowledge; import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeCreateMyReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateMyReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeCreateReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateReqVO; import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeMapper; @@ -38,7 +38,7 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService { private AiApiKeyService apiKeyService; @Override - public Long createKnowledgeMy(AiKnowledgeCreateMyReqVO createReqVO, Long userId) { + public Long createKnowledge(AiKnowledgeCreateReqVO createReqVO, Long userId) { // 1. 校验模型配置 AiChatModelDO model = chatModelService.validateChatModel(createReqVO.getModelId()); @@ -50,7 +50,7 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService { } @Override - public void updateKnowledgeMy(AiKnowledgeUpdateMyReqVO updateReqVO, Long userId) { + public void updateKnowledge(AiKnowledgeUpdateReqVO updateReqVO, Long userId) { // 1.1 校验知识库存在 AiKnowledgeDO knowledgeBaseDO = validateKnowledgeExists(updateReqVO.getId()); if (ObjUtil.notEqual(knowledgeBaseDO.getUserId(), userId)) { @@ -75,8 +75,8 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService { } @Override - public PageResult getKnowledgePageMy(Long userId, PageParam pageReqVO) { - return knowledgeMapper.selectPageByMy(userId, pageReqVO); + public PageResult getKnowledgePage(Long userId, AiKnowledgePageReqVO pageReqVO) { + return knowledgeMapper.selectPage(userId, pageReqVO); } @Override From c05d7c9f9521ad2b1d1534c66423f8ce4fa9f535 Mon Sep 17 00:00:00 2001 From: xiaoxin <718949661@qq.com> Date: Thu, 26 Sep 2024 15:10:55 +0800 Subject: [PATCH 09/10] =?UTF-8?q?=E3=80=90=E6=96=B0=E5=A2=9E=E3=80=91AI?= =?UTF-8?q?=EF=BC=9A=E5=AF=B9=E8=AF=9D=E6=B6=88=E6=81=AF=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E5=8F=AC=E5=9B=9E=E6=AE=B5=E8=90=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dal/dataobject/chat/AiChatMessageDO.java | 18 ++++++- .../chat/AiChatMessageServiceImpl.java | 51 ++++++++++++------- 2 files changed, 48 insertions(+), 21 deletions(-) diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java index 973c593ce..ecd10609f 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java @@ -1,13 +1,18 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.chat; -import com.baomidou.mybatisplus.annotation.TableId; -import org.springframework.ai.chat.messages.MessageType; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import lombok.*; +import org.springframework.ai.chat.messages.MessageType; + +import java.util.List; /** * AI Chat 消息 DO @@ -66,6 +71,15 @@ public class AiChatMessageDO extends BaseDO { */ private Long roleId; + + /** + * 段落编号数组 + * + * 关联 {@link AiKnowledgeSegmentDO#getId()} 字段 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List segmentIds; + /** * 模型标志 */ diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java index 4ef5af8ee..1247ce12d 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java @@ -90,13 +90,16 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model, userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext()); - // 3.2 创建 chat 需要的 Prompt - Prompt prompt = buildPrompt(conversation, historyMessages, model, sendReqVO); + // 3.2 召回段落 + List segmentList = recallSegment(sendReqVO.getContent(), conversation.getKnowledgeId()); + + // 3.3 创建 chat 需要的 Prompt + Prompt prompt = buildPrompt(conversation, historyMessages, segmentList, model, sendReqVO); ChatResponse chatResponse = chatModel.call(prompt); - // 3.3 段式返回 + // 3.4 段式返回 String newContent = chatResponse.getResult().getOutput().getContent(); - chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(newContent)); + chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setSegmentIds(convertList(segmentList, AiKnowledgeSegmentDO::getId)).setContent(newContent)); return new AiChatMessageSendRespVO().setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class)) .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class).setContent(newContent)); } @@ -121,11 +124,15 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model, userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext()); - // 3.2 构建 Prompt,并进行调用 - Prompt prompt = buildPrompt(conversation, historyMessages, model, sendReqVO); + + // 3.2 召回段落 + List segmentList = recallSegment(sendReqVO.getContent(), conversation.getKnowledgeId()); + + // 3.3 构建 Prompt,并进行调用 + Prompt prompt = buildPrompt(conversation, historyMessages, segmentList, model, sendReqVO); Flux streamResponse = chatModel.stream(prompt); - // 3.3 流式返回 + // 3.4 流式返回 // TODO 注意:Schedulers.immediate() 目的是,避免默认 Schedulers.parallel() 并发消费 chunk 导致 SSE 响应前端会乱序问题 StringBuffer contentBuffer = new StringBuffer(); return streamResponse.map(chunk -> { @@ -138,7 +145,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { }).doOnComplete(() -> { // 忽略租户,因为 Flux 异步无法透传租户 TenantUtils.executeIgnore(() -> - chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(contentBuffer.toString()))); + chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setSegmentIds(convertList(segmentList, AiKnowledgeSegmentDO::getId)) + .setContent(contentBuffer.toString()))); }).doOnError(throwable -> { log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable); // 忽略租户,因为 Flux 异步无法透传租户 @@ -147,21 +155,26 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.CHAT_STREAM_ERROR))); } - private Prompt buildPrompt(AiChatConversationDO conversation, List messages, + private List recallSegment(String content, Long knowledgeId) { + List segmentList = new ArrayList<>(); + if (Objects.nonNull(knowledgeId)) { + segmentList = knowledgeSegmentService.similaritySearch(new AiKnowledgeSegmentSearchReqVO().setKnowledgeId(knowledgeId).setContent(content)); + } + return segmentList; + } + + private Prompt buildPrompt(AiChatConversationDO conversation, List messages,List segmentList, AiChatModelDO model, AiChatMessageSendReqVO sendReqVO) { // 1. 构建 Prompt Message 列表 List chatMessages = new ArrayList<>(); - // 1.1 知识库召回 - if (Objects.nonNull(conversation.getKnowledgeId())) { - List segmentList = knowledgeSegmentService.similaritySearch(new AiKnowledgeSegmentSearchReqVO().setKnowledgeId(conversation.getKnowledgeId()).setContent(sendReqVO.getContent())); - if (CollUtil.isNotEmpty(segmentList)) { - PromptTemplate promptTemplate = new PromptTemplate(AiChatRoleEnum.AI_KNOWLEDGE_ROLE.getSystemMessage()); - StringBuilder infoBuilder = StrUtil.builder(); - segmentList.forEach(segment -> infoBuilder.append(System.lineSeparator()).append(segment.getContent())); - Message message = promptTemplate.createMessage(Map.of("info", infoBuilder.toString())); - chatMessages.add(message); - } + // 1.1 召回内容消息构建 + if (CollUtil.isNotEmpty(segmentList)) { + PromptTemplate promptTemplate = new PromptTemplate(AiChatRoleEnum.AI_KNOWLEDGE_ROLE.getSystemMessage()); + StringBuilder infoBuilder = StrUtil.builder(); + segmentList.forEach(segment -> infoBuilder.append(System.lineSeparator()).append(segment.getContent())); + Message message = promptTemplate.createMessage(Map.of("info", infoBuilder.toString())); + chatMessages.add(message); } // 1.2 system context 角色设定 From 1de70eeeb30868204e920ddf286c5d3357823264 Mon Sep 17 00:00:00 2001 From: xiaoxin <718949661@qq.com> Date: Sun, 29 Sep 2024 10:01:49 +0800 Subject: [PATCH 10/10] =?UTF-8?q?=E3=80=90=E4=BC=98=E5=8C=96=E3=80=91AI?= =?UTF-8?q?=E7=9F=A5=E8=AF=86=E5=BA=93=EF=BC=9AvisibilityPermissions=20?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../knowledge/vo/knowledge/AiKnowledgeCreateReqVO.java | 6 ++++-- .../knowledge/vo/knowledge/AiKnowledgeUpdateReqVO.java | 4 +++- .../module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java | 7 ++++++- .../module/ai/service/chat/AiChatMessageServiceImpl.java | 7 +++---- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeCreateReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeCreateReqVO.java index bae20d936..00843665c 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeCreateReqVO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeCreateReqVO.java @@ -5,6 +5,8 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Data; +import java.util.List; + @Schema(description = "管理后台 - AI 知识库创建 Request VO") @Data public class AiKnowledgeCreateReqVO { @@ -16,8 +18,8 @@ public class AiKnowledgeCreateReqVO { @Schema(description = "知识库描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "存储 ruoyi-vue-pro 操作文档") private String description; - @Schema(description = "可见权限,只能选择哪些人可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3") - private String visibilityPermissions; + @Schema(description = "可见权限,只能选择哪些人可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1,2,3]") + private List visibilityPermissions; @Schema(description = "嵌入模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @NotNull(message = "嵌入模型不能为空") diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeUpdateReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeUpdateReqVO.java index 91925f53a..ba98bf0c7 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeUpdateReqVO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/knowledge/AiKnowledgeUpdateReqVO.java @@ -5,6 +5,8 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Data; +import java.util.List; + @Schema(description = "管理后台 - AI 知识库更新【我的】 Request VO") @Data public class AiKnowledgeUpdateReqVO { @@ -21,7 +23,7 @@ public class AiKnowledgeUpdateReqVO { private String description; @Schema(description = "可见权限,只能选择哪些人可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3") - private String visibilityPermissions; + private List visibilityPermissions; @Schema(description = "嵌入模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @NotNull(message = "嵌入模型不能为空") diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java index 5ad2dd05c..b1706154a 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/knowledge/AiKnowledgeDO.java @@ -2,10 +2,14 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.knowledge; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; +import java.util.List; + /** * AI 知识库 DO * @@ -40,7 +44,8 @@ public class AiKnowledgeDO extends BaseDO { *

* -1 所有人可见,其他为各自用户编号 */ - private String visibilityPermissions; + @TableField(typeHandler = LongListTypeHandler.class) + private List visibilityPermissions; /** * 嵌入模型编号 */ diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java index 1247ce12d..d332fbf1a 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java @@ -156,11 +156,10 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { } private List recallSegment(String content, Long knowledgeId) { - List segmentList = new ArrayList<>(); - if (Objects.nonNull(knowledgeId)) { - segmentList = knowledgeSegmentService.similaritySearch(new AiKnowledgeSegmentSearchReqVO().setKnowledgeId(knowledgeId).setContent(content)); + if (Objects.isNull(knowledgeId)) { + return Collections.emptyList(); } - return segmentList; + return knowledgeSegmentService.similaritySearch(new AiKnowledgeSegmentSearchReqVO().setKnowledgeId(knowledgeId).setContent(content)); } private Prompt buildPrompt(AiChatConversationDO conversation, List messages,List segmentList,