!19 AI 知识库

Merge pull request !19 from 小新/master-jdk21-ai
This commit is contained in:
芋道源码 2024-08-31 05:54:04 +00:00 committed by Gitee
commit 261011d1a8
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
41 changed files with 811 additions and 229 deletions

View File

@ -1,11 +1,8 @@
package cn.iocoder.yudao.module.ai.enums; package cn.iocoder.yudao.module.ai.enums;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import java.util.Arrays;
/** /**
* AI 内置聊天角色的枚举 * AI 内置聊天角色的枚举
* *
@ -13,16 +10,16 @@ import java.util.Arrays;
*/ */
@AllArgsConstructor @AllArgsConstructor
@Getter @Getter
public enum AiChatRoleEnum implements IntArrayValuable { public enum AiChatRoleEnum {
AI_WRITE_ROLE(1, "写作助手", """ AI_WRITE_ROLE("写作助手", """
你是一位出色的写作助手能够帮助用户生成创意和灵感并在用户提供场景和提示词时生成对应的回复你的任务包括 你是一位出色的写作助手能够帮助用户生成创意和灵感并在用户提供场景和提示词时生成对应的回复你的任务包括
1. 撰写建议根据用户提供的主题或问题提供详细的写作建议情节发展方向角色设定以及背景描写确保内容结构清晰有逻辑 1. 撰写建议根据用户提供的主题或问题提供详细的写作建议情节发展方向角色设定以及背景描写确保内容结构清晰有逻辑
2. 回复生成根据用户提供的场景和提示词生成合适的对话或文字回复确保语气和风格符合场景需求 2. 回复生成根据用户提供的场景和提示词生成合适的对话或文字回复确保语气和风格符合场景需求
除此之外不需要除了正文内容外的其他回复如标题开头任何解释性语句或道歉 除此之外不需要除了正文内容外的其他回复如标题开头任何解释性语句或道歉
"""), """),
AI_MIND_MAP_ROLE(2, "导图助手", """ AI_MIND_MAP_ROLE("导图助手", """
你是一位非常优秀的思维导图助手你会把用户的所有提问都总结成思维导图然后以 Markdown 格式输出markdown 只需要输出一级标题二级标题三级标题四级标题最多输出四级除此之外不要输出任何其他 markdown 标记下面是一个合格的例子 你是一位非常优秀的思维导图助手你会把用户的所有提问都总结成思维导图然后以 Markdown 格式输出markdown 只需要输出一级标题二级标题三级标题四级标题最多输出四级除此之外不要输出任何其他 markdown 标记下面是一个合格的例子
# Geek-AI 助手 # 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; private final String systemMessage;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AiChatRoleEnum::getRole).toArray();
@Override
public int[] array() {
return ARRAYS;
}
} }

View File

@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
/** /**
* AI 错误码枚举类 * AI 错误码枚举类
* * <p>
* ai 系统使用 1-040-000-000 * ai 系统使用 1-040-000-000
*/ */
public interface ErrorCodeConstants { public interface ErrorCodeConstants {
@ -55,5 +55,7 @@ public interface ErrorCodeConstants {
// ========== API 知识库 1-022-008-000 ========== // ========== API 知识库 1-022-008-000 ==========
ErrorCode KNOWLEDGE_NOT_EXISTS = new ErrorCode(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, "段落不存在!");
} }

View File

@ -1,13 +1,19 @@
package cn.iocoder.yudao.module.ai.controller.admin.knowledge; package cn.iocoder.yudao.module.ai.controller.admin.knowledge;
import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeCreateMyReqVO; import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeUpdateMyReqVO; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeBaseService; 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.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 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 { public class AiKnowledgeController {
@Resource @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") @PostMapping("/create-my")
@Operation(summary = "创建【我的】知识库") @Operation(summary = "创建【我的】知识库")
public CommonResult<Long> createKnowledgeMy(@RequestBody @Valid AiKnowledgeCreateMyReqVO createReqVO) { public CommonResult<Long> createKnowledgeMy(@RequestBody @Valid AiKnowledgeCreateMyReqVO createReqVO) {
return success(knowledgeBaseService.createKnowledgeMy(createReqVO, getLoginUserId())); return success(knowledgeService.createKnowledgeMy(createReqVO, getLoginUserId()));
} }
@PutMapping("/update-my") @PutMapping("/update-my")
@Operation(summary = "更新【我的】知识库") @Operation(summary = "更新【我的】知识库")
public CommonResult<Boolean> updateKnowledgeMy(@RequestBody @Valid AiKnowledgeUpdateMyReqVO updateReqVO) { public CommonResult<Boolean> updateKnowledgeMy(@RequestBody @Valid AiKnowledgeUpdateMyReqVO updateReqVO) {
knowledgeBaseService.updateKnowledgeMy(updateReqVO, getLoginUserId()); knowledgeService.updateKnowledgeMy(updateReqVO, getLoginUserId());
return success(true); return success(true);
} }

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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 io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;

View File

@ -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 io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
@ -6,10 +6,8 @@ import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
import org.hibernate.validator.constraints.URL; import org.hibernate.validator.constraints.URL;
/**
* @author xiaoxin @Schema(description = "管理后台 - AI 知识库创建【文档】 Request VO")
*/
@Schema(description = "管理后台 - AI 知识库【创建文档】 Request VO")
@Data @Data
public class AiKnowledgeDocumentCreateReqVO { public class AiKnowledgeDocumentCreateReqVO {

View File

@ -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;
}

View File

@ -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 io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
@ -7,7 +7,7 @@ import lombok.Data;
import java.util.List; import java.util.List;
@Schema(description = "管理后台 - AI 知识库创建【我的】 Request VO") @Schema(description = "管理后台 - AI 知识库更新【我的】 Request VO")
@Data @Data
public class AiKnowledgeUpdateMyReqVO { public class AiKnowledgeUpdateMyReqVO {

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -10,15 +10,14 @@ import lombok.Data;
import java.util.List; import java.util.List;
// TODO @xin要不把 AiKnowledgeBaseDO 改成 AiKnowledgeDO感觉 base 后缀感觉有点奇怪让人以为是基类然后我们很多地方的外键编号都是 knowledgeId
/** /**
* AI 知识库 DO * AI 知识库 DO
* *
* @author xiaoxin * @author xiaoxin
*/ */
@TableName(value = "ai_knowledge_base", autoResultMap = true) @TableName(value = "ai_knowledge", autoResultMap = true)
@Data @Data
public class AiKnowledgeBaseDO extends BaseDO { public class AiKnowledgeDO extends BaseDO {
/** /**
* 编号 * 编号
@ -46,7 +45,7 @@ public class AiKnowledgeBaseDO extends BaseDO {
@TableField(typeHandler = JacksonTypeHandler.class) @TableField(typeHandler = JacksonTypeHandler.class)
private List<Long> visibilityPermissions; private List<Long> visibilityPermissions;
/** /**
* 嵌入模型编号高质量模式时维护 * 嵌入模型编号
*/ */
private Long modelId; private Long modelId;
/** /**

View File

@ -24,7 +24,7 @@ public class AiKnowledgeDocumentDO extends BaseDO {
/** /**
* 知识库编号 * 知识库编号
* *
* 关联 {@link AiKnowledgeBaseDO#getId()} * 关联 {@link AiKnowledgeDO#getId()}
*/ */
private Long knowledgeId; private Long knowledgeId;
/** /**

View File

@ -15,6 +15,8 @@ import lombok.Data;
@Data @Data
public class AiKnowledgeSegmentDO extends BaseDO { 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; private String vectorId;
// TODO @新knowledgeId 加个会方便点 /**
* 知识库编号
* 关联 {@link AiKnowledgeDO#getId()}
*/
private Long knowledgeId;
/** /**
* 文档编号 * 文档编号
* * <p>
* 关联 {@link AiKnowledgeDocumentDO#getId()} * 关联 {@link AiKnowledgeDocumentDO#getId()}
*/ */
private Long documentId; private Long documentId;

View File

@ -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> {
}

View File

@ -1,6 +1,9 @@
package cn.iocoder.yudao.module.ai.dal.mysql.knowledge; 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.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 cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
@ -11,4 +14,11 @@ import org.apache.ibatis.annotations.Mapper;
*/ */
@Mapper @Mapper
public interface AiKnowledgeDocumentMapper extends BaseMapperX<AiKnowledgeDocumentDO> { 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));
}
} }

View File

@ -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));
}
}

View File

@ -1,6 +1,9 @@
package cn.iocoder.yudao.module.ai.dal.mysql.knowledge; 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.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 cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
@ -11,4 +14,12 @@ import org.apache.ibatis.annotations.Mapper;
*/ */
@Mapper @Mapper
public interface AiKnowledgeSegmentMapper extends BaseMapperX<AiKnowledgeSegmentDO> { 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));
}
} }

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -1,6 +1,10 @@
package cn.iocoder.yudao.module.ai.service.knowledge; 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 接口 * AI 知识库-文档 Service 接口
@ -17,4 +21,19 @@ public interface AiKnowledgeDocumentService {
*/ */
Long createKnowledgeDocument(AiKnowledgeDocumentCreateReqVO createReqVO); Long createKnowledgeDocument(AiKnowledgeDocumentCreateReqVO createReqVO);
/**
* 获取文档分页
*
* @param pageReqVO 分页参数
* @return 文档分页
*/
PageResult<AiKnowledgeDocumentDO> getKnowledgeDocumentPage(AiKnowledgeDocumentPageReqVO pageReqVO);
/**
* 更新文档
*
* @param reqVO 更新信息
*/
void updateKnowledgeDocument(AiKnowledgeDocumentUpdateReqVO reqVO);
} }

