This is an automated email from the ASF dual-hosted git repository. shreemaanabhishek pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push: new 25e2e7371 refactor: ai-content-moderation to ai-aws-content-moderation (#12010) 25e2e7371 is described below commit 25e2e73711f6862d336c79710fad79582eade17a Author: Shreemaan Abhishek <shreemaan.abhis...@gmail.com> AuthorDate: Thu Mar 6 11:33:43 2025 +0545 refactor: ai-content-moderation to ai-aws-content-moderation (#12010) --- Makefile | 3 - apisix/cli/config.lua | 2 +- ...oderation.lua => ai-aws-content-moderation.lua} | 81 ++++++++-------------- apisix/plugins/ai/openai.lua | 33 --------- conf/config.yaml.example | 2 +- docs/en/latest/config.json | 2 +- ...-moderation.md => ai-aws-content-moderation.md} | 60 ++++++++-------- t/admin/plugins.t | 2 +- ...crets.t => ai-aws-content-moderation-secrets.t} | 34 ++++----- ...nt-moderation.t => ai-aws-content-moderation.t} | 69 ++++++++---------- ...-moderation2.t => ai-aws-content-moderation2.t} | 16 ++--- 11 files changed, 110 insertions(+), 194 deletions(-) diff --git a/Makefile b/Makefile index c288463c9..2c3bb8ddb 100644 --- a/Makefile +++ b/Makefile @@ -382,9 +382,6 @@ install: runtime $(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/plugins/ai-rag/vector-search $(ENV_INSTALL) apisix/plugins/ai-rag/vector-search/*.lua $(ENV_INST_LUADIR)/apisix/plugins/ai-rag/vector-search - # ai-content-moderation plugin - $(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/plugins/ai - $(ENV_INSTALL) apisix/plugins/ai/*.lua $(ENV_INST_LUADIR)/apisix/plugins/ai $(ENV_INSTALL) bin/apisix $(ENV_INST_BINDIR)/apisix diff --git a/apisix/cli/config.lua b/apisix/cli/config.lua index 376b5ed15..8760cea73 100644 --- a/apisix/cli/config.lua +++ b/apisix/cli/config.lua @@ -217,7 +217,7 @@ local _M = { "ai-prompt-template", "ai-prompt-decorator", "ai-rag", - "ai-content-moderation", + "ai-aws-content-moderation", "proxy-mirror", "proxy-rewrite", "workflow", diff --git a/apisix/plugins/ai-content-moderation.lua b/apisix/plugins/ai-aws-content-moderation.lua similarity index 66% rename from apisix/plugins/ai-content-moderation.lua rename to apisix/plugins/ai-aws-content-moderation.lua index f4958f321..c7b54ed4e 100644 --- a/apisix/plugins/ai-content-moderation.lua +++ b/apisix/plugins/ai-aws-content-moderation.lua @@ -19,48 +19,34 @@ local aws_instance = require("resty.aws")() local http = require("resty.http") local fetch_secrets = require("apisix.secret").fetch_secrets -local next = next local pairs = pairs local unpack = unpack local type = type local ipairs = ipairs -local require = require local HTTP_INTERNAL_SERVER_ERROR = ngx.HTTP_INTERNAL_SERVER_ERROR local HTTP_BAD_REQUEST = ngx.HTTP_BAD_REQUEST - -local aws_comprehend_schema = { - type = "object", - properties = { - access_key_id = { type = "string" }, - secret_access_key = { type = "string" }, - region = { type = "string" }, - endpoint = { - type = "string", - pattern = [[^https?://]] - }, - ssl_verify = { - type = "boolean", - default = true - } - }, - required = { "access_key_id", "secret_access_key", "region", } -} - local moderation_categories_pattern = "^(PROFANITY|HATE_SPEECH|INSULT|".. "HARASSMENT_OR_ABUSE|SEXUAL|VIOLENCE_OR_THREAT)$" local schema = { type = "object", properties = { - provider = { + comprehend = { type = "object", properties = { - aws_comprehend = aws_comprehend_schema + access_key_id = { type = "string" }, + secret_access_key = { type = "string" }, + region = { type = "string" }, + endpoint = { + type = "string", + pattern = [[^https?://]] + }, + ssl_verify = { + type = "boolean", + default = true + } }, - maxProperties = 1, - -- ensure only one provider can be configured while implementing support for - -- other providers - required = { "aws_comprehend" } + required = { "access_key_id", "secret_access_key", "region", } }, moderation_categories = { type = "object", @@ -78,20 +64,16 @@ local schema = { minimum = 0, maximum = 1, default = 0.5 - }, - llm_provider = { - type = "string", - enum = { "openai" }, } }, - required = { "provider", "llm_provider" }, + required = { "comprehend" }, } local _M = { version = 0.1, priority = 1040, -- TODO: might change - name = "ai-content-moderation", + name = "ai-aws-content-moderation", schema = schema, } @@ -107,51 +89,44 @@ function _M.rewrite(conf, ctx) return HTTP_INTERNAL_SERVER_ERROR, "failed to retrieve secrets from conf" end - local body, err = core.request.get_json_request_body_table() + local body, err = core.request.get_body() if not body then return HTTP_BAD_REQUEST, err end - local msgs = body.messages - if type(msgs) ~= "table" or #msgs < 1 then - return HTTP_BAD_REQUEST, "messages not found in request body" - end - - local provider = conf.provider[next(conf.provider)] + local comprehend = conf.comprehend local credentials = aws_instance:Credentials({ - accessKeyId = provider.access_key_id, - secretAccessKey = provider.secret_access_key, - sessionToken = provider.session_token, + accessKeyId = comprehend.access_key_id, + secretAccessKey = comprehend.secret_access_key, + sessionToken = comprehend.session_token, }) - local default_endpoint = "https://comprehend." .. provider.region .. ".amazonaws.com" - local scheme, host, port = unpack(http:parse_uri(provider.endpoint or default_endpoint)) + local default_endpoint = "https://comprehend." .. comprehend.region .. ".amazonaws.com" + local scheme, host, port = unpack(http:parse_uri(comprehend.endpoint or default_endpoint)) local endpoint = scheme .. "://" .. host aws_instance.config.endpoint = endpoint - aws_instance.config.ssl_verify = provider.ssl_verify + aws_instance.config.ssl_verify = comprehend.ssl_verify local comprehend = aws_instance:Comprehend({ credentials = credentials, endpoint = endpoint, - region = provider.region, + region = comprehend.region, port = port, }) - local ai_module = require("apisix.plugins.ai." .. conf.llm_provider) - local create_request_text_segments = ai_module.create_request_text_segments - - local text_segments = create_request_text_segments(msgs) local res, err = comprehend:detectToxicContent({ LanguageCode = "en", - TextSegments = text_segments, + TextSegments = {{ + Text = body + }}, }) if not res then core.log.error("failed to send request to ", endpoint, ": ", err) return HTTP_INTERNAL_SERVER_ERROR, err end - + core.log.warn("dibag: ", core.json.encode(res)) local results = res.body and res.body.ResultList if type(results) ~= "table" or core.table.isempty(results) then return HTTP_INTERNAL_SERVER_ERROR, "failed to get moderation results from response" diff --git a/apisix/plugins/ai/openai.lua b/apisix/plugins/ai/openai.lua deleted file mode 100644 index 203debb7e..000000000 --- a/apisix/plugins/ai/openai.lua +++ /dev/null @@ -1,33 +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. --- -local core = require("apisix.core") -local ipairs = ipairs - -local _M = {} - - -function _M.create_request_text_segments(msgs) - local text_segments = {} - for _, msg in ipairs(msgs) do - core.table.insert_tail(text_segments, { - Text = msg.content - }) - end - return text_segments -end - -return _M diff --git a/conf/config.yaml.example b/conf/config.yaml.example index 780340dcb..59aef457d 100644 --- a/conf/config.yaml.example +++ b/conf/config.yaml.example @@ -481,7 +481,7 @@ plugins: # plugin list (sorted by priority) - ai-prompt-template # priority: 1071 - ai-prompt-decorator # priority: 1070 - ai-rag # priority: 1060 - - ai-content-moderation # priority: 1040 TODO: compare priority with other ai plugins + - ai-aws-content-moderation # priority: 1040 TODO: compare priority with other ai plugins - proxy-mirror # priority: 1010 - proxy-rewrite # priority: 1008 - workflow # priority: 1006 diff --git a/docs/en/latest/config.json b/docs/en/latest/config.json index c8bf09ca7..55a770255 100644 --- a/docs/en/latest/config.json +++ b/docs/en/latest/config.json @@ -82,7 +82,7 @@ "plugins/ext-plugin-post-resp", "plugins/inspect", "plugins/ocsp-stapling", - "plugins/ai-content-moderation" + "plugins/ai-aws-content-moderation" ] }, { diff --git a/docs/en/latest/plugins/ai-content-moderation.md b/docs/en/latest/plugins/ai-aws-content-moderation.md similarity index 69% rename from docs/en/latest/plugins/ai-content-moderation.md rename to docs/en/latest/plugins/ai-aws-content-moderation.md index 781b203d9..96844820d 100644 --- a/docs/en/latest/plugins/ai-content-moderation.md +++ b/docs/en/latest/plugins/ai-aws-content-moderation.md @@ -1,11 +1,11 @@ --- -title: ai-content-moderation +title: ai-aws-content-moderation keywords: - Apache APISIX - API Gateway - Plugin - - ai-content-moderation -description: This document contains information about the Apache APISIX ai-content-moderation Plugin. + - ai-aws-content-moderation +description: This document contains information about the Apache APISIX ai-aws-content-moderation Plugin. --- <!-- @@ -29,7 +29,7 @@ description: This document contains information about the Apache APISIX ai-conte ## Description -The `ai-content-moderation` plugin processes the request body to check for toxicity and rejects the request if it exceeds the configured threshold. +The `ai-aws-content-moderation` plugin processes the request body to check for toxicity and rejects the request if it exceeds the configured threshold. **_This plugin must be used in routes that proxy requests to LLMs only._** @@ -37,15 +37,15 @@ The `ai-content-moderation` plugin processes the request body to check for toxic ## Plugin Attributes -| **Field** | **Required** | **Type** | **Description** | -| ----------------------------------------- | ------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| provider.aws_comprehend.access_key_id | Yes | String | AWS access key ID | -| provider.aws_comprehend.secret_access_key | Yes | String | AWS secret access key | -| provider.aws_comprehend.region | Yes | String | AWS region | -| provider.aws_comprehend.endpoint | No | String | AWS Comprehend service endpoint. Must match the pattern `^https?://` | -| moderation_categories | No | Object | Key-value pairs of moderation category and their score. In each pair, the key should be one of the `PROFANITY`, `HATE_SPEECH`, `INSULT`, `HARASSMENT_OR_ABUSE`, `SEXUAL`, or `VIOLENCE_OR_THREAT`; and the value should be between 0 and 1 (inclusive). | -| moderation_threshold | No | Number | The degree to which content is harmful, offensive, or inappropriate. A higher value indicates more toxic content allowed. Range: 0 - 1. Default: 0.5 | -| llm_provider | Yes | String | Name of the LLM provider that this route will proxy requests to. | +| **Field** | **Required** | **Type** | **Description** | +| ---------------------------- | ------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| comprehend.access_key_id | Yes | String | AWS access key ID | +| comprehend.secret_access_key | Yes | String | AWS secret access key | +| comprehend.region | Yes | String | AWS region | +| comprehend.endpoint | No | String | AWS Comprehend service endpoint. Must match the pattern `^https?://` | +| comprehend.ssl_verify | No | String | Enables SSL certificate verification. | +| moderation_categories | No | Object | Key-value pairs of moderation category and their score. In each pair, the key should be one of the `PROFANITY`, `HATE_SPEECH`, `INSULT`, `HARASSMENT_OR_ABUSE`, `SEXUAL`, or `VIOLENCE_OR_THREAT`; and the value should be between 0 and 1 (inclusive). | +| moderation_threshold | No | Number | The degree to which content is harmful, offensive, or inappropriate. A higher value indicates more toxic content allowed. Range: 0 - 1. Default: 0.5 | ## Example usage @@ -58,7 +58,7 @@ SECRET_ACCESS_KEY=aws-comprehend-secret-access-key-here OPENAI_KEY=open-ai-key-here ``` -Create a route with the `ai-content-moderation` and `ai-proxy` plugin like so: +Create a route with the `ai-aws-content-moderation` and `ai-proxy` plugin like so: ```shell curl "http://127.0.0.1:9180/apisix/admin/routes/1" -X PUT \ @@ -66,18 +66,15 @@ curl "http://127.0.0.1:9180/apisix/admin/routes/1" -X PUT \ -d '{ "uri": "/post", "plugins": { - "ai-content-moderation": { - "provider": { - "aws_comprehend": { - "access_key_id": "'"$ACCESS_KEY_ID"'", - "secret_access_key": "'"$SECRET_ACCESS_KEY"'", - "region": "us-east-1" - } + "ai-aws-content-moderation": { + "comprehend": { + "access_key_id": "'"$ACCESS_KEY_ID"'", + "secret_access_key": "'"$SECRET_ACCESS_KEY"'", + "region": "us-east-1" }, "moderation_categories": { "PROFANITY": 0.5 - }, - "llm_provider": "openai" + } }, "ai-proxy": { "auth": { @@ -167,15 +164,12 @@ curl "http://127.0.0.1:9180/apisix/admin/routes/1" -X PUT \ -d '{ "uri": "/post", "plugins": { - "ai-content-moderation": { - "provider": { - "aws_comprehend": { - "access_key_id": "'"$ACCESS_KEY_ID"'", - "secret_access_key": "'"$SECRET_ACCESS_KEY"'", - "region": "us-east-1" - } + "ai-aws-content-moderation": { + "comprehend": { + "access_key_id": "'"$ACCESS_KEY_ID"'", + "secret_access_key": "'"$SECRET_ACCESS_KEY"'", + "region": "us-east-1" }, - "llm_provider": "openai", "moderation_categories": { "PROFANITY": 0.5, "HARASSMENT_OR_ABUSE": 0.7, @@ -216,9 +210,9 @@ curl "http://127.0.0.1:9180/apisix/admin/routes/1" -X PUT \ -d '{ "uri": "/post", "plugins": { - "ai-content-moderation": { + "ai-aws-content-moderation": { "provider": { - "aws_comprehend": { + "comprehend": { "access_key_id": "'"$ACCESS_KEY_ID"'", "secret_access_key": "'"$SECRET_ACCESS_KEY"'", "region": "us-east-1" diff --git a/t/admin/plugins.t b/t/admin/plugins.t index 7cb852cbf..a0d8dc1b0 100644 --- a/t/admin/plugins.t +++ b/t/admin/plugins.t @@ -97,7 +97,7 @@ body-transformer ai-prompt-template ai-prompt-decorator ai-rag -ai-content-moderation +ai-aws-content-moderation proxy-mirror proxy-rewrite workflow diff --git a/t/plugin/ai-content-moderation-secrets.t b/t/plugin/ai-aws-content-moderation-secrets.t similarity index 83% rename from t/plugin/ai-content-moderation-secrets.t rename to t/plugin/ai-aws-content-moderation-secrets.t index 06d7941f7..6c531b243 100644 --- a/t/plugin/ai-content-moderation-secrets.t +++ b/t/plugin/ai-aws-content-moderation-secrets.t @@ -114,16 +114,13 @@ Success! Data written to: kv/apisix/foo [[{ "uri": "/echo", "plugins": { - "ai-content-moderation": { - "provider": { - "aws_comprehend": { - "access_key_id": "$secret://vault/test1/foo/access_key_id", - "secret_access_key": "$secret://vault/test1/foo/secret_access_key", - "region": "us-east-1", - "endpoint": "http://localhost:2668" - } - }, - "llm_provider": "openai" + "ai-aws-content-moderation": { + "comprehend": { + "access_key_id": "$secret://vault/test1/foo/access_key_id", + "secret_access_key": "$secret://vault/test1/foo/secret_access_key", + "region": "us-east-1", + "endpoint": "http://localhost:2668" + } } }, "upstream": { @@ -169,16 +166,13 @@ POST /echo [[{ "uri": "/echo", "plugins": { - "ai-content-moderation": { - "provider": { - "aws_comprehend": { - "access_key_id": "$env://ACCESS_KEY_ID", - "secret_access_key": "$env://SECRET_ACCESS_KEY", - "region": "us-east-1", - "endpoint": "http://localhost:2668" - } - }, - "llm_provider": "openai" + "ai-aws-content-moderation": { + "comprehend": { + "access_key_id": "$env://ACCESS_KEY_ID", + "secret_access_key": "$env://SECRET_ACCESS_KEY", + "region": "us-east-1", + "endpoint": "http://localhost:2668" + } } }, "upstream": { diff --git a/t/plugin/ai-content-moderation.t b/t/plugin/ai-aws-content-moderation.t similarity index 75% rename from t/plugin/ai-content-moderation.t rename to t/plugin/ai-aws-content-moderation.t index 66393ef98..1fb3eed8f 100644 --- a/t/plugin/ai-content-moderation.t +++ b/t/plugin/ai-aws-content-moderation.t @@ -101,16 +101,13 @@ __DATA__ [[{ "uri": "/echo", "plugins": { - "ai-content-moderation": { - "provider": { - "aws_comprehend": { - "access_key_id": "access", - "secret_access_key": "ea+secret", - "region": "us-east-1", - "endpoint": "http://localhost:2668" - } - }, - "llm_provider": "openai" + "ai-aws-content-moderation": { + "comprehend": { + "access_key_id": "access", + "secret_access_key": "ea+secret", + "region": "us-east-1", + "endpoint": "http://localhost:2668" + } } }, "upstream": { @@ -136,7 +133,7 @@ passed === TEST 2: toxic request should fail --- request POST /echo -{"model":"gpt-4o-mini","messages":[{"role":"user","content":"toxic"}]} +toxic --- error_code: 400 --- response_body chomp request body exceeds toxicity threshold @@ -146,7 +143,7 @@ request body exceeds toxicity threshold === TEST 3: good request should pass --- request POST /echo -{"model":"gpt-4o-mini","messages":[{"role":"user","content":"good_request"}]} +good_request --- error_code: 200 @@ -161,19 +158,16 @@ POST /echo [[{ "uri": "/echo", "plugins": { - "ai-content-moderation": { - "provider": { - "aws_comprehend": { - "access_key_id": "access", - "secret_access_key": "ea+secret", - "region": "us-east-1", - "endpoint": "http://localhost:2668" - } + "ai-aws-content-moderation": { + "comprehend": { + "access_key_id": "access", + "secret_access_key": "ea+secret", + "region": "us-east-1", + "endpoint": "http://localhost:2668" }, "moderation_categories": { "PROFANITY": 0.5 - }, - "llm_provider": "openai" + } } }, "upstream": { @@ -199,7 +193,7 @@ passed === TEST 5: profane request should fail --- request POST /echo -{"model":"gpt-4o-mini","messages":[{"role":"user","content":"profane"}]} +profane --- error_code: 400 --- response_body chomp request body exceeds PROFANITY threshold @@ -209,7 +203,7 @@ request body exceeds PROFANITY threshold === TEST 6: very profane request should also fail --- request POST /echo -{"model":"gpt-4o-mini","messages":[{"role":"user","content":"very_profane"}]} +very_profane --- error_code: 400 --- response_body chomp request body exceeds PROFANITY threshold @@ -219,7 +213,7 @@ request body exceeds PROFANITY threshold === TEST 7: good_request should pass --- request POST /echo -{"model":"gpt-4o-mini","messages":[{"role":"user","content":"good_request"}]} +good_request --- error_code: 200 @@ -234,19 +228,16 @@ POST /echo [[{ "uri": "/echo", "plugins": { - "ai-content-moderation": { - "provider": { - "aws_comprehend": { - "access_key_id": "access", - "secret_access_key": "ea+secret", - "region": "us-east-1", - "endpoint": "http://localhost:2668" - } + "ai-aws-content-moderation": { + "comprehend": { + "access_key_id": "access", + "secret_access_key": "ea+secret", + "region": "us-east-1", + "endpoint": "http://localhost:2668" }, "moderation_categories": { "PROFANITY": 0.7 - }, - "llm_provider": "openai" + } } }, "upstream": { @@ -272,7 +263,7 @@ passed === TEST 9: profane request should pass profanity check but fail toxicity check --- request POST /echo -{"model":"gpt-4o-mini","messages":[{"role":"user","content":"profane"}]} +profane --- error_code: 400 --- response_body chomp request body exceeds toxicity threshold @@ -282,7 +273,7 @@ request body exceeds toxicity threshold === TEST 10: profane_but_not_toxic request should pass --- request POST /echo -{"model":"gpt-4o-mini","messages":[{"role":"user","content":"profane_but_not_toxic"}]} +profane_but_not_toxic --- error_code: 200 @@ -290,7 +281,7 @@ POST /echo === TEST 11: but very profane request will fail --- request POST /echo -{"model":"gpt-4o-mini","messages":[{"role":"user","content":"very_profane"}]} +very_profane --- error_code: 400 --- response_body chomp request body exceeds PROFANITY threshold @@ -300,5 +291,5 @@ request body exceeds PROFANITY threshold === TEST 12: good_request should pass --- request POST /echo -{"model":"gpt-4o-mini","messages":[{"role":"user","content":"good_request"}]} +good_request --- error_code: 200 diff --git a/t/plugin/ai-content-moderation2.t b/t/plugin/ai-aws-content-moderation2.t similarity index 81% rename from t/plugin/ai-content-moderation2.t rename to t/plugin/ai-aws-content-moderation2.t index f509b1864..93897017d 100644 --- a/t/plugin/ai-content-moderation2.t +++ b/t/plugin/ai-aws-content-moderation2.t @@ -45,14 +45,12 @@ __DATA__ [[{ "uri": "/echo", "plugins": { - "ai-content-moderation": { - "provider": { - "aws_comprehend": { - "access_key_id": "access", - "secret_access_key": "ea+secret", - "region": "us-east-1", - "endpoint": "http://localhost:2668" - } + "ai-aws-content-moderation": { + "comprehend": { + "access_key_id": "access", + "secret_access_key": "ea+secret", + "region": "us-east-1", + "endpoint": "http://localhost:2668" }, "llm_provider": "openai" } @@ -80,7 +78,7 @@ passed === TEST 2: request should fail --- request POST /echo -{"model":"gpt-4o-mini","messages":[{"role":"user","content":"toxic"}]} +toxic --- error_code: 500 --- response_body chomp Comprehend:detectToxicContent() failed to connect to 'http://localhost:2668': connection refused