Commit 25a0fe908a6a9a272fb76b5ef43058b421354c98

Authored by YunaiV
1 parent f7ab30c5

【功能新增】AI:新增 function call 示例。会继续完善!

yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/tool/ListDirTool.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.service.tool;
  2 +
  3 +import cn.hutool.core.date.LocalDateTimeUtil;
  4 +import cn.hutool.core.io.FileUtil;
  5 +import cn.hutool.core.util.ArrayUtil;
  6 +import cn.hutool.core.util.StrUtil;
  7 +import lombok.AllArgsConstructor;
  8 +import lombok.Data;
  9 +import lombok.NoArgsConstructor;
  10 +import org.springframework.ai.tool.annotation.Tool;
  11 +import org.springframework.ai.tool.annotation.ToolParam;
  12 +import org.springframework.stereotype.Component;
  13 +
  14 +import java.io.File;
  15 +import java.nio.file.Path;
  16 +import java.nio.file.Paths;
  17 +import java.util.Arrays;
  18 +import java.util.Collections;
  19 +import java.util.List;
  20 +
  21 +import static cn.hutool.core.date.DatePattern.NORM_DATETIME_PATTERN;
  22 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  23 +
  24 +/**
  25 + * 目录内容列表工具:列出指定目录的内容
  26 + *
  27 + * @author 芋道源码
  28 + */
  29 +@Component
  30 +public class ListDirTool {
  31 +
  32 + /**
  33 + * 列出指定目录的内容
  34 + *
  35 + * @param relativePath 要列出内容的目录路径,相对于工作区根目录
  36 + * @return 目录内容列表
  37 + */
  38 + @Tool(name = "listDir", description = "列出指定目录的内容")
  39 + public Response listDir(@ToolParam(description = "要列出内容的目录路径,相对于工作区根目录") String relativePath) {
  40 + // 校验目录存在
  41 + String path = StrUtil.blankToDefault(relativePath, ".");
  42 + Path dirPath = Paths.get(path);
  43 + if (!FileUtil.exist(dirPath.toString()) || !FileUtil.isDirectory(dirPath.toString())) {
  44 + return new Response(Collections.emptyList());
  45 + }
  46 + // 列出目录内容
  47 + File[] files = dirPath.toFile().listFiles();
  48 + if (ArrayUtil.isEmpty(files)) {
  49 + return new Response(Collections.emptyList());
  50 + }
  51 + return new Response(convertList(Arrays.asList(files), file -> new Response.File()
  52 + .setDirectory(file.isDirectory()).setName(file.getName())
  53 + .setSize(file.isFile() ? FileUtil.readableFileSize(file.length()) : null)
  54 + .setLastModified(
  55 + LocalDateTimeUtil.format(LocalDateTimeUtil.of(file.lastModified()), NORM_DATETIME_PATTERN))));
  56 + }
  57 +
  58 + @Data
  59 + @AllArgsConstructor
  60 + @NoArgsConstructor
  61 + public static class Response {
  62 +
  63 + /**
  64 + * 目录内容列表
  65 + */
  66 + private List<File> files;
  67 +
  68 + @Data
  69 + public static class File {
  70 +
  71 + /**
  72 + * 是否为目录
  73 + */
  74 + private Boolean directory;
  75 +
  76 + /**
  77 + * 名称
  78 + */
  79 + private String name;
  80 +
  81 + /**
  82 + * 大小,仅对文件有效
  83 + */
  84 + private String size;
  85 +
  86 + /**
  87 + * 最后修改时间
  88 + */
  89 + private String lastModified;
  90 +
  91 + }
  92 +
  93 + }
  94 +
  95 +}