View File

@ -1,27 +1,39 @@
package cn.iocoder.yudao.module.ai.service.knowledge; package cn.iocoder.yudao.module.ai.service.knowledge;
import cn.hutool.core.collection.CollUtil; 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.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.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 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.AiKnowledgeDocumentDO;
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; 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.AiKnowledgeDocumentMapper;
import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeSegmentMapper; 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.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 jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.document.Document; import org.springframework.ai.document.Document;
import org.springframework.ai.reader.tika.TikaDocumentReader; 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.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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.List; 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 实现类 * AI 知识库-文档 Service 实现类
@ -39,53 +51,99 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic
@Resource @Resource
private TokenTextSplitter tokenTextSplitter; private TokenTextSplitter tokenTextSplitter;
@Resource
private TokenCountEstimator tokenCountEstimator;
@Resource @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 芋艿需要 review 代码格式
// TODO @xin最好有 1/2/3 这种让代码更有层次感
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public Long createKnowledgeDocument(AiKnowledgeDocumentCreateReqVO createReqVO) { public Long createKnowledgeDocument(AiKnowledgeDocumentCreateReqVO createReqVO) {
// TODO xiaoxin 后续从 url 加载 // 1.1 下载文档
TikaDocumentReader loader = new TikaDocumentReader(data); String url = createReqVO.getUrl();
// 加载文档 // 1.2 加载文档
TikaDocumentReader loader = new TikaDocumentReader(downloadFile(url));
List<Document> documents = loader.get(); List<Document> documents = loader.get();
Document document = CollUtil.getFirst(documents); Document document = CollUtil.getFirst(documents);
// TODO @xin是不是不存在就抛出异常呀厚泽 return String content = document.getContent();
// TODO 芋艿 文档层面有没有可能会比较大这两个字段是否可以从分段表计算得出回复先直接算 Integer tokens = tokenCountEstimator.estimate(content);
Integer tokens = Objects.nonNull(document) ? TOKEN_COUNT_ESTIMATOR.estimate(document.getContent()) : 0; Integer wordCount = content.length();
Integer wordCount = Objects.nonNull(document) ? document.getContent().length() : 0;
// 1.3 文档记录入库
AiKnowledgeDocumentDO documentDO = BeanUtils.toBean(createReqVO, AiKnowledgeDocumentDO.class) AiKnowledgeDocumentDO documentDO = BeanUtils.toBean(createReqVO, AiKnowledgeDocumentDO.class)
.setTokens(tokens).setWordCount(wordCount) .setTokens(tokens).setWordCount(wordCount)
.setStatus(CommonStatusEnum.ENABLE.getStatus()).setSliceStatus(AiKnowledgeDocumentStatusEnum.SUCCESS.getStatus()); .setStatus(CommonStatusEnum.ENABLE.getStatus()).setSliceStatus(AiKnowledgeDocumentStatusEnum.SUCCESS.getStatus());
// 文档记录入库
documentMapper.insert(documentDO); documentMapper.insert(documentDO);
Long documentId = documentDO.getId(); Long documentId = documentDO.getId();
if (CollUtil.isEmpty(documents)) { if (CollUtil.isEmpty(documents)) {
return documentId; return documentId;
} }
// 文档分段 // 2.1 文档分段
List<Document> segments = tokenTextSplitter.apply(documents); List<Document> segments = tokenTextSplitter.apply(documents);
// 分段内容入库 // 2.2 分段内容入库
List<AiKnowledgeSegmentDO> segmentDOList = CollectionUtils.convertList(segments, List<AiKnowledgeSegmentDO> segmentDOList = CollectionUtils.convertList(segments,
segment -> new AiKnowledgeSegmentDO().setContent(segment.getContent()).setDocumentId(documentId) segment -> new AiKnowledgeSegmentDO().setContent(segment.getContent()).setDocumentId(documentId).setKnowledgeId(createReqVO.getKnowledgeId()).setVectorId(segment.getId())
.setTokens(TOKEN_COUNT_ESTIMATOR.estimate(segment.getContent())).setWordCount(segment.getContent().length()) .setTokens(tokenCountEstimator.estimate(segment.getContent())).setWordCount(segment.getContent().length())
.setStatus(CommonStatusEnum.ENABLE.getStatus())); .setStatus(CommonStatusEnum.ENABLE.getStatus()));
segmentMapper.insertBatch(segmentDOList); 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; 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);
}
}
} }

