Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,18 @@ public class FrontEndI18nResourceChecker {

private static final Set<String> IMPLICITLY_USED_RESOURCE_KEYS = Sets.newHashSet("index.title", "init.title",
"login" + ".title", "aiRoute.edit", "tlsCertificate.editTlsCertificate", "serviceSource.editServiceSource",
"llmProvider.edit", "plugins.editPlugin", "route.editRoute", "domain.editDomain", "consumer.edit");
"llmProvider.edit", "plugins.editPlugin", "route.editRoute", "domain.editDomain", "consumer.edit",
"serviceSource.proxyServerModal.editProxyServer", "serviceSource.proxyServerModal.createProxyServer");
private static final List<String> IMPLICITLY_USED_RESOURCE_KEY_PREFIXES =
Arrays.asList("menu.", "request.error.", "serviceSource.types.", "llmProvider.providerTypes.",
"route.factorGroup.required.", "route.keyValueGroup.required.", "plugins.configForm.", "plugins.subTitle.");

private static final String LANG_CN = "zh-CN";
private static final String LANG_EN = "en-US";

private static final List<Pattern> RESOURCE_USAGE_PATTERNS = Arrays.asList(Pattern.compile("\\bt\\('([^']+)'\\)"),
Pattern.compile("t\\(\"([^\"]+)\"\\)"), Pattern.compile("\\bi18nKey=\"([^\"]+)\""));
private static final List<Pattern> RESOURCE_USAGE_PATTERNS =
Arrays.asList(Pattern.compile("\\bt\\('([^']+)'(,.+)?\\)"), Pattern.compile("t\\(\"([^\"]+)\"\\)"),
Pattern.compile("\\bi18nKey=\"([^\"]+)\""));
private static final Pattern BAD_RESOURCE_CONTENT = Pattern.compile("^[a-zA-Z0-9]+(\\.[a-zA-Z0-9]+)+$");

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ public PaginatedResult<Service> list(CommonPageQuery query) {
service.setPort(port.getPort());
service.setNamespace(namespace);
service.setEndpoints(endpoints);
service.setProtocol(port.getProtocol());
services.add(service);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ public interface WasmPluginInstanceService {

void delete(Map<WasmPluginInstanceScope, String> targets, String pluginName);

void delete(WasmPluginInstanceScope scope, String target, String pluginName, Boolean internal);

void delete(Map<WasmPluginInstanceScope, String> targets, String pluginName, Boolean internal);

void deleteAll(WasmPluginInstanceScope scope, String target);

void deleteAll(Map<WasmPluginInstanceScope, String> targets);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,20 +257,34 @@ public List<WasmPluginInstance> addOrUpdateAll(List<WasmPluginInstance> instance

@Override
public void delete(WasmPluginInstanceScope scope, String target, String pluginName) {
delete(MapUtil.of(scope, target), pluginName);
delete(MapUtil.of(scope, target), pluginName, null);
}

@Override
public void delete(Map<WasmPluginInstanceScope, String> targets, String pluginName) {
delete(targets, pluginName, null);
}

@Override
public void delete(WasmPluginInstanceScope scope, String target, String pluginName, Boolean internal) {
delete(MapUtil.of(scope, target), pluginName, internal);
}

@Override
public void delete(Map<WasmPluginInstanceScope, String> targets, String pluginName, Boolean internal) {
if (MapUtils.isEmpty(targets)) {
return;
}
List<V1alpha1WasmPlugin> existedCrs;
try {
existedCrs = kubernetesClientService.listWasmPlugin(pluginName);
existedCrs = kubernetesClientService.listWasmPlugin(pluginName, null);
} catch (ApiException e) {
throw new BusinessException("Error occurs when getting WasmPlugin.", e);
}
if (internal != null) {
existedCrs = existedCrs.stream().filter(cr -> internal == KubernetesUtil.isInternalResource(cr))
.collect(Collectors.toList());
}
deletePluginInstances(existedCrs, targets);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ private void writeModelRouteResources(List<AiModelPredicate> modelPredicates) {

private void writeModelMappingResources(String routeName, List<AiUpstream> upstreams) {
if (CollectionUtils.isEmpty(upstreams)) {
wasmPluginInstanceService.delete(WasmPluginInstanceScope.ROUTE, routeName, BuiltInPluginName.MODEL_MAPPER);
wasmPluginInstanceService.delete(WasmPluginInstanceScope.ROUTE, routeName, BuiltInPluginName.MODEL_MAPPER, true);
return;
}

Expand All @@ -342,7 +342,7 @@ private void writeModelMappingResources(String routeName, List<AiUpstream> upstr
WasmPluginInstanceScope.SERVICE, upstreamService.getName());

if (MapUtils.isEmpty(upstream.getModelMapping())) {
wasmPluginInstanceService.delete(targets, pluginName);
wasmPluginInstanceService.delete(targets, pluginName, true);
continue;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,7 @@ public class LlmProviderServiceImpl implements LlmProviderService {
new DefaultLlmProviderHandler(LlmProviderType.YI, "api.lingyiwanwu.com", 443, V1McpBridge.PROTOCOL_HTTPS),
new DefaultLlmProviderHandler(LlmProviderType.DEEPSEEK, "api.deepseek.com", 443,
V1McpBridge.PROTOCOL_HTTPS),
new DefaultLlmProviderHandler(LlmProviderType.ZHIPUAI, "open.bigmodel.cn", 443,
V1McpBridge.PROTOCOL_HTTPS),
new DefaultLlmProviderHandler(LlmProviderType.ZHIPUAI, "open.bigmodel.cn", 443, V1McpBridge.PROTOCOL_HTTPS),
new OllamaLlmProviderHandler(),
new DefaultLlmProviderHandler(LlmProviderType.CLAUDE, "api.anthropic.com", 443, V1McpBridge.PROTOCOL_HTTPS),
new DefaultLlmProviderHandler(LlmProviderType.BAIDU, "qianfan.baidubce.com", 443,
Expand Down Expand Up @@ -158,15 +157,16 @@ public LlmProvider addOrUpdate(LlmProvider provider) {
providers.add(providerConfig);
}

ServiceSource serviceSource = handler.buildServiceSource(provider.getName(), providerConfig);
serviceSource.setProxyName(provider.getProxyName());

List<ServiceSource> extraServiceSources =
handler.getExtraServiceSources(provider.getName(), providerConfig, false);
if (CollectionUtils.isNotEmpty(extraServiceSources)) {
for (ServiceSource extraSource : extraServiceSources) {
extraSource.setProxyName(provider.getProxyName());
serviceSourceService.addOrUpdate(extraSource);
List<ServiceSource> serviceSources = new ArrayList<>();
{
ServiceSource serviceSource = handler.buildServiceSource(provider.getName(), providerConfig);
if (serviceSource != null) {
serviceSources.add(serviceSource);
}
List<ServiceSource> extraServiceSources =
handler.getExtraServiceSources(provider.getName(), providerConfig, false);
if (CollectionUtils.isNotEmpty(extraServiceSources)) {
serviceSources.addAll(extraServiceSources);
}
}

Expand All @@ -180,8 +180,13 @@ public LlmProvider addOrUpdate(LlmProvider provider) {
serviceInstance.setConfigurations(MapUtil.of(ACTIVE_PROVIDER_ID, provider.getName()));

// Perform all the updates here just to avoid possible errors in resource building.
if (!serviceSources.isEmpty()) {
for (ServiceSource serviceSource : serviceSources) {
serviceSource.setProxyName(provider.getProxyName());
serviceSourceService.addOrUpdate(serviceSource);
}
}
wasmPluginInstanceService.addOrUpdate(instance);
serviceSourceService.addOrUpdate(serviceSource);
wasmPluginInstanceService.addOrUpdate(serviceInstance);

if (handler.needSyncRouteAfterUpdate()) {
Expand Down Expand Up @@ -253,9 +258,12 @@ public void delete(String providerName) {
if (handler != null) {
UpstreamService upstreamService = handler.buildUpstreamService(providerName, deletedProvider);
wasmPluginInstanceService.delete(WasmPluginInstanceScope.SERVICE, upstreamService.getName(),
BuiltInPluginName.AI_PROXY);
BuiltInPluginName.AI_PROXY, true);

ServiceSource serviceSource = handler.buildServiceSource(providerName, deletedProvider);
serviceSourceService.delete(serviceSource.getName());
if (serviceSource != null) {
serviceSourceService.delete(serviceSource.getName());
}

List<ServiceSource> extraServiceSources =
handler.getExtraServiceSources(providerName, deletedProvider, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import java.util.Map;
import java.util.stream.Collectors;

import com.alibaba.higress.sdk.model.ServiceSource;
import com.alibaba.higress.sdk.model.route.UpstreamService;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
Expand All @@ -34,6 +36,8 @@ public class OpenaiLlmProviderHandler extends AbstractLlmProviderHandler {

private static final String CUSTOM_URL_KEY = "openaiCustomUrl";
private static final String EXTRA_CUSTOM_URLS_KEY = "openaiExtraCustomUrls";
private static final String CUSTOM_SERVICE_NAME_KEY = "openaiCustomServiceName";
private static final String CUSTOM_SERVICE_PORT_KEY = "openaiCustomServicePort";

private static final String DEFAULT_SERVICE_DOMAIN = "api.openai.com";
private static final int DEFAULT_SERVICE_PORT = 443;
Expand Down Expand Up @@ -72,6 +76,27 @@ public boolean needSyncRouteAfterUpdate() {
return true;
}

@Override
public ServiceSource buildServiceSource(String providerName, Map<String, Object> providerConfig) {
UpstreamService upstreamService = getCustomUpstreamService(providerConfig);
if (upstreamService != null) {
// User has specified a custom upstream service, use it directly.
// We don't need to create a service source in this case.
return null;
}
return super.buildServiceSource(providerName, providerConfig);
}

@Override
public UpstreamService buildUpstreamService(String providerName, Map<String, Object> providerConfig) {
UpstreamService upstreamService = getCustomUpstreamService(providerConfig);
if (upstreamService != null) {
// User has specified a custom upstream service, use it directly.
return upstreamService;
}
return super.buildUpstreamService(providerName, providerConfig);
}

@Override
protected List<LlmProviderEndpoint> getProviderEndpoints(Map<String, Object> providerConfig) {
List<URI> customUris = getCustomUris(providerConfig);
Expand All @@ -81,6 +106,22 @@ protected List<LlmProviderEndpoint> getProviderEndpoints(Map<String, Object> pro
return customUris.stream().map(LlmProviderEndpoint::fromUri).collect(Collectors.toList());
}

private UpstreamService getCustomUpstreamService(Map<String, Object> providerConfig) {
if (MapUtils.isEmpty(providerConfig)) {
return null;
}
Object rawCustomServiceNameObject = providerConfig.get(CUSTOM_SERVICE_NAME_KEY);
if (!(rawCustomServiceNameObject instanceof String)) {
return null;
}
Object rawCustomServicePortObject = providerConfig.get(CUSTOM_SERVICE_PORT_KEY);
if (!(rawCustomServicePortObject instanceof Integer)) {
return null;
}
return UpstreamService.builder().name((String)rawCustomServiceNameObject)
.port((Integer)rawCustomServicePortObject).build();
}

private List<URI> getCustomUris(Map<String, Object> providerConfig) {
if (MapUtils.isEmpty(providerConfig)) {
return null;
Expand Down
16 changes: 14 additions & 2 deletions frontend/src/locales/en-US/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,11 @@
"vertexTokenRefreshAhead": "Lead time of rereshing Google Cloud Access Token (sec)",
"geminiSafetySettings": "Gemini API Safety Settings",
"geminiSafetyCategory": "Harm Category",
"geminiSafetyThreshold": "Block Threshold"
"geminiSafetyThreshold": "Block Threshold",
"openaiCustomServerType": "Custom OpenAI Service Config Method",
"openaiCustomService": "Custom OpenAI Service",
"openaiCustomServiceHost": "Custom OpenAI Service Host",
"openaiCustomServicePath": "Custom OpenAI Service Path"
},
"rules": {
"tokenRequired": "Please input auth token",
Expand All @@ -283,6 +287,8 @@
"openaiCustomUrlMultipleValuesWithIpOnly": "Only URLs with IP and port are supported when specifying multiple custom OpenAI service base URLs",
"openaiCustomUrlInconsistentProtocols": "Inconsistent protocols found in custom OpenAI service base URLs",
"openaiCustomUrlInconsistentContextPaths": "Inconsistent paths found in custom OpenAI service base URLs",
"openaiCustomServiceRequired": "Please select custom OpenAI service",
"openaiCustomServicePathRequired": "Please select custom OpenAI service path",
"awsRegionRequired": "Please input AWS Region",
"awsAccessKeyRequired": "Please input AWS Access Key ID",
"awsSecretKeyRequired": "Please input AWS Secret Access Key",
Expand All @@ -301,12 +307,18 @@
"placeholder": {
"azureServiceUrlPlaceholder": "It shall contain \"/chat/completions\" in the path and \"api-version\" in the query string",
"ollamaServerHostPlaceholder": "Please input a hostname, domain name or IP address",
"openaiCustomUrlPlaceholder": "Sample: https://api.openai.com/v1"
"openaiCustomUrlPlaceholder": "Sample: https://api.openai.com/v1",
"openaiCustomServiceHostPlaceholder": "Sample: api.openai.com",
"openaiCustomServicePathPlaceholder": "Sample: /v1"
},
"openaiServerType": {
"official": "OpenAI Official Service",
"custom": "Custom Service"
},
"openaiCustomServerType": {
"service": "Select a service",
"url": "Input URL(s)"
},
"secretRefModal": {
"entry": "I'd like to use token info stored in a Secret resource.",
"title": "How to refer to token info stored in a Secret resource?",
Expand Down
16 changes: 14 additions & 2 deletions frontend/src/locales/zh-CN/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,11 @@
"vertexTokenRefreshAhead": "Google Cloud 访问令牌刷新提前量(秒)",
"geminiSafetySettings": "Gemini API 安全设置",
"geminiSafetyCategory": "危害类别",
"geminiSafetyThreshold": "屏蔽阈值"
"geminiSafetyThreshold": "屏蔽阈值",
"openaiCustomServerType": "自定义 OpenAI 服务配置方式",
"openaiCustomService": "自定义 OpenAI 服务",
"openaiCustomServiceHost": "自定义 OpenAI 服务主机域",
"openaiCustomServicePath": "自定义 OpenAI 服务路径"
},
"rules": {
"tokenRequired": "请输入凭证",
Expand All @@ -283,6 +287,8 @@
"openaiCustomUrlMultipleValuesWithIpOnly": "仅支持指定多个使用 IP 和端口的自定义 OpenAI 服务 BaseURL",
"openaiCustomUrlInconsistentProtocols": "各个自定义 OpenAI 服务 BaseURL 所使用的协议不一致",
"openaiCustomUrlInconsistentContextPaths": "各个自定义 OpenAI 服务 BaseURL 所使用的路径不一致",
"openaiCustomServiceRequired": "请选择自定义 OpenAI 服务",
"openaiCustomServicePathRequired": "请输入定义 OpenAI 服务路径",
"awsRegionRequired": "请输入 AWS Region",
"awsAccessKeyRequired": "请输入 AWS Access Key ID",
"awsSecretKeyRequired": "请输入 AWS Secret Access Key",
Expand All @@ -301,12 +307,18 @@
"placeholder": {
"azureServiceUrlPlaceholder": "需包含“/chat/completions”路径和“api-version”查询参数",
"ollamaServerHostPlaceholder": "请填写机器名、域名或 IP 地址",
"openaiCustomUrlPlaceholder": "示例:https://api.openai.com/v1"
"openaiCustomUrlPlaceholder": "示例:https://api.openai.com/v1",
"openaiCustomServiceHostPlaceholder": "示例:api.openai.com",
"openaiCustomServicePathPlaceholder": "示例:/v1"
},
"openaiServerType": {
"official": "OpenAI 官方服务",
"custom": "自定义服务"
},
"openaiCustomServerType": {
"service": "选择服务",
"url": "输入 URL"
},
"secretRefModal": {
"entry": "我想引用保存在 Secret 中的凭证信息",
"title": "如何引用保存在 Secret 中的凭证信息?",
Expand Down
Loading
Loading