mirror of
				https://github.com/YunaiV/ruoyi-vue-pro.git
				synced 2025-10-31 02:28:03 +08:00 
			
		
		
		
	| @ -34,7 +34,12 @@ public enum AiChatRoleEnum { | |||||||
|              ### 支付宝 |              ### 支付宝 | ||||||
|              ### 微信 |              ### 微信 | ||||||
|             除此之外不要任何解释性语句。 |             除此之外不要任何解释性语句。 | ||||||
|             """); |             """), | ||||||
|  |  | ||||||
|  |     AI_KNOWLEDGE_ROLE("知识库助手", """ | ||||||
|  |                 给你提供一些数据参考:{info},请回答我的问题。 | ||||||
|  |                 请你跟进数据参考与工具返回结果回复用户的请求。 | ||||||
|  |                 """); | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 角色名 |      * 角色名 | ||||||
|  | |||||||
| @ -10,4 +10,7 @@ public class AiChatConversationCreateMyReqVO { | |||||||
|     @Schema(description = "聊天角色编号", example = "666") |     @Schema(description = "聊天角色编号", example = "666") | ||||||
|     private Long roleId; |     private Long roleId; | ||||||
|  |  | ||||||
|  |     @Schema(description = "知识库编号", example = "1204") | ||||||
|  |     private Long knowledgeId; | ||||||
|  |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -21,6 +21,9 @@ public class AiChatConversationUpdateMyReqVO { | |||||||
|     @Schema(description = "模型编号", example = "1") |     @Schema(description = "模型编号", example = "1") | ||||||
|     private Long modelId; |     private Long modelId; | ||||||
|  |  | ||||||
|  |     @Schema(description = "知识库编号", example = "1") | ||||||
|  |     private Long knowledgeId; | ||||||
|  |  | ||||||
|     @Schema(description = "角色设定", example = "一个快乐的程序员") |     @Schema(description = "角色设定", example = "一个快乐的程序员") | ||||||
|     private String systemMessage; |     private String systemMessage; | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,12 +1,12 @@ | |||||||
| 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.framework.common.pojo.PageParam; |  | ||||||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; | 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.knowledge.AiKnowledgeCreateMyReqVO; | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeCreateReqVO; | ||||||
|  | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO; | ||||||
| import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeRespVO; | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeRespVO; | ||||||
| import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateMyReqVO; | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateReqVO; | ||||||
| import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; | ||||||
| import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeService; | import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeService; | ||||||
| import io.swagger.v3.oas.annotations.Operation; | import io.swagger.v3.oas.annotations.Operation; | ||||||
| @ -28,24 +28,23 @@ public class AiKnowledgeController { | |||||||
|     @Resource |     @Resource | ||||||
|     private AiKnowledgeService knowledgeService; |     private AiKnowledgeService knowledgeService; | ||||||
|  |  | ||||||
|     @GetMapping("/my-page") |     @GetMapping("/page") | ||||||
|     @Operation(summary = "获取【我的】知识库分页") |     @Operation(summary = "获取知识库分页") | ||||||
|     public CommonResult<PageResult<AiKnowledgeRespVO>> getKnowledgePageMy(@Validated PageParam pageReqVO) { |     public CommonResult<PageResult<AiKnowledgeRespVO>> getKnowledgePage(@Valid AiKnowledgePageReqVO pageReqVO) { | ||||||
|         PageResult<AiKnowledgeDO> pageResult = knowledgeService.getKnowledgePageMy(getLoginUserId(), pageReqVO); |         PageResult<AiKnowledgeDO> pageResult = knowledgeService.getKnowledgePage(getLoginUserId(), pageReqVO); | ||||||
|         return success(BeanUtils.toBean(pageResult, AiKnowledgeRespVO.class)); |         return success(BeanUtils.toBean(pageResult, AiKnowledgeRespVO.class)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @PostMapping("/create-my") |     @PostMapping("/create") | ||||||
|     @Operation(summary = "创建【我的】知识库") |     @Operation(summary = "创建知识库") | ||||||
|     public CommonResult<Long> createKnowledgeMy(@RequestBody @Valid AiKnowledgeCreateMyReqVO createReqVO) { |     public CommonResult<Long> createKnowledge(@RequestBody @Valid AiKnowledgeCreateReqVO createReqVO) { | ||||||
|         return success(knowledgeService.createKnowledgeMy(createReqVO, getLoginUserId())); |         return success(knowledgeService.createKnowledge(createReqVO, getLoginUserId())); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @PutMapping("/update-my") |     @PutMapping("/update") | ||||||
|     @Operation(summary = "更新【我的】知识库") |     @Operation(summary = "更新知识库") | ||||||
|     public CommonResult<Boolean> updateKnowledgeMy(@RequestBody @Valid AiKnowledgeUpdateMyReqVO updateReqVO) { |     public CommonResult<Boolean> updateKnowledge(@RequestBody @Valid AiKnowledgeUpdateReqVO updateReqVO) { | ||||||
|         knowledgeService.updateKnowledgeMy(updateReqVO, getLoginUserId()); |         knowledgeService.updateKnowledge(updateReqVO, getLoginUserId()); | ||||||
|         return success(true); |         return success(true); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -36,7 +36,7 @@ public class AiKnowledgeDocumentController { | |||||||
|  |  | ||||||
|     @GetMapping("/page") |     @GetMapping("/page") | ||||||
|     @Operation(summary = "获取文档分页") |     @Operation(summary = "获取文档分页") | ||||||
|     public CommonResult<PageResult<AiKnowledgeDocumentRespVO>> getKnowledgeDocumentPageMy(@Valid AiKnowledgeDocumentPageReqVO pageReqVO) { |     public CommonResult<PageResult<AiKnowledgeDocumentRespVO>> getKnowledgeDocumentPage(@Valid AiKnowledgeDocumentPageReqVO pageReqVO) { | ||||||
|         PageResult<AiKnowledgeDocumentDO> pageResult = documentService.getKnowledgeDocumentPage(pageReqVO); |         PageResult<AiKnowledgeDocumentDO> pageResult = documentService.getKnowledgeDocumentPage(pageReqVO); | ||||||
|         return success(BeanUtils.toBean(pageResult, AiKnowledgeDocumentRespVO.class)); |         return success(BeanUtils.toBean(pageResult, AiKnowledgeDocumentRespVO.class)); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -29,7 +29,7 @@ public class AiKnowledgeSegmentController { | |||||||
|  |  | ||||||
|     @GetMapping("/page") |     @GetMapping("/page") | ||||||
|     @Operation(summary = "获取段落分页") |     @Operation(summary = "获取段落分页") | ||||||
|     public CommonResult<PageResult<AiKnowledgeSegmentRespVO>> getKnowledgeSegmentPageMy(@Valid AiKnowledgeSegmentPageReqVO pageReqVO) { |     public CommonResult<PageResult<AiKnowledgeSegmentRespVO>> getKnowledgeSegmentPage(@Valid AiKnowledgeSegmentPageReqVO pageReqVO) { | ||||||
|         PageResult<AiKnowledgeSegmentDO> pageResult = segmentService.getKnowledgeSegmentPage(pageReqVO); |         PageResult<AiKnowledgeSegmentDO> pageResult = segmentService.getKnowledgeSegmentPage(pageReqVO); | ||||||
|         return success(BeanUtils.toBean(pageResult, AiKnowledgeSegmentRespVO.class)); |         return success(BeanUtils.toBean(pageResult, AiKnowledgeSegmentRespVO.class)); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -7,9 +7,9 @@ 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 AiKnowledgeCreateMyReqVO { | public class AiKnowledgeCreateReqVO { | ||||||
| 
 | 
 | ||||||
|     @Schema(description = "知识库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "ruoyi-vue-pro 用户指南") |     @Schema(description = "知识库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "ruoyi-vue-pro 用户指南") | ||||||
|     @NotBlank(message = "知识库名称不能为空") |     @NotBlank(message = "知识库名称不能为空") | ||||||
| @ -18,7 +18,7 @@ public class AiKnowledgeCreateMyReqVO { | |||||||
|     @Schema(description = "知识库描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "存储 ruoyi-vue-pro 操作文档") |     @Schema(description = "知识库描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "存储 ruoyi-vue-pro 操作文档") | ||||||
|     private String description; |     private String description; | ||||||
| 
 | 
 | ||||||
|     @Schema(description = "可见权限,只能选择哪些人可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1]") |     @Schema(description = "可见权限,只能选择哪些人可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1,2,3]") | ||||||
|     private List<Long> visibilityPermissions; |     private List<Long> visibilityPermissions; | ||||||
| 
 | 
 | ||||||
|     @Schema(description = "嵌入模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |     @Schema(description = "嵌入模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | ||||||
| @ -23,21 +23,21 @@ public class AiKnowledgeDocumentCreateReqVO { | |||||||
|     @URL(message = "文档 URL 格式不正确") |     @URL(message = "文档 URL 格式不正确") | ||||||
|     private String url; |     private String url; | ||||||
|  |  | ||||||
|     @Schema(description = "每个文本块的目标 token 数", requiredMode = Schema.RequiredMode.REQUIRED, example = "800") |     @Schema(description = "每个段落的目标 token 数", requiredMode = Schema.RequiredMode.REQUIRED, example = "800") | ||||||
|     @NotNull(message = "每个文本块的目标 token 数不能为空") |     @NotNull(message = "每个段落的目标 token 数不能为空") | ||||||
|     private Integer defaultChunkSize; |     private Integer defaultSegmentTokens; | ||||||
|  |  | ||||||
|     @Schema(description = "每个文本块的最小字符数", requiredMode = Schema.RequiredMode.REQUIRED, example = "350") |     @Schema(description = "每个段落的最小字符数", requiredMode = Schema.RequiredMode.REQUIRED, example = "350") | ||||||
|     @NotNull(message = "每个文本块的最小字符数不能为空") |     @NotNull(message = "每个段落的最小字符数不能为空") | ||||||
|     private Integer minChunkSizeChars; |     private Integer minSegmentWordCount; | ||||||
|  |  | ||||||
|     @Schema(description = "丢弃阈值", requiredMode = Schema.RequiredMode.REQUIRED, example = "5") |     @Schema(description = "丢弃阈值:低于此阈值的段落会被丢弃", requiredMode = Schema.RequiredMode.REQUIRED, example = "5") | ||||||
|     @NotNull(message = "丢弃阈值不能为空") |     @NotNull(message = "丢弃阈值不能为空") | ||||||
|     private Integer minChunkLengthToEmbed; |     private Integer minChunkLengthToEmbed; | ||||||
|  |  | ||||||
|     @Schema(description = "最大块数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000") |     @Schema(description = "最大段落数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000") | ||||||
|     @NotNull(message = "最大块数不能为空") |     @NotNull(message = "最大段落数不能为空") | ||||||
|     private Integer maxNumChunks; |     private Integer maxNumSegments; | ||||||
|  |  | ||||||
|     @Schema(description = "分块是否保留分隔符", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") |     @Schema(description = "分块是否保留分隔符", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") | ||||||
|     @NotNull(message = "分块是否保留分隔符不能为空") |     @NotNull(message = "分块是否保留分隔符不能为空") | ||||||
|  | |||||||
| @ -0,0 +1,14 @@ | |||||||
|  | package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge; | ||||||
|  |  | ||||||
|  | import cn.iocoder.yudao.framework.common.pojo.PageParam; | ||||||
|  | import io.swagger.v3.oas.annotations.media.Schema; | ||||||
|  | import lombok.Data; | ||||||
|  |  | ||||||
|  | @Schema(description = "管理后台 - AI 知识库的分页 Request VO") | ||||||
|  | @Data | ||||||
|  | public class AiKnowledgePageReqVO extends PageParam { | ||||||
|  |  | ||||||
|  |     @Schema(description = "知识库名称", example = "Java 开发手册") | ||||||
|  |     private String name; | ||||||
|  |  | ||||||
|  | } | ||||||
| @ -9,7 +9,7 @@ import java.util.List; | |||||||
| 
 | 
 | ||||||
| @Schema(description = "管理后台 - AI 知识库更新【我的】 Request VO") | @Schema(description = "管理后台 - AI 知识库更新【我的】 Request VO") | ||||||
| @Data | @Data | ||||||
| public class AiKnowledgeUpdateMyReqVO { | public class AiKnowledgeUpdateReqVO { | ||||||
| 
 | 
 | ||||||
|     @Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1204") |     @Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1204") | ||||||
|     @NotNull(message = "知识库编号不能为空") |     @NotNull(message = "知识库编号不能为空") | ||||||
| @ -22,7 +22,7 @@ public class AiKnowledgeUpdateMyReqVO { | |||||||
|     @Schema(description = "知识库描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "") |     @Schema(description = "知识库描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "") | ||||||
|     private String description; |     private String description; | ||||||
| 
 | 
 | ||||||
|     @Schema(description = "可见权限,只能选择哪些人可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1]") |     @Schema(description = "可见权限,只能选择哪些人可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3") | ||||||
|     private List<Long> visibilityPermissions; |     private List<Long> visibilityPermissions; | ||||||
| 
 | 
 | ||||||
|     @Schema(description = "嵌入模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |     @Schema(description = "嵌入模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | ||||||
| @ -1,6 +1,7 @@ | |||||||
| package cn.iocoder.yudao.module.ai.dal.dataobject.chat; | package cn.iocoder.yudao.module.ai.dal.dataobject.chat; | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | ||||||
|  | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; | ||||||
| import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; | ||||||
| import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; | ||||||
| import com.baomidou.mybatisplus.annotation.KeySequence; | import com.baomidou.mybatisplus.annotation.KeySequence; | ||||||
| @ -64,6 +65,13 @@ public class AiChatConversationDO extends BaseDO { | |||||||
|      */ |      */ | ||||||
|     private Long roleId; |     private Long roleId; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 知识库编号 | ||||||
|  |      * <p> | ||||||
|  |      * 关联 {@link AiKnowledgeDO#getId()} | ||||||
|  |      */ | ||||||
|  |     private Long knowledgeId; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 模型编号 |      * 模型编号 | ||||||
|      * |      * | ||||||
|  | |||||||
| @ -1,13 +1,18 @@ | |||||||
| package cn.iocoder.yudao.module.ai.dal.dataobject.chat; | package cn.iocoder.yudao.module.ai.dal.dataobject.chat; | ||||||
|  |  | ||||||
| import com.baomidou.mybatisplus.annotation.TableId; |  | ||||||
| import org.springframework.ai.chat.messages.MessageType; |  | ||||||
| import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | ||||||
|  | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; | ||||||
| import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; | ||||||
| import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; | ||||||
| import com.baomidou.mybatisplus.annotation.KeySequence; | import com.baomidou.mybatisplus.annotation.KeySequence; | ||||||
|  | import com.baomidou.mybatisplus.annotation.TableField; | ||||||
|  | import com.baomidou.mybatisplus.annotation.TableId; | ||||||
| import com.baomidou.mybatisplus.annotation.TableName; | import com.baomidou.mybatisplus.annotation.TableName; | ||||||
|  | import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; | ||||||
| import lombok.*; | import lombok.*; | ||||||
|  | import org.springframework.ai.chat.messages.MessageType; | ||||||
|  |  | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * AI Chat 消息 DO |  * AI Chat 消息 DO | ||||||
| @ -66,6 +71,15 @@ public class AiChatMessageDO extends BaseDO { | |||||||
|      */ |      */ | ||||||
|     private Long roleId; |     private Long roleId; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 段落编号数组 | ||||||
|  |      * | ||||||
|  |      * 关联 {@link AiKnowledgeSegmentDO#getId()} 字段 | ||||||
|  |      */ | ||||||
|  |     @TableField(typeHandler = JacksonTypeHandler.class) | ||||||
|  |     private List<Long> segmentIds; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 模型标志 |      * 模型标志 | ||||||
|      */ |      */ | ||||||
|  | |||||||
| @ -2,10 +2,10 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.knowledge; | |||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | ||||||
| import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | ||||||
|  | import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler; | ||||||
| import com.baomidou.mybatisplus.annotation.TableField; | import com.baomidou.mybatisplus.annotation.TableField; | ||||||
| import com.baomidou.mybatisplus.annotation.TableId; | import com.baomidou.mybatisplus.annotation.TableId; | ||||||
| import com.baomidou.mybatisplus.annotation.TableName; | import com.baomidou.mybatisplus.annotation.TableName; | ||||||
| import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; |  | ||||||
| import lombok.Data; | import lombok.Data; | ||||||
|  |  | ||||||
| import java.util.List; | import java.util.List; | ||||||
| @ -38,11 +38,13 @@ public class AiKnowledgeDO extends BaseDO { | |||||||
|      * 知识库描述 |      * 知识库描述 | ||||||
|      */ |      */ | ||||||
|     private String description; |     private String description; | ||||||
|     // TODO @新:如果全部可见,需要怎么设置? |  | ||||||
|     /** |     /** | ||||||
|      * 可见权限,只能选择哪些人可见 |      * 可见权限,选择哪些人可见 | ||||||
|  |      * <p> | ||||||
|  |      * -1 所有人可见,其他为各自用户编号 | ||||||
|      */ |      */ | ||||||
|     @TableField(typeHandler = JacksonTypeHandler.class) |     @TableField(typeHandler = LongListTypeHandler.class) | ||||||
|     private List<Long> visibilityPermissions; |     private List<Long> visibilityPermissions; | ||||||
|     /** |     /** | ||||||
|      * 嵌入模型编号 |      * 嵌入模型编号 | ||||||
|  | |||||||
| @ -40,23 +40,25 @@ public class AiKnowledgeDocumentDO extends BaseDO { | |||||||
|      */ |      */ | ||||||
|     private String url; |     private String url; | ||||||
|     /** |     /** | ||||||
|      * token 数量 |      * 文档 token 数量 | ||||||
|      */ |      */ | ||||||
|     private Integer tokens; |     private Integer tokens; | ||||||
|     /** |     /** | ||||||
|      * 字符数 |      * 文档字符数 | ||||||
|      */ |      */ | ||||||
|     private Integer wordCount; |     private Integer wordCount; | ||||||
|     // TODO @新:chunk 1)是不是 segment,这样命名保持一致会好点哈?2)Size 是不是改成 Tokens 会统一点;3)defaultChunkSize、defaultChunkSize、minChunkSizeChars、maxNumChunks 这几个字段的命名,可能要微信一起讨论下。尽量命名保持风格统一哈。 |  | ||||||
|  |  | ||||||
|  |     // ========== 自定义分段所用参数 ========== | ||||||
|  |     // TODO @新:3)defaultChunkSize、defaultChunkSize、minChunkSizeChars、maxNumChunks 这几个字段的命名,可能要微信一起讨论下。尽量命名保持风格统一哈。 | ||||||
|     /** |     /** | ||||||
|      * 每个文本块的目标 token 数 |      * 每个文本块的目标 token 数 | ||||||
|      */ |      */ | ||||||
|     private Integer defaultChunkSize; |     private Integer defaultSegmentTokens; | ||||||
|     // TODO @xin:SizeChars 和 wordCount 好像是一个意思,是不是也要统一哈。 |  | ||||||
|     /** |     /** | ||||||
|      * 每个文本块的最小字符数 |      * 每个文本块的最小字符数 | ||||||
|      */ |      */ | ||||||
|     private Integer minChunkSizeChars; |     private Integer minSegmentWordCount; | ||||||
|     /** |     /** | ||||||
|      * 低于此值的块会被丢弃 |      * 低于此值的块会被丢弃 | ||||||
|      */ |      */ | ||||||
| @ -64,11 +66,13 @@ public class AiKnowledgeDocumentDO extends BaseDO { | |||||||
|     /** |     /** | ||||||
|      * 最大块数 |      * 最大块数 | ||||||
|      */ |      */ | ||||||
|     private Integer maxNumChunks; |     private Integer maxNumSegments; | ||||||
|     /** |     /** | ||||||
|      * 分块是否保留分隔符 |      * 分块是否保留分隔符 | ||||||
|      */ |      */ | ||||||
|     private Boolean keepSeparator; |     private Boolean keepSeparator; | ||||||
|  |     // =================================== | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 切片状态 |      * 切片状态 | ||||||
|      * <p> |      * <p> | ||||||
|  | |||||||
| @ -2,8 +2,6 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.knowledge; | |||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | ||||||
| import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | ||||||
| import com.baomidou.mybatisplus.annotation.FieldStrategy; |  | ||||||
| import com.baomidou.mybatisplus.annotation.TableField; |  | ||||||
| import com.baomidou.mybatisplus.annotation.TableId; | import com.baomidou.mybatisplus.annotation.TableId; | ||||||
| import com.baomidou.mybatisplus.annotation.TableName; | import com.baomidou.mybatisplus.annotation.TableName; | ||||||
| import lombok.Data; | import lombok.Data; | ||||||
| @ -27,7 +25,6 @@ public class AiKnowledgeSegmentDO extends BaseDO { | |||||||
|     /** |     /** | ||||||
|      * 向量库的编号 |      * 向量库的编号 | ||||||
|      */ |      */ | ||||||
|     @TableField(updateStrategy = FieldStrategy.ALWAYS) // TODO @新:尽量规避要这个注解。万一后面加个 status 单独更新,可能会踩坑。 |  | ||||||
|     private String vectorId; |     private String vectorId; | ||||||
|     /** |     /** | ||||||
|      * 知识库编号 |      * 知识库编号 | ||||||
|  | |||||||
| @ -1,10 +1,10 @@ | |||||||
| package cn.iocoder.yudao.module.ai.dal.mysql.knowledge; | package cn.iocoder.yudao.module.ai.dal.mysql.knowledge; | ||||||
|  |  | ||||||
| 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.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.framework.mybatis.core.query.LambdaQueryWrapperX; | ||||||
|  | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO; | ||||||
| import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; | ||||||
| import org.apache.ibatis.annotations.Mapper; | import org.apache.ibatis.annotations.Mapper; | ||||||
|  |  | ||||||
| @ -16,10 +16,11 @@ import org.apache.ibatis.annotations.Mapper; | |||||||
| @Mapper | @Mapper | ||||||
| public interface AiKnowledgeMapper extends BaseMapperX<AiKnowledgeDO> { | public interface AiKnowledgeMapper extends BaseMapperX<AiKnowledgeDO> { | ||||||
|  |  | ||||||
|     default PageResult<AiKnowledgeDO> selectPageByMy(Long userId, PageParam pageReqVO) { |     default PageResult<AiKnowledgeDO> selectPage(Long userId, AiKnowledgePageReqVO pageReqVO) { | ||||||
|         return selectPage(pageReqVO, new LambdaQueryWrapperX<AiKnowledgeDO>() |         return selectPage(pageReqVO, new LambdaQueryWrapperX<AiKnowledgeDO>() | ||||||
|                 .eq(AiKnowledgeDO::getUserId, userId) |  | ||||||
|                 .eq(AiKnowledgeDO::getStatus, CommonStatusEnum.ENABLE.getStatus()) |                 .eq(AiKnowledgeDO::getStatus, CommonStatusEnum.ENABLE.getStatus()) | ||||||
|  |                 .likeIfPresent(AiKnowledgeDO::getName, pageReqVO.getName()) | ||||||
|  |                 .and(e -> e.apply("FIND_IN_SET(" + userId + ",visibility_permissions)").or(m -> m.apply("FIND_IN_SET(-1,visibility_permissions)"))) | ||||||
|                 .orderByDesc(AiKnowledgeDO::getId)); |                 .orderByDesc(AiKnowledgeDO::getId)); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -25,8 +25,7 @@ public interface AiKnowledgeSegmentMapper extends BaseMapperX<AiKnowledgeSegment | |||||||
|                 .orderByDesc(AiKnowledgeSegmentDO::getId)); |                 .orderByDesc(AiKnowledgeSegmentDO::getId)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // TODO @新:selectListByXXX 哈 |     default List<AiKnowledgeSegmentDO> selectListByVectorIds(List<String> vectorIdList) { | ||||||
|     default List<AiKnowledgeSegmentDO> selectList(List<String> vectorIdList) { |  | ||||||
|         return selectList(new LambdaQueryWrapperX<AiKnowledgeSegmentDO>() |         return selectList(new LambdaQueryWrapperX<AiKnowledgeSegmentDO>() | ||||||
|                 .in(AiKnowledgeSegmentDO::getVectorId, vectorIdList) |                 .in(AiKnowledgeSegmentDO::getVectorId, vectorIdList) | ||||||
|                 .orderByDesc(AiKnowledgeSegmentDO::getId)); |                 .orderByDesc(AiKnowledgeSegmentDO::getId)); | ||||||
|  | |||||||
| @ -13,6 +13,7 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; | |||||||
| import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; | ||||||
| import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; | ||||||
| import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatConversationMapper; | import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatConversationMapper; | ||||||
|  | import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeService; | ||||||
| import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; | import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; | ||||||
| import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService; | import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService; | ||||||
| import jakarta.annotation.Resource; | import jakarta.annotation.Resource; | ||||||
| @ -22,6 +23,7 @@ import org.springframework.validation.annotation.Validated; | |||||||
|  |  | ||||||
| import java.time.LocalDateTime; | import java.time.LocalDateTime; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  | import java.util.Objects; | ||||||
|  |  | ||||||
| import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | ||||||
| import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | ||||||
| @ -45,6 +47,8 @@ public class AiChatConversationServiceImpl implements AiChatConversationService | |||||||
|     private AiChatModelService chatModalService; |     private AiChatModelService chatModalService; | ||||||
|     @Resource |     @Resource | ||||||
|     private AiChatRoleService chatRoleService; |     private AiChatRoleService chatRoleService; | ||||||
|  |     @Resource | ||||||
|  |     private AiKnowledgeService knowledgeService; | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public Long createChatConversationMy(AiChatConversationCreateMyReqVO createReqVO, Long userId) { |     public Long createChatConversationMy(AiChatConversationCreateMyReqVO createReqVO, Long userId) { | ||||||
| @ -56,9 +60,14 @@ public class AiChatConversationServiceImpl implements AiChatConversationService | |||||||
|         Assert.notNull(model, "必须找到默认模型"); |         Assert.notNull(model, "必须找到默认模型"); | ||||||
|         validateChatModel(model); |         validateChatModel(model); | ||||||
|  |  | ||||||
|  |         // 1.3 校验知识库 | ||||||
|  |         if (Objects.nonNull(createReqVO.getKnowledgeId())) { | ||||||
|  |             knowledgeService.validateKnowledgeExists(createReqVO.getKnowledgeId()); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         // 2. 创建 AiChatConversationDO 聊天对话 |         // 2. 创建 AiChatConversationDO 聊天对话 | ||||||
|         AiChatConversationDO conversation = new AiChatConversationDO().setUserId(userId).setPinned(false) |         AiChatConversationDO conversation = new AiChatConversationDO().setUserId(userId).setPinned(false) | ||||||
|                 .setModelId(model.getId()).setModel(model.getModel()) |                 .setModelId(model.getId()).setModel(model.getModel()).setKnowledgeId(createReqVO.getKnowledgeId()) | ||||||
|                 .setTemperature(model.getTemperature()).setMaxTokens(model.getMaxTokens()).setMaxContexts(model.getMaxContexts()); |                 .setTemperature(model.getTemperature()).setMaxTokens(model.getMaxTokens()).setMaxContexts(model.getMaxContexts()); | ||||||
|         if (role != null) { |         if (role != null) { | ||||||
|             conversation.setTitle(role.getName()).setRoleId(role.getId()).setSystemMessage(role.getSystemMessage()); |             conversation.setTitle(role.getName()).setRoleId(role.getId()).setSystemMessage(role.getSystemMessage()); | ||||||
| @ -82,6 +91,11 @@ public class AiChatConversationServiceImpl implements AiChatConversationService | |||||||
|             model = chatModalService.validateChatModel(updateReqVO.getModelId()); |             model = chatModalService.validateChatModel(updateReqVO.getModelId()); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         // 1.3 校验知识库是否存在 | ||||||
|  |         if (updateReqVO.getKnowledgeId() != null) { | ||||||
|  |             knowledgeService.validateKnowledgeExists(updateReqVO.getKnowledgeId()); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         // 2. 更新对话信息 |         // 2. 更新对话信息 | ||||||
|         AiChatConversationDO updateObj = BeanUtils.toBean(updateReqVO, AiChatConversationDO.class); |         AiChatConversationDO updateObj = BeanUtils.toBean(updateReqVO, AiChatConversationDO.class); | ||||||
|         if (Boolean.TRUE.equals(updateReqVO.getPinned())) { |         if (Boolean.TRUE.equals(updateReqVO.getPinned())) { | ||||||
|  | |||||||
| @ -12,21 +12,29 @@ import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; | |||||||
| import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO; | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO; | ||||||
| import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO; | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO; | ||||||
| import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO; | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO; | ||||||
|  | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentSearchReqVO; | ||||||
| import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; | import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; | ||||||
| import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; | import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; | ||||||
|  | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; | ||||||
| import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; | ||||||
| import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper; | import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper; | ||||||
|  | import cn.iocoder.yudao.module.ai.enums.AiChatRoleEnum; | ||||||
| import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants; | import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants; | ||||||
|  | import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService; | ||||||
| import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; | import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; | ||||||
| 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; | ||||||
| import org.springframework.ai.chat.messages.*; | import org.springframework.ai.chat.messages.Message; | ||||||
|  | import org.springframework.ai.chat.messages.MessageType; | ||||||
|  | import org.springframework.ai.chat.messages.SystemMessage; | ||||||
|  | import org.springframework.ai.chat.messages.UserMessage; | ||||||
| import org.springframework.ai.chat.model.ChatModel; | import org.springframework.ai.chat.model.ChatModel; | ||||||
| import org.springframework.ai.chat.model.ChatResponse; | import org.springframework.ai.chat.model.ChatResponse; | ||||||
| import org.springframework.ai.chat.model.StreamingChatModel; | import org.springframework.ai.chat.model.StreamingChatModel; | ||||||
| import org.springframework.ai.chat.prompt.ChatOptions; | import org.springframework.ai.chat.prompt.ChatOptions; | ||||||
| import org.springframework.ai.chat.prompt.Prompt; | import org.springframework.ai.chat.prompt.Prompt; | ||||||
|  | import org.springframework.ai.chat.prompt.PromptTemplate; | ||||||
| import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||||
| import org.springframework.transaction.annotation.Transactional; | import org.springframework.transaction.annotation.Transactional; | ||||||
| import reactor.core.publisher.Flux; | import reactor.core.publisher.Flux; | ||||||
| @ -59,6 +67,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | |||||||
|     private AiChatModelService chatModalService; |     private AiChatModelService chatModalService; | ||||||
|     @Resource |     @Resource | ||||||
|     private AiApiKeyService apiKeyService; |     private AiApiKeyService apiKeyService; | ||||||
|  |     @Resource | ||||||
|  |     private AiKnowledgeSegmentService knowledgeSegmentService; | ||||||
|  |  | ||||||
|     @Transactional(rollbackFor = Exception.class) |     @Transactional(rollbackFor = Exception.class) | ||||||
|     public AiChatMessageSendRespVO sendMessage(AiChatMessageSendReqVO sendReqVO, Long userId) { |     public AiChatMessageSendRespVO sendMessage(AiChatMessageSendReqVO sendReqVO, Long userId) { | ||||||
| @ -80,13 +90,16 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | |||||||
|         AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model, |         AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model, | ||||||
|                 userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext()); |                 userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext()); | ||||||
|  |  | ||||||
|         // 3.2 创建 chat 需要的 Prompt |         // 3.2 召回段落 | ||||||
|         Prompt prompt = buildPrompt(conversation, historyMessages, model, sendReqVO); |         List<AiKnowledgeSegmentDO> segmentList = recallSegment(sendReqVO.getContent(), conversation.getKnowledgeId()); | ||||||
|  |  | ||||||
|  |         // 3.3 创建 chat 需要的 Prompt | ||||||
|  |         Prompt prompt = buildPrompt(conversation, historyMessages, segmentList, model, sendReqVO); | ||||||
|         ChatResponse chatResponse = chatModel.call(prompt); |         ChatResponse chatResponse = chatModel.call(prompt); | ||||||
|  |  | ||||||
|         // 3.3 段式返回 |         // 3.4 段式返回 | ||||||
|         String newContent = chatResponse.getResult().getOutput().getContent(); |         String newContent = chatResponse.getResult().getOutput().getContent(); | ||||||
|         chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(newContent)); |         chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setSegmentIds(convertList(segmentList, AiKnowledgeSegmentDO::getId)).setContent(newContent)); | ||||||
|         return new AiChatMessageSendRespVO().setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class)) |         return new AiChatMessageSendRespVO().setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class)) | ||||||
|                 .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class).setContent(newContent)); |                 .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class).setContent(newContent)); | ||||||
|     } |     } | ||||||
| @ -111,11 +124,15 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | |||||||
|         AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model, |         AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model, | ||||||
|                 userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext()); |                 userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext()); | ||||||
|  |  | ||||||
|         // 3.2 构建 Prompt,并进行调用 |  | ||||||
|         Prompt prompt = buildPrompt(conversation, historyMessages, model, sendReqVO); |         // 3.2 召回段落 | ||||||
|  |         List<AiKnowledgeSegmentDO> segmentList = recallSegment(sendReqVO.getContent(), conversation.getKnowledgeId()); | ||||||
|  |  | ||||||
|  |         // 3.3 构建 Prompt,并进行调用 | ||||||
|  |         Prompt prompt = buildPrompt(conversation, historyMessages, segmentList, model, sendReqVO); | ||||||
|         Flux<ChatResponse> streamResponse = chatModel.stream(prompt); |         Flux<ChatResponse> streamResponse = chatModel.stream(prompt); | ||||||
|  |  | ||||||
|         // 3.3 流式返回 |         // 3.4 流式返回 | ||||||
|         // TODO 注意:Schedulers.immediate() 目的是,避免默认 Schedulers.parallel() 并发消费 chunk 导致 SSE 响应前端会乱序问题 |         // TODO 注意:Schedulers.immediate() 目的是,避免默认 Schedulers.parallel() 并发消费 chunk 导致 SSE 响应前端会乱序问题 | ||||||
|         StringBuffer contentBuffer = new StringBuffer(); |         StringBuffer contentBuffer = new StringBuffer(); | ||||||
|         return streamResponse.map(chunk -> { |         return streamResponse.map(chunk -> { | ||||||
| @ -128,7 +145,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | |||||||
|         }).doOnComplete(() -> { |         }).doOnComplete(() -> { | ||||||
|             // 忽略租户,因为 Flux 异步无法透传租户 |             // 忽略租户,因为 Flux 异步无法透传租户 | ||||||
|             TenantUtils.executeIgnore(() -> |             TenantUtils.executeIgnore(() -> | ||||||
|                     chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(contentBuffer.toString()))); |                     chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setSegmentIds(convertList(segmentList, AiKnowledgeSegmentDO::getId)) | ||||||
|  |                             .setContent(contentBuffer.toString()))); | ||||||
|         }).doOnError(throwable -> { |         }).doOnError(throwable -> { | ||||||
|             log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable); |             log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable); | ||||||
|             // 忽略租户,因为 Flux 异步无法透传租户 |             // 忽略租户,因为 Flux 异步无法透传租户 | ||||||
| @ -137,18 +155,35 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | |||||||
|         }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.CHAT_STREAM_ERROR))); |         }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.CHAT_STREAM_ERROR))); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private Prompt buildPrompt(AiChatConversationDO conversation, List<AiChatMessageDO> messages, |     private List<AiKnowledgeSegmentDO> recallSegment(String content, Long knowledgeId) { | ||||||
|  |         if (Objects.isNull(knowledgeId)) { | ||||||
|  |             return Collections.emptyList(); | ||||||
|  |         } | ||||||
|  |         return knowledgeSegmentService.similaritySearch(new AiKnowledgeSegmentSearchReqVO().setKnowledgeId(knowledgeId).setContent(content)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private Prompt buildPrompt(AiChatConversationDO conversation, List<AiChatMessageDO> messages,List<AiKnowledgeSegmentDO> segmentList, | ||||||
|                                AiChatModelDO model, AiChatMessageSendReqVO sendReqVO) { |                                AiChatModelDO model, AiChatMessageSendReqVO sendReqVO) { | ||||||
|         // 1. 构建 Prompt Message 列表 |         // 1. 构建 Prompt Message 列表 | ||||||
|         List<Message> chatMessages = new ArrayList<>(); |         List<Message> chatMessages = new ArrayList<>(); | ||||||
|         // 1.1 system context 角色设定 |  | ||||||
|  |         // 1.1 召回内容消息构建 | ||||||
|  |         if (CollUtil.isNotEmpty(segmentList)) { | ||||||
|  |             PromptTemplate promptTemplate = new PromptTemplate(AiChatRoleEnum.AI_KNOWLEDGE_ROLE.getSystemMessage()); | ||||||
|  |             StringBuilder infoBuilder = StrUtil.builder(); | ||||||
|  |             segmentList.forEach(segment -> infoBuilder.append(System.lineSeparator()).append(segment.getContent())); | ||||||
|  |             Message message = promptTemplate.createMessage(Map.of("info", infoBuilder.toString())); | ||||||
|  |             chatMessages.add(message); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 1.2 system context 角色设定 | ||||||
|         if (StrUtil.isNotBlank(conversation.getSystemMessage())) { |         if (StrUtil.isNotBlank(conversation.getSystemMessage())) { | ||||||
|             chatMessages.add(new SystemMessage(conversation.getSystemMessage())); |             chatMessages.add(new SystemMessage(conversation.getSystemMessage())); | ||||||
|         } |         } | ||||||
|         // 1.2 history message 历史消息 |         // 1.3 history message 历史消息 | ||||||
|         List<AiChatMessageDO> contextMessages = filterContextMessages(messages, conversation, sendReqVO); |         List<AiChatMessageDO> contextMessages = filterContextMessages(messages, conversation, sendReqVO); | ||||||
|         contextMessages.forEach(message -> chatMessages.add(AiUtils.buildMessage(message.getType(), message.getContent()))); |         contextMessages.forEach(message -> chatMessages.add(AiUtils.buildMessage(message.getType(), message.getContent()))); | ||||||
|         // 1.3 user message 新发送消息 |         // 1.4 user message 新发送消息 | ||||||
|         chatMessages.add(new UserMessage(sendReqVO.getContent())); |         chatMessages.add(new UserMessage(sendReqVO.getContent())); | ||||||
|  |  | ||||||
|         // 2. 构建 ChatOptions 对象 |         // 2. 构建 ChatOptions 对象 | ||||||
| @ -160,12 +195,12 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | |||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 从历史消息中,获得倒序的 n 组消息作为消息上下文 |      * 从历史消息中,获得倒序的 n 组消息作为消息上下文 | ||||||
|      * |      * <p> | ||||||
|      * n 组:指的是 user + assistant 形成一组 |      * n 组:指的是 user + assistant 形成一组 | ||||||
|      * |      * | ||||||
|      * @param messages 消息列表 |      * @param messages     消息列表 | ||||||
|      * @param conversation 对话 |      * @param conversation 对话 | ||||||
|      * @param sendReqVO 发送请求 |      * @param sendReqVO    发送请求 | ||||||
|      * @return 消息上下文 |      * @return 消息上下文 | ||||||
|      */ |      */ | ||||||
|     private List<AiChatMessageDO> filterContextMessages(List<AiChatMessageDO> messages, |     private List<AiChatMessageDO> filterContextMessages(List<AiChatMessageDO> messages, | ||||||
| @ -182,7 +217,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | |||||||
|             } |             } | ||||||
|             AiChatMessageDO userMessage = CollUtil.get(messages, i - 1); |             AiChatMessageDO userMessage = CollUtil.get(messages, i - 1); | ||||||
|             if (userMessage == null || ObjUtil.notEqual(assistantMessage.getReplyId(), userMessage.getId()) |             if (userMessage == null || ObjUtil.notEqual(assistantMessage.getReplyId(), userMessage.getId()) | ||||||
|                 || StrUtil.isEmpty(assistantMessage.getContent())) { |                     || StrUtil.isEmpty(assistantMessage.getContent())) { | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
|             // 由于后续要 reverse 反转,所以先添加 assistantMessage |             // 由于后续要 reverse 反转,所以先添加 assistantMessage | ||||||
|  | |||||||
| @ -71,8 +71,8 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         // 2 构造文本分段器 |         // 2 构造文本分段器 | ||||||
|         TokenTextSplitter tokenTextSplitter = new TokenTextSplitter(createReqVO.getDefaultChunkSize(), createReqVO.getMinChunkSizeChars(), createReqVO.getMinChunkLengthToEmbed(), |         TokenTextSplitter tokenTextSplitter = new TokenTextSplitter(createReqVO.getDefaultSegmentTokens(), createReqVO.getMinSegmentWordCount(), createReqVO.getMinChunkLengthToEmbed(), | ||||||
|                 createReqVO.getMaxNumChunks(), createReqVO.getKeepSeparator()); |                 createReqVO.getMaxNumSegments(), createReqVO.getKeepSeparator()); | ||||||
|         // 2.1 文档分段 |         // 2.1 文档分段 | ||||||
|         List<Document> segments = tokenTextSplitter.apply(documents); |         List<Document> segments = tokenTextSplitter.apply(documents); | ||||||
|         // 2.2 分段内容入库 |         // 2.2 分段内容入库 | ||||||
|  | |||||||
| @ -90,7 +90,7 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService | |||||||
|         } else { |         } else { | ||||||
|             // 2.2 禁用删除向量 |             // 2.2 禁用删除向量 | ||||||
|             vectorStore.delete(List.of(oldKnowledgeSegment.getVectorId())); |             vectorStore.delete(List.of(oldKnowledgeSegment.getVectorId())); | ||||||
|             knowledgeSegment.setVectorId(null); |             knowledgeSegment.setVectorId(""); | ||||||
|         } |         } | ||||||
|         // 3 更新段落状态 |         // 3 更新段落状态 | ||||||
|         segmentMapper.updateById(knowledgeSegment); |         segmentMapper.updateById(knowledgeSegment); | ||||||
| @ -114,7 +114,7 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService | |||||||
|             return ListUtil.empty(); |             return ListUtil.empty(); | ||||||
|         } |         } | ||||||
|         // 3.2 段落召回 |         // 3.2 段落召回 | ||||||
|         return segmentMapper.selectList(CollUtil.getFieldValues(documentList, "id", String.class)); |         return segmentMapper.selectListByVectorIds(CollUtil.getFieldValues(documentList, "id", String.class)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -1,9 +1,9 @@ | |||||||
| package cn.iocoder.yudao.module.ai.service.knowledge; | 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.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.AiKnowledgeCreateReqVO; | ||||||
| import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateMyReqVO; | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO; | ||||||
|  | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateReqVO; | ||||||
| import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; | ||||||
| import org.springframework.ai.vectorstore.VectorStore; | import org.springframework.ai.vectorstore.VectorStore; | ||||||
|  |  | ||||||
| @ -15,21 +15,21 @@ import org.springframework.ai.vectorstore.VectorStore; | |||||||
| public interface AiKnowledgeService { | public interface AiKnowledgeService { | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 创建【我的】知识库 |      * 创建知识库 | ||||||
|      * |      * | ||||||
|      * @param createReqVO 创建信息 |      * @param createReqVO 创建信息 | ||||||
|      * @param userId      用户编号 |      * @param userId      用户编号 | ||||||
|      * @return 编号 |      * @return 编号 | ||||||
|      */ |      */ | ||||||
|     Long createKnowledgeMy(AiKnowledgeCreateMyReqVO createReqVO, Long userId); |     Long createKnowledge(AiKnowledgeCreateReqVO createReqVO, Long userId); | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 创建【我的】知识库 |      * 更新知识库 | ||||||
|      * |      * | ||||||
|      * @param updateReqVO 更新信息 |      * @param updateReqVO 更新信息 | ||||||
|      * @param userId      用户编号 |      * @param userId      用户编号 | ||||||
|      */ |      */ | ||||||
|     void updateKnowledgeMy(AiKnowledgeUpdateMyReqVO updateReqVO, Long userId); |     void updateKnowledge(AiKnowledgeUpdateReqVO updateReqVO, Long userId); | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 校验知识库是否存在 |      * 校验知识库是否存在 | ||||||
| @ -39,21 +39,20 @@ public interface AiKnowledgeService { | |||||||
|     AiKnowledgeDO validateKnowledgeExists(Long id); |     AiKnowledgeDO validateKnowledgeExists(Long id); | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 获得【我的】知识库分页 |      * 获得知识库分页 | ||||||
|      * |      * | ||||||
|      * @param userId 用户编号 |      * @param userId    用户编号 | ||||||
|      * @param pageReqVO   分页查询 |      * @param pageReqVO 分页查询 | ||||||
|      * @return 知识库分页 |      * @return 知识库分页 | ||||||
|      */ |      */ | ||||||
|     PageResult<AiKnowledgeDO> getKnowledgePageMy(Long userId, PageParam pageReqVO); |     PageResult<AiKnowledgeDO> getKnowledgePage(Long userId, AiKnowledgePageReqVO pageReqVO); | ||||||
|  |  | ||||||
|     // TODO @新:knowledgeId 和 validateKnowledgeExists 的 id 是同一个么?如果是的话,建议变量也用 id 哈,然后两边的 id 注释,保持一致 |  | ||||||
|     /** |     /** | ||||||
|      * 根据知识库编号获取向量存储实例 |      * 根据知识库编号获取向量存储实例 | ||||||
|      * |      * | ||||||
|      * @param knowledgeId 知识库编号 |      * @param id 知识库编号 | ||||||
|      * @return 向量存储实例 |      * @return 向量存储实例 | ||||||
|      */ |      */ | ||||||
|     VectorStore getVectorStoreById(Long knowledgeId); |     VectorStore getVectorStoreById(Long id); | ||||||
|  |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,11 +2,11 @@ package cn.iocoder.yudao.module.ai.service.knowledge; | |||||||
|  |  | ||||||
| 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.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.knowledge.AiKnowledgeCreateMyReqVO; | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeCreateReqVO; | ||||||
| import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateMyReqVO; | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO; | ||||||
|  | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeUpdateReqVO; | ||||||
| import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; | import cn.iocoder.yudao.module.ai.dal.dataobject.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.AiKnowledgeMapper; | import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeMapper; | ||||||
| @ -29,21 +29,18 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_NOT_ | |||||||
| @Slf4j | @Slf4j | ||||||
| public class AiKnowledgeServiceImpl implements AiKnowledgeService { | public class AiKnowledgeServiceImpl implements AiKnowledgeService { | ||||||
|  |  | ||||||
|     @Resource |  | ||||||
|     private AiChatModelService chatModalService; |  | ||||||
|  |  | ||||||
|     @Resource |     @Resource | ||||||
|     private AiKnowledgeMapper knowledgeMapper; |     private AiKnowledgeMapper knowledgeMapper; | ||||||
|  |  | ||||||
|     @Resource |     @Resource | ||||||
|     private AiChatModelService chatModelService; |     private AiChatModelService chatModelService; | ||||||
|     @Resource |     @Resource | ||||||
|     private AiApiKeyService apiKeyService; |     private AiApiKeyService apiKeyService; | ||||||
|     // TODO @新:chatModelService 和 apiKeyService 可以放到 33 行的 chatModalService 后面。尽量保持,想通类型的变量在一块。例如说,Service 一块,Mapper 一块。 |  | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public Long createKnowledgeMy(AiKnowledgeCreateMyReqVO createReqVO, Long userId) { |     public Long createKnowledge(AiKnowledgeCreateReqVO createReqVO, Long userId) { | ||||||
|         // 1. 校验模型配置 |         // 1. 校验模型配置 | ||||||
|         AiChatModelDO model = chatModalService.validateChatModel(createReqVO.getModelId()); |         AiChatModelDO model = chatModelService.validateChatModel(createReqVO.getModelId()); | ||||||
|  |  | ||||||
|         // 2. 插入知识库 |         // 2. 插入知识库 | ||||||
|         AiKnowledgeDO knowledgeBase = BeanUtils.toBean(createReqVO, AiKnowledgeDO.class) |         AiKnowledgeDO knowledgeBase = BeanUtils.toBean(createReqVO, AiKnowledgeDO.class) | ||||||
| @ -53,14 +50,14 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void updateKnowledgeMy(AiKnowledgeUpdateMyReqVO updateReqVO, Long userId) { |     public void updateKnowledge(AiKnowledgeUpdateReqVO updateReqVO, Long userId) { | ||||||
|         // 1.1 校验知识库存在 |         // 1.1 校验知识库存在 | ||||||
|         AiKnowledgeDO 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 = chatModalService.validateChatModel(updateReqVO.getModelId()); |         AiChatModelDO model = chatModelService.validateChatModel(updateReqVO.getModelId()); | ||||||
|  |  | ||||||
|         // 2. 更新知识库 |         // 2. 更新知识库 | ||||||
|         AiKnowledgeDO updateDO = BeanUtils.toBean(updateReqVO, AiKnowledgeDO.class); |         AiKnowledgeDO updateDO = BeanUtils.toBean(updateReqVO, AiKnowledgeDO.class); | ||||||
| @ -78,13 +75,13 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public PageResult<AiKnowledgeDO> getKnowledgePageMy(Long userId, PageParam pageReqVO) { |     public PageResult<AiKnowledgeDO> getKnowledgePage(Long userId, AiKnowledgePageReqVO pageReqVO) { | ||||||
|         return knowledgeMapper.selectPageByMy(userId, pageReqVO); |         return knowledgeMapper.selectPage(userId, pageReqVO); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public VectorStore getVectorStoreById(Long knowledgeId) { |     public VectorStore getVectorStoreById(Long id) { | ||||||
|         AiKnowledgeDO knowledge = validateKnowledgeExists(knowledgeId); |         AiKnowledgeDO knowledge = validateKnowledgeExists(id); | ||||||
|         AiChatModelDO model = chatModelService.validateChatModel(knowledge.getModelId()); |         AiChatModelDO model = chatModelService.validateChatModel(knowledge.getModelId()); | ||||||
|         // 创建或获取 VectorStore 对象 |         // 创建或获取 VectorStore 对象 | ||||||
|         return apiKeyService.getOrCreateVectorStore(model.getKeyId()); |         return apiKeyService.getOrCreateVectorStore(model.getKeyId()); | ||||||
|  | |||||||
| @ -197,7 +197,6 @@ public class AiModelFactoryImpl implements AiModelFactory { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // TODO @新:貌似可以创建一个大的 VectorStore。然后搜的时候,通过 Filter.Expression 过滤对应的数据。 |  | ||||||
|     @Override |     @Override | ||||||
|     public VectorStore getOrCreateVectorStore(EmbeddingModel embeddingModel, AiPlatformEnum platform, String apiKey, String url) { |     public VectorStore getOrCreateVectorStore(EmbeddingModel embeddingModel, AiPlatformEnum platform, String apiKey, String url) { | ||||||
|         String cacheKey = buildClientCacheKey(VectorStore.class, platform, apiKey, url); |         String cacheKey = buildClientCacheKey(VectorStore.class, platform, apiKey, url); | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 芋道源码
					芋道源码