Skip to content

Commit

Permalink
feat: Add more functions to AI console APIs (#361)
Browse files Browse the repository at this point in the history
  • Loading branch information
CH3CHO authored Oct 28, 2024
1 parent 0bfa6fa commit 8dbeb49
Show file tree
Hide file tree
Showing 15 changed files with 333 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public final class BuiltInPluginName {
public static final String AI_INTENT = "ai-intent";
public static final String AI_QUOTA = "ai-quota";
public static final String AI_AGENT = "ai-agent";
public static final String MODEL_ROUTER = "model-router";

// Auth
public static final String BASIC_AUTH = "basic-auth";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,6 @@ public class CommonKey {
public static final String AI_ROUTE = "ai-route";

public static final String AI_ROUTE_PREFIX = AI_ROUTE + Separators.DASH;

public static final String LLM_SERVICE_NAME_PREFIX = "llm-";
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ public class HigressConstants {
public static final String INTERNAL_RESOURCE_NAME_SUFFIX = ".internal";
public static final String FALLBACK_ROUTE_NAME_SUFFIX = ".fallback";
public static final String FALLBACK_FROM_HEADER = "x-higress-fallback-from";
public static final String LLM_SERVICE_NAME_PREFIX = "llm-";
public static final String MODEL_ROUTER_HEADER = "x-higress-llm-provider";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2022-2023 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.alibaba.higress.sdk.model.ai;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AiModelPredicate {

private Boolean enabled;
private String prefix;

public void validate() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class AiRoute {
private String version;
private List<String> domains;
private List<AiUpstream> upstreams;
private AiModelPredicate modelPredicate;
private AiRouteAuthConfig authConfig;
private AiRouteFallbackConfig fallbackConfig;

Expand All @@ -47,6 +48,9 @@ public void validate() {
throw new ValidationException("upstreams cannot be empty.");
}
upstreams.forEach(AiUpstream::validate);
if (modelPredicate != null){
modelPredicate.validate();
}
if (authConfig != null){
authConfig.validate();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ private LlmProviderType() {
public static final String QWEN = "qwen";

public static final String OPENAI = "openai";

public static final String MOONSHOT = "moonshot";
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class HigressServiceProviderImpl implements HigressServiceProvider {
llmProviderService =
new LlmProviderServiceImpl(serviceSourceService, wasmPluginService, wasmPluginInstanceService);
aiRouteService = new AiRouteServiceImpl(kubernetesModelConverter, kubernetesClientService, routeService,
llmProviderService, consumerService);
llmProviderService, consumerService, wasmPluginService, wasmPluginInstanceService);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;

import com.alibaba.higress.sdk.constant.CommonKey;
import com.alibaba.higress.sdk.constant.HigressConstants;
import com.alibaba.higress.sdk.model.ai.LlmProvider;
import com.alibaba.higress.sdk.model.ai.LlmProviderProtocol;
Expand Down Expand Up @@ -126,7 +127,7 @@ public void saveConfig(LlmProvider provider, Map<String, Object> configurations)
}

protected static String generateServiceProviderName(String llmProviderName) {
return HigressConstants.LLM_SERVICE_NAME_PREFIX + llmProviderName
return CommonKey.LLM_SERVICE_NAME_PREFIX + llmProviderName
+ HigressConstants.INTERNAL_RESOURCE_NAME_SUFFIX;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,18 @@
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;

import com.alibaba.higress.sdk.constant.BuiltInPluginName;
import com.alibaba.higress.sdk.constant.CommonKey;
import com.alibaba.higress.sdk.constant.HigressConstants;
import com.alibaba.higress.sdk.constant.KubernetesConstants;
import com.alibaba.higress.sdk.exception.BusinessException;
Expand All @@ -31,7 +35,10 @@
import com.alibaba.higress.sdk.model.CommonPageQuery;
import com.alibaba.higress.sdk.model.PaginatedResult;
import com.alibaba.higress.sdk.model.Route;
import com.alibaba.higress.sdk.model.WasmPlugin;
import com.alibaba.higress.sdk.model.WasmPluginInstance;
import com.alibaba.higress.sdk.model.WasmPluginInstanceScope;
import com.alibaba.higress.sdk.model.ai.AiModelPredicate;
import com.alibaba.higress.sdk.model.ai.AiRoute;
import com.alibaba.higress.sdk.model.ai.AiRouteAuthConfig;
import com.alibaba.higress.sdk.model.ai.AiRouteFallbackConfig;
Expand All @@ -42,6 +49,8 @@
import com.alibaba.higress.sdk.model.route.RoutePredicateTypeEnum;
import com.alibaba.higress.sdk.model.route.UpstreamService;
import com.alibaba.higress.sdk.service.RouteService;
import com.alibaba.higress.sdk.service.WasmPluginInstanceService;
import com.alibaba.higress.sdk.service.WasmPluginService;
import com.alibaba.higress.sdk.service.consumer.ConsumerService;
import com.alibaba.higress.sdk.service.kubernetes.KubernetesClientService;
import com.alibaba.higress.sdk.service.kubernetes.KubernetesModelConverter;
Expand All @@ -60,6 +69,9 @@ public class AiRouteServiceImpl implements AiRouteService {
private static final Map<String, String> AI_ROUTE_LABEL_SELECTORS =
Map.of(KubernetesConstants.Label.CONFIG_MAP_TYPE_KEY, KubernetesConstants.Label.CONFIG_MAP_TYPE_VALUE_AI_ROUTE);

private static final String MODEL_ROUTER_ENABLE_KEY = "enable";
private static final String ADD_HEADER_KEY_KEY = "add_header_key";

private final KubernetesModelConverter kubernetesModelConverter;

private final KubernetesClientService kubernetesClientService;
Expand All @@ -70,16 +82,23 @@ public class AiRouteServiceImpl implements AiRouteService {

private final ConsumerService consumerService;

private final WasmPluginService wasmPluginService;

private final WasmPluginInstanceService wasmPluginInstanceService;

private final String routeFallbackEnvoyFilterConfig;

public AiRouteServiceImpl(KubernetesModelConverter kubernetesModelConverter,
KubernetesClientService kubernetesClientService, RouteService routeService,
LlmProviderService llmProviderService, ConsumerService consumerService) {
LlmProviderService llmProviderService, ConsumerService consumerService, WasmPluginService wasmPluginService,
WasmPluginInstanceService wasmPluginInstanceService) {
this.kubernetesModelConverter = kubernetesModelConverter;
this.kubernetesClientService = kubernetesClientService;
this.routeService = routeService;
this.llmProviderService = llmProviderService;
this.consumerService = consumerService;
this.wasmPluginService = wasmPluginService;
this.wasmPluginInstanceService = wasmPluginInstanceService;

try {
this.routeFallbackEnvoyFilterConfig =
Expand All @@ -94,6 +113,7 @@ public AiRouteServiceImpl(KubernetesModelConverter kubernetesModelConverter,

@Override
public AiRoute add(AiRoute route) {
fillDefaultValues(route);
V1ConfigMap configMap = kubernetesModelConverter.aiRoute2ConfigMap(route);
V1ConfigMap newConfigMap;
try {
Expand Down Expand Up @@ -147,6 +167,7 @@ public void delete(String routeName) {

@Override
public AiRoute update(AiRoute route) {
fillDefaultValues(route);
V1ConfigMap configMap = kubernetesModelConverter.aiRoute2ConfigMap(route);
V1ConfigMap updatedConfigMap;
try {
Expand All @@ -165,12 +186,21 @@ public AiRoute update(AiRoute route) {
return kubernetesModelConverter.configMap2AiRoute(updatedConfigMap);
}

private void fillDefaultValues(AiRoute route) {
AiModelPredicate modelPredicate = route.getModelPredicate();
if (modelPredicate != null && Boolean.TRUE.equals(modelPredicate.getEnabled())
&& StringUtils.isEmpty(modelPredicate.getPrefix())) {
modelPredicate.setPrefix(route.getName());
}
}

private void writeAiRouteResources(AiRoute aiRoute) {
String routeName = aiRoute.getName() + HigressConstants.INTERNAL_RESOURCE_NAME_SUFFIX;
String routeName = buildRouteResourceName(aiRoute.getName());
Route route = buildRoute(routeName, aiRoute);
setUpstreams(route, aiRoute.getUpstreams());
saveRoute(route);
writeAuthConfigResources(routeName, aiRoute.getAuthConfig());
writeModelRouteResources(aiRoute.getModelPredicate());
}

private void writeAiRouteFallbackResources(AiRoute aiRoute) {
Expand All @@ -180,16 +210,18 @@ private void writeAiRouteFallbackResources(AiRoute aiRoute) {
return;
}

final String originalRouteName = aiRoute.getName() + HigressConstants.INTERNAL_RESOURCE_NAME_SUFFIX;
final String originalRouteName = buildRouteResourceName(aiRoute.getName());

final String fallbackRouteName = aiRoute.getName() + HigressConstants.FALLBACK_ROUTE_NAME_SUFFIX
+ HigressConstants.INTERNAL_RESOURCE_NAME_SUFFIX;
final String fallbackRouteName = buildFallbackRouteResourceName(aiRoute.getName());
Route route = buildRoute(fallbackRouteName, aiRoute);
KeyedRoutePredicate fallbackHeaderPredicate = new KeyedRoutePredicate(HigressConstants.FALLBACK_FROM_HEADER);
fallbackHeaderPredicate.setMatchType(RoutePredicateTypeEnum.EQUAL.name());
fallbackHeaderPredicate.setMatchValue(originalRouteName);
fallbackHeaderPredicate.setCaseSensitive(true);
route.setHeaders(List.of(fallbackHeaderPredicate));
if (route.getHeaders() == null) {
route.setHeaders(new ArrayList<>());
}
route.getHeaders().add(fallbackHeaderPredicate);
String fallbackStrategy = fallbackConfig.getFallbackStrategy();
List<AiUpstream> fallbackUpStreams;
if (StringUtils.isEmpty(fallbackStrategy) || AiRouteFallbackStrategy.RANDOM.equals(fallbackStrategy)) {
Expand Down Expand Up @@ -232,11 +264,59 @@ private void writeAuthConfigResources(String routeName, AiRouteAuthConfig authCo
consumerService.updateAllowList(WasmPluginInstanceScope.ROUTE, routeName, allowedConsumers);
}

private void writeModelRouteResources(AiModelPredicate modelPredicate) {
if (modelPredicate == null || !Boolean.TRUE.equals(modelPredicate.getEnabled())) {
return;
}

final String pluginName = BuiltInPluginName.MODEL_ROUTER;
WasmPluginInstance instance =
wasmPluginInstanceService.query(WasmPluginInstanceScope.GLOBAL, null, pluginName, true);
if (instance == null) {
WasmPlugin plugin = wasmPluginService.query(pluginName, null);
if (plugin == null) {
throw new BusinessException("Plugin " + pluginName + " not found");
}
instance = new WasmPluginInstance();
instance.setPluginName(plugin.getName());
instance.setPluginVersion(plugin.getPluginVersion());
instance.setInternal(true);
instance.setScope(WasmPluginInstanceScope.GLOBAL);
}
instance.setEnabled(true);

Map<String, Object> configurations = instance.getConfigurations();
if (MapUtils.isEmpty(configurations)) {
// Just in case it is a readonly empty map.
configurations = new HashMap<>();
instance.setConfigurations(configurations);
}

configurations.put(MODEL_ROUTER_ENABLE_KEY, Boolean.TRUE);
configurations.put(ADD_HEADER_KEY_KEY, HigressConstants.MODEL_ROUTER_HEADER);

wasmPluginInstanceService.addOrUpdate(instance);
}

private Route buildRoute(String routeName, AiRoute aiRoute) {
Route route = new Route();
route.setName(routeName);
route.setPath(new RoutePredicate(RoutePredicateTypeEnum.PRE.name(), "/", true));
route.setDomains(aiRoute.getDomains());

AiModelPredicate modelPredicate = aiRoute.getModelPredicate();
if (modelPredicate != null && Boolean.TRUE.equals(modelPredicate.getEnabled())
&& StringUtils.isNotEmpty(modelPredicate.getPrefix())) {
KeyedRoutePredicate modelHeaderPredicate = new KeyedRoutePredicate(HigressConstants.MODEL_ROUTER_HEADER);
modelHeaderPredicate.setMatchType(RoutePredicateTypeEnum.EQUAL.name());
modelHeaderPredicate.setMatchValue(modelPredicate.getPrefix());
modelHeaderPredicate.setCaseSensitive(true);
if (route.getHeaders() == null) {
route.setHeaders(new ArrayList<>());
}
route.getHeaders().add(modelHeaderPredicate);
}

return route;
}

Expand All @@ -257,20 +337,19 @@ private void setUpstreams(Route route, List<AiUpstream> upstreams) {
}

private void deleteAiRouteResources(String aiRouteName) {
String routeName = aiRouteName + HigressConstants.INTERNAL_RESOURCE_NAME_SUFFIX;
routeService.delete(routeName);
String resourceName = buildRouteResourceName(aiRouteName);
routeService.delete(resourceName);

try {
kubernetesClientService.deleteEnvoyFilter(routeName);
kubernetesClientService.deleteEnvoyFilter(resourceName);
} catch (ApiException e) {
if (e.getCode() != HttpStatus.NOT_FOUND) {
throw new BusinessException("Error occurs when deleting the EnvoyFilter with name: " + routeName, e);
throw new BusinessException("Error occurs when deleting the EnvoyFilter with name: " + resourceName, e);
}
}

String fallbackRouteName =
aiRouteName + HigressConstants.FALLBACK_ROUTE_NAME_SUFFIX + HigressConstants.INTERNAL_RESOURCE_NAME_SUFFIX;
routeService.delete(fallbackRouteName);
String fallbackResourceName = buildFallbackRouteResourceName(aiRouteName);
routeService.delete(fallbackResourceName);
}

private void saveRoute(Route route) {
Expand All @@ -282,4 +361,13 @@ private void saveRoute(Route route) {
routeService.update(route);
}
}

private static String buildRouteResourceName(String routeName) {
return CommonKey.AI_ROUTE_PREFIX + routeName + HigressConstants.INTERNAL_RESOURCE_NAME_SUFFIX;
}

private static String buildFallbackRouteResourceName(String routeName) {
return CommonKey.AI_ROUTE_PREFIX + routeName + HigressConstants.FALLBACK_ROUTE_NAME_SUFFIX
+ HigressConstants.INTERNAL_RESOURCE_NAME_SUFFIX;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public class LlmProviderServiceImpl implements LlmProviderService {
static {
PROVIDER_HANDLERS = Stream.of(
new DefaultLlmProviderHandler(LlmProviderType.OPENAI, "api.openai.com", 443, V1McpBridge.PROTOCOL_HTTPS),
new DefaultLlmProviderHandler(LlmProviderType.MOONSHOT, "api.moonshot.cn", 443, V1McpBridge.PROTOCOL_HTTPS),
new DefaultLlmProviderHandler(LlmProviderType.QWEN, "dashscope.aliyuncs.com", 443,
V1McpBridge.PROTOCOL_HTTPS))
.collect(Collectors.toMap(LlmProviderHandler::getType, p -> p));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.collections4.CollectionUtils;

import com.alibaba.higress.sdk.exception.BusinessException;
import com.alibaba.higress.sdk.model.CommonPageQuery;
import com.alibaba.higress.sdk.model.PaginatedResult;
Expand Down Expand Up @@ -112,6 +114,13 @@ public void delete(String consumerName) {
public void updateAllowList(WasmPluginInstanceScope scope, String target, List<String> consumerNames) {
for (CredentialHandler config : CREDENTIAL_HANDLERS.values()) {
WasmPluginInstance instance = wasmPluginInstanceService.query(scope, target, config.getPluginName(), true);
if (CollectionUtils.isEmpty(consumerNames)) {
if (instance != null) {
wasmPluginInstanceService.delete(scope, target, config.getPluginName());
}
continue;
}

if (instance == null) {
instance = createInstance(scope, target, config.getPluginName());
}
Expand Down
Loading

0 comments on commit 8dbeb49

Please sign in to comment.