0 96 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/tool/ListDirToolB.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.service.tool;
  2 +
  3 +import cn.hutool.core.date.LocalDateTimeUtil;
  4 +import cn.hutool.core.io.FileUtil;
  5 +import cn.hutool.core.util.ArrayUtil;
  6 +import cn.hutool.core.util.StrUtil;
  7 +import com.fasterxml.jackson.annotation.JsonClassDescription;
  8 +import com.fasterxml.jackson.annotation.JsonPropertyDescription;
  9 +import lombok.AllArgsConstructor;
  10 +import lombok.Data;
  11 +import lombok.NoArgsConstructor;
  12 +import org.springframework.stereotype.Component;
  13 +
  14 +import java.io.File;
  15 +import java.nio.file.Path;
  16 +import java.nio.file.Paths;
  17 +import java.util.Arrays;
  18 +import java.util.Collections;
  19 +import java.util.List;
  20 +import java.util.function.Function;
  21 +
  22 +import static cn.hutool.core.date.DatePattern.NORM_DATETIME_PATTERN;
  23 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  24 +
  25 +/**
  26 + * 目录内容列表工具:列出指定目录的内容
  27 + *
  28 + * @author 芋道源码
  29 + */
  30 +@Component("listDir")
  31 +public class ListDirToolB implements Function<ListDirToolB.Request, ListDirToolB.Response> {
  32 +
  33 + @Data
  34 + @JsonClassDescription("列出指定目录的内容")
  35 + public static class Request {
  36 +
  37 + /**
  38 + * 要列出内容的目录路径
  39 + */
  40 + @JsonPropertyDescription("要列出内容的目录路径,例如说:/Users/yunai")
  41 + private String path;
  42 +
  43 + }
  44 +
  45 + @Data
  46 + @AllArgsConstructor
  47 + @NoArgsConstructor
  48 + public static class Response {
  49 +
  50 + /**
  51 + * 目录内容列表
  52 + */
  53 + private List<File> files;
  54 +
  55 + @Data
  56 + public static class File {
  57 +
  58 + /**
  59 + * 是否为目录
  60 + */
  61 + private Boolean directory;
  62 +
  63 + /**
  64 + * 名称
  65 + */
  66 + private String name;
  67 +
  68 + /**
  69 + * 大小,仅对文件有效
  70 + */
  71 + private String size;
  72 +
  73 + /**
  74 + * 最后修改时间
  75 + */
  76 + private String lastModified;
  77 +
  78 + }
  79 +
  80 + }
  81 +
  82 + @Override
  83 + public Response apply(Request request) {
  84 + // 校验目录存在
  85 + String path = StrUtil.blankToDefault(request.getPath(), ".");
  86 + Path dirPath = Paths.get(path);
  87 + if (!FileUtil.exist(dirPath.toString()) || !FileUtil.isDirectory(dirPath.toString())) {
  88 + return new Response(Collections.emptyList());
  89 + }
  90 + // 列出目录内容
  91 + File[] files = dirPath.toFile().listFiles();
  92 + if (ArrayUtil.isEmpty(files)) {
  93 + return new Response(Collections.emptyList());
  94 + }
  95 + return new Response(convertList(Arrays.asList(files), file ->
  96 + new Response.File().setDirectory(file.isDirectory()).setName(file.getName())
  97 + .setLastModified(LocalDateTimeUtil.format(LocalDateTimeUtil.of(file.lastModified()), NORM_DATETIME_PATTERN))));
  98 + }
  99 +
  100 +}