View File

@ -1,5 +1,11 @@
package cn.iocoder.yudao.module.ai.service.knowledge; 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 接口 * AI 知识库分片 Service 接口
* *
@ -7,4 +13,25 @@ package cn.iocoder.yudao.module.ai.service.knowledge;
*/ */
public interface AiKnowledgeSegmentService { public interface AiKnowledgeSegmentService {
/**
* 获取段落分页
*
* @param pageReqVO 分页查询
* @return 文档分页
*/
PageResult<AiKnowledgeSegmentDO> getKnowledgeSegmentPage(AiKnowledgeSegmentPageReqVO pageReqVO);
/**
* 更新段落内容
*
* @param reqVO 更新内容
*/
void updateKnowledgeSegment(AiKnowledgeSegmentUpdateReqVO reqVO);
/**
* 更新状态
*
* @param reqVO 更新内容
*/
void updateKnowledgeSegmentStatus(AiKnowledgeSegmentUpdateStatusReqVO reqVO);
} }

View File

@ -1,5 +1,13 @@
package cn.iocoder.yudao.module.ai.service.knowledge; 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 lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -12,4 +20,23 @@ import org.springframework.stereotype.Service;
@Slf4j @Slf4j
public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService { 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.启用重新向量化
}
} }

View File

@ -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);
}

