This is an automated email from the ASF dual-hosted git repository.

hefengen 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 ae1702bcfa [refactor] refactor ai plugins (#5986)
ae1702bcfa is described below

commit ae1702bcfaf2d4b595becff9e3b683d785d9eece
Author: aias00 <[email protected]>
AuthorDate: Mon Apr 14 23:28:38 2025 +0800

    [refactor] refactor ai plugins (#5986)
    
    * [refactor] refactor ai plugins
    
    * [refactor] refactor ai plugins
    
    * [refactor] refactor ai plugins
    
    * [refactor] refactor ai plugins
    
    * [refactor] refactor ai plugins
    
    * [refactor] refactor ai plugins
    
    * [refactor] refactor ai plugins
---
 db/init/mysql/schema.sql                           |   2 +-
 db/init/ob/schema.sql                              |   2 +-
 db/init/og/create-table.sql                        |   2 +-
 db/init/oracle/schema.sql                          |   2 +-
 db/init/pg/create-table.sql                        |   2 +-
 db/upgrade/2.7.0-upgrade-2.7.1-mysql.sql           |   2 +-
 db/upgrade/2.7.0-upgrade-2.7.1-ob.sql              |   2 +-
 db/upgrade/2.7.0-upgrade-2.7.1-og.sql              |   2 +-
 db/upgrade/2.7.0-upgrade-2.7.1-oracle.sql          |   2 +-
 db/upgrade/2.7.0-upgrade-2.7.1-pg.sql              |   2 +-
 .../apache/shenyu/common/constant/Constants.java   |  26 +++--
 .../dto/convert/rule/AiTokenLimiterHandle.java     |   2 +-
 .../shenyu/common/enums/AiTokenLimiterEnum.java    |   6 +-
 shenyu-plugin/pom.xml                              |   4 +-
 .../resources/META-INF/scripts/check-limit.lua     |  28 -----
 .../resources/META-INF/scripts/increment-token.lua |  35 ------
 .../pom.xml                                        |  32 ++++--
 .../shenyu-plugin-ai-common}/pom.xml               |   4 +-
 .../plugin/ai/common/config/AiCommonConfig.java    |   8 +-
 .../shenyu/plugin/ai/common}/strategy/AiModel.java |  21 +++-
 .../plugin/ai/common}/strategy/AiModelFactory.java |   8 +-
 .../plugin/ai/common}/strategy/openai/OpenAI.java  |  49 +++++++--
 .../shenyu-plugin-ai-prompt/pom.xml                |   2 +-
 .../shenyu/plugin/ai/prompt/AiPromptPlugin.java    |   0
 .../prompt/handler/AiPromptPluginDataHandler.java  |   0
 .../shenyu-plugin-ai-proxy/pom.xml                 |   7 +-
 .../shenyu/plugin/ai/proxy/AiProxyPlugin.java      |  38 ++++---
 .../ai/proxy/handler/AiProxyPluginHandler.java     |   8 +-
 .../shenyu-plugin-ai-token-limiter/pom.xml         |   9 +-
 .../ai/token/limiter/AiTokenLimiterPlugin.java     | 121 ++++++---------------
 .../handler/AiTokenLimiterPluginHandler.java       |   0
 .../token/limiter/redis/RedisConfigProperties.java |   0
 .../limiter/redis/RedisConnectionFactory.java      |   0
 .../limiter/redis/ShenyuReactiveRedisTemplate.java |   0
 .../redis/ShenyuReactiveScriptExecutor.java        |   0
 .../redis/serializer/ByteArrayRedisSerializer.java |   0
 .../ShenyuRedisSerializationContext.java           |   0
 .../shenyu/plugin/api/result/ShenyuResultEnum.java |   5 +
 38 files changed, 191 insertions(+), 242 deletions(-)