0 101 \ No newline at end of file
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java
... ... @@ -33,7 +33,7 @@ import org.springframework.context.annotation.Bean;
33 33 * @author fansili
34 34 */
35 35 @AutoConfiguration
36   -@EnableConfigurationProperties({YudaoAiProperties.class,
  36 +@EnableConfigurationProperties({ YudaoAiProperties.class,
37 37 QdrantVectorStoreProperties.class, // 解析 Qdrant 配置
38 38 RedisVectorStoreProperties.class, // 解析 Redis 配置
39 39 MilvusVectorStoreProperties.class, MilvusServiceClientProperties.class // 解析 Milvus 配置
... ... @@ -139,15 +139,16 @@ public class YudaoAiAutoConfiguration {
139 139 }
140 140 // 特殊:由于混元大模型不提供 deepseek,而是通过知识引擎,所以需要区分下 URL
141 141 if (StrUtil.isEmpty(properties.getBaseUrl())) {
142   - properties.setBaseUrl(StrUtil.startWithIgnoreCase(properties.getModel(), "deepseek") ?
143   - HunYuanChatModel.DEEP_SEEK_BASE_URL : HunYuanChatModel.BASE_URL);
  142 + properties.setBaseUrl(
  143 + StrUtil.startWithIgnoreCase(properties.getModel(), "deepseek") ? HunYuanChatModel.DEEP_SEEK_BASE_URL
  144 + : HunYuanChatModel.BASE_URL);
144 145 }
145 146 // 创建 OpenAiChatModel、HunYuanChatModel 对象
146 147 OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
147 148 .openAiApi(OpenAiApi.builder()
148   - .baseUrl(properties.getBaseUrl())
149   - .apiKey(properties.getApiKey())
150   - .build())
  149 + .baseUrl(properties.getBaseUrl())
  150 + .apiKey(properties.getApiKey())
  151 + .build())
151 152 .defaultOptions(OpenAiChatOptions.builder()
152 153 .model(properties.getModel())
153 154 .temperature(properties.getTemperature())
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java
... ... @@ -61,6 +61,7 @@ import org.springframework.ai.minimax.MiniMaxChatModel;
61 61 import org.springframework.ai.minimax.MiniMaxEmbeddingModel;
62 62 import org.springframework.ai.minimax.MiniMaxEmbeddingOptions;
63 63 import org.springframework.ai.minimax.api.MiniMaxApi;
  64 +import org.springframework.ai.model.tool.ToolCallingManager;
64 65 import org.springframework.ai.moonshot.MoonshotChatModel;
65 66 import org.springframework.ai.moonshot.api.MoonshotApi;
66 67 import org.springframework.ai.ollama.OllamaChatModel;
... ... @@ -431,7 +432,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
431 432 private static OpenAiChatModel buildOpenAiChatModel(String openAiToken, String url) {
432 433 url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL);
433 434 OpenAiApi openAiApi = OpenAiApi.builder().baseUrl(url).apiKey(openAiToken).build();
434   - return OpenAiChatModel.builder().openAiApi(openAiApi).build();
  435 + return OpenAiChatModel.builder().openAiApi(openAiApi).toolCallingManager(getToolCallingManager()).build();
435 436 }
436 437  
437 438 // TODO @芋艿:手头暂时没密钥,使用建议再测试下
... ... @@ -465,7 +466,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
465 466 */
466 467 private static OllamaChatModel buildOllamaChatModel(String url) {
467 468 OllamaApi ollamaApi = new OllamaApi(url);
468   - return OllamaChatModel.builder().ollamaApi(ollamaApi).build();
  469 + return OllamaChatModel.builder().ollamaApi(ollamaApi).toolCallingManager(getToolCallingManager()).build();
469 470 }
470 471  
471 472 private StabilityAiImageModel buildStabilityAiImageModel(String apiKey, String url) {
... ... @@ -699,4 +700,8 @@ public class AiModelFactoryImpl implements AiModelFactory {
699 700 return SpringUtil.getBean(BatchingStrategy.class);
700 701 }
701 702  
  703 + private static ToolCallingManager getToolCallingManager() {
  704 + return SpringUtil.getBean(ToolCallingManager.class);
  705 + }
  706 +
702 707 }
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/util/AiUtils.java
... ... @@ -24,14 +24,18 @@ public class AiUtils {
24 24 // noinspection EnhancedSwitchMigration
25 25 switch (platform) {
26 26 case TONG_YI:
  27 + // TODO functions
27 28 return DashScopeChatOptions.builder().withModel(model).withTemperature(temperature).withMaxToken(maxTokens).build();
28 29 case YI_YAN:
29 30 return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
30 31 case ZHI_PU:
  32 + // TODO functions
31 33 return ZhiPuAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
32 34 case MINI_MAX:
  35 + // TODO functions
33 36 return MiniMaxChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
34 37 case MOONSHOT:
  38 + // TODO functions
35 39 return MoonshotChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
36 40 case OPENAI:
37 41 case DEEP_SEEK: // 复用 OpenAI 客户端
... ... @@ -39,12 +43,18 @@ public class AiUtils {
39 43 case HUN_YUAN: // 复用 OpenAI 客户端
40 44 case XING_HUO: // 复用 OpenAI 客户端
41 45 case SILICON_FLOW: // 复用 OpenAI 客户端
42   - return OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
  46 + return OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
  47 +// .toolNames() TODO
  48 + .toolNames("listDir")
  49 + .build();
43 50 case AZURE_OPENAI:
44 51 // TODO 芋艿:貌似没 model 字段???!
  52 + // TODO 芋艿:.toolNames() TODO
45 53 return AzureOpenAiChatOptions.builder().deploymentName(model).temperature(temperature).maxTokens(maxTokens).build();
46 54 case OLLAMA:
47   - return OllamaOptions.builder().model(model).temperature(temperature).numPredict(maxTokens).build();
  55 + // TODO 芋艿:.toolNames() TODO
  56 + return OllamaOptions.builder().model(model).temperature(temperature).numPredict(maxTokens)
  57 + .toolNames("listDir").build();
48 58 default:
49 59 throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
50 60 }
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/package-info.java
... ... @@ -4,7 +4,10 @@
4 4 * models 包路径:
5 5 * 1. xinghuo 包:【讯飞】星火,自己实现
6 6 * 2. deepseek 包:【深度求索】DeepSeek,自己实现
7   - * 3. midjourney 包:Midjourney API,对接 https://github.com/novicezk/midjourney-proxy 实现
8   - * 4. suno 包:Suno API,对接 https://github.com/gcui-art/suno-api 实现
  7 + * 3. doubao 包:【字节豆包】DouBao,自己实现
  8 + * 4. hunyuan 包:【腾讯混元】HunYuan,自己实现
  9 + * 5. siliconflow 包:【硅基硅流】SiliconFlow,自己实现
  10 + * 6. midjourney 包:Midjourney API,对接 https://github.com/novicezk/midjourney-proxy 实现
  11 + * 7. suno 包:Suno API,对接 https://github.com/gcui-art/suno-api 实现
9 12 */
10 13 package cn.iocoder.yudao.framework.ai;
11 14 \ No newline at end of file
... ...