View File

@ -1,14 +1,15 @@
package cn.iocoder.yudao.module.ai.service.knowledge; package cn.iocoder.yudao.module.ai.service.knowledge;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 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.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.knowledge.AiKnowledgeCreateMyReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.AiKnowledgeUpdateMyReqVO; import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateMyReqVO;
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeBaseDO; import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; import cn.iocoder.yudao.module.ai.dal.dataobject.model.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 cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -24,56 +25,54 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_NOT_
*/ */
@Service @Service
@Slf4j @Slf4j
public class AiKnowledgeBaseServiceImpl implements AiKnowledgeBaseService { public class AiKnowledgeServiceImpl implements AiKnowledgeService {
@Resource @Resource
private AiChatModelService chatModalService; private AiChatModelService chatModalService;
@Resource @Resource
private AiKnowledgeBaseMapper knowledgeBaseMapper; private AiKnowledgeMapper knowledgeMapper;
@Override @Override
public Long createKnowledgeMy(AiKnowledgeCreateMyReqVO createReqVO, Long userId) { public Long createKnowledgeMy(AiKnowledgeCreateMyReqVO createReqVO, Long userId) {
// TODO @xin貌似直接调用 chatModalService.validateChatModel(id) 完事不用搞个方法
// 1. 校验模型配置 // 1. 校验模型配置
AiChatModelDO model = validateChatModel(createReqVO.getModelId()); AiChatModelDO model = chatModalService.validateChatModel(createReqVO.getModelId());
// 2. 插入知识库 // 2. 插入知识库
// TODO @xin不用 DO 结尾 AiKnowledgeDO knowledgeBase = BeanUtils.toBean(createReqVO, AiKnowledgeDO.class)
AiKnowledgeBaseDO knowledgeBaseDO = BeanUtils.toBean(createReqVO, AiKnowledgeBaseDO.class)
.setModel(model.getModel()).setUserId(userId).setStatus(CommonStatusEnum.ENABLE.getStatus()); .setModel(model.getModel()).setUserId(userId).setStatus(CommonStatusEnum.ENABLE.getStatus());
knowledgeBaseMapper.insert(knowledgeBaseDO); knowledgeMapper.insert(knowledgeBase);
return knowledgeBaseDO.getId(); return knowledgeBase.getId();
} }
@Override @Override
public void updateKnowledgeMy(AiKnowledgeUpdateMyReqVO updateReqVO, Long userId) { public void updateKnowledgeMy(AiKnowledgeUpdateMyReqVO updateReqVO, Long userId) {
// 1.1 校验知识库存在 // 1.1 校验知识库存在
AiKnowledgeBaseDO knowledgeBaseDO = validateKnowledgeExists(updateReqVO.getId()); AiKnowledgeDO knowledgeBaseDO = validateKnowledgeExists(updateReqVO.getId());
if (ObjUtil.notEqual(knowledgeBaseDO.getUserId(), userId)) { if (ObjUtil.notEqual(knowledgeBaseDO.getUserId(), userId)) {
throw exception(KNOWLEDGE_NOT_EXISTS); throw exception(KNOWLEDGE_NOT_EXISTS);
} }
// 1.2 校验模型配置 // 1.2 校验模型配置
AiChatModelDO model = validateChatModel(updateReqVO.getModelId()); AiChatModelDO model = chatModalService.validateChatModel(updateReqVO.getModelId());
// 2. 更新知识库 // 2. 更新知识库
AiKnowledgeBaseDO updateDO = BeanUtils.toBean(updateReqVO, AiKnowledgeBaseDO.class); AiKnowledgeDO updateDO = BeanUtils.toBean(updateReqVO, AiKnowledgeDO.class);
updateDO.setModel(model.getModel()); updateDO.setModel(model.getModel());
knowledgeBaseMapper.updateById(updateDO); knowledgeMapper.updateById(updateDO);
} }
private AiChatModelDO validateChatModel(Long id) { @Override
AiChatModelDO model = chatModalService.validateChatModel(id); public AiKnowledgeDO validateKnowledgeExists(Long id) {
Assert.notNull(model, "未找到对应嵌入模型"); AiKnowledgeDO knowledgeBase = knowledgeMapper.selectById(id);
return model;
}
public AiKnowledgeBaseDO validateKnowledgeExists(Long id) {
AiKnowledgeBaseDO knowledgeBase = knowledgeBaseMapper.selectById(id);
if (knowledgeBase == null) { if (knowledgeBase == null) {
throw exception(KNOWLEDGE_NOT_EXISTS); throw exception(KNOWLEDGE_NOT_EXISTS);
} }
return knowledgeBase; return knowledgeBase;
} }
@Override
public PageResult<AiKnowledgeDO> getKnowledgePageMy(Long userId, PageParam pageReqVO) {
return knowledgeMapper.selectPageByMy(userId, pageReqVO);
}
} }

