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 | 10 | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; |
| 11 | 11 | import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService; |
| 12 | 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 | 15 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 14 | 16 | import jakarta.annotation.Resource; |
| 15 | 17 | import jakarta.validation.Valid; |
| 16 | 18 | import org.springframework.validation.annotation.Validated; |
| 17 | 19 | import org.springframework.web.bind.annotation.*; |
| 18 | 20 | |
| 21 | +import java.util.List; | |
| 22 | + | |
| 19 | 23 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 20 | 24 | |
| 21 | 25 | // TODO @芋艿:增加权限标识 |
| ... | ... | @@ -30,7 +34,8 @@ public class AiKnowledgeSegmentController { |
| 30 | 34 | |
| 31 | 35 | @GetMapping("/page") |
| 32 | 36 | @Operation(summary = "获取段落分页") |
| 33 | - public CommonResult<PageResult<AiKnowledgeSegmentRespVO>> getKnowledgeSegmentPage(@Valid AiKnowledgeSegmentPageReqVO pageReqVO) { | |
| 37 | + public CommonResult<PageResult<AiKnowledgeSegmentRespVO>> getKnowledgeSegmentPage( | |
| 38 | + @Valid AiKnowledgeSegmentPageReqVO pageReqVO) { | |
| 34 | 39 | PageResult<AiKnowledgeSegmentDO> pageResult = segmentService.getKnowledgeSegmentPage(pageReqVO); |
| 35 | 40 | return success(BeanUtils.toBean(pageResult, AiKnowledgeSegmentRespVO.class)); |
| 36 | 41 | } |
| ... | ... | @@ -44,9 +49,23 @@ public class AiKnowledgeSegmentController { |
| 44 | 49 | |
| 45 | 50 | @PutMapping("/update-status") |
| 46 | 51 | @Operation(summary = "启禁用段落内容") |
| 47 | - public CommonResult<Boolean> updateKnowledgeSegmentStatus(@Valid @RequestBody AiKnowledgeSegmentUpdateStatusReqVO reqVO) { | |
| 52 | + public CommonResult<Boolean> updateKnowledgeSegmentStatus( | |
| 53 | + @Valid @RequestBody AiKnowledgeSegmentUpdateStatusReqVO reqVO) { | |
| 48 | 54 | segmentService.updateKnowledgeSegmentStatus(reqVO); |
| 49 | 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 | 3 | import io.swagger.v3.oas.annotations.media.Schema; |
| 4 | 4 | import lombok.Data; |
| 5 | 5 | |
| 6 | -@Schema(description = "管理后台 - AI 知识库-文档 Response VO") | |
| 6 | +@Schema(description = "管理后台 - AI 知识库文档分片 Response VO") | |
| 7 | 7 | @Data |
| 8 | 8 | public class AiKnowledgeSegmentRespVO { |
| 9 | 9 | |
| ... | ... | @@ -22,13 +22,19 @@ public class AiKnowledgeSegmentRespVO { |
| 22 | 22 | @Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册") |
| 23 | 23 | private String content; |
| 24 | 24 | |
| 25 | + @Schema(description = "切片内容长度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | |
| 26 | + private Integer contentLength; | |
| 27 | + | |
| 25 | 28 | @Schema(description = "token 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| 26 | 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 | 34 | @Schema(description = "文档状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| 32 | 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
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 | 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 | 134 | try { |
| 134 | 135 | byte[] bytes = HttpUtil.downloadBytes(url); |
| 135 | 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 | 29 | * 基于 content 内容,切片创建多个段落 |
| 30 | 30 | * |
| 31 | 31 | * @param documentId 知识库文档编号 |
| 32 | - * @param content 文档内容 | |
| 32 | + * @param content 文档内容 | |
| 33 | 33 | */ |
| 34 | 34 | void createKnowledgeSegmentBySplitContent(Long documentId, String content); |
| 35 | 35 | |
| ... | ... | @@ -37,7 +37,7 @@ public interface AiKnowledgeSegmentService { |
| 37 | 37 | * 【异步】基于 content 内容,切片创建多个段落 |
| 38 | 38 | * |
| 39 | 39 | * @param documentId 知识库文档编号 |
| 40 | - * @param content 文档内容 | |
| 40 | + * @param content 文档内容 | |
| 41 | 41 | */ |
| 42 | 42 | @Async |
| 43 | 43 | default void createKnowledgeSegmentBySplitContentAsync(Long documentId, String content) { |
| ... | ... | @@ -66,4 +66,13 @@ public interface AiKnowledgeSegmentService { |
| 66 | 66 | */ |
| 67 | 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 | 75 | VectorStore vectorStore = getVectorStoreById(knowledgeDO); |
| 76 | 76 | |
| 77 | 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 | 80 | // 3.1 存储切片 |
| 83 | 81 | List<AiKnowledgeSegmentDO> segmentDOs = convertList(documentSegments, segment -> { |
| ... | ... | @@ -86,7 +84,8 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService |
| 86 | 84 | } |
| 87 | 85 | return new AiKnowledgeSegmentDO().setKnowledgeId(documentDO.getKnowledgeId()).setDocumentId(documentId) |
| 88 | 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 | 89 | .setStatus(CommonStatusEnum.ENABLE.getStatus()); |
| 91 | 90 | }); |
| 92 | 91 | segmentMapper.insertBatch(segmentDOs); |
| ... | ... | @@ -180,6 +179,26 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService |
| 180 | 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 | 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 | 229 | private static TextSplitter buildTokenTextSplitter(Integer segmentMaxTokens) { |
| 206 | 230 | return TokenTextSplitter.builder() |
| 207 | 231 | .withChunkSize(segmentMaxTokens) | ... | ... |