Commit d3a4c3c7188836b8a6abf8761f73341c4421bb93
Merge branch 'refs/heads/up-master-jdk21-ai' into master-jdk21-ai
# Conflicts: # yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java # yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/DocServiceImpl.java
Showing
18 changed files
with
273 additions
and
17 deletions
yudao-module-ai/pom.xml
| ... | ... | @@ -18,7 +18,7 @@ |
| 18 | 18 | |
| 19 | 19 | <name>${project.artifactId}</name> |
| 20 | 20 | <description> |
| 21 | - ai 模块下,接入 LLM 大模型,支持聊天、绘图、音乐、写作、思维脑图等功能。 | |
| 21 | + ai 模块下,接入 LLM 大模型,支持聊天、绘图、音乐、写作、思维导图等功能。 | |
| 22 | 22 | 目前已接入各种模型,不限于: |
| 23 | 23 | 国内:通义千问、文心一言、讯飞星火、智谱 GLM、DeepSeek |
| 24 | 24 | 国外:OpenAI、Ollama、Midjourney、StableDiffusion、Suno | ... | ... |
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiChatRoleEnum.java
| ... | ... | @@ -22,7 +22,7 @@ public enum AiChatRoleEnum implements IntArrayValuable { |
| 22 | 22 | 除此之外不需要除了正文内容外的其他回复,如标题、开头、任何解释性语句或道歉。 |
| 23 | 23 | """), |
| 24 | 24 | |
| 25 | - AI_MIND_MAP_ROLE(2, "脑图助手", """ | |
| 25 | + AI_MIND_MAP_ROLE(2, "导图助手", """ | |
| 26 | 26 | 你是一位非常优秀的思维导图助手,你会把用户的所有提问都总结成思维导图,然后以 Markdown 格式输出。markdown 只需要输出一级标题,二级标题,三级标题,四级标题,最多输出四级,除此之外不要输出任何其他 markdown 标记。下面是一个合格的例子: |
| 27 | 27 | # Geek-AI 助手 |
| 28 | 28 | ## 完整的开源系统 | ... | ... |
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java
| ... | ... | @@ -45,11 +45,13 @@ public interface ErrorCodeConstants { |
| 45 | 45 | // ========== API 音乐 1-040-006-000 ========== |
| 46 | 46 | ErrorCode MUSIC_NOT_EXISTS = new ErrorCode(1_022_006_000, "音乐不存在!"); |
| 47 | 47 | |
| 48 | - | |
| 49 | 48 | // ========== API 写作 1-022-007-000 ========== |
| 50 | 49 | ErrorCode WRITE_NOT_EXISTS = new ErrorCode(1_022_007_000, "作文不存在!"); |
| 51 | 50 | ErrorCode WRITE_STREAM_ERROR = new ErrorCode(1_022_07_001, "写作生成异常!"); |
| 52 | 51 | |
| 52 | + // ========== API 思维导图 1-040-008-000 ========== | |
| 53 | + ErrorCode MIND_MAP_NOT_EXISTS = new ErrorCode(1_040_008_000, "思维导图不存在!"); | |
| 54 | + | |
| 53 | 55 | |
| 54 | 56 | // ========== API 知识库 1-022-008-000 ========== |
| 55 | 57 | ErrorCode KNOWLEDGE_NOT_EXISTS = new ErrorCode(1_022_008_000, "知识库不存在!"); | ... | ... |
yudao-module-ai/yudao-module-ai-biz/pom.xml
| ... | ... | @@ -12,7 +12,7 @@ |
| 12 | 12 | |
| 13 | 13 | <name>${project.artifactId}</name> |
| 14 | 14 | <description> |
| 15 | - ai 模块下,接入 LLM 大模型,支持聊天、绘图、音乐、写作、思维脑图等功能。 | |
| 15 | + ai 模块下,接入 LLM 大模型,支持聊天、绘图、音乐、写作、思维导图等功能。 | |
| 16 | 16 | 目前已接入各种模型,不限于: |
| 17 | 17 | 国内:通义千问、文心一言、讯飞星火、智谱 GLM、DeepSeek |
| 18 | 18 | 国外:OpenAI、Ollama、Midjourney、StableDiffusion、Suno | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/mindmap/AiMindMapController.java
| 1 | 1 | package cn.iocoder.yudao.module.ai.controller.admin.mindmap; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 5 | +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | |
| 4 | 6 | import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapGenerateReqVO; |
| 7 | +import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapPageReqVO; | |
| 8 | +import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapRespVO; | |
| 9 | +import cn.iocoder.yudao.module.ai.dal.dataobject.mindmap.AiMindMapDO; | |
| 5 | 10 | import cn.iocoder.yudao.module.ai.service.mindmap.AiMindMapService; |
| 6 | 11 | import io.swagger.v3.oas.annotations.Operation; |
| 12 | +import io.swagger.v3.oas.annotations.Parameter; | |
| 7 | 13 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 8 | 14 | import jakarta.annotation.Resource; |
| 9 | 15 | import jakarta.annotation.security.PermitAll; |
| 10 | 16 | import jakarta.validation.Valid; |
| 11 | 17 | import org.springframework.http.MediaType; |
| 12 | -import org.springframework.web.bind.annotation.PostMapping; | |
| 13 | -import org.springframework.web.bind.annotation.RequestBody; | |
| 14 | -import org.springframework.web.bind.annotation.RequestMapping; | |
| 15 | -import org.springframework.web.bind.annotation.RestController; | |
| 18 | +import org.springframework.security.access.prepost.PreAuthorize; | |
| 19 | +import org.springframework.web.bind.annotation.*; | |
| 16 | 20 | import reactor.core.publisher.Flux; |
| 17 | 21 | |
| 22 | +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | |
| 18 | 23 | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| 19 | 24 | |
| 20 | 25 | @Tag(name = "管理后台 - AI 思维导图") |
| ... | ... | @@ -26,10 +31,29 @@ public class AiMindMapController { |
| 26 | 31 | private AiMindMapService mindMapService; |
| 27 | 32 | |
| 28 | 33 | @PostMapping(value = "/generate-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) |
| 29 | - @Operation(summary = "脑图生成(流式)", description = "流式返回,响应较快") | |
| 34 | + @Operation(summary = "导图生成(流式)", description = "流式返回,响应较快") | |
| 30 | 35 | @PermitAll // 解决 SSE 最终响应的时候,会被 Access Denied 拦截的问题 |
| 31 | 36 | public Flux<CommonResult<String>> generateMindMap(@RequestBody @Valid AiMindMapGenerateReqVO generateReqVO) { |
| 32 | 37 | return mindMapService.generateMindMap(generateReqVO, getLoginUserId()); |
| 33 | 38 | } |
| 34 | 39 | |
| 40 | + // ================ 导图管理 ================ | |
| 41 | + | |
| 42 | + @DeleteMapping("/delete") | |
| 43 | + @Operation(summary = "删除思维导图") | |
| 44 | + @Parameter(name = "id", description = "编号", required = true) | |
| 45 | + @PreAuthorize("@ss.hasPermission('ai:mind-map:delete')") | |
| 46 | + public CommonResult<Boolean> deleteMindMap(@RequestParam("id") Long id) { | |
| 47 | + mindMapService.deleteMindMap(id); | |
| 48 | + return success(true); | |
| 49 | + } | |
| 50 | + | |
| 51 | + @GetMapping("/page") | |
| 52 | + @Operation(summary = "获得思维导图分页") | |
| 53 | + @PreAuthorize("@ss.hasPermission('ai:mind-map:query')") | |
| 54 | + public CommonResult<PageResult<AiMindMapRespVO>> getMindMapPage(@Valid AiMindMapPageReqVO pageReqVO) { | |
| 55 | + PageResult<AiMindMapDO> pageResult = mindMapService.getMindMapPage(pageReqVO); | |
| 56 | + return success(BeanUtils.toBean(pageResult, AiMindMapRespVO.class)); | |
| 57 | + } | |
| 58 | + | |
| 35 | 59 | } | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/mindmap/vo/AiMindMapPageReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageParam; | |
| 4 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 5 | +import lombok.Data; | |
| 6 | +import lombok.EqualsAndHashCode; | |
| 7 | +import lombok.ToString; | |
| 8 | +import org.springframework.format.annotation.DateTimeFormat; | |
| 9 | + | |
| 10 | +import java.time.LocalDateTime; | |
| 11 | + | |
| 12 | +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; | |
| 13 | + | |
| 14 | +@Schema(description = "管理后台 - AI 思维导图分页 Request VO") | |
| 15 | +@Data | |
| 16 | +@EqualsAndHashCode(callSuper = true) | |
| 17 | +@ToString(callSuper = true) | |
| 18 | +public class AiMindMapPageReqVO extends PageParam { | |
| 19 | + | |
| 20 | + @Schema(description = "用户编号", example = "4325") | |
| 21 | + private Long userId; | |
| 22 | + | |
| 23 | + @Schema(description = "生成内容提示", example = "Java 学习路线") | |
| 24 | + private String prompt; | |
| 25 | + | |
| 26 | + @Schema(description = "创建时间") | |
| 27 | + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | |
| 28 | + private LocalDateTime[] createTime; | |
| 29 | + | |
| 30 | +} | |
| 0 | 31 | \ No newline at end of file | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/mindmap/vo/AiMindMapRespVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo; | |
| 2 | + | |
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +import java.time.LocalDateTime; | |
| 7 | + | |
| 8 | +@Schema(description = "管理后台 - AI 思维导图 Response VO") | |
| 9 | +@Data | |
| 10 | +public class AiMindMapRespVO { | |
| 11 | + | |
| 12 | + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3373") | |
| 13 | + private Long id; | |
| 14 | + | |
| 15 | + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4325") | |
| 16 | + private Long userId; | |
| 17 | + | |
| 18 | + @Schema(description = "生成内容提示", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 学习路线") | |
| 19 | + private String prompt; | |
| 20 | + | |
| 21 | + @Schema(description = "生成的思维导图内容") | |
| 22 | + private String generatedContent; | |
| 23 | + | |
| 24 | + @Schema(description = "平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "OpenAI") | |
| 25 | + private String platform; | |
| 26 | + | |
| 27 | + @Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "gpt-3.5-turbo-0125") | |
| 28 | + private String model; | |
| 29 | + | |
| 30 | + @Schema(description = "错误信息") | |
| 31 | + private String errorMessage; | |
| 32 | + | |
| 33 | + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 34 | + private LocalDateTime createTime; | |
| 35 | + | |
| 36 | +} | |
| 0 | 37 | \ No newline at end of file | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/mindmap/AiMindMapMapper.java
| 1 | 1 | package cn.iocoder.yudao.module.ai.dal.mysql.mindmap; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 3 | 4 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 5 | +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | |
| 6 | +import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapPageReqVO; | |
| 4 | 7 | import cn.iocoder.yudao.module.ai.dal.dataobject.mindmap.AiMindMapDO; |
| 5 | 8 | import org.apache.ibatis.annotations.Mapper; |
| 6 | 9 | |
| ... | ... | @@ -11,4 +14,13 @@ import org.apache.ibatis.annotations.Mapper; |
| 11 | 14 | */ |
| 12 | 15 | @Mapper |
| 13 | 16 | public interface AiMindMapMapper extends BaseMapperX<AiMindMapDO> { |
| 17 | + | |
| 18 | + default PageResult<AiMindMapDO> selectPage(AiMindMapPageReqVO reqVO) { | |
| 19 | + return selectPage(reqVO, new LambdaQueryWrapperX<AiMindMapDO>() | |
| 20 | + .eqIfPresent(AiMindMapDO::getUserId, reqVO.getUserId()) | |
| 21 | + .eqIfPresent(AiMindMapDO::getPrompt, reqVO.getPrompt()) | |
| 22 | + .betweenIfPresent(AiMindMapDO::getCreateTime, reqVO.getCreateTime()) | |
| 23 | + .orderByDesc(AiMindMapDO::getId)); | |
| 24 | + } | |
| 25 | + | |
| 14 | 26 | } | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/mindmap/AiMindMapService.java
| 1 | 1 | package cn.iocoder.yudao.module.ai.service.mindmap; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 4 | 5 | import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapGenerateReqVO; |
| 6 | +import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapPageReqVO; | |
| 7 | +import cn.iocoder.yudao.module.ai.dal.dataobject.mindmap.AiMindMapDO; | |
| 5 | 8 | import reactor.core.publisher.Flux; |
| 6 | 9 | |
| 7 | 10 | /** |
| ... | ... | @@ -20,4 +23,19 @@ public interface AiMindMapService { |
| 20 | 23 | */ |
| 21 | 24 | Flux<CommonResult<String>> generateMindMap(AiMindMapGenerateReqVO generateReqVO, Long userId); |
| 22 | 25 | |
| 26 | + /** | |
| 27 | + * 删除思维导图 | |
| 28 | + * | |
| 29 | + * @param id 编号 | |
| 30 | + */ | |
| 31 | + void deleteMindMap(Long id); | |
| 32 | + | |
| 33 | + /** | |
| 34 | + * 获得思维导图分页 | |
| 35 | + * | |
| 36 | + * @param pageReqVO 分页查询 | |
| 37 | + * @return 思维导图分页 | |
| 38 | + */ | |
| 39 | + PageResult<AiMindMapDO> getMindMapPage(AiMindMapPageReqVO pageReqVO); | |
| 40 | + | |
| 23 | 41 | } | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/mindmap/AiMindMapServiceImpl.java
| ... | ... | @@ -6,9 +6,11 @@ import cn.hutool.core.util.StrUtil; |
| 6 | 6 | import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; |
| 7 | 7 | import cn.iocoder.yudao.framework.ai.core.util.AiUtils; |
| 8 | 8 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 9 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 9 | 10 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 10 | 11 | import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; |
| 11 | 12 | import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapGenerateReqVO; |
| 13 | +import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapPageReqVO; | |
| 12 | 14 | import cn.iocoder.yudao.module.ai.dal.dataobject.mindmap.AiMindMapDO; |
| 13 | 15 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; |
| 14 | 16 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; |
| ... | ... | @@ -33,8 +35,10 @@ import reactor.core.publisher.Flux; |
| 33 | 35 | import java.util.ArrayList; |
| 34 | 36 | import java.util.List; |
| 35 | 37 | |
| 38 | +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | |
| 36 | 39 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error; |
| 37 | 40 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 41 | +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.MIND_MAP_NOT_EXISTS; | |
| 38 | 42 | |
| 39 | 43 | /** |
| 40 | 44 | * AI 思维导图 Service 实现类 |
| ... | ... | @@ -57,10 +61,10 @@ public class AiMindMapServiceImpl implements AiMindMapService { |
| 57 | 61 | |
| 58 | 62 | @Override |
| 59 | 63 | public Flux<CommonResult<String>> generateMindMap(AiMindMapGenerateReqVO generateReqVO, Long userId) { |
| 60 | - // 1. 获取脑图模型。尝试获取思维导图助手角色,如果没有则使用默认模型 | |
| 64 | + // 1. 获取导图模型。尝试获取思维导图助手角色,如果没有则使用默认模型 | |
| 61 | 65 | AiChatRoleDO role = CollUtil.getFirst( |
| 62 | 66 | chatRoleService.getChatRoleListByName(AiChatRoleEnum.AI_MIND_MAP_ROLE.getName())); |
| 63 | - // 1.1 获取脑图执行模型 | |
| 67 | + // 1.1 获取导图执行模型 | |
| 64 | 68 | AiChatModelDO model = getModel(role); |
| 65 | 69 | // 1.2 获取角色设定消息 |
| 66 | 70 | String systemMessage = role != null && StrUtil.isNotBlank(role.getSystemMessage()) |
| ... | ... | @@ -131,4 +135,23 @@ public class AiMindMapServiceImpl implements AiMindMapService { |
| 131 | 135 | return model; |
| 132 | 136 | } |
| 133 | 137 | |
| 138 | + @Override | |
| 139 | + public void deleteMindMap(Long id) { | |
| 140 | + // 校验存在 | |
| 141 | + validateMindMapExists(id); | |
| 142 | + // 删除 | |
| 143 | + mindMapMapper.deleteById(id); | |
| 144 | + } | |
| 145 | + | |
| 146 | + private void validateMindMapExists(Long id) { | |
| 147 | + if (mindMapMapper.selectById(id) == null) { | |
| 148 | + throw exception(MIND_MAP_NOT_EXISTS); | |
| 149 | + } | |
| 150 | + } | |
| 151 | + | |
| 152 | + @Override | |
| 153 | + public PageResult<AiMindMapDO> getMindMapPage(AiMindMapPageReqVO pageReqVO) { | |
| 154 | + return mindMapMapper.selectPage(pageReqVO); | |
| 155 | + } | |
| 156 | + | |
| 134 | 157 | } | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml
| ... | ... | @@ -23,7 +23,6 @@ |
| 23 | 23 | <artifactId>spring-ai-zhipuai-spring-boot-starter</artifactId> |
| 24 | 24 | <version>${spring-ai.version}</version> |
| 25 | 25 | </dependency> |
| 26 | - | |
| 27 | 26 | <dependency> |
| 28 | 27 | <groupId>org.springframework.ai</groupId> |
| 29 | 28 | <artifactId>spring-ai-openai-spring-boot-starter</artifactId> |
| ... | ... | @@ -31,6 +30,11 @@ |
| 31 | 30 | </dependency> |
| 32 | 31 | <dependency> |
| 33 | 32 | <groupId>org.springframework.ai</groupId> |
| 33 | + <artifactId>spring-ai-azure-openai-spring-boot-starter</artifactId> | |
| 34 | + <version>${spring-ai.version}</version> | |
| 35 | + </dependency> | |
| 36 | + <dependency> | |
| 37 | + <groupId>org.springframework.ai</groupId> | |
| 34 | 38 | <artifactId>spring-ai-ollama-spring-boot-starter</artifactId> |
| 35 | 39 | <version>${spring-ai.version}</version> |
| 36 | 40 | </dependency> | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java
| ... | ... | @@ -12,6 +12,7 @@ import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration; |
| 12 | 12 | import lombok.extern.slf4j.Slf4j; |
| 13 | 13 | import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreProperties; |
| 14 | 14 | import org.springframework.ai.document.MetadataMode; |
| 15 | +import org.springframework.ai.embedding.EmbeddingModel; | |
| 15 | 16 | import org.springframework.ai.transformer.splitter.TokenTextSplitter; |
| 16 | 17 | import org.springframework.ai.transformers.TransformersEmbeddingModel; |
| 17 | 18 | import org.springframework.ai.vectorstore.RedisVectorStore; |
| ... | ... | @@ -21,6 +22,7 @@ import org.springframework.boot.autoconfigure.data.redis.RedisProperties; |
| 21 | 22 | import org.springframework.boot.context.properties.EnableConfigurationProperties; |
| 22 | 23 | import org.springframework.context.annotation.Bean; |
| 23 | 24 | import org.springframework.context.annotation.Import; |
| 25 | +import org.springframework.context.annotation.Lazy; | |
| 24 | 26 | import redis.clients.jedis.JedisPooled; |
| 25 | 27 | |
| 26 | 28 | /** |
| ... | ... | @@ -82,7 +84,8 @@ public class YudaoAiAutoConfiguration { |
| 82 | 84 | |
| 83 | 85 | // ========== rag 相关 ========== |
| 84 | 86 | @Bean |
| 85 | - public TransformersEmbeddingModel transformersEmbeddingClient() { | |
| 87 | + @Lazy // TODO 芋艿:临时注释,避免无法启动 | |
| 88 | + public EmbeddingModel transformersEmbeddingClient() { | |
| 86 | 89 | return new TransformersEmbeddingModel(MetadataMode.EMBED); |
| 87 | 90 | } |
| 88 | 91 | |
| ... | ... | @@ -90,6 +93,7 @@ public class YudaoAiAutoConfiguration { |
| 90 | 93 | * 我们启动有加载很多 Embedding 模型,不晓得取哪个好,先 new 个 TransformersEmbeddingModel 跑 |
| 91 | 94 | */ |
| 92 | 95 | @Bean |
| 96 | + @Lazy // TODO 芋艿:临时注释,避免无法启动 | |
| 93 | 97 | public RedisVectorStore vectorStore(TransformersEmbeddingModel transformersEmbeddingModel, RedisVectorStoreProperties properties, |
| 94 | 98 | RedisProperties redisProperties) { |
| 95 | 99 | var config = RedisVectorStore.RedisVectorStoreConfig.builder() |
| ... | ... | @@ -105,6 +109,7 @@ public class YudaoAiAutoConfiguration { |
| 105 | 109 | } |
| 106 | 110 | |
| 107 | 111 | @Bean |
| 112 | + @Lazy // TODO 芋艿:临时注释,避免无法启动 | |
| 108 | 113 | public TokenTextSplitter tokenTextSplitter() { |
| 109 | 114 | return new TokenTextSplitter(500, 100, 5, 10000, true); |
| 110 | 115 | } | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java
| ... | ... | @@ -22,7 +22,8 @@ public enum AiPlatformEnum { |
| 22 | 22 | |
| 23 | 23 | // ========== 国外平台 ========== |
| 24 | 24 | |
| 25 | - OPENAI("OpenAI", "OpenAI"), | |
| 25 | + OPENAI("OpenAI", "OpenAI"), // OpenAI 官方 | |
| 26 | + AZURE_OPENAI("AzureOpenAI", "AzureOpenAI"), // OpenAI 微软 | |
| 26 | 27 | OLLAMA("Ollama", "Ollama"), |
| 27 | 28 | |
| 28 | 29 | STABLE_DIFFUSION("StableDiffusion", "StableDiffusion"), // Stability AI | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java
| ... | ... | @@ -21,6 +21,10 @@ import com.alibaba.cloud.ai.tongyi.image.TongYiImagesModel; |
| 21 | 21 | import com.alibaba.cloud.ai.tongyi.image.TongYiImagesProperties; |
| 22 | 22 | import com.alibaba.dashscope.aigc.generation.Generation; |
| 23 | 23 | import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis; |
| 24 | +import com.azure.ai.openai.OpenAIClient; | |
| 25 | +import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiAutoConfiguration; | |
| 26 | +import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiChatProperties; | |
| 27 | +import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiConnectionProperties; | |
| 24 | 28 | import org.springframework.ai.autoconfigure.ollama.OllamaAutoConfiguration; |
| 25 | 29 | import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration; |
| 26 | 30 | import org.springframework.ai.autoconfigure.qianfan.QianFanAutoConfiguration; |
| ... | ... | @@ -31,6 +35,7 @@ import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiAutoConfiguration; |
| 31 | 35 | import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiChatProperties; |
| 32 | 36 | import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiConnectionProperties; |
| 33 | 37 | import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiImageProperties; |
| 38 | +import org.springframework.ai.azure.openai.AzureOpenAiChatModel; | |
| 34 | 39 | import org.springframework.ai.chat.model.ChatModel; |
| 35 | 40 | import org.springframework.ai.image.ImageModel; |
| 36 | 41 | import org.springframework.ai.model.function.FunctionCallbackContext; |
| ... | ... | @@ -82,6 +87,8 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 82 | 87 | return buildXingHuoChatModel(apiKey); |
| 83 | 88 | case OPENAI: |
| 84 | 89 | return buildOpenAiChatModel(apiKey, url); |
| 90 | + case AZURE_OPENAI: | |
| 91 | + return buildAzureOpenAiChatModel(apiKey, url); | |
| 85 | 92 | case OLLAMA: |
| 86 | 93 | return buildOllamaChatModel(url); |
| 87 | 94 | default: |
| ... | ... | @@ -106,6 +113,8 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 106 | 113 | return SpringUtil.getBean(XingHuoChatModel.class); |
| 107 | 114 | case OPENAI: |
| 108 | 115 | return SpringUtil.getBean(OpenAiChatModel.class); |
| 116 | + case AZURE_OPENAI: | |
| 117 | + return SpringUtil.getBean(AzureOpenAiChatModel.class); | |
| 109 | 118 | case OLLAMA: |
| 110 | 119 | return SpringUtil.getBean(OllamaChatModel.class); |
| 111 | 120 | default: |
| ... | ... | @@ -269,6 +278,21 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 269 | 278 | } |
| 270 | 279 | |
| 271 | 280 | /** |
| 281 | + * 可参考 {@link AzureOpenAiAutoConfiguration} | |
| 282 | + */ | |
| 283 | + private static AzureOpenAiChatModel buildAzureOpenAiChatModel(String apiKey, String url) { | |
| 284 | + AzureOpenAiAutoConfiguration azureOpenAiAutoConfiguration = new AzureOpenAiAutoConfiguration(); | |
| 285 | + // 创建 OpenAIClient 对象 | |
| 286 | + AzureOpenAiConnectionProperties connectionProperties = new AzureOpenAiConnectionProperties(); | |
| 287 | + connectionProperties.setApiKey(apiKey); | |
| 288 | + connectionProperties.setEndpoint(url); | |
| 289 | + OpenAIClient openAIClient = azureOpenAiAutoConfiguration.openAIClient(connectionProperties); | |
| 290 | + // 获取 AzureOpenAiChatProperties 对象 | |
| 291 | + AzureOpenAiChatProperties chatProperties = SpringUtil.getBean(AzureOpenAiChatProperties.class); | |
| 292 | + return azureOpenAiAutoConfiguration.azureOpenAiChatModel(openAIClient, chatProperties, null, null); | |
| 293 | + } | |
| 294 | + | |
| 295 | + /** | |
| 272 | 296 | * 可参考 {@link OpenAiAutoConfiguration} |
| 273 | 297 | */ |
| 274 | 298 | private OpenAiImageModel buildOpenAiImageModel(String openAiToken, String url) { | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/util/AiUtils.java
| ... | ... | @@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; |
| 5 | 5 | import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatOptions; |
| 6 | 6 | import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatOptions; |
| 7 | 7 | import com.alibaba.cloud.ai.tongyi.chat.TongYiChatOptions; |
| 8 | +import org.springframework.ai.azure.openai.AzureOpenAiChatOptions; | |
| 8 | 9 | import org.springframework.ai.chat.messages.*; |
| 9 | 10 | import org.springframework.ai.chat.prompt.ChatOptions; |
| 10 | 11 | import org.springframework.ai.ollama.api.OllamaOptions; |
| ... | ... | @@ -35,6 +36,9 @@ public class AiUtils { |
| 35 | 36 | return XingHuoChatOptions.builder().model(model).temperature(temperatureF).maxTokens(maxTokens).build(); |
| 36 | 37 | case OPENAI: |
| 37 | 38 | return OpenAiChatOptions.builder().withModel(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build(); |
| 39 | + case AZURE_OPENAI: | |
| 40 | + // TODO 芋艿:貌似没 model 字段???! | |
| 41 | + return AzureOpenAiChatOptions.builder().withDeploymentName(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build(); | |
| 38 | 42 | case OLLAMA: |
| 39 | 43 | return OllamaOptions.create().withModel(model).withTemperature(temperatureF).withNumPredict(maxTokens); |
| 40 | 44 | default: | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/AzureOpenAIChatModelTests.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.ai.chat; | |
| 2 | + | |
| 3 | +import com.azure.ai.openai.OpenAIClient; | |
| 4 | +import com.azure.ai.openai.OpenAIClientBuilder; | |
| 5 | +import com.azure.core.credential.AzureKeyCredential; | |
| 6 | +import com.azure.core.util.ClientOptions; | |
| 7 | +import org.junit.jupiter.api.Disabled; | |
| 8 | +import org.junit.jupiter.api.Test; | |
| 9 | +import org.springframework.ai.azure.openai.AzureOpenAiChatModel; | |
| 10 | +import org.springframework.ai.azure.openai.AzureOpenAiChatOptions; | |
| 11 | +import org.springframework.ai.chat.messages.Message; | |
| 12 | +import org.springframework.ai.chat.messages.SystemMessage; | |
| 13 | +import org.springframework.ai.chat.messages.UserMessage; | |
| 14 | +import org.springframework.ai.chat.model.ChatResponse; | |
| 15 | +import org.springframework.ai.chat.prompt.Prompt; | |
| 16 | +import reactor.core.publisher.Flux; | |
| 17 | + | |
| 18 | +import java.util.ArrayList; | |
| 19 | +import java.util.List; | |
| 20 | + | |
| 21 | +import static org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiChatProperties.DEFAULT_DEPLOYMENT_NAME; | |
| 22 | + | |
| 23 | +/** | |
| 24 | + * {@link AzureOpenAiChatModel} 集成测试 | |
| 25 | + * | |
| 26 | + * @author 芋道源码 | |
| 27 | + */ | |
| 28 | +public class AzureOpenAIChatModelTests { | |
| 29 | + | |
| 30 | + private final OpenAIClient openAiApi = (new OpenAIClientBuilder()) | |
| 31 | + .endpoint("https://eastusprejade.openai.azure.com") | |
| 32 | + .credential(new AzureKeyCredential("xxx")) | |
| 33 | + .clientOptions((new ClientOptions()).setApplicationId("spring-ai")) | |
| 34 | + .buildClient(); | |
| 35 | + private final AzureOpenAiChatModel chatModel = new AzureOpenAiChatModel(openAiApi, | |
| 36 | + AzureOpenAiChatOptions.builder().withDeploymentName(DEFAULT_DEPLOYMENT_NAME).build()); | |
| 37 | + | |
| 38 | + @Test | |
| 39 | + @Disabled | |
| 40 | + public void testCall() { | |
| 41 | + // 准备参数 | |
| 42 | + List<Message> messages = new ArrayList<>(); | |
| 43 | + messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | |
| 44 | + messages.add(new UserMessage("1 + 1 = ?")); | |
| 45 | + | |
| 46 | + // 调用 | |
| 47 | + ChatResponse response = chatModel.call(new Prompt(messages)); | |
| 48 | + // 打印结果 | |
| 49 | + System.out.println(response); | |
| 50 | + System.out.println(response.getResult().getOutput()); | |
| 51 | + } | |
| 52 | + | |
| 53 | + @Test | |
| 54 | + @Disabled | |
| 55 | + public void testStream() { | |
| 56 | + // 准备参数 | |
| 57 | + List<Message> messages = new ArrayList<>(); | |
| 58 | + messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | |
| 59 | + messages.add(new UserMessage("1 + 1 = ?")); | |
| 60 | + | |
| 61 | + // 调用 | |
| 62 | + Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages)); | |
| 63 | + // 打印结果 | |
| 64 | + flux.doOnNext(response -> { | |
| 65 | +// System.out.println(response); | |
| 66 | + System.out.println(response.getResult().getOutput()); | |
| 67 | + }).then().block(); | |
| 68 | + } | |
| 69 | + | |
| 70 | +} | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/OpenAIChatModelTests.java
| 1 | 1 | package cn.iocoder.yudao.framework.ai.chat; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; | |
| 4 | 3 | import org.junit.jupiter.api.Disabled; |
| 5 | 4 | import org.junit.jupiter.api.Test; |
| 6 | 5 | import org.springframework.ai.chat.messages.Message; |
| ... | ... | @@ -17,7 +16,7 @@ import java.util.ArrayList; |
| 17 | 16 | import java.util.List; |
| 18 | 17 | |
| 19 | 18 | /** |
| 20 | - * {@link XingHuoChatModel} 集成测试 | |
| 19 | + * {@link OpenAiChatModel} 集成测试 | |
| 21 | 20 | * |
| 22 | 21 | * @author 芋道源码 |
| 23 | 22 | */ | ... | ... |
yudao-server/src/main/resources/application.yaml
| ... | ... | @@ -162,9 +162,13 @@ spring: |
| 162 | 162 | secret-key: R9mYF9dl9KASgi5RUq0FQt3wRisSnOcK |
| 163 | 163 | zhipuai: # 智谱 AI |
| 164 | 164 | api-key: 32f84543e54eee31f8d56b2bd6020573.3vh9idLJZ2ZhxDEs |
| 165 | - openai: | |
| 165 | + openai: # OpenAI 官方 | |
| 166 | 166 | api-key: sk-yzKea6d8e8212c3bdd99f9f44ced1cae37c097e5aa3BTS7z |
| 167 | 167 | base-url: https://api.gptsapi.net |
| 168 | + azure: # OpenAI 微软 | |
| 169 | + openai: | |
| 170 | + endpoint: https://eastusprejade.openai.azure.com | |
| 171 | + api-key: xxx | |
| 168 | 172 | ollama: |
| 169 | 173 | base-url: http://127.0.0.1:11434 |
| 170 | 174 | chat: | ... | ... |