mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-11-04 12:18:42 +08:00 
			
		
		
		
	@@ -1,11 +1,8 @@
 | 
			
		||||
package cn.iocoder.yudao.module.ai.enums;
 | 
			
		||||
 | 
			
		||||
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * AI 内置聊天角色的枚举
 | 
			
		||||
 *
 | 
			
		||||
@@ -13,16 +10,16 @@ import java.util.Arrays;
 | 
			
		||||
 */
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
@Getter
 | 
			
		||||
public enum AiChatRoleEnum implements IntArrayValuable {
 | 
			
		||||
public enum AiChatRoleEnum {
 | 
			
		||||
 | 
			
		||||
    AI_WRITE_ROLE(1, "写作助手", """
 | 
			
		||||
    AI_WRITE_ROLE("写作助手", """
 | 
			
		||||
            你是一位出色的写作助手,能够帮助用户生成创意和灵感,并在用户提供场景和提示词时生成对应的回复。你的任务包括:
 | 
			
		||||
            1.	撰写建议:根据用户提供的主题或问题,提供详细的写作建议、情节发展方向、角色设定以及背景描写,确保内容结构清晰、有逻辑。
 | 
			
		||||
            2.	回复生成:根据用户提供的场景和提示词,生成合适的对话或文字回复,确保语气和风格符合场景需求。
 | 
			
		||||
            除此之外不需要除了正文内容外的其他回复,如标题、开头、任何解释性语句或道歉。
 | 
			
		||||
            """),
 | 
			
		||||
 | 
			
		||||
    AI_MIND_MAP_ROLE(2, "导图助手", """
 | 
			
		||||
    AI_MIND_MAP_ROLE("导图助手", """
 | 
			
		||||
             你是一位非常优秀的思维导图助手,你会把用户的所有提问都总结成思维导图,然后以 Markdown 格式输出。markdown 只需要输出一级标题,二级标题,三级标题,四级标题,最多输出四级,除此之外不要输出任何其他 markdown 标记。下面是一个合格的例子:
 | 
			
		||||
             # Geek-AI 助手
 | 
			
		||||
             ## 完整的开源系统
 | 
			
		||||
@@ -39,11 +36,6 @@ public enum AiChatRoleEnum implements IntArrayValuable {
 | 
			
		||||
            除此之外不要任何解释性语句。
 | 
			
		||||
            """);
 | 
			
		||||
 | 
			
		||||
    // TODO @xin:这个 role 是不是删除掉好点哈。= = 目前主要是没做角色枚举。这里多了 role 反倒容易误解哈
 | 
			
		||||
    /**
 | 
			
		||||
     * 角色
 | 
			
		||||
     */
 | 
			
		||||
    private final Integer role;
 | 
			
		||||
    /**
 | 
			
		||||
     * 角色名
 | 
			
		||||
     */
 | 
			
		||||
@@ -54,11 +46,4 @@ public enum AiChatRoleEnum implements IntArrayValuable {
 | 
			
		||||
     */
 | 
			
		||||
    private final String systemMessage;
 | 
			
		||||
 | 
			
		||||
    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AiChatRoleEnum::getRole).toArray();
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int[] array() {
 | 
			
		||||
        return ARRAYS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * AI 错误码枚举类
 | 
			
		||||
 *
 | 
			
		||||
 * <p>
 | 
			
		||||
 * ai 系统,使用 1-040-000-000 段
 | 
			
		||||
 */
 | 
			
		||||
public interface ErrorCodeConstants {
 | 
			
		||||
@@ -55,5 +55,7 @@ public interface ErrorCodeConstants {
 | 
			
		||||
 | 
			
		||||
    // ========== API 知识库 1-022-008-000 ==========
 | 
			
		||||
    ErrorCode KNOWLEDGE_NOT_EXISTS = new ErrorCode(1_022_008_000, "知识库不存在!");
 | 
			
		||||
    ErrorCode KNOWLEDGE_DOCUMENT_NOT_EXISTS = new ErrorCode(1_022_008_001, "文档不存在!");
 | 
			
		||||
    ErrorCode KNOWLEDGE_SEGMENT_NOT_EXISTS = new ErrorCode(1_022_008_002, "段落不存在!");
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,19 @@
 | 
			
		||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge;
 | 
			
		||||
 | 
			
		||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 | 
			
		||||
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeCreateMyReqVO;
 | 
			
		||||
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeUpdateMyReqVO;
 | 
			
		||||
import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeBaseService;
 | 
			
		||||
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.AiKnowledgeRespVO;
 | 
			
		||||
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateMyReqVO;
 | 
			
		||||
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;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import jakarta.annotation.Resource;
 | 
			
		||||
import jakarta.validation.Valid;
 | 
			
		||||
import org.springframework.validation.annotation.Validated;
 | 
			
		||||
import org.springframework.web.bind.annotation.*;
 | 
			
		||||
 | 
			
		||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 | 
			
		||||
@@ -19,19 +25,27 @@ import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUti
 | 
			
		||||
public class AiKnowledgeController {
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private AiKnowledgeBaseService knowledgeBaseService;
 | 
			
		||||
    private AiKnowledgeService knowledgeService;
 | 
			
		||||
 | 
			
		||||
    @GetMapping("/my-page")
 | 
			
		||||
    @Operation(summary = "获取【我的】知识库分页")
 | 
			
		||||
    public CommonResult<PageResult<AiKnowledgeRespVO>> getKnowledgePageMy(@Validated PageParam pageReqVO) {
 | 
			
		||||
        PageResult<AiKnowledgeDO> pageResult = knowledgeService.getKnowledgePageMy(getLoginUserId(), pageReqVO);
 | 
			
		||||
        return success(BeanUtils.toBean(pageResult, AiKnowledgeRespVO.class));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @PostMapping("/create-my")
 | 
			
		||||
    @Operation(summary = "创建【我的】知识库")
 | 
			
		||||
    public CommonResult<Long> createKnowledgeMy(@RequestBody @Valid AiKnowledgeCreateMyReqVO createReqVO) {
 | 
			
		||||
        return success(knowledgeBaseService.createKnowledgeMy(createReqVO, getLoginUserId()));
 | 
			
		||||
        return success(knowledgeService.createKnowledgeMy(createReqVO, getLoginUserId()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @PutMapping("/update-my")
 | 
			
		||||
    @Operation(summary = "更新【我的】知识库")
 | 
			
		||||
    public CommonResult<Boolean> updateKnowledgeMy(@RequestBody @Valid AiKnowledgeUpdateMyReqVO updateReqVO) {
 | 
			
		||||
        knowledgeBaseService.updateKnowledgeMy(updateReqVO, getLoginUserId());
 | 
			
		||||
        knowledgeService.updateKnowledgeMy(updateReqVO, getLoginUserId());
 | 
			
		||||
        return success(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,54 @@
 | 
			
		||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge;
 | 
			
		||||
 | 
			
		||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 | 
			
		||||
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.document.AiKnowledgeDocumentPageReqVO;
 | 
			
		||||
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentRespVO;
 | 
			
		||||
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.AiKnowledgeDocumentDO;
 | 
			
		||||
import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import jakarta.annotation.Resource;
 | 
			
		||||
import org.springframework.validation.annotation.Validated;
 | 
			
		||||
import org.springframework.web.bind.annotation.*;
 | 
			
		||||
 | 
			
		||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Tag(name = "管理后台 - AI 知识库-文档")
 | 
			
		||||
@RestController
 | 
			
		||||
@RequestMapping("/ai/knowledge/document")
 | 
			
		||||
public class AiKnowledgeDocumentController {
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private AiKnowledgeDocumentService documentService;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @PostMapping("/create")
 | 
			
		||||
    @Operation(summary = "新建文档")
 | 
			
		||||
    public CommonResult<Long> createKnowledgeDocument(@Validated AiKnowledgeDocumentCreateReqVO reqVO) {
 | 
			
		||||
        Long knowledgeDocumentId = documentService.createKnowledgeDocument(reqVO);
 | 
			
		||||
        return success(knowledgeDocumentId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @GetMapping("/page")
 | 
			
		||||
    @Operation(summary = "获取文档分页")
 | 
			
		||||
    public CommonResult<PageResult<AiKnowledgeDocumentRespVO>> getKnowledgeDocumentPageMy(@Validated AiKnowledgeDocumentPageReqVO pageReqVO) {
 | 
			
		||||
        PageResult<AiKnowledgeDocumentDO> pageResult = documentService.getKnowledgeDocumentPage(pageReqVO);
 | 
			
		||||
        return success(BeanUtils.toBean(pageResult, AiKnowledgeDocumentRespVO.class));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @PutMapping("/update")
 | 
			
		||||
    @Operation(summary = "更新文档")
 | 
			
		||||
    public CommonResult<Boolean> updateKnowledgeDocument(@Validated @RequestBody AiKnowledgeDocumentUpdateReqVO reqVO) {
 | 
			
		||||
        documentService.updateKnowledgeDocument(reqVO);
 | 
			
		||||
        return success(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,51 @@
 | 
			
		||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge;
 | 
			
		||||
 | 
			
		||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 | 
			
		||||
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.AiKnowledgeSegmentRespVO;
 | 
			
		||||
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 cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import jakarta.annotation.Resource;
 | 
			
		||||
import org.springframework.validation.annotation.Validated;
 | 
			
		||||
import org.springframework.web.bind.annotation.*;
 | 
			
		||||
 | 
			
		||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Tag(name = "管理后台 - AI 知识库-段落")
 | 
			
		||||
@RestController
 | 
			
		||||
@RequestMapping("/ai/knowledge/segment")
 | 
			
		||||
public class AiKnowledgeSegmentController {
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private AiKnowledgeSegmentService segmentService;
 | 
			
		||||
 | 
			
		||||
    @GetMapping("/page")
 | 
			
		||||
    @Operation(summary = "获取段落分页")
 | 
			
		||||
    public CommonResult<PageResult<AiKnowledgeSegmentRespVO>> getKnowledgeSegmentPageMy(@Validated AiKnowledgeSegmentPageReqVO pageReqVO) {
 | 
			
		||||
        PageResult<AiKnowledgeSegmentDO> pageResult = segmentService.getKnowledgeSegmentPage(pageReqVO);
 | 
			
		||||
        return success(BeanUtils.toBean(pageResult, AiKnowledgeSegmentRespVO.class));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @PutMapping("/update")
 | 
			
		||||
    @Operation(summary = "更新段落内容")
 | 
			
		||||
    public CommonResult<Boolean> updateKnowledgeSegment(@Validated @RequestBody AiKnowledgeSegmentUpdateReqVO reqVO) {
 | 
			
		||||
        segmentService.updateKnowledgeSegment(reqVO);
 | 
			
		||||
        return success(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @PutMapping("/update-status")
 | 
			
		||||
    @Operation(summary = "启禁用段落内容")
 | 
			
		||||
    public CommonResult<Boolean> updateKnowledgeSegmentStatus(@Validated @RequestBody AiKnowledgeSegmentUpdateStatusReqVO reqVO) {
 | 
			
		||||
        segmentService.updateKnowledgeSegmentStatus(reqVO);
 | 
			
		||||
        return success(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,13 @@
 | 
			
		||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document;
 | 
			
		||||
 | 
			
		||||
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 AiKnowledgeDocumentPageReqVO extends PageParam {
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "文档名称", example = "Java 开发手册")
 | 
			
		||||
    private String name;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,37 @@
 | 
			
		||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document;
 | 
			
		||||
 | 
			
		||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
@Schema(description = "管理后台 - AI 知识库-文档 Response VO")
 | 
			
		||||
@Data
 | 
			
		||||
public class AiKnowledgeDocumentRespVO extends PageParam {
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
 | 
			
		||||
    private Long id;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
 | 
			
		||||
    private Long knowledgeId;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
 | 
			
		||||
    private String name;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "内容", example = "Java 是一门面向对象的语言.....")
 | 
			
		||||
    private String content;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "文档 url", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://doc.iocoder.cn")
 | 
			
		||||
    private String url;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "token 数量", example = "1024")
 | 
			
		||||
    private Integer tokens;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "字符数", example = "1008")
 | 
			
		||||
    private Integer wordCount;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "切片状态", example = "1")
 | 
			
		||||
    private Integer sliceStatus;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "文档状态", example = "1")
 | 
			
		||||
    private Integer status;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,23 @@
 | 
			
		||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import jakarta.validation.constraints.NotNull;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Schema(description = "管理后台 - AI 更新 知识库-文档 Request VO")
 | 
			
		||||
@Data
 | 
			
		||||
public class AiKnowledgeDocumentUpdateReqVO {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583")
 | 
			
		||||
    @NotNull(message = "编号不能为空")
 | 
			
		||||
    private Long id;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "是否启用", example = "1")
 | 
			
		||||
    private Integer status;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "名称", example = "Java 开发手册")
 | 
			
		||||
    private String name;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo;
 | 
			
		||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import jakarta.validation.constraints.NotBlank;
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo;
 | 
			
		||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import jakarta.validation.constraints.NotBlank;
 | 
			
		||||
@@ -6,10 +6,8 @@ import jakarta.validation.constraints.NotNull;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import org.hibernate.validator.constraints.URL;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author xiaoxin
 | 
			
		||||
 */
 | 
			
		||||
@Schema(description = "管理后台 - AI 知识库【创建文档】 Request VO")
 | 
			
		||||
 | 
			
		||||
@Schema(description = "管理后台 - AI 知识库创建【文档】 Request VO")
 | 
			
		||||
@Data
 | 
			
		||||
public class AiKnowledgeDocumentCreateReqVO {
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,25 @@
 | 
			
		||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Schema(description = "管理后台 - AI 知识库 Response VO")
 | 
			
		||||
@Data
 | 
			
		||||
public class AiKnowledgeRespVO {
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
 | 
			
		||||
    private Long id;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "知识库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "ruoyi-vue-pro 用户指南")
 | 
			
		||||
    private String name;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "知识库描述", example = "ruoyi-vue-pro 用户指南")
 | 
			
		||||
    private String description;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "14")
 | 
			
		||||
    private Long modelId;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "模型标识", example = "qwen-72b-chat")
 | 
			
		||||
    private String model;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo;
 | 
			
		||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import jakarta.validation.constraints.NotBlank;
 | 
			
		||||
@@ -7,7 +7,7 @@ import lombok.Data;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
@Schema(description = "管理后台 - AI 知识库创建【我的】 Request VO")
 | 
			
		||||
@Schema(description = "管理后台 - AI 知识库更新【我的】 Request VO")
 | 
			
		||||
@Data
 | 
			
		||||
public class AiKnowledgeUpdateMyReqVO {
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,21 @@
 | 
			
		||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
 | 
			
		||||
 | 
			
		||||
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 AiKnowledgeSegmentPageReqVO extends PageParam {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "分段状态", example = "1")
 | 
			
		||||
    private Integer status;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
 | 
			
		||||
    private Integer documentId;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "分段内容关键字", example = "Java 开发")
 | 
			
		||||
    private String keyword;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,33 @@
 | 
			
		||||
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 知识库-文档 Response VO")
 | 
			
		||||
@Data
 | 
			
		||||
public class AiKnowledgeSegmentRespVO {
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
 | 
			
		||||
    private Long id;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
 | 
			
		||||
    private Long documentId;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
 | 
			
		||||
    private Long knowledgeId;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "向量库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1858496a-1dde-4edf-a43e-0aed08f37f8c")
 | 
			
		||||
    private String vectorId;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
 | 
			
		||||
    private String content;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "token 数量", example = "1024")
 | 
			
		||||
    private Integer tokens;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "字符数", example = "1008")
 | 
			
		||||
    private Integer wordCount;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "文档状态", example = "1")
 | 
			
		||||
    private Integer status;
 | 
			
		||||
}
 | 
			
		||||
@@ -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 AiKnowledgeSegmentUpdateReqVO {
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
 | 
			
		||||
    private Long id;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
 | 
			
		||||
    private String content;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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 AiKnowledgeSegmentUpdateStatusReqVO {
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
 | 
			
		||||
    private Long id;
 | 
			
		||||
 | 
			
		||||
    @Schema(description = "是否启用", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
 | 
			
		||||
    private Integer status;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -10,15 +10,14 @@ import lombok.Data;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
// TODO @xin:要不把 AiKnowledgeBaseDO 改成 AiKnowledgeDO。感觉 base 后缀,感觉有点奇怪(让人以为是基类)。然后,我们很多地方的外键编号,都是 knowledgeId
 | 
			
		||||
/**
 | 
			
		||||
 * AI 知识库 DO
 | 
			
		||||
 *
 | 
			
		||||
 * @author xiaoxin
 | 
			
		||||
 */
 | 
			
		||||
@TableName(value = "ai_knowledge_base", autoResultMap = true)
 | 
			
		||||
@TableName(value = "ai_knowledge", autoResultMap = true)
 | 
			
		||||
@Data
 | 
			
		||||
public class AiKnowledgeBaseDO extends BaseDO {
 | 
			
		||||
public class AiKnowledgeDO extends BaseDO {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 编号
 | 
			
		||||
@@ -46,7 +45,7 @@ public class AiKnowledgeBaseDO extends BaseDO {
 | 
			
		||||
    @TableField(typeHandler = JacksonTypeHandler.class)
 | 
			
		||||
    private List<Long> visibilityPermissions;
 | 
			
		||||
    /**
 | 
			
		||||
     * 嵌入模型编号,高质量模式时维护
 | 
			
		||||
     * 嵌入模型编号
 | 
			
		||||
     */
 | 
			
		||||
    private Long modelId;
 | 
			
		||||
    /**
 | 
			
		||||
@@ -24,7 +24,7 @@ public class AiKnowledgeDocumentDO extends BaseDO {
 | 
			
		||||
    /**
 | 
			
		||||
     * 知识库编号
 | 
			
		||||
     *
 | 
			
		||||
     * 关联 {@link AiKnowledgeBaseDO#getId()}
 | 
			
		||||
     * 关联 {@link AiKnowledgeDO#getId()}
 | 
			
		||||
     */
 | 
			
		||||
    private Long knowledgeId;
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,8 @@ import lombok.Data;
 | 
			
		||||
@Data
 | 
			
		||||
public class AiKnowledgeSegmentDO extends BaseDO {
 | 
			
		||||
 | 
			
		||||
    public static final String FIELD_KNOWLEDGE_ID = "knowledgeId";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 编号
 | 
			
		||||
     */
 | 
			
		||||
@@ -24,10 +26,14 @@ public class AiKnowledgeSegmentDO extends BaseDO {
 | 
			
		||||
     * 向量库的编号
 | 
			
		||||
     */
 | 
			
		||||
    private String vectorId;
 | 
			
		||||
    // TODO @新:knowledgeId 加个,会方便点
 | 
			
		||||
    /**
 | 
			
		||||
     * 知识库编号
 | 
			
		||||
     * 关联 {@link AiKnowledgeDO#getId()}
 | 
			
		||||
     */
 | 
			
		||||
    private Long knowledgeId;
 | 
			
		||||
    /**
 | 
			
		||||
     * 文档编号
 | 
			
		||||
     *
 | 
			
		||||
     * <p>
 | 
			
		||||
     * 关联 {@link AiKnowledgeDocumentDO#getId()}
 | 
			
		||||
     */
 | 
			
		||||
    private Long documentId;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +0,0 @@
 | 
			
		||||
package cn.iocoder.yudao.module.ai.dal.mysql.knowledge;
 | 
			
		||||
 | 
			
		||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 | 
			
		||||
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeBaseDO;
 | 
			
		||||
import org.apache.ibatis.annotations.Mapper;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * AI 知识库基础信息 Mapper
 | 
			
		||||
 *
 | 
			
		||||
 * @author xiaoxin
 | 
			
		||||
 */
 | 
			
		||||
@Mapper
 | 
			
		||||
public interface AiKnowledgeBaseMapper extends BaseMapperX<AiKnowledgeBaseDO> {
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,9 @@
 | 
			
		||||
package cn.iocoder.yudao.module.ai.dal.mysql.knowledge;
 | 
			
		||||
 | 
			
		||||
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.document.AiKnowledgeDocumentPageReqVO;
 | 
			
		||||
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
 | 
			
		||||
import org.apache.ibatis.annotations.Mapper;
 | 
			
		||||
 | 
			
		||||
@@ -11,4 +14,11 @@ import org.apache.ibatis.annotations.Mapper;
 | 
			
		||||
 */
 | 
			
		||||
@Mapper
 | 
			
		||||
public interface AiKnowledgeDocumentMapper extends BaseMapperX<AiKnowledgeDocumentDO> {
 | 
			
		||||
 | 
			
		||||
    default PageResult<AiKnowledgeDocumentDO> selectPage(AiKnowledgeDocumentPageReqVO reqVO) {
 | 
			
		||||
        return selectPage(reqVO, new LambdaQueryWrapperX<AiKnowledgeDocumentDO>()
 | 
			
		||||
                .likeIfPresent(AiKnowledgeDocumentDO::getName, reqVO.getName())
 | 
			
		||||
                .orderByDesc(AiKnowledgeDocumentDO::getId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,25 @@
 | 
			
		||||
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.dal.dataobject.knowledge.AiKnowledgeDO;
 | 
			
		||||
import org.apache.ibatis.annotations.Mapper;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * AI 知识库基础信息 Mapper
 | 
			
		||||
 *
 | 
			
		||||
 * @author xiaoxin
 | 
			
		||||
 */
 | 
			
		||||
@Mapper
 | 
			
		||||
public interface AiKnowledgeMapper extends BaseMapperX<AiKnowledgeDO> {
 | 
			
		||||
 | 
			
		||||
    default PageResult<AiKnowledgeDO> selectPageByMy(Long userId, PageParam pageReqVO) {
 | 
			
		||||
        return selectPage(pageReqVO, new LambdaQueryWrapperX<AiKnowledgeDO>()
 | 
			
		||||
                .eq(AiKnowledgeDO::getUserId, userId)
 | 
			
		||||
                .eq(AiKnowledgeDO::getStatus, CommonStatusEnum.ENABLE.getStatus())
 | 
			
		||||
                .orderByDesc(AiKnowledgeDO::getId));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,9 @@
 | 
			
		||||
package cn.iocoder.yudao.module.ai.dal.mysql.knowledge;
 | 
			
		||||
 | 
			
		||||
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.segment.AiKnowledgeSegmentPageReqVO;
 | 
			
		||||
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
 | 
			
		||||
import org.apache.ibatis.annotations.Mapper;
 | 
			
		||||
 | 
			
		||||
@@ -11,4 +14,12 @@ import org.apache.ibatis.annotations.Mapper;
 | 
			
		||||
 */
 | 
			
		||||
@Mapper
 | 
			
		||||
public interface AiKnowledgeSegmentMapper extends BaseMapperX<AiKnowledgeSegmentDO> {
 | 
			
		||||
 | 
			
		||||
    default PageResult<AiKnowledgeSegmentDO> selectPage(AiKnowledgeSegmentPageReqVO reqVO) {
 | 
			
		||||
        return selectPage(reqVO, new LambdaQueryWrapperX<AiKnowledgeSegmentDO>()
 | 
			
		||||
                .eq(AiKnowledgeSegmentDO::getDocumentId, reqVO.getDocumentId())
 | 
			
		||||
                .eqIfPresent(AiKnowledgeSegmentDO::getStatus, reqVO.getStatus())
 | 
			
		||||
                .likeIfPresent(AiKnowledgeSegmentDO::getContent, reqVO.getKeyword())
 | 
			
		||||
                .orderByDesc(AiKnowledgeSegmentDO::getId));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,27 +0,0 @@
 | 
			
		||||
package cn.iocoder.yudao.module.ai.service.knowledge;
 | 
			
		||||
 | 
			
		||||
import org.springframework.ai.document.Document;
 | 
			
		||||
import org.springframework.ai.vectorstore.SearchRequest;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * AI 嵌入 Service 接口
 | 
			
		||||
 *
 | 
			
		||||
 * @author xiaoxin
 | 
			
		||||
 */
 | 
			
		||||
public interface AiEmbeddingService {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 向量化文档并存储
 | 
			
		||||
     */
 | 
			
		||||
    void add(List<Document> documents);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 相似查询
 | 
			
		||||
     *
 | 
			
		||||
     * @param request 查询实体
 | 
			
		||||
     */
 | 
			
		||||
    List<Document> similaritySearch(SearchRequest request);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,35 +0,0 @@
 | 
			
		||||
package cn.iocoder.yudao.module.ai.service.knowledge;
 | 
			
		||||
 | 
			
		||||
import jakarta.annotation.Resource;
 | 
			
		||||
import org.springframework.ai.document.Document;
 | 
			
		||||
import org.springframework.ai.vectorstore.RedisVectorStore;
 | 
			
		||||
import org.springframework.ai.vectorstore.SearchRequest;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
// TODO @xin:是不是不用 AiEmbeddingServiceImpl,直接 vectorStore 注入到需要的地方就好啦。通过 KnowledgeDocumentService 返回就好。
 | 
			
		||||
/**
 | 
			
		||||
 * AI 嵌入 Service 实现类
 | 
			
		||||
 *
 | 
			
		||||
 * @author xiaoxin
 | 
			
		||||
 */
 | 
			
		||||
@Service
 | 
			
		||||
public class AiEmbeddingServiceImpl implements AiEmbeddingService {
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private RedisVectorStore vectorStore;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
//    @Async
 | 
			
		||||
    // TODO xiaoxin 报错先注释
 | 
			
		||||
    public void add(List<Document> documents) {
 | 
			
		||||
        vectorStore.add(documents);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<Document> similaritySearch(SearchRequest request) {
 | 
			
		||||
        return vectorStore.similaritySearch(request);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,30 +0,0 @@
 | 
			
		||||
package cn.iocoder.yudao.module.ai.service.knowledge;
 | 
			
		||||
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeCreateMyReqVO;
 | 
			
		||||
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeUpdateMyReqVO;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * AI 知识库-基础信息 Service 接口
 | 
			
		||||
 *
 | 
			
		||||
 * @author xiaoxin
 | 
			
		||||
 */
 | 
			
		||||
public interface AiKnowledgeBaseService {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 创建【我的】知识库
 | 
			
		||||
     *
 | 
			
		||||
     * @param createReqVO 创建信息
 | 
			
		||||
     * @param userId 用户编号
 | 
			
		||||
     * @return 编号
 | 
			
		||||
     */
 | 
			
		||||
    Long createKnowledgeMy(AiKnowledgeCreateMyReqVO createReqVO, Long userId);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 创建【我的】知识库
 | 
			
		||||
     *
 | 
			
		||||
     * @param updateReqVO 更新信息
 | 
			
		||||
     * @param userId 用户编号
 | 
			
		||||
     */
 | 
			
		||||
    void updateKnowledgeMy(AiKnowledgeUpdateMyReqVO updateReqVO, Long userId);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,10 @@
 | 
			
		||||
package cn.iocoder.yudao.module.ai.service.knowledge;
 | 
			
		||||
 | 
			
		||||
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeDocumentCreateReqVO;
 | 
			
		||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
 | 
			
		||||
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.AiKnowledgeDocumentDO;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * AI 知识库-文档 Service 接口
 | 
			
		||||
@@ -17,4 +21,19 @@ public interface AiKnowledgeDocumentService {
 | 
			
		||||
     */
 | 
			
		||||
    Long createKnowledgeDocument(AiKnowledgeDocumentCreateReqVO createReqVO);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取文档分页
 | 
			
		||||
     *
 | 
			
		||||
     * @param pageReqVO 分页参数
 | 
			
		||||
     * @return 文档分页
 | 
			
		||||
     */
 | 
			
		||||
    PageResult<AiKnowledgeDocumentDO> getKnowledgeDocumentPage(AiKnowledgeDocumentPageReqVO pageReqVO);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新文档
 | 
			
		||||
     *
 | 
			
		||||
     * @param reqVO 更新信息
 | 
			
		||||
     */
 | 
			
		||||
    void updateKnowledgeDocument(AiKnowledgeDocumentUpdateReqVO reqVO);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,27 +1,39 @@
 | 
			
		||||
package cn.iocoder.yudao.module.ai.service.knowledge;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.collection.CollUtil;
 | 
			
		||||
import cn.hutool.http.HttpUtil;
 | 
			
		||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 | 
			
		||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
 | 
			
		||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 | 
			
		||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 | 
			
		||||
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeDocumentCreateReqVO;
 | 
			
		||||
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;
 | 
			
		||||
import org.springframework.ai.reader.tika.TikaDocumentReader;
 | 
			
		||||
import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator;
 | 
			
		||||
import org.springframework.ai.tokenizer.TokenCountEstimator;
 | 
			
		||||
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Value;
 | 
			
		||||
import org.springframework.ai.vectorstore.VectorStore;
 | 
			
		||||
import org.springframework.core.io.ByteArrayResource;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
import org.springframework.transaction.annotation.Transactional;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 | 
			
		||||
import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_DOCUMENT_NOT_EXISTS;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * AI 知识库-文档 Service 实现类
 | 
			
		||||
@@ -39,53 +51,99 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private TokenTextSplitter tokenTextSplitter;
 | 
			
		||||
    @Resource
 | 
			
		||||
    private TokenCountEstimator tokenCountEstimator;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private AiEmbeddingService embeddingService;
 | 
			
		||||
    private AiApiKeyService apiKeyService;
 | 
			
		||||
    @Resource
 | 
			
		||||
    private AiKnowledgeService knowledgeService;
 | 
			
		||||
    @Resource
 | 
			
		||||
    private AiChatModelService chatModelService;
 | 
			
		||||
 | 
			
		||||
    // TODO @xin:@Resource 注入
 | 
			
		||||
    private static final JTokkitTokenCountEstimator TOKEN_COUNT_ESTIMATOR = new JTokkitTokenCountEstimator();
 | 
			
		||||
 | 
			
		||||
    // TODO xiaoxin 临时测试用,后续删
 | 
			
		||||
    @Value("classpath:/webapp/test/Fel.pdf")
 | 
			
		||||
    private org.springframework.core.io.Resource data;
 | 
			
		||||
 | 
			
		||||
    // TODO 芋艿:需要 review 下,代码格式;
 | 
			
		||||
    // TODO @xin:最好有 1、/2、/3 这种,让代码更有层次感
 | 
			
		||||
    @Override
 | 
			
		||||
    @Transactional(rollbackFor = Exception.class)
 | 
			
		||||
    public Long createKnowledgeDocument(AiKnowledgeDocumentCreateReqVO createReqVO) {
 | 
			
		||||
        // TODO xiaoxin 后续从 url 加载
 | 
			
		||||
        TikaDocumentReader loader = new TikaDocumentReader(data);
 | 
			
		||||
        // 加载文档
 | 
			
		||||
        // 1.1 下载文档
 | 
			
		||||
        String url = createReqVO.getUrl();
 | 
			
		||||
        // 1.2 加载文档
 | 
			
		||||
        TikaDocumentReader loader = new TikaDocumentReader(downloadFile(url));
 | 
			
		||||
        List<Document> documents = loader.get();
 | 
			
		||||
        Document document = CollUtil.getFirst(documents);
 | 
			
		||||
        // TODO @xin:是不是不存在,就抛出异常呀;厚泽 return 呀;
 | 
			
		||||
        // TODO 芋艿 文档层面有没有可能会比较大,这两个字段是否可以从分段表计算得出?回复:先直接算;
 | 
			
		||||
        Integer tokens = Objects.nonNull(document) ? TOKEN_COUNT_ESTIMATOR.estimate(document.getContent()) : 0;
 | 
			
		||||
        Integer wordCount = Objects.nonNull(document) ? document.getContent().length() : 0;
 | 
			
		||||
        String content = document.getContent();
 | 
			
		||||
        Integer tokens = tokenCountEstimator.estimate(content);
 | 
			
		||||
        Integer wordCount = content.length();
 | 
			
		||||
 | 
			
		||||
        // 1.3 文档记录入库
 | 
			
		||||
        AiKnowledgeDocumentDO documentDO = BeanUtils.toBean(createReqVO, AiKnowledgeDocumentDO.class)
 | 
			
		||||
                .setTokens(tokens).setWordCount(wordCount)
 | 
			
		||||
                .setStatus(CommonStatusEnum.ENABLE.getStatus()).setSliceStatus(AiKnowledgeDocumentStatusEnum.SUCCESS.getStatus());
 | 
			
		||||
        // 文档记录入库
 | 
			
		||||
        documentMapper.insert(documentDO);
 | 
			
		||||
        Long documentId = documentDO.getId();
 | 
			
		||||
        if (CollUtil.isEmpty(documents)) {
 | 
			
		||||
            return documentId;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 文档分段
 | 
			
		||||
        // 2.1 文档分段
 | 
			
		||||
        List<Document> segments = tokenTextSplitter.apply(documents);
 | 
			
		||||
        // 分段内容入库
 | 
			
		||||
        // 2.2 分段内容入库
 | 
			
		||||
        List<AiKnowledgeSegmentDO> segmentDOList = CollectionUtils.convertList(segments,
 | 
			
		||||
                segment -> new AiKnowledgeSegmentDO().setContent(segment.getContent()).setDocumentId(documentId)
 | 
			
		||||
                        .setTokens(TOKEN_COUNT_ESTIMATOR.estimate(segment.getContent())).setWordCount(segment.getContent().length())
 | 
			
		||||
                segment -> new AiKnowledgeSegmentDO().setContent(segment.getContent()).setDocumentId(documentId).setKnowledgeId(createReqVO.getKnowledgeId()).setVectorId(segment.getId())
 | 
			
		||||
                        .setTokens(tokenCountEstimator.estimate(segment.getContent())).setWordCount(segment.getContent().length())
 | 
			
		||||
                        .setStatus(CommonStatusEnum.ENABLE.getStatus()));
 | 
			
		||||
        segmentMapper.insertBatch(segmentDOList);
 | 
			
		||||
        // 向量化并存储
 | 
			
		||||
        embeddingService.add(segments);
 | 
			
		||||
 | 
			
		||||
        // 3.1 document 补充源数据
 | 
			
		||||
        segments.forEach(segment -> {
 | 
			
		||||
            Map<String, Object> metadata = segment.getMetadata();
 | 
			
		||||
            metadata.put(AiKnowledgeSegmentDO.FIELD_KNOWLEDGE_ID, createReqVO.getKnowledgeId());
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(createReqVO.getKnowledgeId());
 | 
			
		||||
        AiChatModelDO model = chatModelService.validateChatModel(knowledge.getModelId());
 | 
			
		||||
        // 3.2 获取向量存储实例
 | 
			
		||||
        VectorStore vectorStore = apiKeyService.getOrCreateVectorStore(model.getKeyId());
 | 
			
		||||
        // 3.3 向量化并存储
 | 
			
		||||
        vectorStore.add(segments);
 | 
			
		||||
        return documentId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public PageResult<AiKnowledgeDocumentDO> getKnowledgeDocumentPage(AiKnowledgeDocumentPageReqVO pageReqVO) {
 | 
			
		||||
        return documentMapper.selectPage(pageReqVO);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void updateKnowledgeDocument(AiKnowledgeDocumentUpdateReqVO reqVO) {
 | 
			
		||||
        validateKnowledgeDocumentExists(reqVO.getId());
 | 
			
		||||
        AiKnowledgeDocumentDO document = BeanUtils.toBean(reqVO, AiKnowledgeDocumentDO.class);
 | 
			
		||||
        documentMapper.updateById(document);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 校验文档是否存在
 | 
			
		||||
     *
 | 
			
		||||
     * @param id 文档编号
 | 
			
		||||
     * @return 文档信息
 | 
			
		||||
     */
 | 
			
		||||
    private AiKnowledgeDocumentDO validateKnowledgeDocumentExists(Long id) {
 | 
			
		||||
        AiKnowledgeDocumentDO knowledgeDocument = documentMapper.selectById(id);
 | 
			
		||||
        if (knowledgeDocument == null) {
 | 
			
		||||
            throw exception(KNOWLEDGE_DOCUMENT_NOT_EXISTS);
 | 
			
		||||
        }
 | 
			
		||||
        return knowledgeDocument;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private org.springframework.core.io.Resource downloadFile(String url) {
 | 
			
		||||
        try {
 | 
			
		||||
            byte[] bytes = HttpUtil.downloadBytes(url);
 | 
			
		||||
            return new ByteArrayResource(bytes);
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            log.error("[downloadFile][url({}) 下载失败]", url, e);
 | 
			
		||||
            throw new RuntimeException(e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,11 @@
 | 
			
		||||
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.AiKnowledgeSegmentUpdateReqVO;
 | 
			
		||||
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO;
 | 
			
		||||
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * AI 知识库分片 Service 接口
 | 
			
		||||
 *
 | 
			
		||||
@@ -7,4 +13,25 @@ package cn.iocoder.yudao.module.ai.service.knowledge;
 | 
			
		||||
 */
 | 
			
		||||
public interface AiKnowledgeSegmentService {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取段落分页
 | 
			
		||||
     *
 | 
			
		||||
     * @param pageReqVO 分页查询
 | 
			
		||||
     * @return 文档分页
 | 
			
		||||
     */
 | 
			
		||||
    PageResult<AiKnowledgeSegmentDO> getKnowledgeSegmentPage(AiKnowledgeSegmentPageReqVO pageReqVO);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新段落内容
 | 
			
		||||
     *
 | 
			
		||||
     * @param reqVO 更新内容
 | 
			
		||||
     */
 | 
			
		||||
    void updateKnowledgeSegment(AiKnowledgeSegmentUpdateReqVO reqVO);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新状态
 | 
			
		||||
     *
 | 
			
		||||
     * @param reqVO 更新内容
 | 
			
		||||
     */
 | 
			
		||||
    void updateKnowledgeSegmentStatus(AiKnowledgeSegmentUpdateStatusReqVO reqVO);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,13 @@
 | 
			
		||||
package cn.iocoder.yudao.module.ai.service.knowledge;
 | 
			
		||||
 | 
			
		||||
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.AiKnowledgeSegmentUpdateReqVO;
 | 
			
		||||
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO;
 | 
			
		||||
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
 | 
			
		||||
import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeSegmentMapper;
 | 
			
		||||
import jakarta.annotation.Resource;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
 | 
			
		||||
@@ -12,4 +20,23 @@ import org.springframework.stereotype.Service;
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService {
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private AiKnowledgeSegmentMapper segmentMapper;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public PageResult<AiKnowledgeSegmentDO> getKnowledgeSegmentPage(AiKnowledgeSegmentPageReqVO pageReqVO) {
 | 
			
		||||
        return segmentMapper.selectPage(pageReqVO);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void updateKnowledgeSegment(AiKnowledgeSegmentUpdateReqVO reqVO) {
 | 
			
		||||
        segmentMapper.updateById(BeanUtils.toBean(reqVO, AiKnowledgeSegmentDO.class));
 | 
			
		||||
        // TODO @xin 重新向量化
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void updateKnowledgeSegmentStatus(AiKnowledgeSegmentUpdateStatusReqVO reqVO) {
 | 
			
		||||
        segmentMapper.updateById(BeanUtils.toBean(reqVO, AiKnowledgeSegmentDO.class));
 | 
			
		||||
        // TODO @xin 1.禁用删除向量 2.启用重新向量化
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,50 @@
 | 
			
		||||
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.dal.dataobject.knowledge.AiKnowledgeDO;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * AI 知识库-基础信息 Service 接口
 | 
			
		||||
 *
 | 
			
		||||
 * @author xiaoxin
 | 
			
		||||
 */
 | 
			
		||||
public interface AiKnowledgeService {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 创建【我的】知识库
 | 
			
		||||
     *
 | 
			
		||||
     * @param createReqVO 创建信息
 | 
			
		||||
     * @param userId      用户编号
 | 
			
		||||
     * @return 编号
 | 
			
		||||
     */
 | 
			
		||||
    Long createKnowledgeMy(AiKnowledgeCreateMyReqVO createReqVO, Long userId);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 创建【我的】知识库
 | 
			
		||||
     *
 | 
			
		||||
     * @param updateReqVO 更新信息
 | 
			
		||||
     * @param userId      用户编号
 | 
			
		||||
     */
 | 
			
		||||
    void updateKnowledgeMy(AiKnowledgeUpdateMyReqVO updateReqVO, Long userId);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 校验知识库是否存在
 | 
			
		||||
     *
 | 
			
		||||
     * @param id 记录编号
 | 
			
		||||
     */
 | 
			
		||||
    AiKnowledgeDO validateKnowledgeExists(Long id);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获得【我的】知识库分页
 | 
			
		||||
     *
 | 
			
		||||
     * @param userId 用户编号
 | 
			
		||||
     * @param pageReqVO   分页查询
 | 
			
		||||
     * @return 知识库分页
 | 
			
		||||
     */
 | 
			
		||||
    PageResult<AiKnowledgeDO> getKnowledgePageMy(Long userId, PageParam pageReqVO);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,14 +1,15 @@
 | 
			
		||||
package cn.iocoder.yudao.module.ai.service.knowledge;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.lang.Assert;
 | 
			
		||||
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.AiKnowledgeCreateMyReqVO;
 | 
			
		||||
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeUpdateMyReqVO;
 | 
			
		||||
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeBaseDO;
 | 
			
		||||
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 cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
 | 
			
		||||
import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeBaseMapper;
 | 
			
		||||
import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeMapper;
 | 
			
		||||
import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
 | 
			
		||||
import jakarta.annotation.Resource;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
@@ -24,56 +25,54 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_NOT_
 | 
			
		||||
 */
 | 
			
		||||
@Service
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class AiKnowledgeBaseServiceImpl implements AiKnowledgeBaseService {
 | 
			
		||||
public class AiKnowledgeServiceImpl implements AiKnowledgeService {
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private AiChatModelService chatModalService;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private AiKnowledgeBaseMapper knowledgeBaseMapper;
 | 
			
		||||
    private AiKnowledgeMapper knowledgeMapper;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Long createKnowledgeMy(AiKnowledgeCreateMyReqVO createReqVO, Long userId) {
 | 
			
		||||
        // TODO @xin:貌似直接调用 chatModalService.validateChatModel(id) 完事,不用搞个方法
 | 
			
		||||
        // 1. 校验模型配置
 | 
			
		||||
        AiChatModelDO model = validateChatModel(createReqVO.getModelId());
 | 
			
		||||
        AiChatModelDO model = chatModalService.validateChatModel(createReqVO.getModelId());
 | 
			
		||||
 | 
			
		||||
        // 2. 插入知识库
 | 
			
		||||
        // TODO @xin:不用 DO 结尾
 | 
			
		||||
        AiKnowledgeBaseDO knowledgeBaseDO = BeanUtils.toBean(createReqVO, AiKnowledgeBaseDO.class)
 | 
			
		||||
        AiKnowledgeDO knowledgeBase = BeanUtils.toBean(createReqVO, AiKnowledgeDO.class)
 | 
			
		||||
                .setModel(model.getModel()).setUserId(userId).setStatus(CommonStatusEnum.ENABLE.getStatus());
 | 
			
		||||
        knowledgeBaseMapper.insert(knowledgeBaseDO);
 | 
			
		||||
        return knowledgeBaseDO.getId();
 | 
			
		||||
        knowledgeMapper.insert(knowledgeBase);
 | 
			
		||||
        return knowledgeBase.getId();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void updateKnowledgeMy(AiKnowledgeUpdateMyReqVO updateReqVO, Long userId) {
 | 
			
		||||
        // 1.1 校验知识库存在
 | 
			
		||||
        AiKnowledgeBaseDO knowledgeBaseDO = validateKnowledgeExists(updateReqVO.getId());
 | 
			
		||||
        AiKnowledgeDO knowledgeBaseDO = validateKnowledgeExists(updateReqVO.getId());
 | 
			
		||||
        if (ObjUtil.notEqual(knowledgeBaseDO.getUserId(), userId)) {
 | 
			
		||||
            throw exception(KNOWLEDGE_NOT_EXISTS);
 | 
			
		||||
        }
 | 
			
		||||
        // 1.2 校验模型配置
 | 
			
		||||
        AiChatModelDO model = validateChatModel(updateReqVO.getModelId());
 | 
			
		||||
        AiChatModelDO model = chatModalService.validateChatModel(updateReqVO.getModelId());
 | 
			
		||||
 | 
			
		||||
        // 2. 更新知识库
 | 
			
		||||
        AiKnowledgeBaseDO updateDO = BeanUtils.toBean(updateReqVO, AiKnowledgeBaseDO.class);
 | 
			
		||||
        AiKnowledgeDO updateDO = BeanUtils.toBean(updateReqVO, AiKnowledgeDO.class);
 | 
			
		||||
        updateDO.setModel(model.getModel());
 | 
			
		||||
        knowledgeBaseMapper.updateById(updateDO);
 | 
			
		||||
        knowledgeMapper.updateById(updateDO);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private AiChatModelDO validateChatModel(Long id) {
 | 
			
		||||
        AiChatModelDO model = chatModalService.validateChatModel(id);
 | 
			
		||||
        Assert.notNull(model, "未找到对应嵌入模型");
 | 
			
		||||
        return model;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public AiKnowledgeBaseDO validateKnowledgeExists(Long id) {
 | 
			
		||||
        AiKnowledgeBaseDO knowledgeBase = knowledgeBaseMapper.selectById(id);
 | 
			
		||||
    @Override
 | 
			
		||||
    public AiKnowledgeDO validateKnowledgeExists(Long id) {
 | 
			
		||||
        AiKnowledgeDO knowledgeBase = knowledgeMapper.selectById(id);
 | 
			
		||||
        if (knowledgeBase == null) {
 | 
			
		||||
            throw exception(KNOWLEDGE_NOT_EXISTS);
 | 
			
		||||
        }
 | 
			
		||||
        return knowledgeBase;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public PageResult<AiKnowledgeDO> getKnowledgePageMy(Long userId, PageParam pageReqVO) {
 | 
			
		||||
        return knowledgeMapper.selectPageByMy(userId, pageReqVO);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -9,7 +9,9 @@ import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveR
 | 
			
		||||
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
 | 
			
		||||
import jakarta.validation.Valid;
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
@@ -83,6 +85,14 @@ public interface AiApiKeyService {
 | 
			
		||||
     */
 | 
			
		||||
    ChatModel getChatModel(Long id);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获得 EmbeddingModel 对象
 | 
			
		||||
     *
 | 
			
		||||
     * @param id 编号
 | 
			
		||||
     * @return EmbeddingModel 对象
 | 
			
		||||
     */
 | 
			
		||||
    EmbeddingModel getEmbeddingModel(Long id);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获得 ImageModel 对象
 | 
			
		||||
     *
 | 
			
		||||
@@ -111,4 +121,12 @@ public interface AiApiKeyService {
 | 
			
		||||
     */
 | 
			
		||||
    SunoApi getSunoApi();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获得 vector 对象
 | 
			
		||||
     *
 | 
			
		||||
     * @param id 编号
 | 
			
		||||
     * @return VectorStore 对象
 | 
			
		||||
     */
 | 
			
		||||
    VectorStore getOrCreateVectorStore(Long id);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -2,6 +2,7 @@ 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;
 | 
			
		||||
@@ -13,7 +14,9 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
 | 
			
		||||
import cn.iocoder.yudao.module.ai.dal.mysql.model.AiApiKeyMapper;
 | 
			
		||||
import jakarta.annotation.Resource;
 | 
			
		||||
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;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
import org.springframework.validation.annotation.Validated;
 | 
			
		||||
 | 
			
		||||
@@ -36,6 +39,8 @@ public class AiApiKeyServiceImpl implements AiApiKeyService {
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private AiModelFactory modelFactory;
 | 
			
		||||
    @Resource
 | 
			
		||||
    private AiVectorStoreFactory vectorFactory;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Long createApiKey(AiApiKeySaveReqVO createReqVO) {
 | 
			
		||||
@@ -104,6 +109,13 @@ public class AiApiKeyServiceImpl implements AiApiKeyService {
 | 
			
		||||
        return modelFactory.getOrCreateChatModel(platform, apiKey.getApiKey(), apiKey.getUrl());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public EmbeddingModel getEmbeddingModel(Long id) {
 | 
			
		||||
        AiApiKeyDO apiKey = validateApiKey(id);
 | 
			
		||||
        AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform());
 | 
			
		||||
        return modelFactory.getOrCreateEmbeddingModel(platform, apiKey.getApiKey(), apiKey.getUrl());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public ImageModel getImageModel(AiPlatformEnum platform) {
 | 
			
		||||
        AiApiKeyDO apiKey = apiKeyMapper.selectFirstByPlatformAndStatus(platform.getPlatform(), CommonStatusEnum.ENABLE.getStatus());
 | 
			
		||||
@@ -132,4 +144,11 @@ public class AiApiKeyServiceImpl implements AiApiKeyService {
 | 
			
		||||
        }
 | 
			
		||||
        return modelFactory.getOrCreateSunoApi(apiKey.getApiKey(), apiKey.getUrl());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    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());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -2,6 +2,8 @@ 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;
 | 
			
		||||
@@ -10,20 +12,15 @@ import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;
 | 
			
		||||
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatOptions;
 | 
			
		||||
import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreProperties;
 | 
			
		||||
import org.springframework.ai.document.MetadataMode;
 | 
			
		||||
import org.springframework.ai.embedding.EmbeddingModel;
 | 
			
		||||
import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator;
 | 
			
		||||
import org.springframework.ai.tokenizer.TokenCountEstimator;
 | 
			
		||||
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
 | 
			
		||||
import org.springframework.ai.transformers.TransformersEmbeddingModel;
 | 
			
		||||
import org.springframework.ai.vectorstore.RedisVectorStore;
 | 
			
		||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
 | 
			
		||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 | 
			
		||||
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
 | 
			
		||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
 | 
			
		||||
import org.springframework.context.annotation.Bean;
 | 
			
		||||
import org.springframework.context.annotation.Import;
 | 
			
		||||
import org.springframework.context.annotation.Lazy;
 | 
			
		||||
import redis.clients.jedis.JedisPooled;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 芋道 AI 自动配置
 | 
			
		||||
@@ -41,6 +38,12 @@ public class YudaoAiAutoConfiguration {
 | 
			
		||||
        return new AiModelFactoryImpl();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Bean
 | 
			
		||||
    public AiVectorStoreFactory aiVectorFactory() {
 | 
			
		||||
        return new AiVectorStoreFactoryImpl();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // ========== 各种 AI Client 创建 ==========
 | 
			
		||||
 | 
			
		||||
    @Bean
 | 
			
		||||
@@ -83,30 +86,31 @@ public class YudaoAiAutoConfiguration {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ========== rag 相关 ==========
 | 
			
		||||
    @Bean
 | 
			
		||||
    @Lazy // TODO 芋艿:临时注释,避免无法启动
 | 
			
		||||
    public EmbeddingModel transformersEmbeddingClient() {
 | 
			
		||||
        return new TransformersEmbeddingModel(MetadataMode.EMBED);
 | 
			
		||||
    }
 | 
			
		||||
    // TODO @xin 免费版本
 | 
			
		||||
//    @Bean
 | 
			
		||||
//    @Lazy // TODO 芋艿:临时注释,避免无法启动」
 | 
			
		||||
//    public EmbeddingModel transformersEmbeddingClient() {
 | 
			
		||||
//        return new TransformersEmbeddingModel(MetadataMode.EMBED);
 | 
			
		||||
//    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 我们启动有加载很多 Embedding 模型,不晓得取哪个好,先 new 个 TransformersEmbeddingModel 跑
 | 
			
		||||
     * TODO @xin 默认版本先不弄,目前都先取对应的 EmbeddingModel
 | 
			
		||||
     */
 | 
			
		||||
    @Bean
 | 
			
		||||
    @Lazy // TODO 芋艿:临时注释,避免无法启动
 | 
			
		||||
    public RedisVectorStore vectorStore(TransformersEmbeddingModel transformersEmbeddingModel, RedisVectorStoreProperties properties,
 | 
			
		||||
                                        RedisProperties redisProperties) {
 | 
			
		||||
        var config = RedisVectorStore.RedisVectorStoreConfig.builder()
 | 
			
		||||
                .withIndexName(properties.getIndex())
 | 
			
		||||
                .withPrefix(properties.getPrefix())
 | 
			
		||||
                .build();
 | 
			
		||||
 | 
			
		||||
        RedisVectorStore redisVectorStore = new RedisVectorStore(config, transformersEmbeddingModel,
 | 
			
		||||
                new JedisPooled(redisProperties.getHost(), redisProperties.getPort()),
 | 
			
		||||
                properties.isInitializeSchema());
 | 
			
		||||
        redisVectorStore.afterPropertiesSet();
 | 
			
		||||
        return redisVectorStore;
 | 
			
		||||
    }
 | 
			
		||||
//    @Bean
 | 
			
		||||
//    @Lazy // TODO 芋艿:临时注释,避免无法启动
 | 
			
		||||
//    public RedisVectorStore vectorStore(TongYiTextEmbeddingModel tongYiTextEmbeddingModel, RedisVectorStoreProperties properties,
 | 
			
		||||
//                                        RedisProperties redisProperties) {
 | 
			
		||||
//        var config = RedisVectorStore.RedisVectorStoreConfig.builder()
 | 
			
		||||
//                .withIndexName(properties.getIndex())
 | 
			
		||||
//                .withPrefix(properties.getPrefix())
 | 
			
		||||
//                .build();
 | 
			
		||||
//
 | 
			
		||||
//        RedisVectorStore redisVectorStore = new RedisVectorStore(config, tongYiTextEmbeddingModel,
 | 
			
		||||
//                new JedisPooled(redisProperties.getHost(), redisProperties.getPort()),
 | 
			
		||||
//                properties.isInitializeSchema());
 | 
			
		||||
//        redisVectorStore.afterPropertiesSet();
 | 
			
		||||
//        return redisVectorStore;
 | 
			
		||||
//    }
 | 
			
		||||
 | 
			
		||||
    @Bean
 | 
			
		||||
    @Lazy // TODO 芋艿:临时注释,避免无法启动
 | 
			
		||||
@@ -114,4 +118,10 @@ public class YudaoAiAutoConfiguration {
 | 
			
		||||
        return new TokenTextSplitter(500, 100, 5, 10000, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Bean
 | 
			
		||||
    @Lazy // TODO 芋艿:临时注释,避免无法启动
 | 
			
		||||
    public TokenCountEstimator tokenCountEstimator() {
 | 
			
		||||
        return new JTokkitTokenCountEstimator();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
 | 
			
		||||
import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -25,6 +26,18 @@ public interface AiModelFactory {
 | 
			
		||||
     */
 | 
			
		||||
    ChatModel getOrCreateChatModel(AiPlatformEnum platform, String apiKey, String url);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 基于指定配置,获得 EmbeddingModel 对象
 | 
			
		||||
     * <p>
 | 
			
		||||
     * 如果不存在,则进行创建
 | 
			
		||||
     *
 | 
			
		||||
     * @param platform 平台
 | 
			
		||||
     * @param apiKey   API KEY
 | 
			
		||||
     * @param url      API URL
 | 
			
		||||
     * @return ChatModel 对象
 | 
			
		||||
     */
 | 
			
		||||
    EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String apiKey, String url);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 基于默认配置,获得 ChatModel 对象
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ import com.alibaba.cloud.ai.tongyi.image.TongYiImagesModel;
 | 
			
		||||
import com.alibaba.cloud.ai.tongyi.image.TongYiImagesProperties;
 | 
			
		||||
import com.alibaba.dashscope.aigc.generation.Generation;
 | 
			
		||||
import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis;
 | 
			
		||||
import com.alibaba.dashscope.embeddings.TextEmbedding;
 | 
			
		||||
import com.azure.ai.openai.OpenAIClient;
 | 
			
		||||
import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiAutoConfiguration;
 | 
			
		||||
import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiChatProperties;
 | 
			
		||||
@@ -37,6 +38,7 @@ import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiConnectionProperties;
 | 
			
		||||
import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiImageProperties;
 | 
			
		||||
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
 | 
			
		||||
import org.springframework.ai.chat.model.ChatModel;
 | 
			
		||||
import org.springframework.ai.embedding.EmbeddingModel;
 | 
			
		||||
import org.springframework.ai.image.ImageModel;
 | 
			
		||||
import org.springframework.ai.model.function.FunctionCallbackContext;
 | 
			
		||||
import org.springframework.ai.ollama.OllamaChatModel;
 | 
			
		||||
@@ -97,6 +99,21 @@ public class AiModelFactoryImpl implements AiModelFactory {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String apiKey, String url) {
 | 
			
		||||
        String cacheKey = buildClientCacheKey(EmbeddingModel.class, platform, apiKey, url);
 | 
			
		||||
        return Singleton.get(cacheKey, (Func0<EmbeddingModel>) () -> {
 | 
			
		||||
            // TODO @xin 先测试一个
 | 
			
		||||
            switch (platform) {
 | 
			
		||||
                case TONG_YI:
 | 
			
		||||
                    return buildTongYiEmbeddingModel(apiKey);
 | 
			
		||||
                default:
 | 
			
		||||
                    throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public ChatModel getDefaultChatModel(AiPlatformEnum platform) {
 | 
			
		||||
        //noinspection EnhancedSwitchMigration
 | 
			
		||||
@@ -239,7 +256,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 可参考 {@link ZhiPuAiAutoConfiguration#zhiPuAiChatModel(
 | 
			
		||||
     * ZhiPuAiConnectionProperties, ZhiPuAiChatProperties, RestClient.Builder, List, FunctionCallbackContext, RetryTemplate, ResponseErrorHandler)}
 | 
			
		||||
     *ZhiPuAiConnectionProperties, ZhiPuAiChatProperties, RestClient.Builder, List, FunctionCallbackContext, RetryTemplate, ResponseErrorHandler)}
 | 
			
		||||
     */
 | 
			
		||||
    private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) {
 | 
			
		||||
        url = StrUtil.blankToDefault(url, ZhiPuAiConnectionProperties.DEFAULT_BASE_URL);
 | 
			
		||||
@@ -249,7 +266,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 可参考 {@link ZhiPuAiAutoConfiguration#zhiPuAiImageModel(
 | 
			
		||||
     * ZhiPuAiConnectionProperties, ZhiPuAiImageProperties, RestClient.Builder, RetryTemplate, ResponseErrorHandler)}
 | 
			
		||||
     *ZhiPuAiConnectionProperties, ZhiPuAiImageProperties, RestClient.Builder, RetryTemplate, ResponseErrorHandler)}
 | 
			
		||||
     */
 | 
			
		||||
    private ZhiPuAiImageModel buildZhiPuAiImageModel(String apiKey, String url) {
 | 
			
		||||
        url = StrUtil.blankToDefault(url, ZhiPuAiConnectionProperties.DEFAULT_BASE_URL);
 | 
			
		||||
@@ -315,4 +332,15 @@ public class AiModelFactoryImpl implements AiModelFactory {
 | 
			
		||||
        return new StabilityAiImageModel(stabilityAiApi);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ========== 各种创建 EmbeddingModel 的方法 ==========
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 可参考 {@link TongYiAutoConfiguration#tongYiTextEmbeddingClient(TextEmbedding, TongYiConnectionProperties)}
 | 
			
		||||
     */
 | 
			
		||||
    private EmbeddingModel buildTongYiEmbeddingModel(String apiKey) {
 | 
			
		||||
        TongYiConnectionProperties connectionProperties = new TongYiConnectionProperties();
 | 
			
		||||
        connectionProperties.setApiKey(apiKey);
 | 
			
		||||
        return new TongYiAutoConfiguration().tongYiTextEmbeddingClient(SpringUtil.getBean(TextEmbedding.class), connectionProperties);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,27 @@
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * AI Vector 模型工厂的接口类
 | 
			
		||||
 * @author xiaoxin
 | 
			
		||||
 */
 | 
			
		||||
public interface AiVectorStoreFactory {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 基于指定配置,获得 VectorStore 对象
 | 
			
		||||
     * <p>
 | 
			
		||||
     * 如果不存在,则进行创建
 | 
			
		||||
     *
 | 
			
		||||
     * @param embeddingModel 嵌入模型
 | 
			
		||||
     * @param platform       平台
 | 
			
		||||
     * @param apiKey         API KEY
 | 
			
		||||
     * @param url            API URL
 | 
			
		||||
     * @return VectorStore 对象
 | 
			
		||||
     */
 | 
			
		||||
    VectorStore getOrCreateVectorStore(EmbeddingModel embeddingModel, AiPlatformEnum platform, String apiKey, String url);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,51 @@
 | 
			
		||||
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<VectorStore>) () -> {
 | 
			
		||||
            // TODO 芋艿 @xin 这两个配置取哪好呢
 | 
			
		||||
            // TODO 不同模型的向量维度可能会不一样,目前看貌似是以 index 来做区分的,维度不一样存不到一个 index 上
 | 
			
		||||
            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, "_"));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -19,6 +19,7 @@ import org.springframework.ai.embedding.EmbeddingModel;
 | 
			
		||||
import org.springframework.ai.vectorstore.RedisVectorStore;
 | 
			
		||||
import org.springframework.ai.vectorstore.RedisVectorStore.RedisVectorStoreConfig;
 | 
			
		||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
 | 
			
		||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
 | 
			
		||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 | 
			
		||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 | 
			
		||||
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
 | 
			
		||||
@@ -38,7 +39,7 @@ import redis.clients.jedis.JedisPooled;
 | 
			
		||||
 */
 | 
			
		||||
@AutoConfiguration(after = RedisAutoConfiguration.class)
 | 
			
		||||
@ConditionalOnClass({JedisPooled.class, JedisConnectionFactory.class, RedisVectorStore.class, EmbeddingModel.class})
 | 
			
		||||
//@ConditionalOnBean(JedisConnectionFactory.class)
 | 
			
		||||
@ConditionalOnBean(JedisConnectionFactory.class)
 | 
			
		||||
@EnableConfigurationProperties(RedisVectorStoreProperties.class)
 | 
			
		||||
public class RedisVectorStoreAutoConfiguration {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							
		Reference in New Issue
	
	Block a user