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 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:
... ...