3737 * <p>This implementation uses direct HTTP calls to DashScope API via OkHttp,
3838 * without depending on the DashScope Java SDK.
3939 *
40- * <p>Supports both text and vision models through automatic endpoint routing:
41- * <ul>
42- * <li>Vision models (names starting with "qvq" or containing "-vl") use MultiModalGeneration API
43- * <li>Text models use TextGeneration API
44- * </ul>
40+ * <p>Supports both text and vision models through automatic endpoint routing.
41+ * Use {@link EndpointType} to explicitly control the endpoint selection.
4542 *
4643 * <p>Features:
4744 * <ul>
@@ -60,20 +57,67 @@ public class DashScopeChatModel extends ChatModelBase {
6057 private final boolean stream ;
6158 private final Boolean enableThinking ; // nullable
6259 private final Boolean enableSearch ; // nullable
60+ private final EndpointType endpointType ;
6361 private final GenerateOptions defaultOptions ;
6462 private final Formatter <DashScopeMessage , DashScopeResponse , DashScopeRequest > formatter ;
6563
6664 // HTTP client for API calls
6765 private final DashScopeHttpClient httpClient ;
6866
6967 /**
70- * Creates a new DashScope chat model instance.
68+ * Creates a new DashScope chat model instance with automatic API type detection.
69+ *
70+ * <p>This constructor maintains backward compatibility. API type defaults to AUTO,
71+ * which detects the endpoint based on model name.
72+ *
73+ * @param apiKey the API key for DashScope authentication
74+ * @param modelName the model name (e.g., "qwen-max", "qwen-vl-plus")
75+ * @param stream whether streaming should be enabled (ignored if enableThinking is true)
76+ * @param enableThinking whether thinking mode should be enabled (null for disabled)
77+ * @param enableSearch whether search enhancement should be enabled (null for disabled)
78+ * @param defaultOptions default generation options (null for defaults)
79+ * @param baseUrl custom base URL for DashScope API (null for default)
80+ * @param formatter the message formatter to use (null for default DashScope formatter)
81+ * @param httpTransport custom HTTP transport (null for default from factory)
82+ * @param publicKeyId the RSA public key ID for encryption (null to disable encryption)
83+ * @param publicKey the RSA public key for encryption (Base64-encoded, null to disable encryption)
84+ */
85+ public DashScopeChatModel (
86+ String apiKey ,
87+ String modelName ,
88+ boolean stream ,
89+ Boolean enableThinking ,
90+ Boolean enableSearch ,
91+ GenerateOptions defaultOptions ,
92+ String baseUrl ,
93+ Formatter <DashScopeMessage , DashScopeResponse , DashScopeRequest > formatter ,
94+ HttpTransport httpTransport ,
95+ String publicKeyId ,
96+ String publicKey ) {
97+ this (
98+ apiKey ,
99+ modelName ,
100+ stream ,
101+ enableThinking ,
102+ enableSearch ,
103+ null ,
104+ defaultOptions ,
105+ baseUrl ,
106+ formatter ,
107+ httpTransport ,
108+ publicKeyId ,
109+ publicKey );
110+ }
111+
112+ /**
113+ * Creates a new DashScope chat model instance with explicit API type.
71114 *
72115 * @param apiKey the API key for DashScope authentication
73116 * @param modelName the model name (e.g., "qwen-max", "qwen-vl-plus")
74117 * @param stream whether streaming should be enabled (ignored if enableThinking is true)
75118 * @param enableThinking whether thinking mode should be enabled (null for disabled)
76119 * @param enableSearch whether search enhancement should be enabled (null for disabled)
120+ * @param endpointType the endpoint type to use (null for AUTO detection)
77121 * @param defaultOptions default generation options (null for defaults)
78122 * @param baseUrl custom base URL for DashScope API (null for default)
79123 * @param formatter the message formatter to use (null for default DashScope formatter)
@@ -87,6 +131,7 @@ public DashScopeChatModel(
87131 boolean stream ,
88132 Boolean enableThinking ,
89133 Boolean enableSearch ,
134+ EndpointType endpointType ,
90135 GenerateOptions defaultOptions ,
91136 String baseUrl ,
92137 Formatter <DashScopeMessage , DashScopeResponse , DashScopeRequest > formatter ,
@@ -103,6 +148,7 @@ public DashScopeChatModel(
103148 this .stream = enableThinking != null && enableThinking ? true : stream ;
104149 this .enableThinking = enableThinking ;
105150 this .enableSearch = enableSearch ;
151+ this .endpointType = endpointType != null ? endpointType : EndpointType .AUTO ;
106152 this .defaultOptions =
107153 defaultOptions != null ? defaultOptions : GenerateOptions .builder ().build ();
108154 this .formatter = formatter != null ? formatter : new DashScopeChatFormatter ();
@@ -150,8 +196,12 @@ protected Flux<ChatResponse> doStream(
150196 List <Msg > messages , List <ToolSchema > tools , GenerateOptions options ) {
151197
152198 if (log .isDebugEnabled ()) {
153- boolean useMultimodal = httpClient .requiresMultimodalApi (modelName );
154- log .debug ("DashScope API call: model={}, multimodal={}" , modelName , useMultimodal );
199+ boolean useMultimodal = httpClient .requiresMultimodalApi (modelName , endpointType );
200+ log .debug (
201+ "DashScope API call: model={}, endpointType={}, multimodal={}" ,
202+ modelName ,
203+ endpointType ,
204+ useMultimodal );
155205 }
156206
157207 Flux <ChatResponse > responseFlux = streamWithHttpClient (messages , tools , options );
@@ -169,7 +219,7 @@ protected Flux<ChatResponse> doStream(
169219 private Flux <ChatResponse > streamWithHttpClient (
170220 List <Msg > messages , List <ToolSchema > tools , GenerateOptions options ) {
171221 Instant start = Instant .now ();
172- boolean useMultimodal = httpClient .requiresMultimodalApi (modelName );
222+ boolean useMultimodal = httpClient .requiresMultimodalApi (modelName , endpointType );
173223
174224 // Merge options with defaultOptions (options takes precedence)
175225 GenerateOptions effectiveOptions = GenerateOptions .mergeOptions (options , defaultOptions );
@@ -218,6 +268,9 @@ private Flux<ChatResponse> streamWithHttpClient(
218268 // Apply thinking mode if enabled
219269 applyThinkingMode (request , effectiveOptions );
220270
271+ // Set endpoint type for endpoint selection
272+ request .setEndpointType (endpointType );
273+
221274 if (stream ) {
222275 // Streaming mode
223276 return httpClient .stream (
@@ -298,6 +351,7 @@ public static class Builder {
298351 private boolean stream = true ;
299352 private Boolean enableThinking ;
300353 private Boolean enableSearch ;
354+ private EndpointType endpointType ;
301355 private GenerateOptions defaultOptions = null ;
302356 private String baseUrl ;
303357 private Formatter <DashScopeMessage , DashScopeResponse , DashScopeRequest > formatter ;
@@ -318,14 +372,11 @@ public Builder apiKey(String apiKey) {
318372 /**
319373 * Sets the model name to use.
320374 *
321- * <p>The model name determines which API is used:
322- * <ul>
323- * <li>Vision models (qvq* or *-vl*) → MultiModal API</li>
324- * <li>Text models → Text Generation API</li>
325- * </ul>
375+ * <p>The model name determines which API is used when endpointType is AUTO.
326376 *
327377 * @param modelName the model name (e.g., "qwen-max", "qwen-vl-plus")
328378 * @return this builder instance
379+ * @see DashScopeHttpClient#isMultimodalModel(String)
329380 */
330381 public Builder modelName (String modelName ) {
331382 this .modelName = modelName ;
@@ -374,6 +425,19 @@ public Builder enableSearch(Boolean enableSearch) {
374425 return this ;
375426 }
376427
428+ /**
429+ * Sets the endpoint type to use for endpoint routing.
430+ *
431+ * @param endpointType the endpoint type to use (null for AUTO)
432+ * @return this builder instance
433+ * @see EndpointType
434+ * @see DashScopeHttpClient#isMultimodalModel(String)
435+ */
436+ public Builder endpointType (EndpointType endpointType ) {
437+ this .endpointType = endpointType ;
438+ return this ;
439+ }
440+
377441 /**
378442 * Sets the default generation options.
379443 *
@@ -503,6 +567,7 @@ public DashScopeChatModel build() {
503567 stream ,
504568 enableThinking ,
505569 enableSearch ,
570+ endpointType ,
506571 effectiveOptions ,
507572 baseUrl ,
508573 formatter ,
0 commit comments