package cn.iocoder.yudao.module.ai.util; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.util.collection.SetUtils; import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; import org.springaicommunity.moonshot.MoonshotChatOptions; import org.springaicommunity.qianfan.QianFanChatOptions; import org.springframework.ai.anthropic.AnthropicChatOptions; import org.springframework.ai.azure.openai.AzureOpenAiChatOptions; import org.springframework.ai.chat.messages.*; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.ai.deepseek.DeepSeekAssistantMessage; import org.springframework.ai.deepseek.DeepSeekChatOptions; import org.springframework.ai.minimax.MiniMaxChatOptions; import org.springframework.ai.ollama.api.OllamaChatOptions; import org.springframework.ai.openai.OpenAiChatOptions; import org.springframework.ai.tool.ToolCallback; import org.springframework.ai.zhipuai.ZhiPuAiChatOptions; import java.util.*; /** * Spring AI 工具类 * * @author 芋道源码 */ public class AiUtils { public static final String TOOL_CONTEXT_LOGIN_USER = "LOGIN_USER"; public static final String TOOL_CONTEXT_TENANT_ID = "TENANT_ID"; /** * 通义千问支持多模态的模型 * * @see 模型广场 * @see 必须开启 withMultiModel 参数 */ public static final Set TONG_YI_MULTI_MODELS = SetUtils.asSet( // qwen3.5 / 3.6 系列(统一多模态主干) "qwen3.6-plus", "qwen3.6-flash", "qwen3.5-plus", "qwen3.5-flash", // qwen-vl 视觉理解 "qwen3-vl-plus", "qwen3-vl-flash", "qwen-vl-max", "qwen-vl-plus", "qwen2.5-vl-72b-instruct", "qwen2.5-vl-32b-instruct", "qwen2.5-vl-7b-instruct", "qwen2.5-vl-3b-instruct", // qvq 视觉推理 "qvq-max", "qvq-plus", // qwen-omni 全模态 "qwen3.5-omni-plus", "qwen3.5-omni-flash", "qwen3-omni-flash", "qwen-omni-turbo" ); public static ChatOptions buildChatOptions(AiPlatformEnum platform, String model, Double temperature, Integer maxTokens) { return buildChatOptions(platform, model, temperature, maxTokens, null, null); } public static ChatOptions buildChatOptions(AiPlatformEnum platform, String model, Double temperature, Integer maxTokens, List toolCallbacks, Map toolContext) { toolCallbacks = ObjUtil.defaultIfNull(toolCallbacks, Collections.emptyList()); toolContext = ObjUtil.defaultIfNull(toolContext, Collections.emptyMap()); // noinspection EnhancedSwitchMigration switch (platform) { case TONG_YI: return DashScopeChatOptions.builder().model(model).temperature(temperature).maxToken(maxTokens) .enableThinking(true) // TODO 芋艿:默认都开启 thinking 模式,后续可以让用户配置 .multiModel(TONG_YI_MULTI_MODELS.contains(model)) // 是否多模态模型 .toolCallbacks(toolCallbacks).toolContext(toolContext).build(); case YI_YAN: return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build(); case DEEP_SEEK: case DOU_BAO: // 复用 DeepSeek 客户端 case HUN_YUAN: // 复用 DeepSeek 客户端 case SILICON_FLOW: // 复用 DeepSeek 客户端 case XING_HUO: // 复用 DeepSeek 客户端 return DeepSeekChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) .toolCallbacks(toolCallbacks).toolContext(toolContext).build(); case ZHI_PU: return ZhiPuAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) .toolCallbacks(toolCallbacks).toolContext(toolContext).build(); case MINI_MAX: return MiniMaxChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) .toolCallbacks(toolCallbacks).toolContext(toolContext).build(); case MOONSHOT: return MoonshotChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) .toolCallbacks(toolCallbacks).toolContext(toolContext).build(); case OPENAI: case GEMINI: // 复用 OpenAI 客户端 case BAI_CHUAN: // 复用 OpenAI 客户端 case GROK: // 复用 OpenAI 客户端 return OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) .toolCallbacks(toolCallbacks).toolContext(toolContext).build(); case AZURE_OPENAI: return AzureOpenAiChatOptions.builder().deploymentName(model).temperature(temperature).maxTokens(maxTokens) .toolCallbacks(toolCallbacks).toolContext(toolContext).build(); case ANTHROPIC: return AnthropicChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) .toolCallbacks(toolCallbacks).toolContext(toolContext).build(); case OLLAMA: return OllamaChatOptions.builder().model(model).temperature(temperature).numPredict(maxTokens) .toolCallbacks(toolCallbacks).toolContext(toolContext).build(); default: throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); } } public static Message buildMessage(String type, String content) { if (MessageType.USER.getValue().equals(type)) { return new UserMessage(content); } if (MessageType.ASSISTANT.getValue().equals(type)) { return new AssistantMessage(content); } if (MessageType.SYSTEM.getValue().equals(type)) { return new SystemMessage(content); } if (MessageType.TOOL.getValue().equals(type)) { throw new UnsupportedOperationException("暂不支持 tool 消息:" + content); } throw new IllegalArgumentException(StrUtil.format("未知消息类型({})", type)); } public static Map buildCommonToolContext() { Map context = new HashMap<>(); context.put(TOOL_CONTEXT_LOGIN_USER, SecurityFrameworkUtils.getLoginUser()); context.put(TOOL_CONTEXT_TENANT_ID, TenantContextHolder.getTenantId()); return context; } @SuppressWarnings("ConstantValue") public static String getChatResponseContent(ChatResponse response) { if (response == null || response.getResult() == null || response.getResult().getOutput() == null) { return null; } return response.getResult().getOutput().getText(); } @SuppressWarnings("ConstantValue") public static String getChatResponseReasoningContent(ChatResponse response) { if (response == null || response.getResult() == null || response.getResult().getOutput() == null) { return null; } AssistantMessage output = response.getResult().getOutput(); // DeepSeek 通过专属 AssistantMessage 暴露 reasoningContent if (output instanceof DeepSeekAssistantMessage) { return ((DeepSeekAssistantMessage) output).getReasoningContent(); } // 通义千问等通过 metadata 透传 reasoningContent return MapUtil.getStr(output.getMetadata(), "reasoningContent"); } }