Skip to content

Commit b80414f

Browse files
feat: add support for custom qwen service (higress-group#566)
Co-authored-by: ShlyxCoder <40505282@qq.com>
1 parent 2328120 commit b80414f

File tree

6 files changed

+287
-9
lines changed

6 files changed

+287
-9
lines changed

backend/sdk/src/main/java/com/alibaba/higress/sdk/service/ai/LlmProviderServiceImpl.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ public class LlmProviderServiceImpl implements LlmProviderService {
5858
static {
5959
PROVIDER_HANDLERS = Stream.of(new OpenaiLlmProviderHandler(),
6060
new DefaultLlmProviderHandler(LlmProviderType.MOONSHOT, "api.moonshot.cn", 443, V1McpBridge.PROTOCOL_HTTPS),
61-
new DefaultLlmProviderHandler(LlmProviderType.QWEN, "dashscope.aliyuncs.com", 443,
62-
V1McpBridge.PROTOCOL_HTTPS),
61+
new QwenLlmProviderHandler(),
6362
new AzureLlmProviderHandler(),
6463
new DefaultLlmProviderHandler(LlmProviderType.AI360, "api.360.cn", 443, V1McpBridge.PROTOCOL_HTTPS),
6564
new DefaultLlmProviderHandler(LlmProviderType.GITHUB, "models.inference.ai.azure.com", 443,
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright (c) 2022-2023 Alibaba Group Holding Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
package com.alibaba.higress.sdk.service.ai;
14+
15+
import com.alibaba.higress.sdk.exception.ValidationException;
16+
import com.alibaba.higress.sdk.model.ai.LlmProviderEndpoint;
17+
import com.alibaba.higress.sdk.model.ai.LlmProviderType;
18+
import com.alibaba.higress.sdk.service.kubernetes.crd.mcp.V1McpBridge;
19+
import org.apache.commons.collections4.MapUtils;
20+
import org.apache.commons.lang3.StringUtils;
21+
22+
import java.net.URI;
23+
import java.net.URISyntaxException;
24+
import java.util.Collections;
25+
import java.util.List;
26+
import java.util.Map;
27+
28+
29+
public class QwenLlmProviderHandler extends AbstractLlmProviderHandler{
30+
private static final int DEFAULT_SERVICE_PORT = 443;
31+
32+
private static final String DEFAULT_SERVICE_PROTOCOL = V1McpBridge.PROTOCOL_HTTPS;
33+
34+
private static final String DEFAULT_SERVICE_DOMAIN = "dashscope.aliyuncs.com";
35+
36+
private static final String CUSTOM_DOMAIN_KEY = "qwenDomain";
37+
private static final List<LlmProviderEndpoint> DEFAULT_ENDPOINTS =
38+
Collections.singletonList(new LlmProviderEndpoint(DEFAULT_SERVICE_PROTOCOL, DEFAULT_SERVICE_DOMAIN, DEFAULT_SERVICE_PORT, "/"));
39+
40+
private static final String ENABLE_SEARCH_KEY = "qwenEnableSearch";
41+
private static final String ENABLE_COMPATIBLE_KEY = "qwenEnableCompatible";
42+
private static final String FILE_IDS_KEY = "qwenFileIds";
43+
44+
@Override
45+
public void normalizeConfigs(Map<String, Object> configurations) {
46+
if (MapUtils.isEmpty(configurations)) {
47+
throw new ValidationException("Missing Qwen specific configurations.");
48+
}
49+
50+
Boolean searchVal = MapUtils.getBoolean(configurations, ENABLE_SEARCH_KEY, Boolean.FALSE);
51+
configurations.put(ENABLE_SEARCH_KEY, searchVal);
52+
53+
Boolean compatibleVal = MapUtils.getBoolean(configurations, ENABLE_COMPATIBLE_KEY, Boolean.FALSE);
54+
configurations.put(ENABLE_COMPATIBLE_KEY, compatibleVal);
55+
56+
if (configurations.containsKey(FILE_IDS_KEY)) {
57+
Object fileIdsVal = configurations.get(FILE_IDS_KEY);
58+
if (!(fileIdsVal instanceof List)) {
59+
throw new ValidationException("Invalid configuration: " + FILE_IDS_KEY);
60+
}
61+
}
62+
}
63+
64+
65+
@Override
66+
public String getType() {
67+
return LlmProviderType.QWEN;
68+
}
69+
70+
@Override
71+
protected List<LlmProviderEndpoint> getProviderEndpoints(Map<String, Object> providerConfig) {
72+
URI customUrl = getCustomUrl(providerConfig);
73+
if(customUrl != null){
74+
return Collections.singletonList(LlmProviderEndpoint.fromUri(customUrl));
75+
}
76+
return DEFAULT_ENDPOINTS;
77+
}
78+
private URI getCustomUrl(Map<String, Object> providerConfig) {
79+
Object rawCustomDomainObject = providerConfig.get(CUSTOM_DOMAIN_KEY);
80+
if (!(rawCustomDomainObject instanceof String)) {
81+
return null;
82+
}
83+
String rawCustomDomain = ((String) rawCustomDomainObject).trim();
84+
if (StringUtils.isEmpty(rawCustomDomain)) {
85+
return null;
86+
}
87+
88+
String scheme = V1McpBridge.PROTOCOL_HTTPS;
89+
String path = "/";
90+
91+
try {
92+
return new URI(scheme, rawCustomDomain, path, null);
93+
} catch (URISyntaxException e) {
94+
throw new ValidationException(CUSTOM_DOMAIN_KEY + " contains an invalid domain name: " + rawCustomDomain, e);
95+
}
96+
}
97+
98+
}

frontend/src/locales/en-US/translation.json

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,11 @@
254254
"ollamaServerPort": "Ollama Service Port",
255255
"openaiServerType": "OpenAI Service Type",
256256
"openaiCustomUrl": "Custom OpenAI Service Base URL",
257+
"qwenServerType": "Tongyi Qianwen Service Type",
258+
"qwenEnableSearch": "Enable Tongyi Qianwen Internet search function",
259+
"qwenEnableCompatible": "Enable Tongyi Qianwen compatibility mode",
260+
"qwenDomain": "Custom Tongyi Qianwen Domain",
261+
"qwenFileIds": "Tongyi Qianwen file IDs",
257262
"awsRegion": "AWS Region",
258263
"awsAccessKey": "AWS Access Key ID",
259264
"awsSecretKey": "AWS Secret Access Key",
@@ -289,6 +294,8 @@
289294
"openaiCustomUrlInconsistentContextPaths": "Inconsistent paths found in custom OpenAI service base URLs",
290295
"openaiCustomServiceRequired": "Please select custom OpenAI service",
291296
"openaiCustomServicePathRequired": "Please select custom OpenAI service path",
297+
"qwenFileIdRequired": "Please input Tongyi Qianwen file ID",
298+
"qwenDomainRequired": "Please input a valid custom Tongyi Qianwen domain",
292299
"awsRegionRequired": "Please input AWS Region",
293300
"awsAccessKeyRequired": "Please input AWS Access Key ID",
294301
"awsSecretKeyRequired": "Please input AWS Secret Access Key",
@@ -302,14 +309,18 @@
302309
"geminiSafetyThresholdRequired": "Please input block threshold"
303310
},
304311
"tooltips": {
312+
"qwenFileIdsTooltip": "The file IDs uploaded to Dashscope via the file API. The file contents will be used as contextual input for AI conversations.",
313+
"qwenEnableCompatibleTooltip": "When Tongyi Qianwen compatibility mode is enabled, requests will be sent to Tongyi Qianwen’s OpenAI-compatible API endpoint, and no modifications will be applied to the request or response payloads.",
305314
"vertexTokenRefreshAheadTooltip": "The lead time in seconds of refreshing the access token used to send requests to Vertex API. Leave it blank if the token shall only be refreshed after expired."
306315
},
307316
"placeholder": {
308317
"azureServiceUrlPlaceholder": "It shall contain \"/chat/completions\" in the path and \"api-version\" in the query string",
309318
"ollamaServerHostPlaceholder": "Please input a hostname, domain name or IP address",
310319
"openaiCustomUrlPlaceholder": "Sample: https://api.openai.com/v1",
311320
"openaiCustomServiceHostPlaceholder": "Sample: api.openai.com",
312-
"openaiCustomServicePathPlaceholder": "Sample: /v1"
321+
"openaiCustomServicePathPlaceholder": "Sample: /v1",
322+
"qwenDomainPlaceholder": "Sample: dashscope-finance.aliyuncs.com",
323+
"qwenFileIdsPlaceholder": "Sample: file-fe-xxx"
313324
},
314325
"openaiServerType": {
315326
"official": "OpenAI Official Service",
@@ -319,6 +330,10 @@
319330
"service": "Select a service",
320331
"url": "Input URL(s)"
321332
},
333+
"qwenServerType": {
334+
"official": "Tongyi Qianwen Official Service",
335+
"custom": "Custom Service"
336+
},
322337
"secretRefModal": {
323338
"entry": "I'd like to use token info stored in a Secret resource.",
324339
"title": "How to refer to token info stored in a Secret resource?",
@@ -907,4 +922,4 @@
907922
"button": "Copy Command"
908923
}
909924
}
910-
}
925+
}

frontend/src/locales/zh-CN/translation.json

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,11 @@
254254
"ollamaServerPort": "Ollama 服务端口",
255255
"openaiServerType": "OpenAI 服务类型",
256256
"openaiCustomUrl": "自定义 OpenAI 服务 BaseURL",
257+
"qwenServerType": "通义千问服务类型",
258+
"qwenEnableSearch": "开启通义千问互联网搜索功能",
259+
"qwenEnableCompatible": "开启通义千问兼容模式",
260+
"qwenDomain": "自定义通义千问域名",
261+
"qwenFileIds": "通义千问文件 ID",
257262
"awsRegion": "AWS Region",
258263
"awsAccessKey": "AWS Access Key ID",
259264
"awsSecretKey": "AWS Secret Access Key",
@@ -289,6 +294,8 @@
289294
"openaiCustomUrlInconsistentContextPaths": "各个自定义 OpenAI 服务 BaseURL 所使用的路径不一致",
290295
"openaiCustomServiceRequired": "请选择自定义 OpenAI 服务",
291296
"openaiCustomServicePathRequired": "请输入定义 OpenAI 服务路径",
297+
"qwenFileIdRequired": "请输入通义千问文件 ID",
298+
"qwenDomainRequired": "请输入合法的自定义通义千问域名",
292299
"awsRegionRequired": "请输入 AWS Region",
293300
"awsAccessKeyRequired": "请输入 AWS Access Key ID",
294301
"awsSecretKeyRequired": "请输入 AWS Secret Access Key",
@@ -302,14 +309,18 @@
302309
"geminiSafetyThresholdRequired": "请输入屏蔽阈值"
303310
},
304311
"tooltips": {
312+
"qwenFileIdsTooltip": "通过文件接口上传至 Dashscope 的文件 ID,其内容将被用做 AI 对话的上下文。",
313+
"qwenEnableCompatibleTooltip": "启用通义千问兼容模式后,将调用通义千问的 OpenAI 兼容接口,同时对请求/响应不做修改。",
305314
"vertexTokenRefreshAheadTooltip": "提前刷新用于访问 Vertex API 的访问密钥的时间,单位为秒。留空表示仅在密钥过期时刷新。"
306315
},
307316
"placeholder": {
308317
"azureServiceUrlPlaceholder": "需包含“/chat/completions”路径和“api-version”查询参数",
309318
"ollamaServerHostPlaceholder": "请填写机器名、域名或 IP 地址",
310319
"openaiCustomUrlPlaceholder": "示例:https://api.openai.com/v1",
311320
"openaiCustomServiceHostPlaceholder": "示例:api.openai.com",
312-
"openaiCustomServicePathPlaceholder": "示例:/v1"
321+
"openaiCustomServicePathPlaceholder": "示例:/v1",
322+
"qwenDomainPlaceholder": "示例:dashscope-finance.aliyuncs.com",
323+
"qwenFileIdsPlaceholder": "示例:file-fe-xxx"
313324
},
314325
"openaiServerType": {
315326
"official": "OpenAI 官方服务",
@@ -319,6 +330,10 @@
319330
"service": "选择服务",
320331
"url": "输入 URL"
321332
},
333+
"qwenServerType": {
334+
"official": "通义千问官方服务",
335+
"custom": "自定义服务"
336+
},
322337
"secretRefModal": {
323338
"entry": "我想引用保存在 Secret 中的凭证信息",
324339
"title": "如何引用保存在 Secret 中的凭证信息?",
@@ -907,4 +922,4 @@
907922
"button": "复制命令"
908923
}
909924
}
910-
}
925+
}

0 commit comments

Comments
 (0)