View File

@ -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 cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.image.ImageModel; import org.springframework.ai.image.ImageModel;
import org.springframework.ai.vectorstore.VectorStore;
import java.util.List; import java.util.List;
@ -83,6 +85,14 @@ public interface AiApiKeyService {
*/ */
ChatModel getChatModel(Long id); ChatModel getChatModel(Long id);
/**
* 获得 EmbeddingModel 对象
*
* @param id 编号
* @return EmbeddingModel 对象
*/
EmbeddingModel getEmbeddingModel(Long id);
/** /**
* 获得 ImageModel 对象 * 获得 ImageModel 对象
* *
@ -111,4 +121,12 @@ public interface AiApiKeyService {
*/ */
SunoApi getSunoApi(); SunoApi getSunoApi();
/**
* 获得 vector 对象
*
* @param id 编号
* @return VectorStore 对象
*/
VectorStore getOrCreateVectorStore(Long id);
} }

View File

@ -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.enums.AiPlatformEnum;
import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory; 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.midjourney.api.MidjourneyApi;
import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 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 cn.iocoder.yudao.module.ai.dal.mysql.model.AiApiKeyMapper;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.image.ImageModel; import org.springframework.ai.image.ImageModel;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@ -36,6 +39,8 @@ public class AiApiKeyServiceImpl implements AiApiKeyService {
@Resource @Resource
private AiModelFactory modelFactory; private AiModelFactory modelFactory;
@Resource
private AiVectorStoreFactory vectorFactory;
@Override @Override
public Long createApiKey(AiApiKeySaveReqVO createReqVO) { public Long createApiKey(AiApiKeySaveReqVO createReqVO) {
@ -104,6 +109,13 @@ public class AiApiKeyServiceImpl implements AiApiKeyService {
return modelFactory.getOrCreateChatModel(platform, apiKey.getApiKey(), apiKey.getUrl()); 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 @Override
public ImageModel getImageModel(AiPlatformEnum platform) { public ImageModel getImageModel(AiPlatformEnum platform) {
AiApiKeyDO apiKey = apiKeyMapper.selectFirstByPlatformAndStatus(platform.getPlatform(), CommonStatusEnum.ENABLE.getStatus()); 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()); 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());
}
} }

View File

@ -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.AiModelFactory;
import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactoryImpl; 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.DeepSeekChatModel;
import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatOptions; import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatOptions;
import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; 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 cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatOptions;
import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration; import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreProperties; import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator;
import org.springframework.ai.document.MetadataMode; import org.springframework.ai.tokenizer.TokenCountEstimator;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.transformer.splitter.TokenTextSplitter; 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.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 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.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import redis.clients.jedis.JedisPooled;
/** /**
* 芋道 AI 自动配置 * 芋道 AI 自动配置
@ -41,6 +38,12 @@ public class YudaoAiAutoConfiguration {
return new AiModelFactoryImpl(); return new AiModelFactoryImpl();
} }
@Bean
public AiVectorStoreFactory aiVectorFactory() {
return new AiVectorStoreFactoryImpl();
}
// ========== 各种 AI Client 创建 ========== // ========== 各种 AI Client 创建 ==========
@Bean @Bean
@ -83,30 +86,31 @@ public class YudaoAiAutoConfiguration {
} }
// ========== rag 相关 ========== // ========== rag 相关 ==========
@Bean // TODO @xin 免费版本
@Lazy // TODO 芋艿临时注释避免无法启动 // @Bean
public EmbeddingModel transformersEmbeddingClient() { // @Lazy // TODO 芋艿临时注释避免无法启动
return new TransformersEmbeddingModel(MetadataMode.EMBED); // public EmbeddingModel transformersEmbeddingClient() {
} // return new TransformersEmbeddingModel(MetadataMode.EMBED);
// }
/** /**
* 我们启动有加载很多 Embedding 模型不晓得取哪个好 new TransformersEmbeddingModel * TODO @xin 默认版本先不弄目前都先取对应的 EmbeddingModel
*/ */
@Bean // @Bean
@Lazy // TODO 芋艿临时注释避免无法启动 // @Lazy // TODO 芋艿临时注释避免无法启动
public RedisVectorStore vectorStore(TransformersEmbeddingModel transformersEmbeddingModel, RedisVectorStoreProperties properties, // public RedisVectorStore vectorStore(TongYiTextEmbeddingModel tongYiTextEmbeddingModel, RedisVectorStoreProperties properties,
RedisProperties redisProperties) { // RedisProperties redisProperties) {
var config = RedisVectorStore.RedisVectorStoreConfig.builder() // var config = RedisVectorStore.RedisVectorStoreConfig.builder()
.withIndexName(properties.getIndex()) // .withIndexName(properties.getIndex())
.withPrefix(properties.getPrefix()) // .withPrefix(properties.getPrefix())
.build(); // .build();
//
RedisVectorStore redisVectorStore = new RedisVectorStore(config, transformersEmbeddingModel, // RedisVectorStore redisVectorStore = new RedisVectorStore(config, tongYiTextEmbeddingModel,
new JedisPooled(redisProperties.getHost(), redisProperties.getPort()), // new JedisPooled(redisProperties.getHost(), redisProperties.getPort()),
properties.isInitializeSchema()); // properties.isInitializeSchema());
redisVectorStore.afterPropertiesSet(); // redisVectorStore.afterPropertiesSet();
return redisVectorStore; // return redisVectorStore;
} // }
@Bean @Bean
@Lazy // TODO 芋艿临时注释避免无法启动 @Lazy // TODO 芋艿临时注释避免无法启动
@ -114,4 +118,10 @@ public class YudaoAiAutoConfiguration {
return new TokenTextSplitter(500, 100, 5, 10000, true); return new TokenTextSplitter(500, 100, 5, 10000, true);
} }
@Bean
@Lazy // TODO 芋艿临时注释避免无法启动
public TokenCountEstimator tokenCountEstimator() {
return new JTokkitTokenCountEstimator();
}
} }

