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 810e986cad [type:feat] add ai proxy plugin (#5938)
810e986cad is described below

commit 810e986cadbed357270bdc76d3f65d67fd074bb6
Author: aias00 <[email protected]>
AuthorDate: Fri Feb 21 14:33:42 2025 +0800

    [type:feat] add ai proxy plugin (#5938)
    
    * [type:feature] ai proxy plugin
    
    * [type:feature] ai proxy plugin
    
    * [type:feature] ai proxy plugin
    
    * [type:feature] ai proxy plugin
    
    * [type:feature] ai proxy plugin
    
    * [type:feature] ai proxy plugin
    
    * [type:feature] ai proxy plugin
---
 db/init/mysql/schema.sql                           |   1 +
 .../src/main/resources/sql-script/h2/schema.sql    |  19 ++
 shenyu-bootstrap/pom.xml                           |   8 +
 .../apache/shenyu/common/constant/Constants.java   |  15 ++
 .../common/dto/convert/plugin/AiProxyConfig.java   | 243 +++++++++++++++++++++
 .../common/dto/convert/rule/AiProxyHandle.java     | 243 +++++++++++++++++++++
 .../dto/convert/rule/GeneralContextHandle.java     |   2 +-
 .../shenyu/common/dto/convert/rule/MockHandle.java |   2 +-
 .../shenyu/common/enums/AiModelProviderEnum.java   |  79 +++++++
 .../org/apache/shenyu/common/enums/PluginEnum.java |   5 +
 shenyu-plugin/pom.xml                              |   1 +
 shenyu-plugin/shenyu-plugin-ai-proxy/pom.xml       |  49 +++++
 .../shenyu/plugin/ai/proxy/AiProxyPlugin.java      |  77 +++++++
 .../ai/proxy/handler/AiProxyPluginHandler.java     |  73 +++++++
 .../shenyu/plugin/ai/proxy/strategy/AiModel.java   |  30 +++
 .../plugin/ai/proxy/strategy/AiModelFactory.java   |  47 ++++
 .../plugin/ai/proxy/strategy/openai/OpenAI.java    | 161 ++++++++++++++
 .../shenyu-spring-boot-starter-plugin/pom.xml      |   1 +
 .../pom.xml                                        |  35 +++
 .../ai/proxy/AiProxyPluginConfiguration.java       |  54 +++++
 .../src/main/resources/META-INF/spring.factories   |  19 ++
 .../src/main/resources/META-INF/spring.provides    |  18 ++
 ...rk.boot.autoconfigure.AutoConfiguration.imports |  18 ++
 23 files changed, 1198 insertions(+), 2 deletions(-)

diff --git a/db/init/mysql/schema.sql b/db/init/mysql/schema.sql
index d744628f11..94d0697be7 100644
--- a/db/init/mysql/schema.sql
+++ b/db/init/mysql/schema.sql
@@ -923,6 +923,7 @@ INSERT INTO `plugin` VALUES ('42', 'tcp', NULL, 'Proxy', 
320, 1, '2023-05-30 18:
 INSERT INTO `plugin` VALUES ('43', 'loggingHuaweiLts', 
'{\"totalSizeInBytes\":\"104857600\",\"maxBlockMs\":\"0\",\"ioThreadCount\":\"1\",\"batchSizeThresholdInBytes\":\"524288\",\"batchCountThreshold\":\"4096\",\"lingerMs\":\"2000\",\"retries\":\"100\",\"baseRetryBackoffMs\":\"100\",\"maxRetryBackoffMs\":\"100\",\"enableLocalTest\":\"true\",\"setGiveUpExtraLongSingleLog\":\"false\"}',
 'Logging', 177, 0, '2023-07-05 14:03:53.686', '2023-07-06 12:42:07.234', NULL);
 INSERT INTO `plugin` VALUES ('44', 'basicAuth', 
'{\"defaultHandleJson\":\"{\\\"authorization\\\":\\\"test:test123\\\"}\"}', 
'Authentication', 150, 0, '2022-07-24 19:00:00', '2022-07-24 19:00:00', null);
 INSERT INTO `plugin` VALUES ('45', 'loggingRabbitMQ', 
'{\"host\":\"127.0.0.1\",\"port\":5672,\"password\":\"admin\",\"username\":\"admin\",\"exchangeName\":\"exchange.logging.plugin\",\"queueName\":\"queue.logging.plugin\",\"routingKey\":\"topic.logging\",\"virtualHost\":\"/\",\"exchangeType\":\"direct\",\"durable\":\"true\",\"exclusive\":\"false\",\"autoDelete\":\"false\"}',
 'Logging', 171, 0, '2023-11-06 15:49:56.454', '2023-11-10 10:40:58.447', NULL);
+INSERT INTO `plugin` VALUES ('50', 'aiProxy', 
'{\"host\":\"127.0.0.1\",\"port\":5672,\"password\":\"admin\",\"username\":\"admin\",\"exchangeName\":\"exchange.logging.plugin\",\"queueName\":\"queue.logging.plugin\",\"routingKey\":\"topic.logging\",\"virtualHost\":\"/\",\"exchangeType\":\"direct\",\"durable\":\"true\",\"exclusive\":\"false\",\"autoDelete\":\"false\"}',
 'Ai', 171, 0, '2023-11-06 15:49:56.454', '2023-11-10 10:40:58.447', NULL);
 
 -- ----------------------------
 -- Table structure for plugin_handle
diff --git a/shenyu-admin/src/main/resources/sql-script/h2/schema.sql 
b/shenyu-admin/src/main/resources/sql-script/h2/schema.sql
index 4f7b671ae8..d6cdaff353 100755
--- a/shenyu-admin/src/main/resources/sql-script/h2/schema.sql
+++ b/shenyu-admin/src/main/resources/sql-script/h2/schema.sql
@@ -522,6 +522,12 @@ INSERT IGNORE INTO `shenyu_dict` (`id`, 
`type`,`dict_code`, `dict_name`, `dict_v
 INSERT IGNORE INTO `shenyu_dict` (`id`, `type`,`dict_code`, `dict_name`, 
`dict_value`, `desc`, `sort`, `enabled`) VALUES ('1679002911061737478', 
'rewriteMetaData', 'REWRITE_META_DATA', 'true', 'true', '', 4, 1);
 INSERT IGNORE INTO `shenyu_dict` (`id`, `type`,`dict_code`, `dict_name`, 
`dict_value`, `desc`, `sort`, `enabled`) VALUES ('1679002911061737479', 
'rewriteMetaData', 'REWRITE_META_DATA', 'false', 'false', '', 4, 1);
 
+INSERT IGNORE INTO `shenyu_dict` (`id`, `type`,`dict_code`, `dict_name`, 
`dict_value`, `desc`, `sort`, `enabled`) VALUES ('1679002911061737480', 
'provider', 'PROVIDER_TYPE_OPENAI', 'OpenAI', 'OpenAI', 'OpenAI', 0, 1);
+INSERT IGNORE INTO `shenyu_dict` (`id`, `type`,`dict_code`, `dict_name`, 
`dict_value`, `desc`, `sort`, `enabled`) VALUES ('1679002911061737481', 
'provider', 'PROVIDER_TYPE_DEEPSEEK', 'DeepSeek', 'DeepSeek', 'OpenAI', 1, 1);
+INSERT IGNORE INTO `shenyu_dict` (`id`, `type`,`dict_code`, `dict_name`, 
`dict_value`, `desc`, `sort`, `enabled`) VALUES ('1679002911061737482', 
'provider', 'PROVIDER_TYPE_MOONSHOT', 'Moonshot', 'Moonshot', 'Moonshot', 2, 1);
+INSERT IGNORE INTO `shenyu_dict` (`id`, `type`,`dict_code`, `dict_name`, 
`dict_value`, `desc`, `sort`, `enabled`) VALUES ('1679002911061737483', 
'provider', 'PROVIDER_TYPE_OPENAPI', 'OpenAPI', 'OpenAPI', 'OpenAPI', 3, 1);
+INSERT IGNORE INTO `shenyu_dict` (`id`, `type`,`dict_code`, `dict_name`, 
`dict_value`, `desc`, `sort`, `enabled`) VALUES ('1679002911061737484', 
'provider', 'PROVIDER_TYPE_ALIYUN', 'ALiYun', 'ALiYun', 'ALiYun', 4, 1);
+
 /*plugin*/
 INSERT IGNORE INTO `plugin` (`id`, `name`, `role`, `sort`, `enabled`) VALUES 
('1','sign','Authentication',  20, '0');
 INSERT IGNORE INTO `plugin` (`id`, `name`, `role`, `sort`,`config`,`enabled`) 
VALUES ('2','waf', 'Authentication', 50,'{"model":"black"}','0');
@@ -565,6 +571,8 @@ INSERT IGNORE INTO `plugin` (`id`, `name`, `role`, `sort`, 
`enabled`) VALUES ('4
 INSERT IGNORE INTO `plugin` (`id`, `name`, `role`, `sort`, `config`, 
`enabled`) VALUES ('43', 'loggingHuaweiLts', 'Logging', 177, '{ 
"totalSizeInBytes":"104857600","maxBlockMs":"0","ioThreadCount":"1","batchSizeThresholdInBytes":"524288","batchCountThreshold":"4096","lingerMs":"2000","retries":"100","baseRetryBackoffMs":"100","maxRetryBackoffMs":"100","enableLocalTest":"true","setGiveUpExtraLongSingleLog":"false"}',
 '0');
 INSERT IGNORE INTO `plugin` (`id`, `name`, `role`, `sort`, `config`, 
`enabled`) VALUES ('44', 'basicAuth', 'Authentication', 500, 
'{"defaultHandleJson":"{\"authorization\":\"test:test123\"}"}', '0');
 INSERT IGNORE INTO `plugin` (`id`, `name`, `role`, `sort`, `config`, 
`enabled`) VALUES ('45', 'loggingRabbitMQ', 'Logging', 171, 
'{"host":"127.0.0.1","port":5672,"password":"admin","username":"admin","exchangeName":"exchange.logging.plugin","queueName":"queue.logging.plugin","routingKey":"topic.logging","virtualHost":"/","exchangeType":"direct","durable":"true","exclusive":"false","autoDelete":"false"}',
 '0');
+
+INSERT IGNORE INTO `plugin` (`id`, `name`, `role`, `sort`, `config`, 
`enabled`) VALUES ('50', 'aiProxy', 'Ai', 171, 
'{"provider":"OpenAI","baseUrl":"https://api.openai.com/v1/chat/completions","model":"gpt-4o-mini","apiKey":"your_api_key","temperature":"0.5","maxTokens":"1000","stream":"false","prompt":""}',
 '0');
 /*insert plugin_handle data for sentinel*/
 INSERT IGNORE INTO plugin_handle (`id`, 
`plugin_id`,`field`,`label`,`data_type`,`type`,`sort`,`ext_obj`) VALUES 
('1529402613195784246', '10', 'flowRuleGrade', 'flowRuleGrade', 3, 2, 8, 
'{"required":"1","defaultValue":"1","rule":""}');
 INSERT IGNORE INTO plugin_handle (`id`, 
`plugin_id`,`field`,`label`,`data_type`,`type`,`sort`,`ext_obj`) VALUES 
('1529402613199978496', '10', 'flowRuleControlBehavior', 
'flowRuleControlBehavior', 3, 2, 5, 
'{"required":"1","defaultValue":"0","rule":""}');
@@ -926,6 +934,16 @@ INSERT IGNORE INTO plugin_handle (`id`, 
`plugin_id`,`field`,`label`,`data_type`,
 
 INSERT IGNORE INTO plugin_handle (`id`, 
`plugin_id`,`field`,`label`,`data_type`,`type`,`sort`,`ext_obj`) VALUES 
('1722804548510507032', '19', 'handleType', 'handleType', 2, 3, 1, 
'{"required":"0","rule":""}');
 
+INSERT IGNORE INTO plugin_handle (`id`, 
`plugin_id`,`field`,`label`,`data_type`,`type`,`sort`,`ext_obj`) VALUES 
('1722804548510507033', '50', 'provider', 'provider', 3, 3, 1, 
'{"required":"1","defaultValue":"OpenAI","placeholder":"provider","rule":""}');
+INSERT IGNORE INTO plugin_handle (`id`, 
`plugin_id`,`field`,`label`,`data_type`,`type`,`sort`,`ext_obj`) VALUES 
('1722804548510507034', '50', 'baseUrl', 'baseUrl', 2, 3, 2, 
'{"required":"1","rule":""}');
+INSERT IGNORE INTO plugin_handle (`id`, 
`plugin_id`,`field`,`label`,`data_type`,`type`,`sort`,`ext_obj`) VALUES 
('1722804548510507036', '50', 'model', 'model', 2, 3, 3, 
'{"required":"1","rule":""}');
+INSERT IGNORE INTO plugin_handle (`id`, 
`plugin_id`,`field`,`label`,`data_type`,`type`,`sort`,`ext_obj`) VALUES 
('1722804548510507035', '50', 'apiKey', 'apiKey', 2, 3, 4, 
'{"required":"1","rule":""}');
+INSERT IGNORE INTO plugin_handle (`id`, 
`plugin_id`,`field`,`label`,`data_type`,`type`,`sort`,`ext_obj`) VALUES 
('1722804548510507037', '50', 'temperature', 'temperature', 2, 3, 5, 
'{"required":"0","rule":"", "placeholder":"optional,0,0.01~1"}');
+INSERT IGNORE INTO plugin_handle (`id`, 
`plugin_id`,`field`,`label`,`data_type`,`type`,`sort`,`ext_obj`) VALUES 
('1722804548510507038', '50', 'maxTokens', 'maxTokens', 2, 3, 6, 
'{"required":"0","rule":""}');
+INSERT IGNORE INTO plugin_handle (`id`, 
`plugin_id`,`field`,`label`,`data_type`,`type`,`sort`,`ext_obj`) VALUES 
('1722804548510507039', '50', 'stream', 'stream', 3, 3, 7, 
'{"defaultValue":"false","rule":""}');
+INSERT IGNORE INTO plugin_handle (`id`, 
`plugin_id`,`field`,`label`,`data_type`,`type`,`sort`,`ext_obj`) VALUES 
('1722804548510507040', '50', 'prompt', 'prompt', 2, 3, 8, 
'{"required":"0","rule":""}');
+
+
 /** insert resource for resource */
 INSERT IGNORE INTO `resource` (`id`, `parent_id`, `title`, `name`, `url`, 
`component`, `resource_type`, `sort`, `icon`, `is_leaf`, `is_route`, `perms`, 
`status`) 
VALUES('1346775491550474240','','SHENYU.MENU.PLUGIN.LIST','plug','/plug','PluginList','0','0','dashboard','0','0','','1');
 
@@ -1348,6 +1366,7 @@ INSERT IGNORE INTO `namespace_plugin_rel` 
(`id`,`namespace_id`,`plugin_id`, `con
 INSERT IGNORE INTO `namespace_plugin_rel` (`id`,`namespace_id`,`plugin_id`, 
`config`, `sort`, `enabled`, `date_created`, `date_updated`) VALUES 
('1801816010882822184','649330b6-c2d7-4edc-be8e-8a54df9eb385','6', 
'{"register":"zookeeper://localhost:2181","multiSelectorHandle":"1","threadpool":"shared","corethreads":0,"threads":2147483647,"queues":0}',
 310, 0, '2022-05-25 18:02:53.000', '2022-05-25 18:02:53.000');
 INSERT IGNORE INTO `namespace_plugin_rel` (`id`,`namespace_id`,`plugin_id`, 
`config`, `sort`, `enabled`, `date_created`, `date_updated`) VALUES 
('1801816010882822185','649330b6-c2d7-4edc-be8e-8a54df9eb385','8', 
'{"enabled":false,"registerType":"eureka","serverLists":"http://localhost:8761/eureka","props":
 {}}', 200, 0, '2022-05-25 18:02:53.000', '2022-05-25 18:02:53.000');
 INSERT IGNORE INTO `namespace_plugin_rel` (`id`,`namespace_id`,`plugin_id`, 
`config`, `sort`, `enabled`, `date_created`, `date_updated`) VALUES 
('1801816010882822186','649330b6-c2d7-4edc-be8e-8a54df9eb385','9', NULL, 130, 
0, '2022-05-25 18:02:53.000', '2022-05-25 18:02:53.000');
+INSERT IGNORE INTO `namespace_plugin_rel` (`id`,`namespace_id`,`plugin_id`, 
`config`, `sort`, `enabled`, `date_created`, `date_updated`) VALUES 
('1801816010882822187','649330b6-c2d7-4edc-be8e-8a54df9eb385','50', 
'{"provider":"OpenAI","baseUrl":"https://api.openai.com/v1/chat/completions","model":"gpt-4o-mini","apiKey":"your_api_key","temperature":"0.5","maxTokens":"1000","stream":"false","prompt":""}',
 171, 0, '2022-05-25 18:02:53.000', '2022-05-25 18:02:53.000');
 
 
 
diff --git a/shenyu-bootstrap/pom.xml b/shenyu-bootstrap/pom.xml
index c02c89ec23..874274d16d 100644
--- a/shenyu-bootstrap/pom.xml
+++ b/shenyu-bootstrap/pom.xml
@@ -239,6 +239,14 @@
         </dependency>
         <!--Tcp Plugin end-->
 
+        <!--Ai proxy Plugin Start-->
+        <dependency>
+            <groupId>org.apache.shenyu</groupId>
+            <artifactId>shenyu-spring-boot-starter-plugin-ai-proxy</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <!--Ai proxy Plugin end-->
+
         <!--shenyu sofa plugin start-->
         <dependency>
             <groupId>com.alipay.sofa</groupId>
diff --git 
a/shenyu-common/src/main/java/org/apache/shenyu/common/constant/Constants.java 
b/shenyu-common/src/main/java/org/apache/shenyu/common/constant/Constants.java
index 3cd0c7d16f..c6713166b8 100644
--- 
a/shenyu-common/src/main/java/org/apache/shenyu/common/constant/Constants.java
+++ 
b/shenyu-common/src/main/java/org/apache/shenyu/common/constant/Constants.java
@@ -930,6 +930,21 @@ public interface Constants {
      * The constant preserve host.
      */
     String PRESERVE_HOST = "preserveHost";
+
+    /**
+     * The constant model.
+     */
+    String MODEL = "model";
+
+    /**
+     * The constant stream.
+     */
+    String STREAM = "stream";
+
+    /**
+     * The constant prompt.
+     */
+    String PROMPT = "prompt";
     
     /**
      * String q.
diff --git 
a/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/plugin/AiProxyConfig.java
 
b/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/plugin/AiProxyConfig.java
new file mode 100644
index 0000000000..7fedc8881c
--- /dev/null
+++ 
b/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/plugin/AiProxyConfig.java
@@ -0,0 +1,243 @@
+/*
+ * 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.common.dto.convert.plugin;
+
+import java.util.Objects;
+
+/**
+ * this is Ai Proxy plugin config.
+ */
+public class AiProxyConfig {
+
+    /**
+     * provider.
+     */
+    private String provider;
+
+    /**
+     * base url.
+     */
+    private String baseUrl;
+
+    /**
+     * api key.
+     */
+    private String apiKey;
+
+    /**
+     * model.
+     */
+    private String model;
+
+    /**
+     * temperature.
+     */
+    private Double temperature = 0.8;
+
+    /**
+     * max tokens.
+     */
+    private Integer maxTokens;
+
+    /**
+     * prompt.
+     */
+    private String prompt;
+
+    /**
+     * stream.
+     */
+    private Boolean stream = false;
+
+    /**
+     * get provider.
+     *
+     * @return provider
+     */
+    public String getProvider() {
+        return provider;
+    }
+
+    /**
+     * set provider.
+     *
+     * @param provider provider
+     */
+    public void setProvider(final String provider) {
+        this.provider = provider;
+    }
+
+    /**
+     * get base url.
+     *
+     * @return base url
+     */
+    public String getBaseUrl() {
+        return baseUrl;
+    }
+
+    /**
+     * set base url.
+     *
+     * @param baseUrl base url
+     */
+    public void setBaseUrl(final String baseUrl) {
+        this.baseUrl = baseUrl;
+    }
+
+    /**
+     * get api key.
+     *
+     * @return api key
+     */
+    public String getApiKey() {
+        return apiKey;
+    }
+
+    /**
+     * set api key.
+     *
+     * @param apiKey api key
+     */
+    public void setApiKey(final String apiKey) {
+        this.apiKey = apiKey;
+    }
+
+    /**
+     * get model.
+     *
+     * @return model
+     */
+    public String getModel() {
+        return model;
+    }
+
+    /**
+     * set model.
+     *
+     * @param model model
+     */
+    public void setModel(final String model) {
+        this.model = model;
+    }
+
+    /**
+     * get temperature.
+     *
+     * @return temperature
+     */
+    public Double getTemperature() {
+        return temperature;
+    }
+
+    /**
+     * set temperature.
+     *
+     * @param temperature temperature
+     */
+    public void setTemperature(final Double temperature) {
+        this.temperature = temperature;
+    }
+
+    /**
+     * get max tokens.
+     *
+     * @return max tokens
+     */
+    public Integer getMaxTokens() {
+        return maxTokens;
+    }
+
+    /**
+     * set max tokens.
+     *
+     * @param maxTokens max tokens
+     */
+    public void setMaxTokens(final Integer maxTokens) {
+        this.maxTokens = maxTokens;
+    }
+
+    /**
+     * get prompt.
+     *
+     * @return prompt
+     */
+    public String getPrompt() {
+        return prompt;
+    }
+
+    /**
+     * set prompt.
+     *
+     * @param prompt prompt
+     */
+    public void setPrompt(final String prompt) {
+        this.prompt = prompt;
+    }
+
+    /**
+     * get stream.
+     *
+     * @return stream
+     */
+    public Boolean getStream() {
+        return stream;
+    }
+
+    /**
+     * set stream.
+     *
+     * @param stream stream
+     */
+    public void setStream(final Boolean stream) {
+        this.stream = stream;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (Objects.isNull(o) || getClass() != o.getClass()) {
+            return false;
+        }
+        AiProxyConfig that = (AiProxyConfig) o;
+        return Objects.equals(provider, that.provider)
+                && Objects.equals(baseUrl, that.baseUrl)
+                && Objects.equals(apiKey, that.apiKey)
+                && Objects.equals(model, that.model)
+                && Objects.equals(temperature, that.temperature)
+                && Objects.equals(maxTokens, that.maxTokens)
+                && Objects.equals(prompt, that.prompt)
+                && Objects.equals(stream, that.stream);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(provider, baseUrl, apiKey, model, temperature, 
maxTokens, prompt, stream);
+    }
+
+    @Override
+    public String toString() {
+        return "AiProxyHandle{" + "provider='" + provider + '\'' + "baseUrl='" 
+ baseUrl + '\'' + ", apiKey='" + apiKey
+                + '\'' + ", model='" + model
+                + '\''
+                + ", temperature=" + temperature + ", maxTokens=" + maxTokens
+                + ", prompt='" + prompt + '\'' + ", stream=" + stream + '}';
+    }
+}
diff --git 
a/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/rule/AiProxyHandle.java
 
b/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/rule/AiProxyHandle.java
new file mode 100644
index 0000000000..d9cf581a9d
--- /dev/null
+++ 
b/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/rule/AiProxyHandle.java
@@ -0,0 +1,243 @@
+/*
+ * 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.common.dto.convert.rule;
+
+import java.util.Objects;
+
+/**
+ * this is Ai Proxy plugin handle.
+ */
+public class AiProxyHandle {
+
+    /**
+     * type.
+     */
+    private String type;
+
+    /**
+     * base url.
+     */
+    private String baseUrl;
+
+    /**
+     * api key.
+     */
+    private String apiKey;
+
+    /**
+     * model.
+     */
+    private String model;
+
+    /**
+     * temperature.
+     */
+    private Double temperature;
+
+    /**
+     * max tokens.
+     */
+    private Integer maxTokens;
+
+    /**
+     * prompt.
+     */
+    private String prompt;
+
+    /**
+     * stream.
+     */
+    private Boolean stream = false;
+
+    /**
+     * get type.
+     *
+     * @return type
+     */
+    public String getType() {
+        return type;
+    }
+
+    /**
+     * set type.
+     *
+     * @param type type
+     */
+    public void setType(final String type) {
+        this.type = type;
+    }
+
+    /**
+     * get base url.
+     *
+     * @return base url
+     */
+    public String getBaseUrl() {
+        return baseUrl;
+    }
+
+    /**
+     * set base url.
+     *
+     * @param baseUrl base url
+     */
+    public void setBaseUrl(final String baseUrl) {
+        this.baseUrl = baseUrl;
+    }
+
+    /**
+     * get api key.
+     *
+     * @return api key
+     */
+    public String getApiKey() {
+        return apiKey;
+    }
+
+    /**
+     * set api key.
+     *
+     * @param apiKey api key
+     */
+    public void setApiKey(final String apiKey) {
+        this.apiKey = apiKey;
+    }
+
+    /**
+     * get model.
+     *
+     * @return model
+     */
+    public String getModel() {
+        return model;
+    }
+
+    /**
+     * set model.
+     *
+     * @param model model
+     */
+    public void setModel(final String model) {
+        this.model = model;
+    }
+
+    /**
+     * get temperature.
+     *
+     * @return temperature
+     */
+    public Double getTemperature() {
+        return temperature;
+    }
+
+    /**
+     * set temperature.
+     *
+     * @param temperature temperature
+     */
+    public void setTemperature(final Double temperature) {
+        this.temperature = temperature;
+    }
+
+    /**
+     * get max tokens.
+     *
+     * @return max tokens
+     */
+    public Integer getMaxTokens() {
+        return maxTokens;
+    }
+
+    /**
+     * set max tokens.
+     *
+     * @param maxTokens max tokens
+     */
+    public void setMaxTokens(final Integer maxTokens) {
+        this.maxTokens = maxTokens;
+    }
+
+    /**
+     * get prompt.
+     *
+     * @return prompt
+     */
+    public String getPrompt() {
+        return prompt;
+    }
+
+    /**
+     * set prompt.
+     *
+     * @param prompt prompt
+     */
+    public void setPrompt(final String prompt) {
+        this.prompt = prompt;
+    }
+
+    /**
+     * get stream.
+     *
+     * @return stream
+     */
+    public Boolean getStream() {
+        return stream;
+    }
+
+    /**
+     * set stream.
+     *
+     * @param stream stream
+     */
+    public void setStream(final Boolean stream) {
+        this.stream = stream;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (Objects.isNull(o) || getClass() != o.getClass()) {
+            return false;
+        }
+        AiProxyHandle that = (AiProxyHandle) o;
+        return Objects.equals(type, that.type)
+                && Objects.equals(baseUrl, that.baseUrl)
+                && Objects.equals(apiKey, that.apiKey)
+                && Objects.equals(model, that.model)
+                && Objects.equals(temperature, that.temperature)
+                && Objects.equals(maxTokens, that.maxTokens)
+                && Objects.equals(prompt, that.prompt)
+                && Objects.equals(stream, that.stream);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(type, baseUrl, apiKey, model, temperature, 
maxTokens, prompt, stream);
+    }
+
+    @Override
+    public String toString() {
+        return "AiProxyHandle{" + "type='" + type + '\'' + "baseUrl='" + 
baseUrl + '\'' + ", apiKey='" + apiKey + '\''
+                + ", model='" + model
+                + '\''
+                + ", temperature=" + temperature + ", maxTokens=" + maxTokens
+                + ", prompt='" + prompt + '\'' + ", stream=" + stream + '}';
+    }
+}
diff --git 
a/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/rule/GeneralContextHandle.java
 
b/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/rule/GeneralContextHandle.java
index a95e33167a..c8b7d00b51 100644
--- 
a/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/rule/GeneralContextHandle.java
+++ 
b/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/rule/GeneralContextHandle.java
@@ -20,7 +20,7 @@ package org.apache.shenyu.common.dto.convert.rule;
 import java.util.Objects;
 
 /**
- * this is RequestHandle plugin handle.
+ * this is GeneralContextHandle plugin handle.
  */
 public class GeneralContextHandle {
     /**
diff --git 
a/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/rule/MockHandle.java
 
b/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/rule/MockHandle.java
index 0391eb7490..79f0445007 100644
--- 
a/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/rule/MockHandle.java
+++ 
b/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/rule/MockHandle.java
@@ -20,7 +20,7 @@ package org.apache.shenyu.common.dto.convert.rule;
 import java.util.Objects;
 
 /**
- * this is RequestHandle plugin handle.
+ * this is MockHandle plugin handle.
  */
 public class MockHandle {
 
diff --git 
a/shenyu-common/src/main/java/org/apache/shenyu/common/enums/AiModelProviderEnum.java
 
b/shenyu-common/src/main/java/org/apache/shenyu/common/enums/AiModelProviderEnum.java
new file mode 100644
index 0000000000..fae76609cd
--- /dev/null
+++ 
b/shenyu-common/src/main/java/org/apache/shenyu/common/enums/AiModelProviderEnum.java
@@ -0,0 +1,79 @@
+/*
+ * 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.common.enums;
+
+/**
+ * The Ai model provider enum.
+ */
+public enum AiModelProviderEnum {
+    
+    /**
+     * OpenAI.
+     */
+    OPEN_AI("OpenAI"),
+    
+    /**
+     * DeepSeek.
+     */
+    DEEP_SEEK("DeepSeek"),
+    
+    /**
+     * ALiYun.
+     */
+    ALIYUN("ALiYun"),
+    
+    /**
+     * OpenAPI.
+     */
+    OPEN_API("OpenAPI"),
+    
+    /**
+     * Moonshot.
+     */
+    MOONSHOT("Moonshot");
+
+    private final String name;
+
+    AiModelProviderEnum(final String name) {
+        this.name = name;
+    }
+
+    /**
+     * get name.
+     *
+     * @return name
+     */
+    public String getName() {
+        return name;
+    }
+    
+    /**
+     * get model by name.
+     *
+     * @param name name
+     * @return AiModelProviderEnum
+     */
+    public static AiModelProviderEnum getByName(final String name) {
+        for (AiModelProviderEnum value : AiModelProviderEnum.values()) {
+            if (value.getName().equals(name)) {
+                return value;
+            }
+        }
+        return null;
+    }
+}
diff --git 
a/shenyu-common/src/main/java/org/apache/shenyu/common/enums/PluginEnum.java 
b/shenyu-common/src/main/java/org/apache/shenyu/common/enums/PluginEnum.java
index 398e3a65ed..c588aaa937 100644
--- a/shenyu-common/src/main/java/org/apache/shenyu/common/enums/PluginEnum.java
+++ b/shenyu-common/src/main/java/org/apache/shenyu/common/enums/PluginEnum.java
@@ -266,6 +266,11 @@ public enum PluginEnum {
      * Key-auth plugin enum.
      */
     KEY_AUTH(430, 0, "keyAuth"),
+
+    /**
+     * Ai-proxy plugin enum.
+     */
+    AI_PROXY(440, 0, "aiProxy"),
     
     /**
      * Basic-auth plugin enum.
diff --git a/shenyu-plugin/pom.xml b/shenyu-plugin/pom.xml
index 2b6d7dcf5f..5f9b5c8ef9 100644
--- a/shenyu-plugin/pom.xml
+++ b/shenyu-plugin/pom.xml
@@ -49,6 +49,7 @@
         <module>shenyu-plugin-proxy</module>
         <module>shenyu-plugin-security</module>
         <module>shenyu-plugin-fault-tolerance</module>
+        <module>shenyu-plugin-ai-proxy</module>
     </modules>
 
     <dependencies>
diff --git a/shenyu-plugin/shenyu-plugin-ai-proxy/pom.xml 
b/shenyu-plugin/shenyu-plugin-ai-proxy/pom.xml
new file mode 100644
index 0000000000..510a4b9b4c
--- /dev/null
+++ b/shenyu-plugin/shenyu-plugin-ai-proxy/pom.xml
@@ -0,0 +1,49 @@
+<?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</artifactId>
+        <version>2.7.1-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>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-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/AiProxyPlugin.java
 
b/shenyu-plugin/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/AiProxyPlugin.java
new file mode 100644
index 0000000000..ec6a4c72fa
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/AiProxyPlugin.java
@@ -0,0 +1,77 @@
+/*
+ * 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.dto.RuleData;
+import org.apache.shenyu.common.dto.SelectorData;
+import org.apache.shenyu.common.dto.convert.plugin.AiProxyConfig;
+import org.apache.shenyu.common.enums.AiModelProviderEnum;
+import org.apache.shenyu.common.enums.PluginEnum;
+import org.apache.shenyu.common.utils.Singleton;
+import org.apache.shenyu.plugin.ai.proxy.strategy.AiModel;
+import org.apache.shenyu.plugin.ai.proxy.strategy.AiModelFactory;
+import org.apache.shenyu.plugin.api.ShenyuPluginChain;
+import org.apache.shenyu.plugin.base.AbstractShenyuPlugin;
+import org.springframework.core.io.buffer.DataBufferUtils;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
+
+/**
+ * this is ai proxy plugin.
+ */
+public class AiProxyPlugin extends AbstractShenyuPlugin {
+
+    @Override
+    protected Mono<Void> doExecute(final ServerWebExchange exchange, final 
ShenyuPluginChain chain,
+            final SelectorData selector, final RuleData rule) {
+        AiProxyConfig aiProxyConfig = Singleton.INST.get(AiProxyConfig.class);
+        if (Objects.isNull(aiProxyConfig)) {
+            return chain.execute(exchange);
+        }
+
+        return DataBufferUtils.join(exchange.getRequest().getBody())
+                .flatMap(dataBuffer -> {
+                    byte[] bytes = new byte[dataBuffer.readableByteCount()];
+                    dataBuffer.read(bytes);
+                    DataBufferUtils.release(dataBuffer);
+                    String requestBody = new String(bytes, 
StandardCharsets.UTF_8);
+                    
+                    // choose the model by provider
+                    String provider = aiProxyConfig.getProvider();
+                    AiModelProviderEnum providerEnum = 
AiModelProviderEnum.getByName(provider);
+                    assert Objects.nonNull(providerEnum);
+                    AiModel aiModel = 
AiModelFactory.createAiModel(providerEnum);
+                    assert Objects.nonNull(aiModel);
+                    return aiModel.invoke(aiProxyConfig, exchange, 
requestBody);
+                });
+    }
+
+
+    @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-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/handler/AiProxyPluginHandler.java
 
b/shenyu-plugin/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/handler/AiProxyPluginHandler.java
new file mode 100644
index 0000000000..e7dff639b5
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/handler/AiProxyPluginHandler.java
@@ -0,0 +1,73 @@
+/*
+ * 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.RuleData;
+import org.apache.shenyu.common.dto.convert.plugin.AiProxyConfig;
+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.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.Optional;
+import java.util.function.Supplier;
+
+/**
+ * this is ai proxy plugin.
+ */
+public class AiProxyPluginHandler implements PluginDataHandler {
+    
+    public static final Supplier<CommonHandleCache<String, AiProxyHandle>> 
CACHED_HANDLE = new BeanHolder<>(CommonHandleCache::new);
+    
+    @Override
+    public void handlerPlugin(final PluginData pluginData) {
+        if (Objects.nonNull(pluginData) && pluginData.getEnabled()) {
+            AiProxyConfig aiProxyConfig = 
GsonUtils.getInstance().fromJson(pluginData.getConfig(), AiProxyConfig.class);
+            if (Objects.isNull(aiProxyConfig)) {
+                return;
+            }
+            Singleton.INST.single(AiProxyConfig.class, aiProxyConfig);
+        }
+    }
+    
+    @Override
+    public void handlerRule(final RuleData ruleData) {
+        Optional.ofNullable(ruleData.getHandle())
+                .ifPresent(s -> {
+                    AiProxyHandle aiProxyHandle = 
GsonUtils.getInstance().fromJson(s, AiProxyHandle.class);
+                    
CACHED_HANDLE.get().cachedHandle(CacheKeyUtils.INST.getKey(ruleData), 
aiProxyHandle);
+                });
+    }
+    
+    @Override
+    public void removeRule(final RuleData ruleData) {
+        Optional.ofNullable(ruleData)
+                .ifPresent(s -> 
CACHED_HANDLE.get().removeHandle(CacheKeyUtils.INST.getKey(ruleData)));
+    }
+    
+    @Override
+    public String pluginNamed() {
+        return PluginEnum.AI_PROXY.getName();
+    }
+}
diff --git 
a/shenyu-plugin/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/strategy/AiModel.java
 
b/shenyu-plugin/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/strategy/AiModel.java
new file mode 100644
index 0000000000..d08332c60b
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/strategy/AiModel.java
@@ -0,0 +1,30 @@
+/*
+ * 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.strategy;
+
+import org.apache.shenyu.common.dto.convert.plugin.AiProxyConfig;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+/**
+ * The interface Ai model.
+ */
+public interface AiModel {
+    
+    Mono<Void> invoke(AiProxyConfig aiProxyConfig, ServerWebExchange exchange, 
String requestBody);
+}
diff --git 
a/shenyu-plugin/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/strategy/AiModelFactory.java
 
b/shenyu-plugin/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/strategy/AiModelFactory.java
new file mode 100644
index 0000000000..324ace7d92
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/strategy/AiModelFactory.java
@@ -0,0 +1,47 @@
+/*
+ * 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.strategy;
+
+import org.apache.shenyu.common.enums.AiModelProviderEnum;
+import org.apache.shenyu.plugin.ai.proxy.strategy.openai.OpenAI;
+
+import java.util.Objects;
+
+/**
+ * The interface Ai model.
+ */
+public final class AiModelFactory {
+    
+    private AiModelFactory() {
+    }
+
+    /**
+     * Create ai model.
+     *
+     * @param provider the provider
+     * @return the ai model provider
+     */
+    public static AiModel createAiModel(final AiModelProviderEnum provider) {
+        if (Objects.isNull(provider)) {
+            return null;
+        }
+        return switch (provider) {
+            case OPEN_AI, DEEP_SEEK, ALIYUN, OPEN_API, MOONSHOT -> new 
OpenAI();
+        };
+    }
+}
diff --git 
a/shenyu-plugin/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/strategy/openai/OpenAI.java
 
b/shenyu-plugin/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/strategy/openai/OpenAI.java
new file mode 100644
index 0000000000..1e1c41ce53
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/strategy/openai/OpenAI.java
@@ -0,0 +1,161 @@
+/*
+ * 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.strategy.openai;
+
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+import okio.BufferedSource;
+import org.apache.shenyu.common.constant.Constants;
+import org.apache.shenyu.common.dto.convert.plugin.AiProxyConfig;
+import org.apache.shenyu.common.utils.GsonUtils;
+import org.apache.shenyu.plugin.ai.proxy.strategy.AiModel;
+import org.jetbrains.annotations.NotNull;
+import org.reactivestreams.Publisher;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
+import reactor.core.publisher.Mono;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * The OpenAI model.
+ */
+public class OpenAI implements AiModel {
+    
+    private final OkHttpClient client = new OkHttpClient.Builder()
+            .connectTimeout(30, TimeUnit.SECONDS)
+            .writeTimeout(5, TimeUnit.MINUTES)
+            .readTimeout(5, TimeUnit.MINUTES)
+            .build();
+    
+    @Override
+    public Mono<Void> invoke(final AiProxyConfig aiProxyConfig, final 
ServerWebExchange exchange,
+                             final String requestBody) {
+        Map<String, Object> paramMap = 
GsonUtils.getInstance().convertToMap(requestBody);
+        paramMap.put(Constants.MODEL, aiProxyConfig.getModel());
+        paramMap.put(Constants.STREAM, aiProxyConfig.getStream());
+        paramMap.put(Constants.PROMPT, aiProxyConfig.getPrompt());
+        
+        RequestBody body = 
RequestBody.create(MediaType.parse("application/json"),
+                GsonUtils.getInstance().toJson(paramMap));
+        Request request = buildRequest(aiProxyConfig, body);
+        
+        return aiProxyConfig.getStream()
+                ? handleStreamResponse(exchange, request)
+                : handleNormalResponse(exchange, request);
+    }
+    
+    private Request buildRequest(final AiProxyConfig config, final RequestBody 
body) {
+        return new Request.Builder()
+                .url(config.getBaseUrl())
+                .post(body)
+                .addHeader("Content-Type", "application/json")
+                .addHeader("Authorization", "Bearer " + config.getApiKey())
+                .build();
+    }
+    
+    private Mono<Void> handleStreamResponse(final ServerWebExchange exchange, 
final Request request) {
+        ServerHttpResponse exchangeResponse = exchange.getResponse();
+        exchangeResponse.getHeaders().set(HttpHeaders.CONTENT_TYPE,
+                org.springframework.http.MediaType.TEXT_EVENT_STREAM_VALUE);
+        exchangeResponse.getHeaders().set(HttpHeaders.CACHE_CONTROL, 
"no-cache");
+        exchangeResponse.getHeaders().set(HttpHeaders.CONNECTION, 
"keep-alive");
+        
+        return exchangeResponse.writeAndFlushWith(
+                Flux.<Publisher<DataBuffer>>create(
+                        sink -> client.newCall(request).enqueue(new 
StreamResponseCallback(sink, exchangeResponse)),
+                        FluxSink.OverflowStrategy.BUFFER));
+    }
+    
+    private Mono<Void> handleNormalResponse(final ServerWebExchange exchange, 
final Request request) {
+        return Mono.create(sink -> {
+            try (Response response = client.newCall(request).execute()) {
+                if (!response.isSuccessful()) {
+                    sink.error(new IOException("Request failed: " + response));
+                    return;
+                }
+                
+                String responseBody = response.body().string();
+                
exchange.getResponse().getHeaders().setContentType(org.springframework.http.MediaType.APPLICATION_JSON);
+                exchange.getResponse().writeWith(
+                                
Mono.just(exchange.getResponse().bufferFactory()
+                                        
.wrap(responseBody.getBytes(StandardCharsets.UTF_8))))
+                        .subscribe(v -> sink.success(), sink::error);
+            } catch (IOException e) {
+                sink.error(e);
+            }
+        });
+    }
+    
+    private record StreamResponseCallback(FluxSink<Publisher<DataBuffer>> 
sink, ServerHttpResponse response) implements Callback {
+        
+        @Override
+        public void onFailure(@NotNull final Call call, @NotNull final 
IOException e) {
+            sink.error(e);
+        }
+        
+        @Override
+        public void onResponse(@NotNull final Call call, @NotNull final 
Response response) {
+            try (ResponseBody responseBody = response.body()) {
+                if (!response.isSuccessful() || Objects.isNull(responseBody)) {
+                    sink.error(new IOException("Request failed: " + response));
+                    return;
+                }
+                
+                processStreamResponse(responseBody);
+                sink.complete();
+            } catch (IOException e) {
+                sink.error(e);
+            }
+        }
+        
+        private void processStreamResponse(final ResponseBody responseBody) 
throws IOException {
+            BufferedSource source = responseBody.source();
+            while (!source.exhausted()) {
+                String line = source.readUtf8Line();
+                if (Objects.isNull(line)) {
+                    break;
+                }
+                
+                if (line.startsWith("data: ")) {
+                    String data = line.substring(6).trim();
+                    if (!"[DONE]".equals(data)) {
+                        DataBuffer buffer = response.bufferFactory()
+                                .wrap((data + 
"\n").getBytes(StandardCharsets.UTF_8));
+                        sink.next(Flux.just(buffer));
+                    }
+                }
+            }
+        }
+    }
+    
+}
diff --git 
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/pom.xml 
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/pom.xml
index b7efde2f96..48737633dd 100644
--- a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/pom.xml
+++ b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/pom.xml
@@ -74,5 +74,6 @@
         <module>shenyu-spring-boot-starter-plugin-transform</module>
         <module>shenyu-spring-boot-starter-plugin-logging-huawei-lts</module>
         <module>shenyu-spring-boot-starter-plugin-basic-auth</module>
+        <module>shenyu-spring-boot-starter-plugin-ai-proxy</module>
     </modules>
 </project>
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
new file mode 100644
index 0000000000..218e3c8a43
--- /dev/null
+++ 
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-ai-proxy/pom.xml
@@ -0,0 +1,35 @@
+<?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-spring-boot-starter-plugin</artifactId>
+        <version>2.7.1-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>shenyu-spring-boot-starter-plugin-ai-proxy</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.shenyu</groupId>
+            <artifactId>shenyu-plugin-ai-proxy</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+</project>
diff --git 
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-ai-proxy/src/main/java/org/apache/shenyu/springboot/starter/plugin/ai/proxy/AiProxyPluginConfiguration.java
 
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-ai-proxy/src/main/java/org/apache/shenyu/springboot/starter/plugin/ai/proxy/AiProxyPluginConfiguration.java
new file mode 100644
index 0000000000..70e4c0b493
--- /dev/null
+++ 
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-ai-proxy/src/main/java/org/apache/shenyu/springboot/starter/plugin/ai/proxy/AiProxyPluginConfiguration.java
@@ -0,0 +1,54 @@
+/*
+ * 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.springboot.starter.plugin.ai.proxy;
+
+import org.apache.shenyu.plugin.ai.proxy.AiProxyPlugin;
+import org.apache.shenyu.plugin.ai.proxy.handler.AiProxyPluginHandler;
+import org.apache.shenyu.plugin.api.ShenyuPlugin;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * The type ai proxy plugin configuration.
+ */
+@Configuration
+@ConditionalOnProperty(value = {"shenyu.plugins.ai.proxy.enabled"}, 
havingValue = "true", matchIfMissing = true)
+public class AiProxyPluginConfiguration {
+    
+    /**
+     * Ai proxy plugin.
+     *
+     * @return the shenyu plugin
+     */
+    @Bean
+    public ShenyuPlugin aiProxyPlugin() {
+        return new AiProxyPlugin();
+    }
+    
+    /**
+     * Ai proxy plugin handler.
+     *
+     * @return the shenyu plugin handler
+     */
+    @Bean
+    public AiProxyPluginHandler aiProxyPluginHandler() {
+        return new AiProxyPluginHandler();
+    }
+    
+}
diff --git 
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-ai-proxy/src/main/resources/META-INF/spring.factories
 
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-ai-proxy/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000000..6bd1519a01
--- /dev/null
+++ 
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-ai-proxy/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+org.apache.shenyu.springboot.starter.plugin.ai.proxy.AiProxyPluginConfiguration
diff --git 
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-ai-proxy/src/main/resources/META-INF/spring.provides
 
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-ai-proxy/src/main/resources/META-INF/spring.provides
new file mode 100644
index 0000000000..dcbab4a10e
--- /dev/null
+++ 
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-ai-proxy/src/main/resources/META-INF/spring.provides
@@ -0,0 +1,18 @@
+#
+# 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.
+#
+
+provides: shenyu-spring-boot-starter-plugin-ai-proxy
diff --git 
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-ai-proxy/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
 
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-ai-proxy/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000000..43cf97f204
--- /dev/null
+++ 
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-ai-proxy/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1,18 @@
+#
+# 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.
+#
+
+org.apache.shenyu.springboot.starter.plugin.ai.proxy.AiProxyPluginConfiguration

Reply via email to