Commit d3a4c3c7188836b8a6abf8761f73341c4421bb93

Authored by xiaoxin
2 parents 8e54eef8 83d86bcf

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,7 +18,7 @@
18 18
19 <name>${project.artifactId}</name> 19 <name>${project.artifactId}</name>
20 <description> 20 <description>
21 - ai 模块下,接入 LLM 大模型,支持聊天、绘图、音乐、写作、思维脑图等功能。 21 + ai 模块下,接入 LLM 大模型,支持聊天、绘图、音乐、写作、思维导图等功能。
22 目前已接入各种模型,不限于: 22 目前已接入各种模型,不限于:
23 国内:通义千问、文心一言、讯飞星火、智谱 GLM、DeepSeek 23 国内:通义千问、文心一言、讯飞星火、智谱 GLM、DeepSeek
24 国外:OpenAI、Ollama、Midjourney、StableDiffusion、Suno 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,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 你是一位非常优秀的思维导图助手,你会把用户的所有提问都总结成思维导图,然后以 Markdown 格式输出。markdown 只需要输出一级标题,二级标题,三级标题,四级标题,最多输出四级,除此之外不要输出任何其他 markdown 标记。下面是一个合格的例子: 26 你是一位非常优秀的思维导图助手,你会把用户的所有提问都总结成思维导图,然后以 Markdown 格式输出。markdown 只需要输出一级标题,二级标题,三级标题,四级标题,最多输出四级,除此之外不要输出任何其他 markdown 标记。下面是一个合格的例子:
27 # Geek-AI 助手 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,11 +45,13 @@ public interface ErrorCodeConstants {
45 // ========== API 音乐 1-040-006-000 ========== 45 // ========== API 音乐 1-040-006-000 ==========
46 ErrorCode MUSIC_NOT_EXISTS = new ErrorCode(1_022_006_000, "音乐不存在!"); 46 ErrorCode MUSIC_NOT_EXISTS = new ErrorCode(1_022_006_000, "音乐不存在!");
47 47
48 -  
49 // ========== API 写作 1-022-007-000 ========== 48 // ========== API 写作 1-022-007-000 ==========
50 ErrorCode WRITE_NOT_EXISTS = new ErrorCode(1_022_007_000, "作文不存在!"); 49 ErrorCode WRITE_NOT_EXISTS = new ErrorCode(1_022_007_000, "作文不存在!");
51 ErrorCode WRITE_STREAM_ERROR = new ErrorCode(1_022_07_001, "写作生成异常!"); 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 // ========== API 知识库 1-022-008-000 ========== 56 // ========== API 知识库 1-022-008-000 ==========
55 ErrorCode KNOWLEDGE_NOT_EXISTS = new ErrorCode(1_022_008_000, "知识库不存在!"); 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,7 +12,7 @@
12 12
13 <name>${project.artifactId}</name> 13 <name>${project.artifactId}</name>
14 <description> 14 <description>
15 - ai 模块下,接入 LLM 大模型,支持聊天、绘图、音乐、写作、思维脑图等功能。 15 + ai 模块下,接入 LLM 大模型,支持聊天、绘图、音乐、写作、思维导图等功能。
16 目前已接入各种模型,不限于: 16 目前已接入各种模型,不限于:
17 国内:通义千问、文心一言、讯飞星火、智谱 GLM、DeepSeek 17 国内:通义千问、文心一言、讯飞星火、智谱 GLM、DeepSeek
18 国外:OpenAI、Ollama、Midjourney、StableDiffusion、Suno 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 package cn.iocoder.yudao.module.ai.controller.admin.mindmap; 1 package cn.iocoder.yudao.module.ai.controller.admin.mindmap;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 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 import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapGenerateReqVO; 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 import cn.iocoder.yudao.module.ai.service.mindmap.AiMindMapService; 10 import cn.iocoder.yudao.module.ai.service.mindmap.AiMindMapService;
6 import io.swagger.v3.oas.annotations.Operation; 11 import io.swagger.v3.oas.annotations.Operation;
  12 +import io.swagger.v3.oas.annotations.Parameter;
7 import io.swagger.v3.oas.annotations.tags.Tag; 13 import io.swagger.v3.oas.annotations.tags.Tag;
8 import jakarta.annotation.Resource; 14 import jakarta.annotation.Resource;
9 import jakarta.annotation.security.PermitAll; 15 import jakarta.annotation.security.PermitAll;
10 import jakarta.validation.Valid; 16 import jakarta.validation.Valid;
11 import org.springframework.http.MediaType; 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 import reactor.core.publisher.Flux; 20 import reactor.core.publisher.Flux;
17 21
  22 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