View File

@ -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.midjourney.api.MidjourneyApi;
import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.image.ImageModel; import org.springframework.ai.image.ImageModel;
/** /**
@ -25,6 +26,18 @@ public interface AiModelFactory {
*/ */
ChatModel getOrCreateChatModel(AiPlatformEnum platform, String apiKey, String url); 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 对象 * 基于默认配置获得 ChatModel 对象
* *

View File

@ -21,6 +21,7 @@ import com.alibaba.cloud.ai.tongyi.image.TongYiImagesModel;
import com.alibaba.cloud.ai.tongyi.image.TongYiImagesProperties; import com.alibaba.cloud.ai.tongyi.image.TongYiImagesProperties;
import com.alibaba.dashscope.aigc.generation.Generation; import com.alibaba.dashscope.aigc.generation.Generation;
import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis; import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis;
import com.alibaba.dashscope.embeddings.TextEmbedding;
import com.azure.ai.openai.OpenAIClient; import com.azure.ai.openai.OpenAIClient;
import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiAutoConfiguration; import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiAutoConfiguration;
import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiChatProperties; 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.autoconfigure.zhipuai.ZhiPuAiImageProperties;
import org.springframework.ai.azure.openai.AzureOpenAiChatModel; import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.image.ImageModel; import org.springframework.ai.image.ImageModel;
import org.springframework.ai.model.function.FunctionCallbackContext; import org.springframework.ai.model.function.FunctionCallbackContext;
import org.springframework.ai.ollama.OllamaChatModel; 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 @Override
public ChatModel getDefaultChatModel(AiPlatformEnum platform) { public ChatModel getDefaultChatModel(AiPlatformEnum platform) {
//noinspection EnhancedSwitchMigration //noinspection EnhancedSwitchMigration
@ -239,7 +256,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
/** /**
* 可参考 {@link ZhiPuAiAutoConfiguration#zhiPuAiChatModel( * 可参考 {@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) { private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) {
url = StrUtil.blankToDefault(url, ZhiPuAiConnectionProperties.DEFAULT_BASE_URL); url = StrUtil.blankToDefault(url, ZhiPuAiConnectionProperties.DEFAULT_BASE_URL);
@ -249,7 +266,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
/** /**
* 可参考 {@link ZhiPuAiAutoConfiguration#zhiPuAiImageModel( * 可参考 {@link ZhiPuAiAutoConfiguration#zhiPuAiImageModel(
* ZhiPuAiConnectionProperties, ZhiPuAiImageProperties, RestClient.Builder, RetryTemplate, ResponseErrorHandler)} *ZhiPuAiConnectionProperties, ZhiPuAiImageProperties, RestClient.Builder, RetryTemplate, ResponseErrorHandler)}
*/ */
private ZhiPuAiImageModel buildZhiPuAiImageModel(String apiKey, String url) { private ZhiPuAiImageModel buildZhiPuAiImageModel(String apiKey, String url) {
url = StrUtil.blankToDefault(url, ZhiPuAiConnectionProperties.DEFAULT_BASE_URL); url = StrUtil.blankToDefault(url, ZhiPuAiConnectionProperties.DEFAULT_BASE_URL);
@ -315,4 +332,15 @@ public class AiModelFactoryImpl implements AiModelFactory {
return new StabilityAiImageModel(stabilityAiApi); 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);
}
} }

View File

@ -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);
}

View File

@ -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, "_"));
}
}

View File

@ -19,6 +19,7 @@ import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.vectorstore.RedisVectorStore; import org.springframework.ai.vectorstore.RedisVectorStore;
import org.springframework.ai.vectorstore.RedisVectorStore.RedisVectorStoreConfig; import org.springframework.ai.vectorstore.RedisVectorStore.RedisVectorStoreConfig;
import org.springframework.boot.autoconfigure.AutoConfiguration; 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.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
@ -38,7 +39,7 @@ import redis.clients.jedis.JedisPooled;
*/ */
@AutoConfiguration(after = RedisAutoConfiguration.class) @AutoConfiguration(after = RedisAutoConfiguration.class)
@ConditionalOnClass({JedisPooled.class, JedisConnectionFactory.class, RedisVectorStore.class, EmbeddingModel.class}) @ConditionalOnClass({JedisPooled.class, JedisConnectionFactory.class, RedisVectorStore.class, EmbeddingModel.class})
//@ConditionalOnBean(JedisConnectionFactory.class) @ConditionalOnBean(JedisConnectionFactory.class)
@EnableConfigurationProperties(RedisVectorStoreProperties.class) @EnableConfigurationProperties(RedisVectorStoreProperties.class)
public class RedisVectorStoreAutoConfiguration { public class RedisVectorStoreAutoConfiguration {