Commit 814a963335868f0063314be0c53877bd36832fea

Authored by YunaiV
1 parent ef807be8

feat: AI 通义千问支持多模态模型,并兼容从 metadata 解析 reasoningContent

yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/util/AiUtils.java
1 1 package cn.iocoder.yudao.module.ai.util;
2 2  
  3 +import cn.hutool.core.map.MapUtil;
3 4 import cn.hutool.core.util.ObjUtil;
4 5 import cn.hutool.core.util.StrUtil;
  6 +import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
5 7 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
6 8 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
7 9 import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
... ... @@ -33,6 +35,28 @@ public class AiUtils {
33 35 public static final String TOOL_CONTEXT_LOGIN_USER = "LOGIN_USER";
34 36 public static final String TOOL_CONTEXT_TENANT_ID = "TENANT_ID";
35 37  
  38 + /**
  39 + * 通义千问支持多模态的模型
  40 + *
  41 + * @see <a href="https://bailian.console.aliyun.com/cn-beijing/?tab=model#/model-market/all?providers=qwen&capabilities=VU">模型广场</a>
  42 + * @see <a href="https://help.aliyun.com/zh/model-studio/error-code#error-url">必须开启 withMultiModel 参数</a>
  43 + */
  44 + public static final Set<String> TONG_YI_MULTI_MODELS = SetUtils.asSet(
  45 + // qwen3.5 / 3.6 系列(统一多模态主干)
  46 + "qwen3.6-plus", "qwen3.6-flash",
  47 + "qwen3.5-plus", "qwen3.5-flash",
  48 + // qwen-vl 视觉理解
  49 + "qwen3-vl-plus", "qwen3-vl-flash",
  50 + "qwen-vl-max", "qwen-vl-plus",
  51 + "qwen2.5-vl-72b-instruct", "qwen2.5-vl-32b-instruct",
  52 + "qwen2.5-vl-7b-instruct", "qwen2.5-vl-3b-instruct",
  53 + // qvq 视觉推理
  54 + "qvq-max", "qvq-plus",
  55 + // qwen-omni 全模态
  56 + "qwen3.5-omni-plus", "qwen3.5-omni-flash",
  57 + "qwen3-omni-flash", "qwen-omni-turbo"
  58 + );
  59 +
36 60 public static ChatOptions buildChatOptions(AiPlatformEnum platform, String model, Double temperature, Integer maxTokens) {
37 61 return buildChatOptions(platform, model, temperature, maxTokens, null, null);
38 62 }
... ... @@ -44,9 +68,10 @@ public class AiUtils {
44 68 // noinspection EnhancedSwitchMigration
45 69 switch (platform) {
46 70 case TONG_YI:
47   - return DashScopeChatOptions.builder().withModel(model).withTemperature(temperature).withMaxToken(maxTokens)
48   - .withEnableThinking(true) // TODO 芋艿:默认都开启 thinking 模式,后续可以让用户配置
49   - .withToolCallbacks(toolCallbacks).withToolContext(toolContext).build();
  71 + return DashScopeChatOptions.builder().model(model).temperature(temperature).maxToken(maxTokens)
  72 + .enableThinking(true) // TODO 芋艿:默认都开启 thinking 模式,后续可以让用户配置
  73 + .multiModel(TONG_YI_MULTI_MODELS.contains(model)) // 是否多模态模型
  74 + .toolCallbacks(toolCallbacks).toolContext(toolContext).build();
50 75 case YI_YAN:
51 76 return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
52 77 case DEEP_SEEK:
... ... @@ -125,10 +150,13 @@ public class AiUtils {
125 150 || response.getResult().getOutput() == null) {
126 151 return null;
127 152 }
128   - if (response.getResult().getOutput() instanceof DeepSeekAssistantMessage) {
129   - return ((DeepSeekAssistantMessage) (response.getResult().getOutput())).getReasoningContent();
  153 + AssistantMessage output = response.getResult().getOutput();
  154 + // DeepSeek 通过专属 AssistantMessage 暴露 reasoningContent
  155 + if (output instanceof DeepSeekAssistantMessage) {
  156 + return ((DeepSeekAssistantMessage) output).getReasoningContent();
130 157 }
131   - return null;
  158 + // 通义千问等通过 metadata 透传 reasoningContent
  159 + return MapUtil.getStr(output.getMetadata(), "reasoningContent");
132 160 }
133 161  
134 162 }
135 163 \ No newline at end of file
... ...
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/TongYiChatModelTests.java
... ... @@ -34,15 +34,15 @@ public class TongYiChatModelTests {
34 34  
35 35 private final DashScopeChatModel chatModel = DashScopeChatModel.builder()
36 36 .dashScopeApi(DashScopeApi.builder()
37   - .apiKey("sk-47aa124781be4bfb95244cc62f63f7d0")
  37 + .apiKey("sk-cd9f39e99ea54840bd1888282325f55a") // https://bailian.console.aliyun.com/cn-beijing/?tab=model#/api-key 获取密钥
38 38 .build())
39 39 .defaultOptions(DashScopeChatOptions.builder()
40   -// .withModel("qwen1.5-72b-chat") // 模型
41   - .withModel("qwen3-235b-a22b-thinking-2507") // 模型
42   -// .withModel("deepseek-r1") // 模型(deepseek-r1)
43   -// .withModel("deepseek-v3") // 模型(deepseek-v3)
44   -// .withModel("deepseek-r1-distill-qwen-1.5b") // 模型(deepseek-r1-distill-qwen-1.5b)
45   -// .withEnableThinking(true)
  40 + .multiModel(true) // 注意:当使用 qwen3.6-plus 等多模态模型,需要设置为 true,可见 https://help.aliyun.com/zh/model-studio/error-code#error-url 链接
  41 + .model("qwen3.6-plus") // 模型
  42 +// .model("deepseek-r1") // 模型(deepseek-r1)
  43 +// .model("deepseek-v3") // 模型(deepseek-v3)
  44 +// .model("deepseek-r1-distill-qwen-1.5b") // 模型(deepseek-r1-distill-qwen-1.5b)
  45 +// .enableThinking(true)
46 46 .build())
47 47 .build();
48 48  
... ... @@ -85,9 +85,9 @@ public class TongYiChatModelTests {
85 85 List<Message> messages = new ArrayList<>();
86 86 messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
87 87 DashScopeChatOptions options = DashScopeChatOptions.builder()
88   - .withModel("qwen3-235b-a22b-thinking-2507")
  88 + .model("qwen3.6-plus").multiModel(true)
89 89 // .withModel("qwen-max-2025-01-25")
90   - .withEnableThinking(true) // 必须设置,否则会报错
  90 + .enableThinking(true) // 必须设置,否则会报错
91 91 .build();
92 92  
93 93 // 调用
... ... @@ -112,8 +112,8 @@ public class TongYiChatModelTests {
112 112 Document document01 = new Document("abc");
113 113 Document document02 = new Document("sapring");
114 114 RerankOptions options = DashScopeRerankOptions.builder()
115   - .withTopN(1)
116   - .withModel("gte-rerank-v2")
  115 + .topN(1)
  116 + .model("gte-rerank-v2")
117 117 .build();
118 118 RerankRequest rerankRequest = new RerankRequest(
119 119 query,
... ...