diff --git a/db/init/mysql/schema.sql b/db/init/mysql/schema.sql
index 56cff6b938..535fe31fd6 100644
--- a/db/init/mysql/schema.sql
+++ b/db/init/mysql/schema.sql
@@ -2189,7 +2189,7 @@ INSERT INTO `shenyu_dict` VALUES ('1679002911061737581', 
'preRole', 'ROLE_TYPE_U
 INSERT INTO `shenyu_dict` VALUES ('1679002911061737582', 'postRole', 
'ROLE_TYPE_SYSTEM', 'SYSTEM', 'system', 'system', 0, 1, '2024-02-07 14:31:49', 
'2024-02-07 14:31:49');
 INSERT INTO `shenyu_dict` VALUES ('1679002911061737583', 'postRole', 
'ROLE_TYPE_USER', 'USER', 'user', 'user', 1, 1, '2024-02-07 14:31:49', 
'2024-02-07 14:31:49');
 
-INSERT INTO `shenyu_dict` VALUES ('1679002911061737490', 'aiTokenLimitType', 
'DEFAULT_KEY_RESOLVER', 'default', 'default', 'Rate limit by default', 0, 1, 
'2024-02-07 14:31:49', '2024-02-07 14:31:49');
+INSERT INTO `shenyu_dict` VALUES ('1679002911061737490', 'aiTokenLimitType', 
'CONTEXT_PATH_KEY_RESOLVER', 'contextPath', 'contextPath', 'Rate limit by 
contextPath', 0, 1, '2024-02-07 14:31:49', '2024-02-07 14:31:49');
 INSERT INTO `shenyu_dict` VALUES ('1679002911061737491', 'aiTokenLimitType', 
'IP_KEY_RESOLVER', 'ip', 'ip', 'Rate limit by request ip', 1, 1, '2024-02-07 
14:31:49', '2024-02-07 14:31:49');
 INSERT INTO `shenyu_dict` VALUES ('1679002911061737492', 'aiTokenLimitType', 
'URI_KEY_RESOLVER', 'uri', 'uri', 'Rate limit by request uri', 2, 1, 
'2024-02-07 14:31:49', '2024-02-07 14:31:49');
 INSERT INTO `shenyu_dict` VALUES ('1679002911061737493', 'aiTokenLimitType', 
'HEADER_KEY_RESOLVER', 'header', 'header', 'Rate limit by request header', 3, 
1, '2024-02-07 14:31:49', '2024-02-07 14:31:49');
diff --git a/db/init/ob/schema.sql b/db/init/ob/schema.sql
index 98bd70b693..c056107121 100644
--- a/db/init/ob/schema.sql
+++ b/db/init/ob/schema.sql
@@ -2504,7 +2504,7 @@ INSERT INTO `shenyu_dict` VALUES ('1679002911061737583', 
'postRole', 'ROLE_TYPE_
 INSERT INTO `namespace_plugin_rel` (`id`,`namespace_id`,`plugin_id`, `config`, 
`sort`, `enabled`, `date_created`, `date_updated`) VALUES 
('1801816010882822189','649330b6-c2d7-4edc-be8e-8a54df9eb385','52', NULL, 171, 
0, '2022-05-25 18:02:53.000', '2022-05-25 18:02:53.000');
 
 
-INSERT INTO `shenyu_dict` VALUES ('1679002911061737490', 'aiTokenLimitType', 
'DEFAULT_KEY_RESOLVER', 'default', 'default', 'Rate limit by default', 0, 1, 
'2024-02-07 14:31:49', '2024-02-07 14:31:49');
+INSERT INTO `shenyu_dict` VALUES ('1679002911061737490', 'aiTokenLimitType', 
'CONTEXT_PATH_KEY_RESOLVER', 'contextPath', 'contextPath', 'Rate limit by 
contextPath', 0, 1, '2024-02-07 14:31:49', '2024-02-07 14:31:49');
 INSERT INTO `shenyu_dict` VALUES ('1679002911061737491', 'aiTokenLimitType', 
'IP_KEY_RESOLVER', 'ip', 'ip', 'Rate limit by request ip', 1, 1, '2024-02-07 
14:31:49', '2024-02-07 14:31:49');
 INSERT INTO `shenyu_dict` VALUES ('1679002911061737492', 'aiTokenLimitType', 
'URI_KEY_RESOLVER', 'uri', 'uri', 'Rate limit by request uri', 2, 1, 
'2024-02-07 14:31:49', '2024-02-07 14:31:49');
 INSERT INTO `shenyu_dict` VALUES ('1679002911061737493', 'aiTokenLimitType', 
'HEADER_KEY_RESOLVER', 'header', 'header', 'Rate limit by request header', 3, 
1, '2024-02-07 14:31:49', '2024-02-07 14:31:49');
diff --git a/db/init/og/create-table.sql b/db/init/og/create-table.sql
index 07f68a55c6..9a9e033d3b 100644
--- a/db/init/og/create-table.sql
+++ b/db/init/og/create-table.sql
@@ -2887,7 +2887,7 @@ INSERT INTO "public"."permission" VALUES 
('1697146860569542758', '13463585604272
 INSERT INTO "public"."permission" VALUES ('1697146860569542759', 
'1346358560427216896', '1844026099075534867', '2023-08-31 07:18:37', 
'2023-08-31 07:18:37');
 INSERT INTO "public"."permission" VALUES ('1697146860569542760', 
'1346358560427216896', '1844026099075534868', '2023-08-31 07:18:37', 
'2023-08-31 07:18:37');
 
-INSERT INTO "public"."shenyu_dict" VALUES ('1679002911061737490', 
'aiTokenLimitType', 'DEFAULT_KEY_RESOLVER', 'default', 'default', 'Rate limit 
by default', 0, 1, '2024-02-07 14:31:49', '2024-02-07 14:31:49');
+INSERT INTO "public"."shenyu_dict" VALUES ('1679002911061737490', 
'aiTokenLimitType', 'CONTEXT_PATH_KEY_RESOLVER', 'contextPath', 'contextPath', 
'Rate limit by contextPath', 0, 1, '2024-02-07 14:31:49', '2024-02-07 
14:31:49');
 INSERT INTO "public"."shenyu_dict" VALUES ('1679002911061737491', 
'aiTokenLimitType', 'IP_KEY_RESOLVER', 'ip', 'ip', 'Rate limit by request ip', 
1, 1, '2024-02-07 14:31:49', '2024-02-07 14:31:49');
 INSERT INTO "public"."shenyu_dict" VALUES ('1679002911061737492', 
'aiTokenLimitType', 'URI_KEY_RESOLVER', 'uri', 'uri', 'Rate limit by request 
uri', 2, 1, '2024-02-07 14:31:49', '2024-02-07 14:31:49');
 INSERT INTO "public"."shenyu_dict" VALUES ('1679002911061737493', 
'aiTokenLimitType', 'HEADER_KEY_RESOLVER', 'header', 'header', 'Rate limit by 
request header', 3, 1, '2024-02-07 14:31:49', '2024-02-07 14:31:49');
diff --git a/db/init/oracle/schema.sql b/db/init/oracle/schema.sql
index 6cb3a29c09..bc0efd09e8 100644
--- a/db/init/oracle/schema.sql
+++ b/db/init/oracle/schema.sql
@@ -3351,7 +3351,7 @@ INSERT /*+ IGNORE_ROW_ON_DUPKEY_INDEX(plugin_handle(id)) 
*/ INTO plugin_handle (
 VALUES ('1722804548510507057', '51', 'maxWait', 'maxWait', 3, 3, 10, 
'{"required":"0","defaultValue":"-1","rule":""}', sysdate, sysdate);
 
 INSERT /*+ IGNORE_ROW_ON_DUPKEY_INDEX(shenyu_dict(id)) */ INTO shenyu_dict 
(id, type, dict_code, dict_name, dict_value, "desc", sort, enabled, 
date_created, date_updated)
-VALUES ('1679002911061737490', 'aiTokenLimitType', 'DEFAULT_KEY_RESOLVER', 
'default', 'default', 'Rate limit by default', 0, 1, sysdate, sysdate);
+VALUES ('1679002911061737490', 'aiTokenLimitType', 
'CONTEXT_PATH_KEY_RESOLVER', 'contextPath', 'contextPath', 'Rate limit by 
contextPath', 0, 1, sysdate, sysdate);
 
 INSERT /*+ IGNORE_ROW_ON_DUPKEY_INDEX(shenyu_dict(id)) */ INTO shenyu_dict 
(id, type, dict_code, dict_name, dict_value, "desc", sort, enabled, 
date_created, date_updated)
 VALUES ('1679002911061737491', 'aiTokenLimitType', 'IP_KEY_RESOLVER', 'ip', 
'ip', 'Rate limit by request ip', 1, 1, sysdate, sysdate);
diff --git a/db/init/pg/create-table.sql b/db/init/pg/create-table.sql
index 55b0a40d81..0eeae43530 100644
--- a/db/init/pg/create-table.sql
+++ b/db/init/pg/create-table.sql
@@ -3019,7 +3019,7 @@ INSERT INTO "public"."permission" VALUES 
('1697146860569542758', '13463585604272
 INSERT INTO "public"."permission" VALUES ('1697146860569542759', 
'1346358560427216896', '1844026099075534867', '2023-08-31 07:18:37', 
'2023-08-31 07:18:37');
 INSERT INTO "public"."permission" VALUES ('1697146860569542760', 
'1346358560427216896', '1844026099075534868', '2023-08-31 07:18:37', 
'2023-08-31 07:18:37');
 
-INSERT INTO "public"."shenyu_dict" VALUES ('1679002911061737490', 
'aiTokenLimitType', 'DEFAULT_KEY_RESOLVER', 'default', 'default', 'Rate limit 
by default', 0, 1, '2024-02-07 14:31:49', '2024-02-07 14:31:49');
+INSERT INTO "public"."shenyu_dict" VALUES ('1679002911061737490', 
'aiTokenLimitType', 'CONTEXT_PATH_KEY_RESOLVER', 'contextPath', 'contextPath', 
'Rate limit by contextPath', 0, 1, '2024-02-07 14:31:49', '2024-02-07 
14:31:49');
 INSERT INTO "public"."shenyu_dict" VALUES ('1679002911061737491', 
'aiTokenLimitType', 'IP_KEY_RESOLVER', 'ip', 'ip', 'Rate limit by request ip', 
1, 1, '2024-02-07 14:31:49', '2024-02-07 14:31:49');
 INSERT INTO "public"."shenyu_dict" VALUES ('1679002911061737492', 
'aiTokenLimitType', 'URI_KEY_RESOLVER', 'uri', 'uri', 'Rate limit by request 
uri', 2, 1, '2024-02-07 14:31:49', '2024-02-07 14:31:49');
 INSERT INTO "public"."shenyu_dict" VALUES ('1679002911061737493', 
'aiTokenLimitType', 'HEADER_KEY_RESOLVER', 'header', 'header', 'Rate limit by 
request header', 3, 1, '2024-02-07 14:31:49', '2024-02-07 14:31:49');
diff --git a/db/upgrade/2.7.0-upgrade-2.7.1-mysql.sql 
b/db/upgrade/2.7.0-upgrade-2.7.1-mysql.sql
index 7336447859..c3293ccdb8 100755
--- a/db/upgrade/2.7.0-upgrade-2.7.1-mysql.sql
+++ b/db/upgrade/2.7.0-upgrade-2.7.1-mysql.sql
@@ -143,7 +143,7 @@ INSERT INTO `plugin_handle` VALUES ('1722804548510507055', 
'51', 'minIdle', 'min
 INSERT INTO `plugin_handle` VALUES ('1722804548510507056', '51', 'maxActive', 
'maxActive', 1, 3, 9, 
'{\"required\":\"0\",\"defaultValue\":\"8\",\"rule\":\"\"}', '2022-05-25 
18:02:53', '2022-05-25 18:02:53');
 INSERT INTO `plugin_handle` VALUES ('1722804548510507057', '51', 'maxWait', 
'maxWait', 3, 3, 10, 
'{\"required\":\"0\",\"defaultValue\":\"-1\",\"rule\":\"\"}', '2022-05-25 
18:02:53', '2022-05-25 18:02:53');
 
-INSERT INTO `shenyu_dict` VALUES ('1679002911061737490', 'aiTokenLimitKey', 
'DEFAULT_KEY_RESOLVER', 'default', 'default', 'Rate limit by default', 0, 1, 
'2024-02-07 14:31:49', '2024-02-07 14:31:49');
+INSERT INTO `shenyu_dict` VALUES ('1679002911061737490', 'aiTokenLimitKey', 
'CONTEXT_PATH_KEY_RESOLVER', 'contextPath', 'contextPath', 'Rate limit by 
contextPath', 0, 1, '2024-02-07 14:31:49', '2024-02-07 14:31:49');
 INSERT INTO `shenyu_dict` VALUES ('1679002911061737491', 'aiTokenLimitKey', 
'IP_KEY_RESOLVER', 'ip', 'ip', 'Rate limit by request ip', 1, 1, '2024-02-07 
14:31:49', '2024-02-07 14:31:49');
 INSERT INTO `shenyu_dict` VALUES ('1679002911061737492', 'aiTokenLimitKey', 
'URI_KEY_RESOLVER', 'uri', 'uri', 'Rate limit by request uri', 2, 1, 
'2024-02-07 14:31:49', '2024-02-07 14:31:49');
 INSERT INTO `shenyu_dict` VALUES ('1679002911061737493', 'aiTokenLimitKey', 
'HEADER_KEY_RESOLVER', 'header', 'header', 'Rate limit by request header', 3, 
1, '2024-02-07 14:31:49', '2024-02-07 14:31:49');
diff --git a/db/upgrade/2.7.0-upgrade-2.7.1-ob.sql 
b/db/upgrade/2.7.0-upgrade-2.7.1-ob.sql
index 9cd7280b91..009687bfe3 100755
--- a/db/upgrade/2.7.0-upgrade-2.7.1-ob.sql
+++ b/db/upgrade/2.7.0-upgrade-2.7.1-ob.sql
@@ -141,7 +141,7 @@ INSERT INTO `plugin_handle` VALUES ('1722804548510507055', 
'51', 'minIdle', 'min
 INSERT INTO `plugin_handle` VALUES ('1722804548510507056', '51', 'maxActive', 
'maxActive', 1, 3, 9, 
'{\"required\":\"0\",\"defaultValue\":\"8\",\"rule\":\"\"}', '2022-05-25 
18:02:53', '2022-05-25 18:02:53');
 INSERT INTO `plugin_handle` VALUES ('1722804548510507057', '51', 'maxWait', 
'maxWait', 3, 3, 10, 
'{\"required\":\"0\",\"defaultValue\":\"-1\",\"rule\":\"\"}', '2022-05-25 
18:02:53', '2022-05-25 18:02:53');
 
-INSERT INTO `shenyu_dict` VALUES ('1679002911061737490', 'aiTokenLimitKey', 
'DEFAULT_KEY_RESOLVER', 'default', 'default', 'Rate limit by default', 0, 1, 
'2024-02-07 14:31:49', '2024-02-07 14:31:49');
+INSERT INTO `shenyu_dict` VALUES ('1679002911061737490', 'aiTokenLimitKey', 
'CONTEXT_PATH_KEY_RESOLVER', 'contextPath', 'contextPath', 'Rate limit by 
contextPath', 0, 1, '2024-02-07 14:31:49', '2024-02-07 14:31:49');
 INSERT INTO `shenyu_dict` VALUES ('1679002911061737491', 'aiTokenLimitKey', 
'IP_KEY_RESOLVER', 'ip', 'ip', 'Rate limit by request ip', 1, 1, '2024-02-07 
14:31:49', '2024-02-07 14:31:49');
 INSERT INTO `shenyu_dict` VALUES ('1679002911061737492', 'aiTokenLimitKey', 
'URI_KEY_RESOLVER', 'uri', 'uri', 'Rate limit by request uri', 2, 1, 
'2024-02-07 14:31:49', '2024-02-07 14:31:49');
 INSERT INTO `shenyu_dict` VALUES ('1679002911061737493', 'aiTokenLimitKey', 
'HEADER_KEY_RESOLVER', 'header', 'header', 'Rate limit by request header', 3, 
1, '2024-02-07 14:31:49', '2024-02-07 14:31:49');
diff --git a/db/upgrade/2.7.0-upgrade-2.7.1-og.sql 
b/db/upgrade/2.7.0-upgrade-2.7.1-og.sql
index 085c732a86..6894823a00 100644
--- a/db/upgrade/2.7.0-upgrade-2.7.1-og.sql
+++ b/db/upgrade/2.7.0-upgrade-2.7.1-og.sql
@@ -142,7 +142,7 @@ INSERT INTO "public"."plugin_handle" VALUES 
('1722804548510507056', '51', 'maxAc
 INSERT INTO "public"."plugin_handle" VALUES ('1722804548510507057', '51', 
'maxWait', 'maxWait', 3, 3, 10, 
'{"required":"0","defaultValue":"-1","rule":""}', '2022-05-25 18:02:53', 
'2022-05-25 18:02:53');
 
 
-INSERT INTO "public"."shenyu_dict" VALUES ('1679002911061737490', 
'aiTokenLimitType', 'DEFAULT_KEY_RESOLVER', 'default', 'default', 'Rate limit 
by default', 0, 1, '2024-02-07 14:31:49', '2024-02-07 14:31:49');
+INSERT INTO "public"."shenyu_dict" VALUES ('1679002911061737490', 
'aiTokenLimitType', 'CONTEXT_PATH_KEY_RESOLVER', 'contextPath', 'contextPath', 
'Rate limit by contextPath', 0, 1, '2024-02-07 14:31:49', '2024-02-07 
14:31:49');
 INSERT INTO "public"."shenyu_dict" VALUES ('1679002911061737491', 
'aiTokenLimitType', 'IP_KEY_RESOLVER', 'ip', 'ip', 'Rate limit by request ip', 
1, 1, '2024-02-07 14:31:49', '2024-02-07 14:31:49');
 INSERT INTO "public"."shenyu_dict" VALUES ('1679002911061737492', 
'aiTokenLimitType', 'URI_KEY_RESOLVER', 'uri', 'uri', 'Rate limit by request 
uri', 2, 1, '2024-02-07 14:31:49', '2024-02-07 14:31:49');
 INSERT INTO "public"."shenyu_dict" VALUES ('1679002911061737493', 
'aiTokenLimitType', 'HEADER_KEY_RESOLVER', 'header', 'header', 'Rate limit by 
request header', 3, 1, '2024-02-07 14:31:49', '2024-02-07 14:31:49');
diff --git a/db/upgrade/2.7.0-upgrade-2.7.1-oracle.sql 
b/db/upgrade/2.7.0-upgrade-2.7.1-oracle.sql
index 6973e732a7..06a95b8415 100755
--- a/db/upgrade/2.7.0-upgrade-2.7.1-oracle.sql
+++ b/db/upgrade/2.7.0-upgrade-2.7.1-oracle.sql
@@ -329,7 +329,7 @@ INSERT /*+ IGNORE_ROW_ON_DUPKEY_INDEX(plugin_handle(id)) */ 
INTO plugin_handle (
 VALUES ('1722804548510507057', '51', 'maxWait', 'maxWait', 3, 3, 10, 
'{"required":"0","defaultValue":"-1","rule":""}', sysdate, sysdate);
 
 INSERT /*+ IGNORE_ROW_ON_DUPKEY_INDEX(shenyu_dict(id)) */ INTO shenyu_dict 
(id, type, dict_code, dict_name, dict_value, "desc", sort, enabled, 
date_created, date_updated)
-VALUES ('1679002911061737490', 'aiTokenLimitType', 'DEFAULT_KEY_RESOLVER', 
'default', 'default', 'Rate limit by default', 0, 1, sysdate, sysdate);
+VALUES ('1679002911061737490', 'aiTokenLimitType', 
'CONTEXT_PATH_KEY_RESOLVER', 'contextPath', 'contextPath', 'Rate limit by 
contextPath', 0, 1, sysdate, sysdate);
 
 INSERT /*+ IGNORE_ROW_ON_DUPKEY_INDEX(shenyu_dict(id)) */ INTO shenyu_dict 
(id, type, dict_code, dict_name, dict_value, "desc", sort, enabled, 
date_created, date_updated)
 VALUES ('1679002911061737491', 'aiTokenLimitType', 'IP_KEY_RESOLVER', 'ip', 
'ip', 'Rate limit by request ip', 1, 1, sysdate, sysdate);
diff --git a/db/upgrade/2.7.0-upgrade-2.7.1-pg.sql 
b/db/upgrade/2.7.0-upgrade-2.7.1-pg.sql
index db0f090635..eb87cfe36b 100755
--- a/db/upgrade/2.7.0-upgrade-2.7.1-pg.sql
+++ b/db/upgrade/2.7.0-upgrade-2.7.1-pg.sql
@@ -140,7 +140,7 @@ INSERT INTO "public"."plugin_handle" VALUES 
('1722804548510507055', '51', 'minId
 INSERT INTO "public"."plugin_handle" VALUES ('1722804548510507056', '51', 
'maxActive', 'maxActive', 1, 3, 9, 
'{"required":"0","defaultValue":"8","rule":""}', '2022-05-25 18:02:53', 
'2022-05-25 18:02:53');
 INSERT INTO "public"."plugin_handle" VALUES ('1722804548510507057', '51', 
'maxWait', 'maxWait', 3, 3, 10, 
'{"required":"0","defaultValue":"-1","rule":""}', '2022-05-25 18:02:53', 
'2022-05-25 18:02:53');
 
-INSERT INTO "public"."shenyu_dict" VALUES ('1679002911061737490', 
'aiTokenLimitType', 'DEFAULT_KEY_RESOLVER', 'default', 'default', 'Rate limit 
by default', 0, 1, '2024-02-07 14:31:49', '2024-02-07 14:31:49');
+INSERT INTO "public"."shenyu_dict" VALUES ('1679002911061737490', 
'aiTokenLimitType', 'CONTEXT_PATH_KEY_RESOLVER', 'contextPath', 'contextPath', 
'Rate limit by contextPath', 0, 1, '2024-02-07 14:31:49', '2024-02-07 
14:31:49');
 INSERT INTO "public"."shenyu_dict" VALUES ('1679002911061737491', 
'aiTokenLimitType', 'IP_KEY_RESOLVER', 'ip', 'ip', 'Rate limit by request ip', 
1, 1, '2024-02-07 14:31:49', '2024-02-07 14:31:49');
 INSERT INTO "public"."shenyu_dict" VALUES ('1679002911061737492', 
'aiTokenLimitType', 'URI_KEY_RESOLVER', 'uri', 'uri', 'Rate limit by request 
uri', 2, 1, '2024-02-07 14:31:49', '2024-02-07 14:31:49');
 INSERT INTO "public"."shenyu_dict" VALUES ('1679002911061737493', 
'aiTokenLimitType', 'HEADER_KEY_RESOLVER', 'header', 'header', 'Rate limit by 
request header', 3, 1, '2024-02-07 14:31:49', '2024-02-07 14:31:49');
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 62bfd1c399..1341bb4f74 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
@@ -950,16 +950,6 @@ public interface Constants {
      * The constant messages.
      */
     String MESSAGES = "messages";
-
-    /**
-     * The constant usedTokens.
-     */
-    String USED_TOKENS = "used-tokens";
-
-    /**
-     * The constant X-Client-ID.
-     */
-    String CLIENT_ID = "X-Client-ID";
     
     /**
      * The constant Content-Encoding.
@@ -980,9 +970,25 @@ public interface Constants {
      */
     String ROLE = "role";
     
+    /**
+     * The constant USAGE.
+     */
+    String USAGE = "usage";
+    
+    /**
+     * The constant COMPLETION_TOKENS.
+     */
+    String COMPLETION_TOKENS = "completion_tokens";
+    
+    /**
+     * The constant AI_MODEL.
+     */
+    String AI_MODEL = "ai_model";
+    
     /**
      * String q.
      */
     default void findConstants() {
     }
+    
 }
diff --git 
a/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/rule/AiTokenLimiterHandle.java
 
b/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/rule/AiTokenLimiterHandle.java
index fad7ac46fd..52fc3b5ab7 100644
--- 
a/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/rule/AiTokenLimiterHandle.java
+++ 
b/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/rule/AiTokenLimiterHandle.java
@@ -125,7 +125,7 @@ public class AiTokenLimiterHandle {
      */
     public static AiTokenLimiterHandle newDefaultInstance() {
         AiTokenLimiterHandle aiTokenLimiterHandle = new AiTokenLimiterHandle();
-        
aiTokenLimiterHandle.setAiTokenLimitType(AiTokenLimiterEnum.DEFAULT.getName());
+        
aiTokenLimiterHandle.setAiTokenLimitType(AiTokenLimiterEnum.CONTEXT_PATH.getName());
         
aiTokenLimiterHandle.setTimeWindowSeconds(TimeWindowEnum.MINUTE.getSeconds());
         aiTokenLimiterHandle.setKeyName("default");
         aiTokenLimiterHandle.setTokenLimit(100L);
diff --git 
a/shenyu-common/src/main/java/org/apache/shenyu/common/enums/AiTokenLimiterEnum.java
 
b/shenyu-common/src/main/java/org/apache/shenyu/common/enums/AiTokenLimiterEnum.java
index 4ac259f3fd..134972c438 100644
--- 
a/shenyu-common/src/main/java/org/apache/shenyu/common/enums/AiTokenLimiterEnum.java
+++ 
b/shenyu-common/src/main/java/org/apache/shenyu/common/enums/AiTokenLimiterEnum.java
@@ -47,9 +47,9 @@ public enum AiTokenLimiterEnum {
     COOKIE("cookie"),
     
     /**
-     * Default key resolver enum.
+     * contextPath key resolver enum.
      */
-    DEFAULT("default");
+    CONTEXT_PATH("contextPath");
     
     private final String name;
     
@@ -83,6 +83,6 @@ public enum AiTokenLimiterEnum {
                 return value;
             }
         }
-        return DEFAULT;
+        return CONTEXT_PATH;
     }
 }
diff --git a/shenyu-plugin/pom.xml b/shenyu-plugin/pom.xml
index 5bd15e5325..9e540ec17f 100644
--- a/shenyu-plugin/pom.xml
+++ b/shenyu-plugin/pom.xml
@@ -49,9 +49,7 @@
         <module>shenyu-plugin-proxy</module>
         <module>shenyu-plugin-security</module>
         <module>shenyu-plugin-fault-tolerance</module>
-        <module>shenyu-plugin-ai-proxy</module>
-        <module>shenyu-plugin-ai-prompt</module>
-        <module>shenyu-plugin-ai-token-limiter</module>
+        <module>shenyu-plugin-ai</module>
     </modules>
 
     <dependencies>
diff --git 
a/shenyu-plugin/shenyu-plugin-ai-token-limiter/src/main/resources/META-INF/scripts/check-limit.lua
 
b/shenyu-plugin/shenyu-plugin-ai-token-limiter/src/main/resources/META-INF/scripts/check-limit.lua
deleted file mode 100644
index a66888afbf..0000000000
--- 
a/shenyu-plugin/shenyu-plugin-ai-token-limiter/src/main/resources/META-INF/scripts/check-limit.lua
+++ /dev/null
@@ -1,28 +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.
- */
-
--- check-limit.lua
--- KEYS[1]: user's token count key
--- ARGV[1]: the limit of tokens
-
-local key = KEYS[1]
-local currentTokens = redis.call('GET', key)
-if currentTokens and tonumber(currentTokens) >= tonumber(ARGV[1]) then
-  return 0  -- over limit, return 0
-else
-  return 1  -- not over limit, return 1
-end
diff --git 
a/shenyu-plugin/shenyu-plugin-ai-token-limiter/src/main/resources/META-INF/scripts/increment-token.lua
 
b/shenyu-plugin/shenyu-plugin-ai-token-limiter/src/main/resources/META-INF/scripts/increment-token.lua
deleted file mode 100644
index e3cbe119b7..0000000000
--- 
a/shenyu-plugin/shenyu-plugin-ai-token-limiter/src/main/resources/META-INF/scripts/increment-token.lua
+++ /dev/null
@@ -1,35 +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.
- */
-
--- increment-token.lua
--- KEYS[1]: user's token count key
--- ARGV[1]: increment value
--- ARGV[2]: time window in seconds
-
-local key = KEYS[1]
-local increment = tonumber(ARGV[1])
-local ttl = tonumber(ARGV[2])
-local currentTokens = redis.call('GET', key)
-
-if currentTokens then
-  redis.call('INCRBY', key, increment)
-  redis.call('EXPIRE', key, ttl)  -- fresh the expiration time
-else
-  redis.call('SETEX', key, ttl, increment)  -- set initial value and 
expiration time
-end
-
-return redis.call('GET', key)  -- return the current token count
diff --git a/shenyu-plugin/shenyu-plugin-ai-prompt/pom.xml 
b/shenyu-plugin/shenyu-plugin-ai/pom.xml
similarity index 57%
copy from shenyu-plugin/shenyu-plugin-ai-prompt/pom.xml
copy to shenyu-plugin/shenyu-plugin-ai/pom.xml
index e9a66ada67..8ae190877e 100644
--- a/shenyu-plugin/shenyu-plugin-ai-prompt/pom.xml
+++ b/shenyu-plugin/shenyu-plugin-ai/pom.xml
@@ -16,21 +16,29 @@
   ~ 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";>
+<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";>
+    <modelVersion>4.0.0</modelVersion>
+    <packaging>pom</packaging>
     <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-prompt</artifactId>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.apache.shenyu</groupId>
-            <artifactId>shenyu-plugin-base</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-    </dependencies>
+    
+    <artifactId>shenyu-plugin-ai</artifactId>
+    
+    <properties>
+        <maven.compiler.source>18</maven.compiler.source>
+        <maven.compiler.target>18</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
 
-</project>
+    <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>
+    </modules>
+</project>
\ No newline at end of file
diff --git a/shenyu-plugin/shenyu-plugin-ai-proxy/pom.xml 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-common/pom.xml
similarity index 94%
copy from shenyu-plugin/shenyu-plugin-ai-proxy/pom.xml
copy to shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-common/pom.xml
index 510a4b9b4c..0fea45bd48 100644
--- a/shenyu-plugin/shenyu-plugin-ai-proxy/pom.xml
+++ b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-common/pom.xml
@@ -19,11 +19,11 @@
 <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>
+        <artifactId>shenyu-plugin-ai</artifactId>
         <version>2.7.1-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
-    <artifactId>shenyu-plugin-ai-proxy</artifactId>
+    <artifactId>shenyu-plugin-ai-common</artifactId>
 
     <dependencies>
         <dependency>
diff --git 
a/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/plugin/AiProxyConfig.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-common/src/main/java/org/apache/shenyu/plugin/ai/common/config/AiCommonConfig.java
similarity index 96%
rename from 
shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/plugin/AiProxyConfig.java
rename to 
shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-common/src/main/java/org/apache/shenyu/plugin/ai/common/config/AiCommonConfig.java
index f057c39301..18c022c5fd 100644
--- 
a/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/plugin/AiProxyConfig.java
+++ 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-common/src/main/java/org/apache/shenyu/plugin/ai/common/config/AiCommonConfig.java
@@ -15,14 +15,14 @@
  * limitations under the License.
  */
 
-package org.apache.shenyu.common.dto.convert.plugin;
+package org.apache.shenyu.plugin.ai.common.config;
 
 import java.util.Objects;
 
 /**
- * this is Ai Proxy plugin config.
+ * this is Ai plugin common config.
  */
-public class AiProxyConfig {
+public class AiCommonConfig {
 
     /**
      * provider.
@@ -193,7 +193,7 @@ public class AiProxyConfig {
         if (Objects.isNull(o) || getClass() != o.getClass()) {
             return false;
         }
-        AiProxyConfig that = (AiProxyConfig) o;
+        AiCommonConfig that = (AiCommonConfig) o;
         return Objects.equals(provider, that.provider)
                 && Objects.equals(baseUrl, that.baseUrl)
                 && Objects.equals(apiKey, that.apiKey)
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/shenyu-plugin-ai-common/src/main/java/org/apache/shenyu/plugin/ai/common/strategy/AiModel.java
similarity index 68%
rename from 
shenyu-plugin/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/strategy/AiModel.java
rename to 
shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-common/src/main/java/org/apache/shenyu/plugin/ai/common/strategy/AiModel.java
index b30adf4a55..3ba32faac0 100644
--- 
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/shenyu-plugin-ai-common/src/main/java/org/apache/shenyu/plugin/ai/common/strategy/AiModel.java
@@ -15,9 +15,9 @@
  * limitations under the License.
  */
 
-package org.apache.shenyu.plugin.ai.proxy.strategy;
+package org.apache.shenyu.plugin.ai.common.strategy;
 
-import org.apache.shenyu.common.dto.convert.plugin.AiProxyConfig;
+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;
@@ -31,13 +31,24 @@ import java.util.List;
 public interface AiModel {
     
     /**
-     * Invoke mono.
+     * Invoke ai.
      *
-     * @param aiProxyConfig  the ai proxy config
+     * @param aiCommonConfig  the ai config
      * @param exchange       the exchange
      * @param chain          the chain
      * @param messageReaders the message readers
      * @return the mono
      */
-    Mono<Void> invoke(AiProxyConfig aiProxyConfig, ServerWebExchange exchange, 
ShenyuPluginChain chain, List<HttpMessageReader<?>> messageReaders);
+    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-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/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
similarity index 85%
rename from 
shenyu-plugin/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/strategy/AiModelFactory.java
rename to 
shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-common/src/main/java/org/apache/shenyu/plugin/ai/common/strategy/AiModelFactory.java
index 324ace7d92..523e76a81a 100644
--- 
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/shenyu-plugin-ai-common/src/main/java/org/apache/shenyu/plugin/ai/common/strategy/AiModelFactory.java
@@ -15,10 +15,10 @@
  * limitations under the License.
  */
 
-package org.apache.shenyu.plugin.ai.proxy.strategy;
+package org.apache.shenyu.plugin.ai.common.strategy;
 
 import org.apache.shenyu.common.enums.AiModelProviderEnum;
-import org.apache.shenyu.plugin.ai.proxy.strategy.openai.OpenAI;
+import org.apache.shenyu.plugin.ai.common.strategy.openai.OpenAI;
 
 import java.util.Objects;
 
@@ -31,14 +31,14 @@ public final class AiModelFactory {
     }
 
     /**
-     * Create ai model.
+     * Create ai model instance.
      *
      * @param provider the provider
      * @return the ai model provider
      */
     public static AiModel createAiModel(final AiModelProviderEnum provider) {
         if (Objects.isNull(provider)) {
-            return null;
+            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-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/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
similarity index 64%
rename from 
shenyu-plugin/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/strategy/openai/OpenAI.java
rename to 
shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-common/src/main/java/org/apache/shenyu/plugin/ai/common/strategy/openai/OpenAI.java
index 93b0797fcb..a81416c755 100644
--- 
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/shenyu-plugin-ai-common/src/main/java/org/apache/shenyu/plugin/ai/common/strategy/openai/OpenAI.java
@@ -15,16 +15,20 @@
  * limitations under the License.
  */
 
-package org.apache.shenyu.plugin.ai.proxy.strategy.openai;
+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.dto.convert.plugin.AiProxyConfig;
+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.proxy.strategy.AiModel;
+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;
@@ -33,24 +37,27 @@ import reactor.core.publisher.Mono;
 
 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 AiProxyConfig aiProxyConfig, final 
ServerWebExchange exchange,
+    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, 
aiProxyConfig))
+                        .headers(httpHeaders -> convertHeader(httpHeaders, 
aiCommonConfig))
                         .method(exchange.getRequest().getMethod())
                 )
                 .build();
         return ServerWebExchangeUtils.rewriteRequestBody(modifiedExchange, 
messageReaders, originalBody ->
-                        Mono.just(convertBody(originalBody, aiProxyConfig))
+                        Mono.just(convertBody(originalBody, aiCommonConfig))
                 ).flatMap(chain::execute)
                 .onErrorResume(error -> {
                     if (error instanceof ResponsiveException) {
@@ -60,22 +67,40 @@ public class OpenAI implements AiModel {
                 });
     }
     
+    @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 
AiProxyConfig aiProxyConfig) {
+    private static void convertHeader(final HttpHeaders httpHeaders, final 
AiCommonConfig aiCommonConfig) {
         if (!httpHeaders.containsKey("Authorization")) {
-            httpHeaders.add("Authorization", "Bearer " + 
aiProxyConfig.getApiKey());
+            httpHeaders.add("Authorization", "Bearer " + 
aiCommonConfig.getApiKey());
         }
-        if (aiProxyConfig.getStream()) {
+        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 AiProxyConfig 
aiProxyConfig) {
+    private String convertBody(final String originalBody, final AiCommonConfig 
aiCommonConfig) {
         Map<String, Object> requestBodyMap = 
GsonUtils.getInstance().convertToMap(originalBody);
-        requestBodyMap.put(Constants.MODEL, aiProxyConfig.getModel());
-        requestBodyMap.put(Constants.STREAM, aiProxyConfig.getStream());
+        requestBodyMap.put(Constants.MODEL, aiCommonConfig.getModel());
+        requestBodyMap.put(Constants.STREAM, aiCommonConfig.getStream());
         return GsonUtils.getInstance().toJson(requestBodyMap);
     }
     
diff --git a/shenyu-plugin/shenyu-plugin-ai-prompt/pom.xml 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-prompt/pom.xml
similarity index 96%
rename from shenyu-plugin/shenyu-plugin-ai-prompt/pom.xml
rename to shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-prompt/pom.xml
index e9a66ada67..f3a3a2faad 100644
--- a/shenyu-plugin/shenyu-plugin-ai-prompt/pom.xml
+++ b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-prompt/pom.xml
@@ -19,7 +19,7 @@
 <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>
+        <artifactId>shenyu-plugin-ai</artifactId>
         <version>2.7.1-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git 
a/shenyu-plugin/shenyu-plugin-ai-prompt/src/main/java/org/apache/shenyu/plugin/ai/prompt/AiPromptPlugin.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-prompt/src/main/java/org/apache/shenyu/plugin/ai/prompt/AiPromptPlugin.java
similarity index 100%
rename from 
shenyu-plugin/shenyu-plugin-ai-prompt/src/main/java/org/apache/shenyu/plugin/ai/prompt/AiPromptPlugin.java
rename to 
shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-prompt/src/main/java/org/apache/shenyu/plugin/ai/prompt/AiPromptPlugin.java
diff --git 
a/shenyu-plugin/shenyu-plugin-ai-prompt/src/main/java/org/apache/shenyu/plugin/ai/prompt/handler/AiPromptPluginDataHandler.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-prompt/src/main/java/org/apache/shenyu/plugin/ai/prompt/handler/AiPromptPluginDataHandler.java
similarity index 100%
rename from 
shenyu-plugin/shenyu-plugin-ai-prompt/src/main/java/org/apache/shenyu/plugin/ai/prompt/handler/AiPromptPluginDataHandler.java
rename to 
shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-prompt/src/main/java/org/apache/shenyu/plugin/ai/prompt/handler/AiPromptPluginDataHandler.java
diff --git a/shenyu-plugin/shenyu-plugin-ai-proxy/pom.xml 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/pom.xml
similarity index 88%
rename from shenyu-plugin/shenyu-plugin-ai-proxy/pom.xml
rename to shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/pom.xml
index 510a4b9b4c..3070bc9e59 100644
--- a/shenyu-plugin/shenyu-plugin-ai-proxy/pom.xml
+++ b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/pom.xml
@@ -19,7 +19,7 @@
 <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>
+        <artifactId>shenyu-plugin-ai</artifactId>
         <version>2.7.1-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
@@ -31,6 +31,11 @@
             <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>
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/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/AiProxyPlugin.java
similarity index 76%
rename from 
shenyu-plugin/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/AiProxyPlugin.java
rename to 
shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/AiProxyPlugin.java
index e51f955f5f..931276b382 100644
--- 
a/shenyu-plugin/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
@@ -20,15 +20,15 @@ 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.plugin.AiProxyConfig;
 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.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.api.context.ShenyuContext;
 import org.apache.shenyu.plugin.base.AbstractShenyuPlugin;
@@ -58,9 +58,9 @@ 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)) {
-            aiProxyConfig = new AiProxyConfig();
+        AiCommonConfig aiCommonConfig = 
Singleton.INST.get(AiCommonConfig.class);
+        if (Objects.isNull(aiCommonConfig)) {
+            aiCommonConfig = new AiCommonConfig();
         }
         final ShenyuContext shenyuContext = 
exchange.getAttribute(Constants.CONTEXT);
         Objects.requireNonNull(shenyuContext);
@@ -71,16 +71,16 @@ public class AiProxyPlugin extends AbstractShenyuPlugin {
         
         // Create final config with selector handle taking precedence
         if (Objects.nonNull(selectorHandle)) {
-            aiProxyConfig.setProvider(selectorHandle.getProvider());
-            aiProxyConfig.setBaseUrl(selectorHandle.getBaseUrl());
-            aiProxyConfig.setApiKey(selectorHandle.getApiKey());
-            aiProxyConfig.setModel(selectorHandle.getModel());
-            aiProxyConfig.setTemperature(selectorHandle.getTemperature());
-            aiProxyConfig.setMaxTokens(selectorHandle.getMaxTokens());
-            aiProxyConfig.setStream(selectorHandle.getStream());
+            aiCommonConfig.setProvider(selectorHandle.getProvider());
+            aiCommonConfig.setBaseUrl(selectorHandle.getBaseUrl());
+            aiCommonConfig.setApiKey(selectorHandle.getApiKey());
+            aiCommonConfig.setModel(selectorHandle.getModel());
+            aiCommonConfig.setTemperature(selectorHandle.getTemperature());
+            aiCommonConfig.setMaxTokens(selectorHandle.getMaxTokens());
+            aiCommonConfig.setStream(selectorHandle.getStream());
         }
         
-        if (Objects.isNull(aiProxyConfig.getBaseUrl())) {
+        if (Objects.isNull(aiCommonConfig.getBaseUrl())) {
             LOG.error("AI proxy plugin: baseUrl is null");
             return chain.execute(exchange);
         }
@@ -89,21 +89,25 @@ public class AiProxyPlugin extends AbstractShenyuPlugin {
         exchange.getAttributes().put(Constants.CONTEXT, shenyuContext);
         
         // set domain
-        exchange.getAttributes().put(Constants.HTTP_DOMAIN, 
aiProxyConfig.getBaseUrl());
+        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 = aiProxyConfig.getProvider();
+        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"));
         }
-        return aiModel.invoke(aiProxyConfig, exchange, chain, messageReaders);
+        
+        exchange.getAttributes().put(Constants.AI_MODEL, aiModel);
+        
+        return aiModel.invoke(aiCommonConfig, exchange, chain, messageReaders);
         
     }
     
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/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/handler/AiProxyPluginHandler.java
similarity index 89%
rename from 
shenyu-plugin/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/handler/AiProxyPluginHandler.java
rename to 
shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/handler/AiProxyPluginHandler.java
index ad9b49d843..e3f5d34426 100644
--- 
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/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/handler/AiProxyPluginHandler.java
@@ -20,11 +20,11 @@ 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.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.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;
@@ -43,11 +43,11 @@ public class AiProxyPluginHandler implements 
PluginDataHandler {
     @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)) {
+            AiCommonConfig aiCommonConfig = 
GsonUtils.getInstance().fromJson(pluginData.getConfig(), AiCommonConfig.class);
+            if (Objects.isNull(aiCommonConfig)) {
                 return;
             }
-            Singleton.INST.single(AiProxyConfig.class, aiProxyConfig);
+            Singleton.INST.single(AiCommonConfig.class, aiCommonConfig);
         }
     }
     
diff --git a/shenyu-plugin/shenyu-plugin-ai-token-limiter/pom.xml 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-token-limiter/pom.xml
similarity index 84%
rename from shenyu-plugin/shenyu-plugin-ai-token-limiter/pom.xml
rename to shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-token-limiter/pom.xml
index 9addf5a86b..82e713b5f6 100644
--- a/shenyu-plugin/shenyu-plugin-ai-token-limiter/pom.xml
+++ b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-token-limiter/pom.xml
@@ -19,7 +19,7 @@
 <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>
+        <artifactId>shenyu-plugin-ai</artifactId>
         <version>2.7.1-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
@@ -27,9 +27,14 @@
     <artifactId>shenyu-plugin-ai-token-limiter</artifactId>
 
     <dependencies>
+<!--        <dependency>-->
+<!--            <groupId>org.apache.shenyu</groupId>-->
+<!--            <artifactId>shenyu-plugin-logging-common</artifactId>-->
+<!--            <version>${project.version}</version>-->
+<!--        </dependency>-->
         <dependency>
             <groupId>org.apache.shenyu</groupId>
-            <artifactId>shenyu-plugin-logging-common</artifactId>
+            <artifactId>shenyu-plugin-ai-common</artifactId>
             <version>${project.version}</version>
         </dependency>
         <dependency>
diff --git 
a/shenyu-plugin/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/AiTokenLimiterPlugin.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/AiTokenLimiterPlugin.java
similarity index 71%
rename from 
shenyu-plugin/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/AiTokenLimiterPlugin.java
rename to 
shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/AiTokenLimiterPlugin.java
index fcb5912d4e..8781885c7d 100644
--- 
a/shenyu-plugin/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/AiTokenLimiterPlugin.java
+++ 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/AiTokenLimiterPlugin.java
@@ -17,8 +17,6 @@
 
 package org.apache.shenyu.plugin.ai.token.limiter;
 
-import com.fasterxml.jackson.databind.JsonNode;
-import com.google.common.collect.Lists;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.shenyu.common.constant.Constants;
 import org.apache.shenyu.common.dto.RuleData;
@@ -26,7 +24,7 @@ import org.apache.shenyu.common.dto.SelectorData;
 import org.apache.shenyu.common.dto.convert.rule.AiTokenLimiterHandle;
 import org.apache.shenyu.common.enums.AiTokenLimiterEnum;
 import org.apache.shenyu.common.enums.PluginEnum;
-import org.apache.shenyu.common.utils.JsonUtils;
+import org.apache.shenyu.plugin.ai.common.strategy.AiModel;
 import 
org.apache.shenyu.plugin.ai.token.limiter.handler.AiTokenLimiterPluginHandler;
 import org.apache.shenyu.plugin.api.ShenyuPluginChain;
 import org.apache.shenyu.plugin.api.result.ShenyuResultEnum;
@@ -37,18 +35,14 @@ import org.apache.shenyu.plugin.base.utils.CacheKeyUtils;
 import org.reactivestreams.Publisher;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.core.io.ClassPathResource;
 import org.springframework.core.io.buffer.DataBuffer;
 import org.springframework.data.redis.core.ReactiveRedisTemplate;
-import org.springframework.data.redis.core.script.DefaultRedisScript;
 import org.springframework.http.HttpCookie;
-import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.HttpStatusCode;
 import org.springframework.http.server.reactive.ServerHttpRequest;
 import org.springframework.http.server.reactive.ServerHttpResponse;
 import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
-import org.springframework.scripting.support.ResourceScriptSource;
 import org.springframework.util.Assert;
 import org.springframework.web.server.ServerWebExchange;
 import reactor.core.publisher.Flux;
@@ -62,7 +56,7 @@ import java.nio.ByteBuffer;
 import java.nio.channels.Channels;
 import java.nio.channels.WritableByteChannel;
 import java.nio.charset.StandardCharsets;
-import java.util.List;
+import java.time.Duration;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -78,10 +72,6 @@ public class AiTokenLimiterPlugin extends 
AbstractShenyuPlugin {
     
     private static final String REDIS_KEY_PREFIX = "SHENYU:AI:TOKENLIMIT:";
     
-    private static final String CHECK_LIMIT_SCRIPT = "check-limit.lua";
-    
-    private static final String INCREMENT_TOKEN_SCRIPT = "increment-token.lua";
-    
     @Override
     protected Mono<Void> doExecute(final ServerWebExchange exchange, final 
ShenyuPluginChain chain,
                                    final SelectorData selector, final RuleData 
rule) {
@@ -96,14 +86,14 @@ public class AiTokenLimiterPlugin extends 
AbstractShenyuPlugin {
         Assert.notNull(reactiveRedisTemplate, "reactiveRedisTemplate is null");
         
         // generate redis key
-        String keyResolverName = aiTokenLimiterHandle.getAiTokenLimitType();
+        String tokenLimitType = aiTokenLimiterHandle.getAiTokenLimitType();
         String keyName = aiTokenLimiterHandle.getKeyName();
         Long tokenLimit = aiTokenLimiterHandle.getTokenLimit();
         Long timeWindowSeconds = aiTokenLimiterHandle.getTimeWindowSeconds();
         
-        String cacheKey = REDIS_KEY_PREFIX + 
aiTokenLimiterHandle.getAiTokenLimitType() + ":" + getCacheKey(exchange, 
keyResolverName, keyName);
+        String cacheKey = REDIS_KEY_PREFIX + getCacheKey(exchange, 
tokenLimitType, keyName);
         
-        final AiStatisticServerHttpResponse loggingServerHttpResponse = new 
AiStatisticServerHttpResponse(exchange.getResponse(),
+        final AiStatisticServerHttpResponse loggingServerHttpResponse = new 
AiStatisticServerHttpResponse(exchange, exchange.getResponse(),
                 tokens -> recordTokensUsage(reactiveRedisTemplate,
                         cacheKey,
                         tokens,
@@ -116,7 +106,7 @@ public class AiTokenLimiterPlugin extends 
AbstractShenyuPlugin {
                         
exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
                         final Consumer<HttpStatusCode> consumer = 
exchange.getAttribute(Constants.METRICS_RATE_LIMITER);
                         Optional.ofNullable(consumer).ifPresent(c -> 
c.accept(exchange.getResponse().getStatusCode()));
-                        Object error = ShenyuResultWrap.error(exchange, 
ShenyuResultEnum.TOO_MANY_REQUESTS);
+                        Object error = ShenyuResultWrap.error(exchange, 
ShenyuResultEnum.RUN_OUT_OF_TOKENS);
                         return WebFluxResultUtils.result(exchange, error);
                     }
                     // record tokens usage
@@ -138,36 +128,32 @@ public class AiTokenLimiterPlugin extends 
AbstractShenyuPlugin {
      * @return whether the request is allowed
      */
     private Mono<Boolean> isAllowed(final ReactiveRedisTemplate 
reactiveRedisTemplate, final String cacheKey, final Long tokenLimit) {
-        DefaultRedisScript checkLimitScript = new DefaultRedisScript<>();
-        String scriptPath = Constants.SCRIPT_PATH + CHECK_LIMIT_SCRIPT;
-        checkLimitScript.setScriptSource(new ResourceScriptSource(new 
ClassPathResource(scriptPath)));
-        checkLimitScript.setResultType(Long.class);
-        return Mono.from(reactiveRedisTemplate.execute(
-                        checkLimitScript,
-                        Lists.newArrayList(cacheKey),
-                        Lists.newArrayList(tokenLimit.toString()))
-                .map(result -> {
-                    // Convert result to String since StatusOutput doesn't 
support long
-                    // Return true if result is "1", false otherwise
-                    return (Long) result == 1L;
-                }));
+        
+        return reactiveRedisTemplate.opsForValue().get(cacheKey)
+                .defaultIfEmpty(0L)
+                .flatMap(currentTokens -> {
+                    if (Long.parseLong(currentTokens.toString()) >= 
tokenLimit) {
+                        return Mono.just(false);
+                    }
+                    return Mono.just(true);
+                });
     }
     
     /**
      * Get the cache key based on the configured key resolver type.
      *
      * @param exchange the server web exchange
-     * @param keyResolverName the name of the key resolver
+     * @param tokenLimitType the type of token limit
      * @param keyName the name of the key
      * @return the cache key
      */
-    private String getCacheKey(final ServerWebExchange exchange, final String 
keyResolverName, final String keyName) {
+    private String getCacheKey(final ServerWebExchange exchange, final String 
tokenLimitType, final String keyName) {
         ServerHttpRequest request = exchange.getRequest();
         String key;
         // Determine the key based on the configured key resolver type
-        AiTokenLimiterEnum keyResolverEnum = 
AiTokenLimiterEnum.getByName(keyResolverName);
+        AiTokenLimiterEnum tokenLimiterEnum = 
AiTokenLimiterEnum.getByName(tokenLimitType);
         
-        switch (keyResolverEnum) {
+        switch (tokenLimiterEnum) {
             case IP:
                 key = 
Objects.requireNonNull(request.getRemoteAddress()).getHostString();
                 break;
@@ -184,46 +170,20 @@ public class AiTokenLimiterPlugin extends 
AbstractShenyuPlugin {
                 HttpCookie cookie = request.getCookies().getFirst(keyName);
                 key = Objects.nonNull(cookie) ? cookie.getValue() : "";
                 break;
+            case CONTEXT_PATH:
             default:
-                key = exchange.getAttribute(Constants.CONTEXT_PATH)
-                        + ":"
-                        + exchange.getRequest().getURI().getPath();
+                key = exchange.getAttribute(Constants.CONTEXT_PATH);
         }
         
         return StringUtils.isBlank(key) ? "" : key;
     }
     
-    private String extractClientId(final ServerWebExchange exchange) {
-        ServerHttpRequest request = exchange.getRequest();
-        // Get client identifier from request headers first
-        String clientId = request.getHeaders().getFirst(Constants.CLIENT_ID);
-        
-        if (StringUtils.isBlank(clientId)) {
-            // If not present, try to get it from the exchange attributes
-            if (exchange.getAttributes().containsKey(Constants.CLIENT_ID)) {
-                clientId = 
exchange.getAttributes().get(Constants.CLIENT_ID).toString();
-            }
-        }
-        
-        if (StringUtils.isBlank(clientId)) {
-            // Finally, try using the combination of IP and User-Agent
-            String ip = 
request.getRemoteAddress().getAddress().getHostAddress();
-            String userAgent = 
request.getHeaders().getFirst(HttpHeaders.USER_AGENT);
-            clientId = ip + "|" + (Objects.nonNull(userAgent) ? userAgent : 
"unknown");
-        }
-        exchange.getAttributes().put(Constants.CLIENT_ID, clientId);
-        
-        return clientId;
-    }
-    
     private void recordTokensUsage(final ReactiveRedisTemplate 
reactiveRedisTemplate, final String cacheKey, final Long tokens, final Long 
windowSeconds) {
-        DefaultRedisScript incrementTokenScript = new DefaultRedisScript<>();
-        String scriptPath = Constants.SCRIPT_PATH + INCREMENT_TOKEN_SCRIPT;
-        incrementTokenScript.setScriptSource(new ResourceScriptSource(new 
ClassPathResource(scriptPath)));
-        incrementTokenScript.setResultType(List.class);
-        reactiveRedisTemplate.execute(incrementTokenScript,
-                Lists.newArrayList(cacheKey),
-                Lists.newArrayList(tokens.toString(), 
windowSeconds.toString())).subscribe();
+        // Record token usage with expiration
+        reactiveRedisTemplate.opsForValue()
+                .increment(cacheKey, tokens)
+                .flatMap(currentValue -> 
reactiveRedisTemplate.expire(cacheKey, Duration.ofSeconds(windowSeconds)))
+                .subscribe();
     }
     
     @Override
@@ -238,12 +198,15 @@ public class AiTokenLimiterPlugin extends 
AbstractShenyuPlugin {
     
     static class AiStatisticServerHttpResponse extends 
ServerHttpResponseDecorator {
         
+        private final ServerWebExchange exchange;
+        
         private final ServerHttpResponse serverHttpResponse;
         
         private final Consumer<Long> tokensRecorder;
         
-        AiStatisticServerHttpResponse(final ServerHttpResponse delegate, final 
Consumer<Long> tokensRecorder) {
+        AiStatisticServerHttpResponse(final ServerWebExchange exchange, final 
ServerHttpResponse delegate, final Consumer<Long> tokensRecorder) {
             super(delegate);
+            this.exchange = exchange;
             this.serverHttpResponse = delegate;
             this.tokensRecorder = tokensRecorder;
         }
@@ -263,7 +226,7 @@ public class AiTokenLimiterPlugin extends 
AbstractShenyuPlugin {
                     bufferIterator.forEachRemaining(byteBuffer -> {
                         // Handle gzip encoded response
                         if 
(serverHttpResponse.getHeaders().containsKey(Constants.CONTENT_ENCODING)
-                                && 
serverHttpResponse.getHeaders().getFirst(Constants.CONTENT_ENCODING).contains("gzip"))
 {
+                                && 
serverHttpResponse.getHeaders().getFirst(Constants.CONTENT_ENCODING).contains(Constants.HTTP_ACCEPT_ENCODING_GZIP))
 {
                             try {
                                 ByteBuffer readOnlyBuffer = 
byteBuffer.asReadOnlyBuffer();
                                 byte[] compressed = new 
byte[readOnlyBuffer.remaining()];
@@ -284,7 +247,8 @@ public class AiTokenLimiterPlugin extends 
AbstractShenyuPlugin {
                 }
             }).doFinally(signal -> {
                 String responseBody = writer.output();
-                long tokens = extractTokensFromResponse(responseBody);
+                AiModel aiModel = exchange.getAttribute(Constants.AI_MODEL);
+                long tokens = 
Objects.requireNonNull(aiModel).getCompletionTokens(responseBody);
                 tokensRecorder.accept(tokens);
             });
         }
@@ -301,25 +265,6 @@ public class AiTokenLimiterPlugin extends 
AbstractShenyuPlugin {
             }
         }
         
-        private long extractTokensFromResponse(final String responseBody) {
-            // 根据 OpenAI API 响应格式解析出 tokens 使用量
-            // 不同的 API 端点响应格式可能不同,需要相应调整
-            try {
-                JsonNode root = JsonUtils.toJsonNode(responseBody);
-                JsonNode usage = root.get("usage");
-                if (Objects.nonNull(usage)) {
-                    JsonNode totalTokens = usage.get("total_tokens");
-                    if (Objects.nonNull(totalTokens)) {
-                        return totalTokens.asLong();
-                    }
-                }
-            } catch (Exception e) {
-                // 处理解析异常
-                LOG.error("Failed to parse response body: {}", responseBody, 
e);
-            }
-            return 0;
-        }
-        
     }
     
     static class BodyWriter {
diff --git 
a/shenyu-plugin/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/handler/AiTokenLimiterPluginHandler.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/handler/AiTokenLimiterPluginHandler.java
similarity index 100%
rename from 
shenyu-plugin/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/handler/AiTokenLimiterPluginHandler.java
rename to 
shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/handler/AiTokenLimiterPluginHandler.java
diff --git 
a/shenyu-plugin/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/redis/RedisConfigProperties.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/redis/RedisConfigProperties.java
similarity index 100%
rename from 
shenyu-plugin/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/redis/RedisConfigProperties.java
rename to 
shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/redis/RedisConfigProperties.java
diff --git 
a/shenyu-plugin/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/redis/RedisConnectionFactory.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/redis/RedisConnectionFactory.java
similarity index 100%
rename from 
shenyu-plugin/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/redis/RedisConnectionFactory.java
rename to 
shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/redis/RedisConnectionFactory.java
diff --git 
a/shenyu-plugin/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/redis/ShenyuReactiveRedisTemplate.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/redis/ShenyuReactiveRedisTemplate.java
similarity index 100%
rename from 
shenyu-plugin/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/redis/ShenyuReactiveRedisTemplate.java
rename to 
shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/redis/ShenyuReactiveRedisTemplate.java
diff --git 
a/shenyu-plugin/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/redis/ShenyuReactiveScriptExecutor.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/redis/ShenyuReactiveScriptExecutor.java
similarity index 100%
rename from 
shenyu-plugin/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/redis/ShenyuReactiveScriptExecutor.java
rename to 
shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/redis/ShenyuReactiveScriptExecutor.java
diff --git 
a/shenyu-plugin/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/redis/serializer/ByteArrayRedisSerializer.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/redis/serializer/ByteArrayRedisSerializer.java
similarity index 100%
rename from 
shenyu-plugin/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/redis/serializer/ByteArrayRedisSerializer.java
rename to 
shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/redis/serializer/ByteArrayRedisSerializer.java
diff --git 
a/shenyu-plugin/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/redis/serializer/ShenyuRedisSerializationContext.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/redis/serializer/ShenyuRedisSerializationContext.java
similarity index 100%
rename from 
shenyu-plugin/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/redis/serializer/ShenyuRedisSerializationContext.java
rename to 
shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-token-limiter/src/main/java/org/apache/shenyu/plugin/ai/token/limiter/redis/serializer/ShenyuRedisSerializationContext.java
diff --git 
a/shenyu-plugin/shenyu-plugin-api/src/main/java/org/apache/shenyu/plugin/api/result/ShenyuResultEnum.java
 
b/shenyu-plugin/shenyu-plugin-api/src/main/java/org/apache/shenyu/plugin/api/result/ShenyuResultEnum.java
index c957a80c21..a52976c5a8 100644
--- 
a/shenyu-plugin/shenyu-plugin-api/src/main/java/org/apache/shenyu/plugin/api/result/ShenyuResultEnum.java
+++ 
b/shenyu-plugin/shenyu-plugin-api/src/main/java/org/apache/shenyu/plugin/api/result/ShenyuResultEnum.java
@@ -52,6 +52,11 @@ public enum ShenyuResultEnum {
      */
     TOO_MANY_REQUESTS(429, "You have been restricted, please try again 
later!"),
     
+    /**
+     * run out of tokens shenyu result enum.
+     */
+    RUN_OUT_OF_TOKENS(429, "You have reach your tokens limit, please try again 
later!"),
+    
     /**
      * Hystrix plugin fallback, due to a circuit break.
      */

Reply via email to