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 package cn.iocoder.yudao.module.ai.util; 1 package cn.iocoder.yudao.module.ai.util;
2 2
  3 +import cn.hutool.core.map.MapUtil;
3 import cn.hutool.core.util.ObjUtil; 4 import cn.hutool.core.util.ObjUtil;
4 import cn.hutool.core.util.StrUtil; 5 import cn.hutool.core.util.StrUtil;
  6 +import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
5 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; 7 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
6 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; 8 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
7 import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; 9 import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
@@ -33,6 +35,28 @@ public class AiUtils { @@ -33,6 +35,28 @@ public class AiUtils {
33 public static final String TOOL_CONTEXT_LOGIN_USER = "LOGIN_USER"; 35 public static final String TOOL_CONTEXT_LOGIN_USER = "LOGIN_USER";
34 public static final String TOOL_CONTEXT_TENANT_ID = "TENANT_ID"; 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 public static ChatOptions buildChatOptions(AiPlatformEnum platform, String model, Double temperature, Integer maxTokens) { 60 public static ChatOptions buildChatOptions(AiPlatformEnum platform, String model, Double temperature, Integer maxTokens) {
37 return buildChatOptions(platform, model, temperature, maxTokens, null, null); 61 return buildChatOptions(platform, model, temperature, maxTokens, null, null);
38 } 62 }
@@ -44,9 +68,10 @@ public class AiUtils { @@ -44,9 +68,10 @@ public class AiUtils {
44 // noinspection EnhancedSwitchMigration 68 // noinspection EnhancedSwitchMigration
45 switch (platform) { 69 switch (platform) {
46 case TONG_YI: 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 case YI_YAN: 75 case YI_YAN:
51 return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build(); 76 return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
52 case DEEP_SEEK: 77 case DEEP_SEEK:
@@ -125,10 +150,13 @@ public class AiUtils { @@ -125,10 +150,13 @@ public class AiUtils {
125 || response.getResult().getOutput() == null) { 150 || response.getResult().getOutput() == null) {
126 return null; 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 \ No newline at end of file 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,15 +34,15 @@ public class TongYiChatModelTests {
34 34
35 private final DashScopeChatModel chatModel = DashScopeChatModel.builder() 35 private final DashScopeChatModel chatModel = DashScopeChatModel.builder()
36 .dashScopeApi(DashScopeApi.builder() 36 .dashScopeApi(DashScopeApi.builder()
37 - .apiKey("sk-47aa124781be4bfb95244cc62f63f7d0") 37 + .apiKey("sk-cd9f39e99ea54840bd1888282325f55a") // https://bailian.console.aliyun.com/cn-beijing/?tab=model#/api-key 获取密钥
38 .build()) 38 .build())
39 .defaultOptions(DashScopeChatOptions.builder() 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 .build()) 46 .build())
47 .build(); 47 .build();
48 48
@@ -85,9 +85,9 @@ public class TongYiChatModelTests { @@ -85,9 +85,9 @@ public class TongYiChatModelTests {
85 List<Message> messages = new ArrayList<>(); 85 List<Message> messages = new ArrayList<>();
86 messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); 86 messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
87 DashScopeChatOptions options = DashScopeChatOptions.builder() 87 DashScopeChatOptions options = DashScopeChatOptions.builder()
88 - .withModel("qwen3-235b-a22b-thinking-2507") 88 + .model("qwen3.6-plus").multiModel(true)
89 // .withModel("qwen-max-2025-01-25") 89 // .withModel("qwen-max-2025-01-25")
90 - .withEnableThinking(true) // 必须设置,否则会报错 90 + .enableThinking(true) // 必须设置,否则会报错
91 .build(); 91 .build();
92 92
93 // 调用 93 // 调用
@@ -112,8 +112,8 @@ public class TongYiChatModelTests { @@ -112,8 +112,8 @@ public class TongYiChatModelTests {
112 Document document01 = new Document("abc"); 112 Document document01 = new Document("abc");
113 Document document02 = new Document("sapring"); 113 Document document02 = new Document("sapring");
114 RerankOptions options = DashScopeRerankOptions.builder() 114 RerankOptions options = DashScopeRerankOptions.builder()
115 - .withTopN(1)  
116 - .withModel("gte-rerank-v2") 115 + .topN(1)
  116 + .model("gte-rerank-v2")
117 .build(); 117 .build();
118 RerankRequest rerankRequest = new RerankRequest( 118 RerankRequest rerankRequest = new RerankRequest(
119 query, 119 query,