Commit d1e207899a1b7e4497104cd3321f4d2fcf5c3a4c
1 parent
e5cc9d2a
【功能新增】AI:知识库,新增切片接口
Showing
7 changed files
with
85 additions
and
13 deletions
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeSegmentController.http
0 → 100644
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeSegmentController.java
| @@ -10,12 +10,16 @@ import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowle | @@ -10,12 +10,16 @@ import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowle | ||
| 10 | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; | 10 | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; |
| 11 | import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService; | 11 | import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService; |
| 12 | import io.swagger.v3.oas.annotations.Operation; | 12 | import io.swagger.v3.oas.annotations.Operation; |
| 13 | +import io.swagger.v3.oas.annotations.Parameter; | ||
| 14 | +import io.swagger.v3.oas.annotations.Parameters; | ||
| 13 | import io.swagger.v3.oas.annotations.tags.Tag; | 15 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 14 | import jakarta.annotation.Resource; | 16 | import jakarta.annotation.Resource; |
| 15 | import jakarta.validation.Valid; | 17 | import jakarta.validation.Valid; |
| 16 | import org.springframework.validation.annotation.Validated; | 18 | import org.springframework.validation.annotation.Validated; |
| 17 | import org.springframework.web.bind.annotation.*; | 19 | import org.springframework.web.bind.annotation.*; |
| 18 | 20 | ||
| 21 | +import java.util.List; | ||
| 22 | + | ||
| 19 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | 23 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 20 | 24 | ||
| 21 | // TODO @芋艿:增加权限标识 | 25 | // TODO @芋艿:增加权限标识 |
| @@ -30,7 +34,8 @@ public class AiKnowledgeSegmentController { | @@ -30,7 +34,8 @@ public class AiKnowledgeSegmentController { | ||
| 30 | 34 | ||
| 31 | @GetMapping("/page") | 35 | @GetMapping("/page") |
| 32 | @Operation(summary = "获取段落分页") | 36 | @Operation(summary = "获取段落分页") |
| 33 | - public CommonResult<PageResult<AiKnowledgeSegmentRespVO>> getKnowledgeSegmentPage(@Valid AiKnowledgeSegmentPageReqVO pageReqVO) { | 37 | + public CommonResult<PageResult<AiKnowledgeSegmentRespVO>> getKnowledgeSegmentPage( |
| 38 | + @Valid AiKnowledgeSegmentPageReqVO pageReqVO) { | ||
| 34 | PageResult<AiKnowledgeSegmentDO> pageResult = segmentService.getKnowledgeSegmentPage(pageReqVO); | 39 | PageResult<AiKnowledgeSegmentDO> pageResult = segmentService.getKnowledgeSegmentPage(pageReqVO); |
| 35 | return success(BeanUtils.toBean(pageResult, AiKnowledgeSegmentRespVO.class)); | 40 | return success(BeanUtils.toBean(pageResult, AiKnowledgeSegmentRespVO.class)); |
| 36 | } | 41 | } |
| @@ -44,9 +49,23 @@ public class AiKnowledgeSegmentController { | @@ -44,9 +49,23 @@ public class AiKnowledgeSegmentController { | ||
| 44 | 49 | ||
| 45 | @PutMapping("/update-status") | 50 | @PutMapping("/update-status") |
| 46 | @Operation(summary = "启禁用段落内容") | 51 | @Operation(summary = "启禁用段落内容") |
| 47 | - public CommonResult<Boolean> updateKnowledgeSegmentStatus(@Valid @RequestBody AiKnowledgeSegmentUpdateStatusReqVO reqVO) { | 52 | + public CommonResult<Boolean> updateKnowledgeSegmentStatus( |
| 53 | + @Valid @RequestBody AiKnowledgeSegmentUpdateStatusReqVO reqVO) { | ||
| 48 | segmentService.updateKnowledgeSegmentStatus(reqVO); | 54 | segmentService.updateKnowledgeSegmentStatus(reqVO); |
| 49 | return success(true); | 55 | return success(true); |
| 50 | } | 56 | } |
| 51 | 57 | ||
| 58 | + @GetMapping("/split") | ||
| 59 | + @Operation(summary = "切片内容") | ||
| 60 | + @Parameters({ | ||
| 61 | + @Parameter(name = "url", description = "文档 URL", required = true), | ||
| 62 | + @Parameter(name = "segmentMaxTokens", description = "分段的最大 Token 数", required = true) | ||
| 63 | + }) | ||
| 64 | + public CommonResult<List<AiKnowledgeSegmentRespVO>> splitContent( | ||
| 65 | + @RequestParam("url") String url, | ||
| 66 | + @RequestParam(value = "segmentMaxTokens") Integer segmentMaxTokens) { | ||
| 67 | + List<AiKnowledgeSegmentDO> segments = segmentService.splitContent(url, segmentMaxTokens); | ||
| 68 | + return success(BeanUtils.toBean(segments, AiKnowledgeSegmentRespVO.class)); | ||
| 69 | + } | ||
| 70 | + | ||
| 52 | } | 71 | } |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentRespVO.java
| @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment; | @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment; | ||
| 3 | import io.swagger.v3.oas.annotations.media.Schema; | 3 | import io.swagger.v3.oas.annotations.media.Schema; |
| 4 | import lombok.Data; | 4 | import lombok.Data; |
| 5 | 5 | ||
| 6 | -@Schema(description = "管理后台 - AI 知识库-文档 Response VO") | 6 | +@Schema(description = "管理后台 - AI 知识库文档分片 Response VO") |
| 7 | @Data | 7 | @Data |
| 8 | public class AiKnowledgeSegmentRespVO { | 8 | public class AiKnowledgeSegmentRespVO { |
| 9 | 9 | ||
| @@ -22,13 +22,19 @@ public class AiKnowledgeSegmentRespVO { | @@ -22,13 +22,19 @@ public class AiKnowledgeSegmentRespVO { | ||
| 22 | @Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册") | 22 | @Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册") |
| 23 | private String content; | 23 | private String content; |
| 24 | 24 | ||
| 25 | + @Schema(description = "切片内容长度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | ||
| 26 | + private Integer contentLength; | ||
| 27 | + | ||
| 25 | @Schema(description = "token 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | 28 | @Schema(description = "token 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| 26 | private Integer tokens; | 29 | private Integer tokens; |
| 27 | 30 | ||
| 28 | - @Schema(description = "字符数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1008") | ||
| 29 | - private Integer wordCount; | 31 | + @Schema(description = "召回次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") |
| 32 | + private Integer retrievalCount; | ||
| 30 | 33 | ||
| 31 | @Schema(description = "文档状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | 34 | @Schema(description = "文档状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| 32 | private Integer status; | 35 | private Integer status; |
| 33 | 36 | ||
| 37 | + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) | ||
| 38 | + private Long createTime; | ||
| 39 | + | ||
| 34 | } | 40 | } |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentService.java
| @@ -63,4 +63,12 @@ public interface AiKnowledgeDocumentService { | @@ -63,4 +63,12 @@ public interface AiKnowledgeDocumentService { | ||
| 63 | */ | 63 | */ |
| 64 | AiKnowledgeDocumentDO validateKnowledgeDocumentExists(Long id); | 64 | AiKnowledgeDocumentDO validateKnowledgeDocumentExists(Long id); |
| 65 | 65 | ||
| 66 | + /** | ||
| 67 | + * 读取 URL 内容 | ||
| 68 | + * | ||
| 69 | + * @param url URL | ||
| 70 | + * @return 内容 | ||
| 71 | + */ | ||
| 72 | + String readUrl(String url); | ||
| 73 | + | ||
| 66 | } | 74 | } |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentServiceImpl.java
| @@ -127,9 +127,10 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic | @@ -127,9 +127,10 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic | ||
| 127 | return knowledgeDocument; | 127 | return knowledgeDocument; |
| 128 | } | 128 | } |
| 129 | 129 | ||
| 130 | - private static String readUrl(String url) { | 130 | + @Override |
| 131 | + public String readUrl(String url) { | ||
| 131 | // 下载文件 | 132 | // 下载文件 |
| 132 | - ByteArrayResource resource = null; | 133 | + ByteArrayResource resource; |
| 133 | try { | 134 | try { |
| 134 | byte[] bytes = HttpUtil.downloadBytes(url); | 135 | byte[] bytes = HttpUtil.downloadBytes(url); |
| 135 | if (bytes.length == 0) { | 136 | if (bytes.length == 0) { |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentService.java
| @@ -29,7 +29,7 @@ public interface AiKnowledgeSegmentService { | @@ -29,7 +29,7 @@ public interface AiKnowledgeSegmentService { | ||
| 29 | * 基于 content 内容,切片创建多个段落 | 29 | * 基于 content 内容,切片创建多个段落 |
| 30 | * | 30 | * |
| 31 | * @param documentId 知识库文档编号 | 31 | * @param documentId 知识库文档编号 |
| 32 | - * @param content 文档内容 | 32 | + * @param content 文档内容 |
| 33 | */ | 33 | */ |
| 34 | void createKnowledgeSegmentBySplitContent(Long documentId, String content); | 34 | void createKnowledgeSegmentBySplitContent(Long documentId, String content); |
| 35 | 35 | ||
| @@ -37,7 +37,7 @@ public interface AiKnowledgeSegmentService { | @@ -37,7 +37,7 @@ public interface AiKnowledgeSegmentService { | ||
| 37 | * 【异步】基于 content 内容,切片创建多个段落 | 37 | * 【异步】基于 content 内容,切片创建多个段落 |
| 38 | * | 38 | * |
| 39 | * @param documentId 知识库文档编号 | 39 | * @param documentId 知识库文档编号 |
| 40 | - * @param content 文档内容 | 40 | + * @param content 文档内容 |
| 41 | */ | 41 | */ |
| 42 | @Async | 42 | @Async |
| 43 | default void createKnowledgeSegmentBySplitContentAsync(Long documentId, String content) { | 43 | default void createKnowledgeSegmentBySplitContentAsync(Long documentId, String content) { |
| @@ -66,4 +66,13 @@ public interface AiKnowledgeSegmentService { | @@ -66,4 +66,13 @@ public interface AiKnowledgeSegmentService { | ||
| 66 | */ | 66 | */ |
| 67 | List<AiKnowledgeSegmentDO> similaritySearch(AiKnowledgeSegmentSearchReqVO reqVO); | 67 | List<AiKnowledgeSegmentDO> similaritySearch(AiKnowledgeSegmentSearchReqVO reqVO); |
| 68 | 68 | ||
| 69 | + /** | ||
| 70 | + * 根据 URL 内容,切片创建多个段落 | ||
| 71 | + * | ||
| 72 | + * @param url URL 地址 | ||
| 73 | + * @param segmentMaxTokens 段落最大 Token 数 | ||
| 74 | + * @return 切片后的段落列表 | ||
| 75 | + */ | ||
| 76 | + List<AiKnowledgeSegmentDO> splitContent(String url, Integer segmentMaxTokens); | ||
| 77 | + | ||
| 69 | } | 78 | } |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java
| @@ -75,9 +75,7 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService | @@ -75,9 +75,7 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService | ||
| 75 | VectorStore vectorStore = getVectorStoreById(knowledgeDO); | 75 | VectorStore vectorStore = getVectorStoreById(knowledgeDO); |
| 76 | 76 | ||
| 77 | // 2. 文档切片 | 77 | // 2. 文档切片 |
| 78 | - Document document = new Document(content); | ||
| 79 | - TextSplitter textSplitter = buildTokenTextSplitter(documentDO.getSegmentMaxTokens()); | ||
| 80 | - List<Document> documentSegments = textSplitter.apply(Collections.singletonList(document)); | 78 | + List<Document> documentSegments = splitContentByToken(content, documentDO.getSegmentMaxTokens()); |
| 81 | 79 | ||
| 82 | // 3.1 存储切片 | 80 | // 3.1 存储切片 |
| 83 | List<AiKnowledgeSegmentDO> segmentDOs = convertList(documentSegments, segment -> { | 81 | List<AiKnowledgeSegmentDO> segmentDOs = convertList(documentSegments, segment -> { |
| @@ -86,7 +84,8 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService | @@ -86,7 +84,8 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService | ||
| 86 | } | 84 | } |
| 87 | return new AiKnowledgeSegmentDO().setKnowledgeId(documentDO.getKnowledgeId()).setDocumentId(documentId) | 85 | return new AiKnowledgeSegmentDO().setKnowledgeId(documentDO.getKnowledgeId()).setDocumentId(documentId) |
| 88 | .setContent(segment.getText()).setContentLength(segment.getText().length()) | 86 | .setContent(segment.getText()).setContentLength(segment.getText().length()) |
| 89 | - .setVectorId(AiKnowledgeSegmentDO.VECTOR_ID_EMPTY).setTokens(tokenCountEstimator.estimate(segment.getText())) | 87 | + .setVectorId(AiKnowledgeSegmentDO.VECTOR_ID_EMPTY) |
| 88 | + .setTokens(tokenCountEstimator.estimate(segment.getText())) | ||
| 90 | .setStatus(CommonStatusEnum.ENABLE.getStatus()); | 89 | .setStatus(CommonStatusEnum.ENABLE.getStatus()); |
| 91 | }); | 90 | }); |
| 92 | segmentMapper.insertBatch(segmentDOs); | 91 | segmentMapper.insertBatch(segmentDOs); |
| @@ -180,6 +179,26 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService | @@ -180,6 +179,26 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService | ||
| 180 | return segmentMapper.selectListByVectorIds(convertList(documents, Document::getId)); | 179 | return segmentMapper.selectListByVectorIds(convertList(documents, Document::getId)); |
| 181 | } | 180 | } |
| 182 | 181 | ||
| 182 | + @Override | ||
| 183 | + public List<AiKnowledgeSegmentDO> splitContent(String url, Integer segmentMaxTokens) { | ||
| 184 | + // 1. 读取 URL 内容 | ||
| 185 | + String content = knowledgeDocumentService.readUrl(url); | ||
| 186 | + | ||
| 187 | + // 2. 文档切片 | ||
| 188 | + List<Document> documentSegments = splitContentByToken(content, segmentMaxTokens); | ||
| 189 | + | ||
| 190 | + // 3. 转换为段落对象 | ||
| 191 | + return convertList(documentSegments, segment -> { | ||
| 192 | + if (StrUtil.isEmpty(segment.getText())) { | ||
| 193 | + return null; | ||
| 194 | + } | ||
| 195 | + return new AiKnowledgeSegmentDO() | ||
| 196 | + .setContent(segment.getText()) | ||
| 197 | + .setContentLength(segment.getText().length()) | ||
| 198 | + .setTokens(tokenCountEstimator.estimate(segment.getText())); | ||
| 199 | + }); | ||
| 200 | + } | ||
| 201 | + | ||
| 183 | /** | 202 | /** |
| 184 | * 校验段落是否存在 | 203 | * 校验段落是否存在 |
| 185 | * | 204 | * |
| @@ -202,6 +221,11 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService | @@ -202,6 +221,11 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService | ||
| 202 | return getVectorStoreById(knowledgeService.validateKnowledgeExists(knowledgeId)); | 221 | return getVectorStoreById(knowledgeService.validateKnowledgeExists(knowledgeId)); |
| 203 | } | 222 | } |
| 204 | 223 | ||
| 224 | + private static List<Document> splitContentByToken(String content, Integer segmentMaxTokens) { | ||
| 225 | + TextSplitter textSplitter = buildTokenTextSplitter(segmentMaxTokens); | ||
| 226 | + return textSplitter.apply(Collections.singletonList(new Document(content))); | ||
| 227 | + } | ||
| 228 | + | ||
| 205 | private static TextSplitter buildTokenTextSplitter(Integer segmentMaxTokens) { | 229 | private static TextSplitter buildTokenTextSplitter(Integer segmentMaxTokens) { |
| 206 | return TokenTextSplitter.builder() | 230 | return TokenTextSplitter.builder() |
| 207 | .withChunkSize(segmentMaxTokens) | 231 | .withChunkSize(segmentMaxTokens) |