This is an automated email from the ASF dual-hosted git repository.
liuhongyu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git
The following commit(s) were added to refs/heads/master by this push:
new 44995b73bb feat: ai proxy replace old with enhanced (#6174)
44995b73bb is described below
commit 44995b73bbe72172c3f259573a3a841f790b5093
Author: LingXiao Qi <[email protected]>
AuthorDate: Wed Sep 24 16:14:52 2025 +0800
feat: ai proxy replace old with enhanced (#6174)
* add fallback config
* fix spring-ai stale api
* add spring-ai bom & ai-proxy-enhanced pom
* change starter
* add fallback strategy
* feat fallback logic
* add fallback test
* fix pom
* fix pom
* rename fallbackConfig
* add stream & stream fallback
* Update
shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/rule/AiProxyHandle.java
Co-authored-by: Copilot <[email protected]>
* Update
shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/rule/AiProxyHandle.java
Co-authored-by: Copilot <[email protected]>
* Update
shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy-enhanced/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/handler/AiProxyPluginHandler.java
Co-authored-by: Copilot <[email protected]>
* Update
shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy-enhanced/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/service/AiProxyExecutorService.java
Co-authored-by: Copilot <[email protected]>
* Update
shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy-enhanced/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/AiProxyPlugin.java
Co-authored-by: Copilot <[email protected]>
* fix handle
* ci trigger
* add proxy api key ws
* feat proxy api key
* add rat
* try fix
* fix
* fix rat
* add h2 sql
* add sqls
* fix ConfigController
* ci
* backward
* fix
* fix http longpolling
* fix linter
* add npe guard
* fix test
* ci
* feat:config dashborad
* fix
* fix
* rm real-key
* fix test
* refact cache & sync
* mvc
* fix
* db
* fix
* fix
* fix proxy sync
* fix sql
* fix
* fix
* ci
* ci
* remove old ai proxy artifact
* Apply suggestion from @Copilot
Co-authored-by: Copilot <[email protected]>
* fix irregular code
* fix checkstyle
* fix
* add spring-ai-retry license
* ci
* remove old ai proxy artifact
* rm unused common class
* rm unused test
* ci
* ci
* remove old ai proxy artifact
* remove old ai proxy artifact
* rm unused common class
* rm unused test
* resolve conflict
* fix artifact
* fix
* fix starter pom
---------
Co-authored-by: xiaoyu <[email protected]>
Co-authored-by: aias00 <[email protected]>
Co-authored-by: zhengpeng <[email protected]>
Co-authored-by: Copilot <[email protected]>
---
shenyu-plugin/shenyu-plugin-ai/pom.xml | 1 -
.../shenyu/plugin/ai/common/strategy/AiModel.java | 54 ---------
.../plugin/ai/common/strategy/AiModelFactory.java | 47 --------
.../plugin/ai/common/strategy/openai/OpenAI.java | 113 -----------------
.../ai/common/strategy/AiModelFactoryTest.java | 45 -------
.../ai/common/strategy/openai/OpenAITest.java | 81 -------------
.../shenyu-plugin-ai-proxy-enhanced/pom.xml | 2 +-
.../shenyu-plugin-ai-proxy/pom.xml | 54 ---------
.../shenyu/plugin/ai/proxy/AiProxyPlugin.java | 127 --------------------
.../ai/proxy/handler/AiProxyPluginHandler.java | 72 -----------
.../shenyu/plugin/ai/proxy/AiProxyPluginTest.java | 133 ---------------------
.../ai/proxy/handler/AiProxyPluginHandlerTest.java | 109 -----------------
.../pom.xml | 2 +-
13 files changed, 2 insertions(+), 838 deletions(-)
diff --git a/shenyu-plugin/shenyu-plugin-ai/pom.xml
b/shenyu-plugin/shenyu-plugin-ai/pom.xml
index 8835b005aa..2b999a7a98 100644
--- a/shenyu-plugin/shenyu-plugin-ai/pom.xml
+++ b/shenyu-plugin/shenyu-plugin-ai/pom.xml
@@ -30,7 +30,6 @@
<modules>
<module>shenyu-plugin-ai-common</module>
- <module>shenyu-plugin-ai-proxy</module>
<module>shenyu-plugin-ai-prompt</module>
<module>shenyu-plugin-ai-token-limiter</module>
<module>shenyu-plugin-ai-request-transformer</module>
diff --git
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-common/src/main/java/org/apache/shenyu/plugin/ai/common/strategy/AiModel.java
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-common/src/main/java/org/apache/shenyu/plugin/ai/common/strategy/AiModel.java
deleted file mode 100644
index 3ba32faac0..0000000000
---
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-common/src/main/java/org/apache/shenyu/plugin/ai/common/strategy/AiModel.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 org.apache.shenyu.plugin.ai.common.strategy;
-
-import org.apache.shenyu.plugin.ai.common.config.AiCommonConfig;
-import org.apache.shenyu.plugin.api.ShenyuPluginChain;
-import org.springframework.http.codec.HttpMessageReader;
-import org.springframework.web.server.ServerWebExchange;
-import reactor.core.publisher.Mono;
-
-import java.util.List;
-
-/**
- * The interface Ai model.
- */
-public interface AiModel {
-
- /**
- * Invoke ai.
- *
- * @param aiCommonConfig the ai config
- * @param exchange the exchange
- * @param chain the chain
- * @param messageReaders the message readers
- * @return the mono
- */
- Mono<Void> invoke(AiCommonConfig aiCommonConfig,
- ServerWebExchange exchange,
- ShenyuPluginChain chain,
- List<HttpMessageReader<?>> messageReaders);
-
- /**
- * getCompletionTokens.
- *
- * @param responseBody the response body
- * @return the completion tokens
- */
- Long getCompletionTokens(String responseBody);
-}
diff --git
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-common/src/main/java/org/apache/shenyu/plugin/ai/common/strategy/AiModelFactory.java
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-common/src/main/java/org/apache/shenyu/plugin/ai/common/strategy/AiModelFactory.java
deleted file mode 100644
index 523e76a81a..0000000000
---
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-common/src/main/java/org/apache/shenyu/plugin/ai/common/strategy/AiModelFactory.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 org.apache.shenyu.plugin.ai.common.strategy;
-
-import org.apache.shenyu.common.enums.AiModelProviderEnum;
-import org.apache.shenyu.plugin.ai.common.strategy.openai.OpenAI;
-
-import java.util.Objects;
-
-/**
- * The interface Ai model.
- */
-public final class AiModelFactory {
-
- private AiModelFactory() {
- }
-
- /**
- * Create ai model instance.
- *
- * @param provider the provider
- * @return the ai model provider
- */
- public static AiModel createAiModel(final AiModelProviderEnum provider) {
- if (Objects.isNull(provider)) {
- throw new IllegalArgumentException("not supported provider");
- }
- return switch (provider) {
- case OPEN_AI, DEEP_SEEK, ALIYUN, OPEN_API, MOONSHOT -> new
OpenAI();
- };
- }
-}
diff --git
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-common/src/main/java/org/apache/shenyu/plugin/ai/common/strategy/openai/OpenAI.java
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-common/src/main/java/org/apache/shenyu/plugin/ai/common/strategy/openai/OpenAI.java
deleted file mode 100644
index aa1a4f30e6..0000000000
---
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-common/src/main/java/org/apache/shenyu/plugin/ai/common/strategy/openai/OpenAI.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 org.apache.shenyu.plugin.ai.common.strategy.openai;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import org.apache.shenyu.common.constant.Constants;
-import org.apache.shenyu.common.utils.JsonUtils;
-import org.apache.shenyu.plugin.ai.common.config.AiCommonConfig;
-import org.apache.shenyu.common.utils.GsonUtils;
-import org.apache.shenyu.plugin.ai.common.strategy.AiModel;
-import org.apache.shenyu.plugin.api.ShenyuPluginChain;
-import org.apache.shenyu.plugin.api.exception.ResponsiveException;
-import org.apache.shenyu.plugin.api.utils.WebFluxResultUtils;
-import org.apache.shenyu.plugin.base.utils.ServerWebExchangeUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.MediaType;
-import org.springframework.http.codec.HttpMessageReader;
-import org.springframework.web.server.ServerWebExchange;
-import reactor.core.publisher.Mono;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-
-/**
- * The OpenAI model.
- */
-public class OpenAI implements AiModel {
-
- private static final Logger LOG = LoggerFactory.getLogger(OpenAI.class);
-
- @Override
- public Mono<Void> invoke(final AiCommonConfig aiCommonConfig, final
ServerWebExchange exchange,
- final ShenyuPluginChain chain,
- final List<HttpMessageReader<?>> messageReaders) {
- ServerWebExchange modifiedExchange = exchange.mutate()
- .request(originalRequest -> originalRequest
- .headers(httpHeaders -> convertHeader(httpHeaders,
aiCommonConfig))
- .method(exchange.getRequest().getMethod())
- )
- .build();
- return ServerWebExchangeUtils.rewriteRequestBody(modifiedExchange,
messageReaders, originalBody ->
- Mono.just(convertBody(originalBody, aiCommonConfig))
- ).flatMap(chain::execute)
- .onErrorResume(error -> {
- if (error instanceof ResponsiveException) {
- return
WebFluxResultUtils.failedResult((ResponsiveException) error);
- }
- return Mono.error(error);
- });
- }
-
- @Override
- public Long getCompletionTokens(final String responseBody) {
- try {
- JsonNode root = JsonUtils.toJsonNode(responseBody);
- JsonNode usage = root.get(Constants.USAGE);
- if (Objects.nonNull(usage)) {
- JsonNode totalTokens = usage.get(Constants.COMPLETION_TOKENS);
- if (Objects.nonNull(totalTokens)) {
- return totalTokens.asLong();
- }
- }
- } catch (Exception e) {
- // Handle parsing exceptions
- LOG.error("Failed to parse response body: {}", responseBody, e);
- }
- return 0L;
- }
-
-
- private static void convertHeader(final HttpHeaders httpHeaders, final
AiCommonConfig aiCommonConfig) {
- if (!httpHeaders.containsKey("Authorization")) {
- httpHeaders.add("Authorization", "Bearer " +
aiCommonConfig.getApiKey());
- }
- if (aiCommonConfig.getStream()) {
- httpHeaders.add(HttpHeaders.CONTENT_TYPE,
MediaType.TEXT_EVENT_STREAM_VALUE);
- httpHeaders.add(HttpHeaders.CACHE_CONTROL, "no-cache");
- httpHeaders.add(HttpHeaders.CONNECTION, "keep-alive");
- }
- }
-
- private String convertBody(final String originalBody, final AiCommonConfig
aiCommonConfig) {
- Map<String, Object> requestBodyMap =
GsonUtils.getInstance().convertToMap(originalBody);
- requestBodyMap.put(Constants.MODEL, aiCommonConfig.getModel());
- requestBodyMap.put(Constants.STREAM, aiCommonConfig.getStream());
- if (aiCommonConfig.getStream()) {
- Map<String, Object> streamOptions = new HashMap<>();
- streamOptions.put(Constants.INCLUDE_USAGE, true);
- requestBodyMap.put(Constants.STREAM_OPTIONS, streamOptions);
- }
- return GsonUtils.getInstance().toJson(requestBodyMap);
- }
-
-}
diff --git
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-common/src/test/java/org/apache/shenyu/plugin/ai/common/strategy/AiModelFactoryTest.java
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-common/src/test/java/org/apache/shenyu/plugin/ai/common/strategy/AiModelFactoryTest.java
deleted file mode 100644
index 10a3052d96..0000000000
---
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-common/src/test/java/org/apache/shenyu/plugin/ai/common/strategy/AiModelFactoryTest.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 org.apache.shenyu.plugin.ai.common.strategy;
-
-import org.apache.shenyu.common.enums.AiModelProviderEnum;
-import org.apache.shenyu.plugin.ai.common.strategy.openai.OpenAI;
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-class AiModelFactoryTest {
-
- @Test
- void testCreateAiModelWithValidProvider() {
- AiModel aiModel =
AiModelFactory.createAiModel(AiModelProviderEnum.OPEN_AI);
- assertNotNull(aiModel);
- assertTrue(aiModel instanceof OpenAI);
- }
-
- @Test
- void testCreateAiModelWithNullProvider() {
- IllegalArgumentException exception =
assertThrows(IllegalArgumentException.class, () -> {
- AiModelFactory.createAiModel(null);
- });
- assertEquals("not supported provider", exception.getMessage());
- }
-}
diff --git
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-common/src/test/java/org/apache/shenyu/plugin/ai/common/strategy/openai/OpenAITest.java
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-common/src/test/java/org/apache/shenyu/plugin/ai/common/strategy/openai/OpenAITest.java
deleted file mode 100644
index 38bc2a9edb..0000000000
---
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-common/src/test/java/org/apache/shenyu/plugin/ai/common/strategy/openai/OpenAITest.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 org.apache.shenyu.plugin.ai.common.strategy.openai;
-
-import org.apache.shenyu.plugin.ai.common.config.AiCommonConfig;
-import org.apache.shenyu.plugin.api.ShenyuPluginChain;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.springframework.http.codec.HttpMessageReader;
-import org.springframework.web.server.ServerWebExchange;
-
-import java.util.List;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.mockito.Mockito.mock;
-
-class OpenAITest {
-
- private OpenAI openAI;
-
- private AiCommonConfig aiCommonConfig;
-
- private ServerWebExchange exchange;
-
- private ShenyuPluginChain chain;
-
- private List<HttpMessageReader<?>> messageReaders;
-
- @BeforeEach
- void setUp() {
- openAI = new OpenAI();
- aiCommonConfig = new AiCommonConfig();
- aiCommonConfig.setApiKey("test-api-key");
- aiCommonConfig.setModel("test-model");
- aiCommonConfig.setStream(true);
-
- exchange = mock(ServerWebExchange.class);
- chain = mock(ShenyuPluginChain.class);
- messageReaders = mock(List.class);
- }
-
- @Test
- void testGetCompletionTokensValidResponse() {
-
- String responseBody = "{\"usage\":{\"completion_tokens\":42}}";
- Long tokens = openAI.getCompletionTokens(responseBody);
- assertEquals(42L, tokens);
- }
-
- @Test
- void testGetCompletionTokensInvalidResponse() {
-
- String responseBody = "{\"invalid\":\"data\"}";
- Long tokens = openAI.getCompletionTokens(responseBody);
- assertEquals(0L, tokens);
- }
-
- @Test
- void testGetCompletionTokensEmptyResponse() {
-
- String responseBody = "";
- Long tokens = openAI.getCompletionTokens(responseBody);
- assertEquals(0L, tokens);
- }
-
-}
diff --git
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy-enhanced/pom.xml
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy-enhanced/pom.xml
index eb8ddbb89a..e1e3a4b20c 100644
--- a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy-enhanced/pom.xml
+++ b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy-enhanced/pom.xml
@@ -24,7 +24,7 @@
<version>2.7.0.3-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
- <artifactId>shenyu-plugin-ai-proxy-enhanced</artifactId>
+ <artifactId>shenyu-plugin-ai-proxy</artifactId>
<dependencies>
<dependency>
diff --git a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/pom.xml
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/pom.xml
deleted file mode 100644
index 6a06f5586d..0000000000
--- a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/pom.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- ~ Licensed to the Apache Software Foundation (ASF) under one or more
- ~ contributor license agreements. See the NOTICE file distributed with
- ~ this work for additional information regarding copyright ownership.
- ~ The ASF licenses this file to You 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.
- -->
-
-<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <parent>
- <groupId>org.apache.shenyu</groupId>
- <artifactId>shenyu-plugin-ai</artifactId>
- <version>2.7.0.3-SNAPSHOT</version>
- </parent>
- <modelVersion>4.0.0</modelVersion>
- <artifactId>shenyu-plugin-ai-proxy</artifactId>
-
- <dependencies>
- <dependency>
- <groupId>org.apache.shenyu</groupId>
- <artifactId>shenyu-plugin-base</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.apache.shenyu</groupId>
- <artifactId>shenyu-plugin-ai-common</artifactId>
- <version>${project.version}</version>
- </dependency>
-
- <dependency>
- <groupId>io.netty</groupId>
- <artifactId>netty-codec-http</artifactId>
- </dependency>
-
- <dependency>
- <groupId>io.projectreactor.netty</groupId>
- <artifactId>reactor-netty</artifactId>
- </dependency>
- <dependency>
- <groupId>com.squareup.okhttp3</groupId>
- <artifactId>okhttp</artifactId>
- </dependency>
- </dependencies>
-</project>
diff --git
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/AiProxyPlugin.java
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/AiProxyPlugin.java
deleted file mode 100644
index 0cfbcc2a45..0000000000
---
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/AiProxyPlugin.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 org.apache.shenyu.plugin.ai.proxy;
-
-import org.apache.shenyu.common.constant.Constants;
-import org.apache.shenyu.common.dto.RuleData;
-import org.apache.shenyu.common.dto.SelectorData;
-import org.apache.shenyu.common.dto.convert.rule.AiProxyHandle;
-import org.apache.shenyu.common.enums.AiModelProviderEnum;
-import org.apache.shenyu.common.enums.PluginEnum;
-import org.apache.shenyu.common.enums.RpcTypeEnum;
-import org.apache.shenyu.common.utils.Singleton;
-import org.apache.shenyu.plugin.ai.common.config.AiCommonConfig;
-import org.apache.shenyu.plugin.ai.common.strategy.AiModel;
-import org.apache.shenyu.plugin.ai.common.strategy.AiModelFactory;
-import org.apache.shenyu.plugin.ai.proxy.handler.AiProxyPluginHandler;
-import org.apache.shenyu.plugin.api.ShenyuPluginChain;
-import org.apache.shenyu.plugin.api.context.ShenyuContext;
-import org.apache.shenyu.plugin.base.AbstractShenyuPlugin;
-import org.apache.shenyu.plugin.base.utils.CacheKeyUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.http.codec.HttpMessageReader;
-import org.springframework.web.server.ServerWebExchange;
-import reactor.core.publisher.Mono;
-
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-
-/**
- * this is ai proxy plugin.
- */
-public class AiProxyPlugin extends AbstractShenyuPlugin {
-
- private static final Logger LOG =
LoggerFactory.getLogger(AiProxyPlugin.class);
-
- private final List<HttpMessageReader<?>> messageReaders;
-
- public AiProxyPlugin(final List<HttpMessageReader<?>> messageReaders) {
- this.messageReaders = messageReaders;
- }
-
- @Override
- protected Mono<Void> doExecute(final ServerWebExchange exchange, final
ShenyuPluginChain chain,
- final SelectorData selector, final RuleData
rule) {
- AiCommonConfig aiCommonConfig =
Singleton.INST.get(AiCommonConfig.class);
- if (Objects.isNull(aiCommonConfig)) {
- aiCommonConfig = new AiCommonConfig();
- }
- final ShenyuContext shenyuContext =
exchange.getAttribute(Constants.CONTEXT);
- Objects.requireNonNull(shenyuContext);
-
- // Get selector handle from cache
- AiProxyHandle selectorHandle =
AiProxyPluginHandler.SELECTOR_CACHED_HANDLE.get()
- .obtainHandle(CacheKeyUtils.INST.getKey(selector.getId(),
Constants.DEFAULT_RULE));
-
- // Create final config with selector handle taking precedence
- if (Objects.nonNull(selectorHandle)) {
- AiCommonConfig tmp = new AiCommonConfig();
-
tmp.setProvider(Optional.ofNullable(selectorHandle.getProvider()).orElse(aiCommonConfig.getProvider()));
-
tmp.setBaseUrl(Optional.ofNullable(selectorHandle.getBaseUrl()).orElse(aiCommonConfig.getBaseUrl()));
-
tmp.setApiKey(Optional.ofNullable(selectorHandle.getApiKey()).orElse(aiCommonConfig.getApiKey()));
-
tmp.setModel(Optional.ofNullable(selectorHandle.getModel()).orElse(aiCommonConfig.getModel()));
-
tmp.setTemperature(Optional.ofNullable(selectorHandle.getTemperature()).orElse(aiCommonConfig.getTemperature()));
-
tmp.setMaxTokens(Optional.ofNullable(selectorHandle.getMaxTokens()).orElse(aiCommonConfig.getMaxTokens()));
-
tmp.setStream(Optional.ofNullable(selectorHandle.getStream()).orElse(aiCommonConfig.getStream()));
- aiCommonConfig = tmp;
- }
-
- if (Objects.isNull(aiCommonConfig.getBaseUrl())) {
- LOG.error("AI proxy plugin: baseUrl is null");
- return chain.execute(exchange);
- }
-
- shenyuContext.setRpcType(RpcTypeEnum.AI.getName());
- exchange.getAttributes().put(Constants.CONTEXT, shenyuContext);
-
- // set domain
- exchange.getAttributes().put(Constants.HTTP_DOMAIN,
aiCommonConfig.getBaseUrl());
- // set the http timeout
- exchange.getAttributes().put(Constants.HTTP_TIME_OUT, 60 * 3000L);
- exchange.getAttributes().put(Constants.HTTP_RETRY, 0);
-
- String provider = aiCommonConfig.getProvider();
- AiModelProviderEnum providerEnum =
AiModelProviderEnum.getByName(provider);
- if (Objects.isNull(providerEnum)) {
- return Mono.error(new IllegalArgumentException("Invalid AI model
provider"));
- }
- // Create AI model instance
- AiModel aiModel = AiModelFactory.createAiModel(providerEnum);
- if (Objects.isNull(aiModel)) {
- return Mono.error(new IllegalStateException("Failed to create AI
model"));
- }
-
- exchange.getAttributes().put(Constants.AI_MODEL, aiModel);
-
- return aiModel.invoke(aiCommonConfig, exchange, chain, messageReaders);
-
- }
-
-
- @Override
- public int getOrder() {
- return PluginEnum.AI_PROXY.getCode();
- }
-
- @Override
- public String named() {
- return PluginEnum.AI_PROXY.getName();
- }
-}
diff --git
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/handler/AiProxyPluginHandler.java
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/handler/AiProxyPluginHandler.java
deleted file mode 100644
index e3f5d34426..0000000000
---
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/handler/AiProxyPluginHandler.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 org.apache.shenyu.plugin.ai.proxy.handler;
-
-import org.apache.shenyu.common.constant.Constants;
-import org.apache.shenyu.common.dto.PluginData;
-import org.apache.shenyu.common.dto.SelectorData;
-import org.apache.shenyu.common.dto.convert.rule.AiProxyHandle;
-import org.apache.shenyu.common.enums.PluginEnum;
-import org.apache.shenyu.common.utils.GsonUtils;
-import org.apache.shenyu.common.utils.Singleton;
-import org.apache.shenyu.plugin.ai.common.config.AiCommonConfig;
-import org.apache.shenyu.plugin.base.cache.CommonHandleCache;
-import org.apache.shenyu.plugin.base.handler.PluginDataHandler;
-import org.apache.shenyu.plugin.base.utils.BeanHolder;
-import org.apache.shenyu.plugin.base.utils.CacheKeyUtils;
-
-import java.util.Objects;
-import java.util.function.Supplier;
-
-/**
- * this is ai proxy plugin handler.
- */
-public class AiProxyPluginHandler implements PluginDataHandler {
-
- public static final Supplier<CommonHandleCache<String, AiProxyHandle>>
SELECTOR_CACHED_HANDLE = new BeanHolder<>(CommonHandleCache::new);
-
- @Override
- public void handlerPlugin(final PluginData pluginData) {
- if (Objects.nonNull(pluginData) && pluginData.getEnabled()) {
- AiCommonConfig aiCommonConfig =
GsonUtils.getInstance().fromJson(pluginData.getConfig(), AiCommonConfig.class);
- if (Objects.isNull(aiCommonConfig)) {
- return;
- }
- Singleton.INST.single(AiCommonConfig.class, aiCommonConfig);
- }
- }
-
- @Override
- public void handlerSelector(final SelectorData selectorData) {
- if (Objects.isNull(selectorData.getHandle())) {
- return;
- }
- AiProxyHandle aiProxyHandle =
GsonUtils.getInstance().fromJson(selectorData.getHandle(), AiProxyHandle.class);
-
SELECTOR_CACHED_HANDLE.get().cachedHandle(CacheKeyUtils.INST.getKey(selectorData.getId(),
Constants.DEFAULT_RULE), aiProxyHandle);
- }
-
- @Override
- public void removeSelector(final SelectorData selectorData) {
-
SELECTOR_CACHED_HANDLE.get().removeHandle(CacheKeyUtils.INST.getKey(selectorData.getId(),
Constants.DEFAULT_RULE));
- }
-
- @Override
- public String pluginNamed() {
- return PluginEnum.AI_PROXY.getName();
- }
-}
diff --git
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/AiProxyPluginTest.java
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/AiProxyPluginTest.java
deleted file mode 100644
index 4ccd8a9d93..0000000000
---
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/AiProxyPluginTest.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 org.apache.shenyu.plugin.ai.proxy;
-
-import org.apache.shenyu.common.constant.Constants;
-import org.apache.shenyu.common.dto.RuleData;
-import org.apache.shenyu.common.dto.SelectorData;
-import org.apache.shenyu.common.dto.convert.rule.AiProxyHandle;
-import org.apache.shenyu.common.enums.PluginEnum;
-import org.apache.shenyu.common.utils.Singleton;
-import org.apache.shenyu.plugin.ai.common.config.AiCommonConfig;
-import org.apache.shenyu.plugin.ai.common.strategy.AiModel;
-import org.apache.shenyu.plugin.ai.proxy.handler.AiProxyPluginHandler;
-import org.apache.shenyu.plugin.api.ShenyuPluginChain;
-import org.apache.shenyu.plugin.api.context.ShenyuContext;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.springframework.http.codec.HttpMessageReader;
-import org.springframework.web.server.ServerWebExchange;
-import reactor.core.publisher.Mono;
-
-import java.util.List;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-class AiProxyPluginTest {
-
- private AiProxyPlugin plugin;
-
- private List<HttpMessageReader<?>> messageReaders;
-
- @BeforeEach
- void setUp() {
- messageReaders = mock(List.class);
- plugin = new AiProxyPlugin(messageReaders);
- }
-
- @Test
- void testDoExecuteWithValidConfig() {
-
- ShenyuContext shenyuContext = new ShenyuContext();
- ServerWebExchange exchange = mock(ServerWebExchange.class);
-
when(exchange.getAttribute(Constants.CONTEXT)).thenReturn(shenyuContext);
-
- AiCommonConfig config = new AiCommonConfig();
- config.setBaseUrl("https://api.example.com");
- config.setProvider("OPEN_AI");
- Singleton.INST.single(AiCommonConfig.class, config);
-
- AiModel aiModel = mock(AiModel.class);
- when(aiModel.invoke(any(), any(), any(),
any())).thenReturn(Mono.empty());
-
AiProxyPluginHandler.SELECTOR_CACHED_HANDLE.get().cachedHandle("selector-id_default_rule",
new AiProxyHandle());
-
- SelectorData selector = mock(SelectorData.class);
- RuleData rule = mock(RuleData.class);
- ShenyuPluginChain chain = mock(ShenyuPluginChain.class);
- Mono<Void> result = plugin.doExecute(exchange, chain, selector, rule);
-
- assertNotNull(result);
- verify(aiModel, times(0)).invoke(any(), any(), any(), any());
- }
-
- @Test
- void testDoExecuteWithValidContext() {
-
- ShenyuContext shenyuContext = new ShenyuContext();
- ServerWebExchange exchange = mock(ServerWebExchange.class);
-
when(exchange.getAttribute(Constants.CONTEXT)).thenReturn(shenyuContext);
-
- AiCommonConfig config = new AiCommonConfig();
- config.setBaseUrl("https://api.example.com");
- Singleton.INST.single(AiCommonConfig.class, config);
-
- AiModel aiModel = mock(AiModel.class);
- when(aiModel.invoke(any(), any(), any(),
any())).thenReturn(Mono.empty());
-
AiProxyPluginHandler.SELECTOR_CACHED_HANDLE.get().cachedHandle("selector-id_default_rule",
new AiProxyHandle());
-
- ShenyuPluginChain chain = mock(ShenyuPluginChain.class);
- SelectorData selector = mock(SelectorData.class);
- RuleData rule = mock(RuleData.class);
-
- Mono<Void> result = plugin.doExecute(exchange, chain, selector, rule);
-
- assertNotNull(result);
- verify(chain, times(0)).execute(exchange);
- }
-
- @Test
- void testDoExecuteWithInvalidProvider() {
- ServerWebExchange exchange = mock(ServerWebExchange.class);
- ShenyuPluginChain chain = mock(ShenyuPluginChain.class);
- SelectorData selector = mock(SelectorData.class);
- RuleData rule = mock(RuleData.class);
-
- AiCommonConfig config = new AiCommonConfig();
- config.setProvider("INVALID_PROVIDER");
- Singleton.INST.single(AiCommonConfig.class, config);
-
- assertThrows(NullPointerException.class, () ->
plugin.doExecute(exchange, chain, selector, rule));
- }
-
- @Test
- void testGetOrder() {
- assertEquals(PluginEnum.AI_PROXY.getCode(), plugin.getOrder());
- }
-
- @Test
- void testNamed() {
- assertEquals(PluginEnum.AI_PROXY.getName(), plugin.named());
- }
-}
diff --git
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/handler/AiProxyPluginHandlerTest.java
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/handler/AiProxyPluginHandlerTest.java
deleted file mode 100644
index 4e8fdeea68..0000000000
---
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/handler/AiProxyPluginHandlerTest.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 org.apache.shenyu.plugin.ai.proxy.handler;
-
-import org.apache.shenyu.common.dto.PluginData;
-import org.apache.shenyu.common.dto.SelectorData;
-import org.apache.shenyu.common.dto.convert.rule.AiProxyHandle;
-import org.apache.shenyu.common.enums.PluginEnum;
-import org.apache.shenyu.common.utils.Singleton;
-import org.apache.shenyu.plugin.ai.common.config.AiCommonConfig;
-import org.apache.shenyu.plugin.base.cache.CommonHandleCache;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-class AiProxyPluginHandlerTest {
-
- private AiProxyPluginHandler handler;
-
- @BeforeEach
- void setUp() {
- handler = new AiProxyPluginHandler();
- }
-
- @Test
- void testHandlerPluginWithValidData() {
- PluginData pluginData = mock(PluginData.class);
- when(pluginData.getEnabled()).thenReturn(true);
-
when(pluginData.getConfig()).thenReturn("{\"baseUrl\":\"https://api.example.com\",\"apiKey\":\"test-key\"}");
-
- handler.handlerPlugin(pluginData);
-
- AiCommonConfig config = Singleton.INST.get(AiCommonConfig.class);
- assertNotNull(config);
- assertEquals("https://api.example.com", config.getBaseUrl());
- assertEquals("test-key", config.getApiKey());
- }
-
- @Test
- void testHandlerPluginWithNullData() {
- handler.handlerPlugin(null);
- AiCommonConfig config = Singleton.INST.get(AiCommonConfig.class);
- assertNull(config);
- }
-
- @Test
- void testHandlerSelectorWithValidData() {
- SelectorData selectorData = mock(SelectorData.class);
- when(selectorData.getId()).thenReturn("selector-id");
-
when(selectorData.getHandle()).thenReturn("{\"provider\":\"OPEN_AI\",\"baseUrl\":\"https://api.example.com\"}");
-
- handler.handlerSelector(selectorData);
-
- CommonHandleCache<String, AiProxyHandle> cache =
AiProxyPluginHandler.SELECTOR_CACHED_HANDLE.get();
- AiProxyHandle handle = cache.obtainHandle("selector-id_default_rule");
- assertNotNull(handle);
- assertEquals("OPEN_AI", handle.getProvider());
- assertEquals("https://api.example.com", handle.getBaseUrl());
- }
-
- @Test
- void testHandlerSelectorWithNullHandle() {
- SelectorData selectorData = mock(SelectorData.class);
- when(selectorData.getHandle()).thenReturn(null);
-
- handler.handlerSelector(selectorData);
-
- CommonHandleCache<String, AiProxyHandle> cache =
AiProxyPluginHandler.SELECTOR_CACHED_HANDLE.get();
- assertNull(cache.obtainHandle("selector-id_default_rule"));
- }
-
- @Test
- void testRemoveSelector() {
- SelectorData selectorData = mock(SelectorData.class);
- when(selectorData.getId()).thenReturn("selector-id");
-
when(selectorData.getHandle()).thenReturn("{\"provider\":\"OPEN_AI\",\"baseUrl\":\"https://api.example.com\"}");
-
- handler.handlerSelector(selectorData);
- handler.removeSelector(selectorData);
-
- CommonHandleCache<String, AiProxyHandle> cache =
AiProxyPluginHandler.SELECTOR_CACHED_HANDLE.get();
- assertNull(cache.obtainHandle("selector-id_default_rule"));
- }
-
- @Test
- void testPluginNamed() {
- assertEquals(PluginEnum.AI_PROXY.getName(), handler.pluginNamed());
- }
-}
diff --git
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-ai-proxy/pom.xml
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-ai-proxy/pom.xml
index 4d8d13a0df..7dfc1fe534 100644
---
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-ai-proxy/pom.xml
+++
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-ai-proxy/pom.xml
@@ -29,7 +29,7 @@
<dependencies>
<dependency>
<groupId>org.apache.shenyu</groupId>
- <artifactId>shenyu-plugin-ai-proxy-enhanced</artifactId>
+ <artifactId>shenyu-plugin-ai-proxy</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>