18 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; 23 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
19 24
20 @Tag(name = "管理后台 - AI 思维导图") 25 @Tag(name = "管理后台 - AI 思维导图")
@@ -26,10 +31,29 @@ public class AiMindMapController { @@ -26,10 +31,29 @@ public class AiMindMapController {
26 private AiMindMapService mindMapService; 31 private AiMindMapService mindMapService;
27 32
28 @PostMapping(value = "/generate-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) 33 @PostMapping(value = "/generate-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
29 - @Operation(summary = "脑图生成(流式)", description = "流式返回,响应较快") 34 + @Operation(summary = "导图生成(流式)", description = "流式返回,响应较快")
30 @PermitAll // 解决 SSE 最终响应的时候,会被 Access Denied 拦截的问题 35 @PermitAll // 解决 SSE 最终响应的时候,会被 Access Denied 拦截的问题
31 public Flux<CommonResult<String>> generateMindMap(@RequestBody @Valid AiMindMapGenerateReqVO generateReqVO) { 36 public Flux<CommonResult<String>> generateMindMap(@RequestBody @Valid AiMindMapGenerateReqVO generateReqVO) {
32 return mindMapService.generateMindMap(generateReqVO, getLoginUserId()); 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 \ No newline at end of file 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 \ No newline at end of file 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 package cn.iocoder.yudao.module.ai.dal.mysql.mindmap; 1 package cn.iocoder.yudao.module.ai.dal.mysql.mindmap;
2 2
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
3 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 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 import cn.iocoder.yudao.module.ai.dal.dataobject.mindmap.AiMindMapDO; 7 import cn.iocoder.yudao.module.ai.dal.dataobject.mindmap.AiMindMapDO;
5 import org.apache.ibatis.annotations.Mapper; 8 import org.apache.ibatis.annotations.Mapper;
6 9
@@ -11,4 +14,13 @@ import org.apache.ibatis.annotations.Mapper; @@ -11,4 +14,13 @@ import org.apache.ibatis.annotations.Mapper;
11 */ 14 */
12 @Mapper 15 @Mapper
13 public interface AiMindMapMapper extends BaseMapperX<AiMindMapDO> { 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 package cn.iocoder.yudao.module.ai.service.mindmap; 1 package cn.iocoder.yudao.module.ai.service.mindmap;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapGenerateReqVO; 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 import reactor.core.publisher.Flux; 8 import reactor.core.publisher.Flux;
6 9
7 /** 10 /**
@@ -20,4 +23,19 @@ public interface AiMindMapService { @@ -20,4 +23,19 @@ public interface AiMindMapService {
20 */ 23 */
21 Flux<CommonResult<String>> generateMindMap(AiMindMapGenerateReqVO generateReqVO, Long userId); 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,9 +6,11 @@ import cn.hutool.core.util.StrUtil;
6 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; 6 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
7 import cn.iocoder.yudao.framework.ai.core.util.AiUtils; 7 import cn.iocoder.yudao.framework.ai.core.util.AiUtils;
8 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 8 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  9 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
9 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 10 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
10 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; 11 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
11 import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapGenerateReqVO; 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 import cn.iocoder.yudao.module.ai.dal.dataobject.mindmap.AiMindMapDO; 14 import cn.iocoder.yudao.module.ai.dal.dataobject.mindmap.AiMindMapDO;
13 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; 15 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
14 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; 16 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
@@ -33,8 +35,10 @@ import reactor.core.publisher.Flux; @@ -33,8 +35,10 @@ import reactor.core.publisher.Flux;
33 import java.util.ArrayList; 35 import java.util.ArrayList;
34 import java.util.List; 36 import java.util.List;
35 37
  38 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
36 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error; 39 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error;
37 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 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 * AI 思维导图 Service 实现类 44 * AI 思维导图 Service 实现类
@@ -57,10 +61,10 @@ public class AiMindMapServiceImpl implements AiMindMapService { @@ -57,10 +61,10 @@ public class AiMindMapServiceImpl implements AiMindMapService {
57 61
58 @Override 62 @Override
59 public Flux<CommonResult<String>> generateMindMap(AiMindMapGenerateReqVO generateReqVO, Long userId) { 63 public Flux<CommonResult<String>> generateMindMap(AiMindMapGenerateReqVO generateReqVO, Long userId) {
60 - // 1. 获取脑图模型。尝试获取思维导图助手角色,如果没有则使用默认模型 64 + // 1. 获取导图模型。尝试获取思维导图助手角色,如果没有则使用默认模型
61 AiChatRoleDO role = CollUtil.getFirst( 65 AiChatRoleDO role = CollUtil.getFirst(
62 chatRoleService.getChatRoleListByName(AiChatRoleEnum.AI_MIND_MAP_ROLE.getName())); 66 chatRoleService.getChatRoleListByName(AiChatRoleEnum.AI_MIND_MAP_ROLE.getName()));
63 - // 1.1 获取脑图执行模型 67 + // 1.1 获取导图执行模型
64 AiChatModelDO model = getModel(role); 68 AiChatModelDO model = getModel(role);
65 // 1.2 获取角色设定消息 69 // 1.2 获取角色设定消息
66 String systemMessage = role != null && StrUtil.isNotBlank(role.getSystemMessage()) 70 String systemMessage = role != null && StrUtil.isNotBlank(role.getSystemMessage())
@@ -131,4 +135,23 @@ public class AiMindMapServiceImpl implements AiMindMapService { @@ -131,4 +135,23 @@ public class AiMindMapServiceImpl implements AiMindMapService {
131 return model; 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,7 +23,6 @@
23 <artifactId>spring-ai-zhipuai-spring-boot-starter</artifactId> 23 <artifactId>spring-ai-zhipuai-spring-boot-starter</artifactId>
24 <version>${spring-ai.version}</version> 24 <version>${spring-ai.version}</version>
25 </dependency> 25 </dependency>
26 -  
27 <dependency> 26 <dependency>
28 <groupId>org.springframework.ai</groupId> 27 <groupId>org.springframework.ai</groupId>
29 <artifactId>spring-ai-openai-spring-boot-starter</artifactId> 28 <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
@@ -31,6 +30,11 @@ @@ -31,6 +30,11 @@
31 </dependency> 30 </dependency>
32 <dependency> 31 <dependency>
33 <groupId>org.springframework.ai</groupId> 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 <artifactId>spring-ai-ollama-spring-boot-starter</artifactId> 38 <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
35 <version>${spring-ai.version}</version> 39 <version>${spring-ai.version}</version>
36 </dependency> 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,6 +12,7 @@ import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration;
12 import lombok.extern.slf4j.Slf4j; 12 import lombok.extern.slf4j.Slf4j;
13 import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreProperties; 13 import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreProperties;
14 import org.springframework.ai.document.MetadataMode; 14 import org.springframework.ai.document.MetadataMode;
  15 +import org.springframework.ai.embedding.EmbeddingModel;
15 import org.springframework.ai.transformer.splitter.TokenTextSplitter; 16 import org.springframework.ai.transformer.splitter.TokenTextSplitter;
16 import org.springframework.ai.transformers.TransformersEmbeddingModel; 17 import org.springframework.ai.transformers.TransformersEmbeddingModel;
17 import org.springframework.ai.vectorstore.RedisVectorStore; 18 import org.springframework.ai.vectorstore.RedisVectorStore;
@@ -21,6 +22,7 @@ import org.springframework.boot.autoconfigure.data.redis.RedisProperties; @@ -21,6 +22,7 @@ import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
21 import org.springframework.boot.context.properties.EnableConfigurationProperties; 22 import org.springframework.boot.context.properties.EnableConfigurationProperties;
22 import org.springframework.context.annotation.Bean; 23 import org.springframework.context.annotation.Bean;
23 import org.springframework.context.annotation.Import; 24 import org.springframework.context.annotation.Import;
  25 +import org.springframework.context.annotation.Lazy;
24 import redis.clients.jedis.JedisPooled; 26 import redis.clients.jedis.JedisPooled;
25 27
26 /** 28 /**
@@ -82,7 +84,8 @@ public class YudaoAiAutoConfiguration { @@ -82,7 +84,8 @@ public class YudaoAiAutoConfiguration {
82 84
83 // ========== rag 相关 ========== 85 // ========== rag 相关 ==========
84 @Bean 86 @Bean
85 - public TransformersEmbeddingModel transformersEmbeddingClient() { 87 + @Lazy // TODO 芋艿:临时注释,避免无法启动
  88 + public EmbeddingModel transformersEmbeddingClient() {
86 return new TransformersEmbeddingModel(MetadataMode.EMBED); 89 return new TransformersEmbeddingModel(MetadataMode.EMBED);
87 } 90 }
88 91
@@ -90,6 +93,7 @@ public class YudaoAiAutoConfiguration { @@ -90,6 +93,7 @@ public class YudaoAiAutoConfiguration {
90 * 我们启动有加载很多 Embedding 模型,不晓得取哪个好,先 new 个 TransformersEmbeddingModel 跑 93 * 我们启动有加载很多 Embedding 模型,不晓得取哪个好,先 new 个 TransformersEmbeddingModel 跑
91 */ 94 */
92 @Bean 95 @Bean
  96 + @Lazy // TODO 芋艿:临时注释,避免无法启动
93 public RedisVectorStore vectorStore(TransformersEmbeddingModel transformersEmbeddingModel, RedisVectorStoreProperties properties, 97 public RedisVectorStore vectorStore(TransformersEmbeddingModel transformersEmbeddingModel, RedisVectorStoreProperties properties,
94 RedisProperties redisProperties) { 98 RedisProperties redisProperties) {
95 var config = RedisVectorStore.RedisVectorStoreConfig.builder() 99 var config = RedisVectorStore.RedisVectorStoreConfig.builder()
@@ -105,6 +109,7 @@ public class YudaoAiAutoConfiguration { @@ -105,6 +109,7 @@ public class YudaoAiAutoConfiguration {
105 } 109 }
106 110
107 @Bean 111 @Bean
  112 + @Lazy // TODO 芋艿:临时注释,避免无法启动
108 public TokenTextSplitter tokenTextSplitter() { 113 public TokenTextSplitter tokenTextSplitter() {
109 return new TokenTextSplitter(500, 100, 5, 10000, true); 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,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 OLLAMA("Ollama", "Ollama"), 27 OLLAMA("Ollama", "Ollama"),
27 28
28 STABLE_DIFFUSION("StableDiffusion", "StableDiffusion"), // Stability AI 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,6 +21,10 @@ import com.alibaba.cloud.ai.tongyi.image.TongYiImagesModel;
21 import com.alibaba.cloud.ai.tongyi.image.TongYiImagesProperties; 21 import com.alibaba.cloud.ai.tongyi.image.TongYiImagesProperties;
22 import com.alibaba.dashscope.aigc.generation.Generation; 22 import com.alibaba.dashscope.aigc.generation.Generation;
23 import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis; 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 import org.springframework.ai.autoconfigure.ollama.OllamaAutoConfiguration; 28 import org.springframework.ai.autoconfigure.ollama.OllamaAutoConfiguration;
25 import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration; 29 import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration;
26 import org.springframework.ai.autoconfigure.qianfan.QianFanAutoConfiguration; 30 import org.springframework.ai.autoconfigure.qianfan.QianFanAutoConfiguration;
@@ -31,6 +35,7 @@ import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiAutoConfiguration; @@ -31,6 +35,7 @@ import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiAutoConfiguration;
31 import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiChatProperties; 35 import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiChatProperties;
32 import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiConnectionProperties; 36 import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiConnectionProperties;
33 import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiImageProperties; 37 import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiImageProperties;
  38 +import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
34 import org.springframework.ai.chat.model.ChatModel; 39 import org.springframework.ai.chat.model.ChatModel;
35 import org.springframework.ai.image.ImageModel; 40 import org.springframework.ai.image.ImageModel;
36 import org.springframework.ai.model.function.FunctionCallbackContext; 41 import org.springframework.ai.model.function.FunctionCallbackContext;
@@ -82,6 +87,8 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -82,6 +87,8 @@ public class AiModelFactoryImpl implements AiModelFactory {
82 return buildXingHuoChatModel(apiKey); 87 return buildXingHuoChatModel(apiKey);
83 case OPENAI: 88 case OPENAI:
84 return buildOpenAiChatModel(apiKey, url); 89 return buildOpenAiChatModel(apiKey, url);
  90 + case AZURE_OPENAI:
  91 + return buildAzureOpenAiChatModel(apiKey, url);
85 case OLLAMA: 92 case OLLAMA:
86 return buildOllamaChatModel(url); 93 return buildOllamaChatModel(url);
87 default: 94 default:
@@ -106,6 +113,8 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -106,6 +113,8 @@ public class AiModelFactoryImpl implements AiModelFactory {
106 return SpringUtil.getBean(XingHuoChatModel.class); 113 return SpringUtil.getBean(XingHuoChatModel.class);
107 case OPENAI: 114 case OPENAI:
108 return SpringUtil.getBean(OpenAiChatModel.class); 115 return SpringUtil.getBean(OpenAiChatModel.class);
  116 + case AZURE_OPENAI:
  117 + return SpringUtil.getBean(AzureOpenAiChatModel.class);
109 case OLLAMA: 118 case OLLAMA:
110 return SpringUtil.getBean(OllamaChatModel.class); 119 return SpringUtil.getBean(OllamaChatModel.class);
111 default: 120 default:
@@ -269,6 +278,21 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -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 * 可参考 {@link OpenAiAutoConfiguration} 296 * 可参考 {@link OpenAiAutoConfiguration}
273 */ 297 */
274 private OpenAiImageModel buildOpenAiImageModel(String openAiToken, String url) { 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,6 +5,7 @@ import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
5 import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatOptions; 5 import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatOptions;
6 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatOptions; 6 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatOptions;
7 import com.alibaba.cloud.ai.tongyi.chat.TongYiChatOptions; 7 import com.alibaba.cloud.ai.tongyi.chat.TongYiChatOptions;
  8 +import org.springframework.ai.azure.openai.AzureOpenAiChatOptions;
8 import org.springframework.ai.chat.messages.*; 9 import org.springframework.ai.chat.messages.*;
9 import org.springframework.ai.chat.prompt.ChatOptions; 10 import org.springframework.ai.chat.prompt.ChatOptions;
10 import org.springframework.ai.ollama.api.OllamaOptions; 11 import org.springframework.ai.ollama.api.OllamaOptions;
@@ -35,6 +36,9 @@ public class AiUtils { @@ -35,6 +36,9 @@ public class AiUtils {
35 return XingHuoChatOptions.builder().model(model).temperature(temperatureF).maxTokens(maxTokens).build(); 36 return XingHuoChatOptions.builder().model(model).temperature(temperatureF).maxTokens(maxTokens).build();
36 case OPENAI: 37 case OPENAI:
37 return OpenAiChatOptions.builder().withModel(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build(); 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 case OLLAMA: 42 case OLLAMA:
39 return OllamaOptions.create().withModel(model).withTemperature(temperatureF).withNumPredict(maxTokens); 43 return OllamaOptions.create().withModel(model).withTemperature(temperatureF).withNumPredict(maxTokens);
40 default: 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 package cn.iocoder.yudao.framework.ai.chat; 1 package cn.iocoder.yudao.framework.ai.chat;
2 2
3 -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;  
4 import org.junit.jupiter.api.Disabled; 3 import org.junit.jupiter.api.Disabled;
5 import org.junit.jupiter.api.Test; 4 import org.junit.jupiter.api.Test;
6 import org.springframework.ai.chat.messages.Message; 5 import org.springframework.ai.chat.messages.Message;
@@ -17,7 +16,7 @@ import java.util.ArrayList; @@ -17,7 +16,7 @@ import java.util.ArrayList;
17 import java.util.List; 16 import java.util.List;
18 17
19 /** 18 /**
20 - * {@link XingHuoChatModel} 集成测试 19 + * {@link OpenAiChatModel} 集成测试
21 * 20 *
22 * @author 芋道源码 21 * @author 芋道源码
23 */ 22 */
yudao-server/src/main/resources/application.yaml
@@ -162,9 +162,13 @@ spring: @@ -162,9 +162,13 @@ spring:
162 secret-key: R9mYF9dl9KASgi5RUq0FQt3wRisSnOcK 162 secret-key: R9mYF9dl9KASgi5RUq0FQt3wRisSnOcK
163 zhipuai: # 智谱 AI 163 zhipuai: # 智谱 AI
164 api-key: 32f84543e54eee31f8d56b2bd6020573.3vh9idLJZ2ZhxDEs 164 api-key: 32f84543e54eee31f8d56b2bd6020573.3vh9idLJZ2ZhxDEs
165 - openai: 165 + openai: # OpenAI 官方
166 api-key: sk-yzKea6d8e8212c3bdd99f9f44ced1cae37c097e5aa3BTS7z 166 api-key: sk-yzKea6d8e8212c3bdd99f9f44ced1cae37c097e5aa3BTS7z
167 base-url: https://api.gptsapi.net 167 base-url: https://api.gptsapi.net
  168 + azure: # OpenAI 微软
  169 + openai:
  170 + endpoint: https://eastusprejade.openai.azure.com
  171 + api-key: xxx
168 ollama: 172 ollama:
169 base-url: http://127.0.0.1:11434 173 base-url: http://127.0.0.1:11434
170 chat: 174 chat: