Copilot commented on code in PR #13207: URL: https://github.com/apache/apisix/pull/13207#discussion_r3076969248
########## docs/en/latest/plugins/ai-prompt-guard.md: ########## @@ -27,63 +27,606 @@ description: This document contains information about the Apache APISIX ai-promp # --> +<head> + <link rel="canonical" href="https://docs.api7.ai/hub/ai-prompt-guard" /> +</head> + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + ## Description -The `ai-prompt-guard` plugin safeguards your AI endpoints by inspecting and validating incoming prompt messages. It checks the content of requests against user-defined allowed and denied patterns to ensure that only approved inputs are processed. Based on its configuration, the plugin can either examine just the latest message or the entire conversation history, and it can be set to check prompts from all roles or only from end users. +The `ai-prompt-guard` Plugin safeguards your LLM endpoints by inspecting and validating incoming prompt messages. It checks the content of requests against user-defined allowed and denied patterns to ensure that only approved inputs are forwarded to upstream LLM. Based on its configuration, the Plugin can either examine just the latest message or the entire conversation history, and it can be set to check prompts from all roles or only from end users. Review Comment: Minor grammar: “forwarded to upstream LLM” is missing an article and reads awkwardly. Consider rephrasing to “forwarded to the upstream LLM” or “forwarded to an upstream LLM” for clarity. ```suggestion The `ai-prompt-guard` Plugin safeguards your LLM endpoints by inspecting and validating incoming prompt messages. It checks the content of requests against user-defined allowed and denied patterns to ensure that only approved inputs are forwarded to the upstream LLM. Based on its configuration, the Plugin can either examine just the latest message or the entire conversation history, and it can be set to check prompts from all roles or only from end users. ``` ########## docs/en/latest/plugins/ai-prompt-guard.md: ########## @@ -27,63 +27,606 @@ description: This document contains information about the Apache APISIX ai-promp # --> +<head> + <link rel="canonical" href="https://docs.api7.ai/hub/ai-prompt-guard" /> +</head> + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + ## Description -The `ai-prompt-guard` plugin safeguards your AI endpoints by inspecting and validating incoming prompt messages. It checks the content of requests against user-defined allowed and denied patterns to ensure that only approved inputs are processed. Based on its configuration, the plugin can either examine just the latest message or the entire conversation history, and it can be set to check prompts from all roles or only from end users. +The `ai-prompt-guard` Plugin safeguards your LLM endpoints by inspecting and validating incoming prompt messages. It checks the content of requests against user-defined allowed and denied patterns to ensure that only approved inputs are forwarded to upstream LLM. Based on its configuration, the Plugin can either examine just the latest message or the entire conversation history, and it can be set to check prompts from all roles or only from end users. -When both **allow** and **deny** patterns are configured, the plugin first ensures that at least one allowed pattern is matched. If none match, the request is rejected with a _"Request doesn't match allow patterns"_ error. If an allowed pattern is found, it then checks for any occurrences of denied patterns—rejecting the request with a _"Request contains prohibited content"_ error if any are detected. +When both `allow_patterns` and `deny_patterns` are configured, the Plugin first ensures that at least one `allow_patterns` is matched. If none match, the request is rejected. If an allowed pattern is matched, it then checks for any occurrences of denied patterns. ## Plugin Attributes -| **Field** | **Required** | **Type** | **Description** | -| ------------------------------ | ------------ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| match_all_roles | No | boolean | If set to `true`, the plugin will check prompt messages from all roles. Otherwise, it only validates when its role is `"user"`. Default is `false`. | -| match_all_conversation_history | No | boolean | When enabled, all messages in the conversation history are concatenated and checked. If `false`, only the content of the last message is examined. Default is `false`. | -| allow_patterns | No | array | A list of regex patterns. When provided, the prompt must match **at least one** pattern to be considered valid. | -| deny_patterns | No | array | A list of regex patterns. If any of these patterns match the prompt content, the request is rejected. | +| **Field** | **Required** | **Type** | **Description** | +| --- | --- | --- | --- | +| `match_all_roles` | False | boolean | If `true`, validate messages from all roles. If `false`, validate the message from `user` role only. Default: `false`. | +| `match_all_conversation_history` | False | boolean | If `true`, concatenate and check all messages in the conversation history. If `false`, only check the content of the last message. Default: `false`. | +| `allow_patterns` | False | array | An array of regex patterns that messages should match. When configured, messages must match at least one pattern to be considered valid. Default: `[]`. | +| `deny_patterns` | False | array | An array of regex patterns that messages should not match. If messages match any of the patterns, the request is rejected. If both `allow_patterns` and `deny_patterns` are configured, the Plugin first ensures that at least one `allow_patterns` is matched. Default: `[]`. | + +## Examples + +The following examples will be using OpenAI as the Upstream service provider. Before proceeding, create an [OpenAI account](https://openai.com) and an [API key](https://openai.com/blog/openai-api). You can optionally save the key to an environment variable as such: + +```shell +export OPENAI_API_KEY=<YOUR_OPENAI_API_KEY> +``` + +If you are working with other LLM providers, please refer to the provider's documentation to obtain an API key. + +### Implement Allow and Deny Patterns + +The following example demonstrates how to use the `ai-prompt-guard` Plugin to validate user prompts by defining both allow and deny patterns and understand how the allow pattern takes precedence. + +Define the allow and deny patterns. You can optionally save them to environment variables for easier escape: + +```shell +# allow US dollar amount +export ALLOW_PATTERN_1='\\$?\\(?\\d{1,3}(,\\d{3})*(\\.\\d{1,2})?\\)?' +# deny phone number in US number format +export DENY_PATTERN_1='(\\([0-9]{3}\\)|[0-9]{3}-)[0-9]{3}-[0-9]{4}' +``` + +:::note + +You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command: + +```shell +admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g') +``` + +::: -## Example usage +<Tabs groupId="api"> +<TabItem value="admin-api" label="Admin API"> -Create a route with the `ai-prompt-guard` plugin like so: +Create a Route that uses [ai-proxy](./ai-proxy.md) to proxy to OpenAI and `ai-prompt-guard` to inspect input prompts: ```shell curl "http://127.0.0.1:9180/apisix/admin/routes/1" -X PUT \ - -H "X-API-KEY: ${ADMIN_API_KEY}" \ + -H "X-API-KEY: ${admin_key}" \ -d '{ - "uri": "/v1/chat/completions", + "uri": "/anything", + "methods": ["POST"], "plugins": { + "ai-proxy": { + "provider": "openai", + "auth": { + "header": { + "Authorization": "Bearer '"$OPENAI_API_KEY"'" + } + }, + "options":{ + "model": "gpt-4" + } + }, "ai-prompt-guard": { - "match_all_roles": true, - "allow_patterns": [ - "goodword" - ], + "allow_patterns": [ + "'"$ALLOW_PATTERN_1"'" + ], "deny_patterns": [ - "badword" + "'"$DENY_PATTERN_1"'" ] - } - }, - "upstream": { - "type": "roundrobin", - "nodes": { - "api.openai.com:443": 1 - }, - "pass_host": "node", - "scheme": "https" + } } }' ``` -Now send a request: +</TabItem> +<TabItem value="adc" label="ADC"> + +Create a Route with the `ai-prompt-guard` and [ai-proxy](./ai-proxy.md) Plugins configured as such: + +```yaml title="adc.yaml" +services: + - name: prompt-guard-service + routes: + - name: prompt-guard-route + uris: + - /anything + methods: + - POST + plugins: + ai-proxy: + provider: openai + auth: + header: + Authorization: "Bearer ${OPENAI_API_KEY}" + options: + model: gpt-4 + ai-prompt-guard: + allow_patterns: + - '\$?\(?\d{1,3}(,\d{3})*(\.\d{1,2})?\)?' + deny_patterns: + - '(\([0-9]{3}\)|[0-9]{3}-)[0-9]{3}-[0-9]{4}' +``` + +Synchronize the configuration to the gateway: ```shell -curl http://127.0.0.1:9080/v1/chat/completions -i -XPOST -H 'Content-Type: application/json' -d '{ - "model": "gpt-4", - "messages": [{ "role": "user", "content": "badword request" }] -}' -H "Authorization: Bearer <your token here>" +adc sync -f adc.yaml +``` + +</TabItem> +<TabItem value="ingress" label="Ingress Controller"> + +<Tabs groupId="k8s-api"> +<TabItem value="gateway-api" label="Gateway API"> + +Create a Route with the `ai-prompt-guard` and [ai-proxy](./ai-proxy.md) Plugins configured as such: + +```yaml title="ai-prompt-guard-ic.yaml" +apiVersion: apisix.apache.org/v1alpha1 +kind: PluginConfig +metadata: + namespace: aic + name: ai-prompt-guard-plugin-config +spec: + plugins: + - name: ai-prompt-guard + config: + allow_patterns: + - '\$?\(?\d{1,3}(,\d{3})*(\.\d{1,2})?\)?' + deny_patterns: + - '(\([0-9]{3}\)|[0-9]{3}-)[0-9]{3}-[0-9]{4}' + - name: ai-proxy + config: + provider: openai + auth: + header: + Authorization: "Bearer your-api-key" + options: + model: gpt-4 +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + namespace: aic + name: prompt-guard-route +spec: + parentRefs: + - name: apisix + rules: + - matches: + - path: + type: Exact + value: /anything + method: POST + filters: + - type: ExtensionRef + extensionRef: + group: apisix.apache.org + kind: PluginConfig + name: ai-prompt-guard-plugin-config +``` + +</TabItem> +<TabItem value="ingress" label="APISIX Ingress Controller"> + +Create a Route with the `ai-prompt-guard` and [ai-proxy](./ai-proxy.md) Plugins configured as such: + +```yaml title="ai-prompt-guard-ic.yaml" +apiVersion: apisix.apache.org/v2 +kind: ApisixRoute +metadata: + namespace: aic + name: prompt-guard-route +spec: + ingressClassName: apisix + http: + - name: prompt-guard-route + match: + paths: + - /anything + methods: + - POST + plugins: + - name: ai-prompt-guard + enable: true + config: + allow_patterns: + - '\$?\(?\d{1,3}(,\d{3})*(\.\d{1,2})?\)?' + deny_patterns: + - '(\([0-9]{3}\)|[0-9]{3}-)[0-9]{3}-[0-9]{4}' + - name: ai-proxy + enable: true + config: + provider: openai + auth: + header: + Authorization: "Bearer your-api-key" + options: + model: gpt-4 ``` -The request will fail with 400 error and following response. +</TabItem> +</Tabs> + +Apply the configuration to your cluster: -```bash +```shell +kubectl apply -f ai-prompt-guard-ic.yaml +``` + +</TabItem> +</Tabs> + +Send a request to the Route to rate the fairness of a purchase: + +```shell +curl -i "http://127.0.0.1:9080/anything" -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "messages": [ + { "role": "system", "content": "Rate if the purchase is at a decent price in USD." }, + { "role": "user", "content": "John paid $12.5 for a hot brewed coffee in El Paso." } + ] + }' +``` + +You should receive an `HTTP/1.1 200 OK` response similar to the following: + +```json +{ + ... + "model": "gpt-4-0613", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "The purchase is not at a decent price. Typically, a hot brewed coffee costs anywhere from $1 to $3 in most places in the US, so $12.5 is quite expensive.", + "refusal": null + }, + "logprobs": null, + "finish_reason": "stop" + } + ], + ... +} +``` + +Send another request to the Route without any price in the message: + +```shell +curl -i "http://127.0.0.1:9080/anything" -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "messages": [ + { "role": "system", "content": "Rate if the purchase is at a decent price in USD." }, + { "role": "user", "content": "John paid a bit for a hot brewed coffee in El Paso." } + ] + }' +``` + +You should receive an `HTTP/1.1 400 Bad Request` response and see the following message: + +```text {"message":"Request doesn't match allow patterns"} ``` + +Send a third request to the Route with a phone number in the message: + +```shell +curl -i "http://127.0.0.1:9080/anything" -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "messages": [ + { "role": "system", "content": "Rate if the purchase is at a decent price in USD." }, + { "role": "user", "content": "John (647-200-9393) paid $12.5 for a hot brewed coffee in El Paso." } + ] + }' +``` + +You should receive an `HTTP/1.1 400 Bad Request` response and see the following message: + +```text +{"message":"Request contains prohibited content"} +``` + +By default, the Plugin only inspects the input of `user` role and the last message. For instance, if you send a request including the prohibited content in the `system` prompt: + +```shell +curl -i "http://127.0.0.1:9080/anything" -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "messages": [ + { "role": "system", "content": "Rate if the purchase from 647-200-9393 is at a decent price in USD." }, + { "role": "user", "content": "John paid $12.5 for a hot brewed coffee in El Paso." } + ] + }' +``` + +You will receive an `HTTP/1.1 200 OK` response. + +If you send a request including the prohibited content in the second last message: + +```shell +curl -i "http://127.0.0.1:9080/anything" -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "messages": [ + { "role": "system", "content": "Rate if the purchase is at a decent price in USD." }, + { "role": "user", "content": "Customer John contact: 647-200-9393" }, + { "role": "user", "content": "John paid $12.5 for a hot brewed coffee in El Paso." } + ] + }' +``` + +You will also receive an `HTTP/1.1 200 OK` response. + +See the [next example](#validate-messages-from-all-roles-and-conversation-history) to see how to inspect messages from all roles and all messages. + +### Validate Messages From All Roles and Conversation History + +The following example demonstrates how to use the `ai-prompt-guard` Plugin to validate prompts from all roles, such as `system` and `user`, and validate the entire conversation history instead of the last message. + +Define the allow and deny patterns. You can optionally save them to environment variables for easier escape: + +```shell +export ALLOW_PATTERN_1='\\$?\\(?\\d{1,3}(,\\d{3})*(\\.\\d{1,2})?\\)?' +export DENY_PATTERN_1='(\\([0-9]{3}\\)|[0-9]{3}-)[0-9]{3}-[0-9]{4}' +``` + +<Tabs groupId="api"> +<TabItem value="admin-api" label="Admin API"> + +Create a Route that uses [ai-proxy](./ai-proxy.md) to proxy to OpenAI and `ai-prompt-guard` to inspect input prompts. Set `match_all_roles` and `match_all_conversation_history` to `true` to validate messages from all roles and the entire conversation: + +```shell +curl "http://127.0.0.1:9180/apisix/admin/routes/1" -X PUT \ + -H "X-API-KEY: ${admin_key}" \ + -d '{ + "uri": "/anything", + "methods": ["POST"], + "plugins": { + "ai-proxy": { + "provider": "openai", + "auth": { + "header": { + "Authorization": "Bearer '"$OPENAI_API_KEY"'" + } + }, + "options":{ + "model": "gpt-4" + } + }, + "ai-prompt-guard": { + "match_all_roles": true, + "match_all_conversation_history": true, + "allow_patterns": [ + "'"$ALLOW_PATTERN_1"'" + ], + "deny_patterns": [ + "'"$DENY_PATTERN_1"'" + ] + } + } + }' +``` + +</TabItem> +<TabItem value="adc" label="ADC"> + +Create a Route with the `ai-prompt-guard` and [ai-proxy](./ai-proxy.md) Plugins configured as such. Set `match_all_roles` and `match_all_conversation_history` to `true` to validate messages from all roles and the entire conversation: + +```yaml title="adc.yaml" +services: + - name: prompt-guard-service + routes: + - name: prompt-guard-route + uris: + - /anything + methods: + - POST + plugins: + ai-proxy: + provider: openai + auth: + header: + Authorization: "Bearer ${OPENAI_API_KEY}" + options: + model: gpt-4 + ai-prompt-guard: + match_all_roles: true + match_all_conversation_history: true + allow_patterns: + - '\$?\(?\d{1,3}(,\d{3})*(\.\d{1,2})?\)?' + deny_patterns: + - '(\([0-9]{3}\)|[0-9]{3}-)[0-9]{3}-[0-9]{4}' +``` + +Synchronize the configuration to the gateway: + +```shell +adc sync -f adc.yaml +``` + +</TabItem> +<TabItem value="ingress" label="Ingress Controller"> + +<Tabs groupId="k8s-api"> +<TabItem value="gateway-api" label="Gateway API"> + +Create a Route with the `ai-prompt-guard` and [ai-proxy](./ai-proxy.md) Plugins configured as such. Set `match_all_roles` and `match_all_conversation_history` to `true` to validate messages from all roles and the entire conversation: + +```yaml title="ai-prompt-guard-history-ic.yaml" +apiVersion: apisix.apache.org/v1alpha1 +kind: PluginConfig +metadata: + namespace: aic + name: ai-prompt-guard-plugin-config +spec: + plugins: + - name: ai-prompt-guard + config: + match_all_roles: true + match_all_conversation_history: true + allow_patterns: + - '\$?\(?\d{1,3}(,\d{3})*(\.\d{1,2})?\)?' + deny_patterns: + - '(\([0-9]{3}\)|[0-9]{3}-)[0-9]{3}-[0-9]{4}' + - name: ai-proxy + config: + provider: openai + auth: + header: + Authorization: "Bearer your-api-key" + options: + model: gpt-4 +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + namespace: aic + name: prompt-guard-route +spec: + parentRefs: + - name: apisix + rules: + - matches: + - path: + type: Exact + value: /anything + method: POST + filters: + - type: ExtensionRef + extensionRef: + group: apisix.apache.org + kind: PluginConfig + name: ai-prompt-guard-plugin-config +``` + +</TabItem> +<TabItem value="ingress" label="APISIX Ingress Controller"> + +Create a Route with the `ai-prompt-guard` and [ai-proxy](./ai-proxy.md) Plugins configured as such. Set `match_all_roles` and `match_all_conversation_history` to `true` to validate messages from all roles and the entire conversation: + +```yaml title="ai-prompt-guard-history-ic.yaml" +apiVersion: apisix.apache.org/v2 +kind: ApisixRoute +metadata: + namespace: aic + name: prompt-guard-route +spec: + ingressClassName: apisix + http: + - name: prompt-guard-route + match: + paths: + - /anything + methods: + - POST + plugins: + - name: ai-prompt-guard + enable: true + config: + match_all_roles: true + match_all_conversation_history: true + allow_patterns: + - '\$?\(?\d{1,3}(,\d{3})*(\.\d{1,2})?\)?' + deny_patterns: + - '(\([0-9]{3}\)|[0-9]{3}-)[0-9]{3}-[0-9]{4}' + - name: ai-proxy + enable: true + config: + provider: openai + auth: + header: + Authorization: "Bearer your-api-key" + options: + model: gpt-4 +``` + +</TabItem> +</Tabs> + +Apply the configuration to your cluster: + +```shell +kubectl apply -f ai-prompt-guard-history-ic.yaml +``` + +</TabItem> +</Tabs> + +Send a request with prohibited content in the `system` prompt: + +```shell +curl -i "http://127.0.0.1:9080/anything" -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "messages": [ + { "role": "system", "content": "Rate if the purchase from 647-200-9393 is at a decent price in USD." }, + { "role": "user", "content": "John paid $12.5 for a hot brewed coffee in El Paso." } + ] + }' +``` + +You should receive an `HTTP/1.1 400 Bad Request` response and see the following message: + +```text +{"message":"Request contains prohibited content"} +``` + +Send a request with multiple messages from the same role with prohibited content: + +```shell +curl -i "http://127.0.0.1:9080/anything" -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "messages": [ + { "role": "system", "content": "Rate if the purchase is at a decent price in USD." }, + { "role": "user", "content": "Customer John contact: 647-200-9393" }, + { "role": "user", "content": "John paid $12.5 for a hot brewed coffee in El Paso." } + ] + }' +``` + +You should receive an `HTTP/1.1 400 Bad Request` response and see the following message: + +```text +{"message":"Request contains prohibited content"} +``` + +Send a request that conforms to the patterns: + +```shell +curl -i "http://127.0.0.1:9080/anything" -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "messages": [ + { "role": "system", "content": "Rate if the purchase is at a decent price in USD." }, + { "role": "system", "content": "The puchase is made in El Paso." }, Review Comment: In the sample request body, `The puchase is made in El Paso.` contains a typo (`puchase`). Please correct it to `purchase` to keep the example professional and searchable. ```suggestion { "role": "system", "content": "The purchase is made in El Paso." }, ``` ########## docs/zh/latest/plugins/ai-prompt-guard.md: ########## @@ -27,63 +27,606 @@ description: 本文档包含有关 Apache APISIX ai-prompt-guard 插件的信息 # --> +<head> + <link rel="canonical" href="https://docs.api7.ai/hub/ai-prompt-guard" /> +</head> + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + ## 描述 -`ai-prompt-guard` 插件通过检查和验证传入的提示消息来保护您的 AI 端点。它根据用户定义的允许和拒绝模式检查请求内容,确保只有经过批准的输入才会被处理。根据其配置,插件可以检查最新消息或整个对话历史,并且可以设置为检查所有角色的提示或仅检查最终用户的提示。 +`ai-prompt-guard` 插件通过检查和验证传入的提示消息来保护您的 LLM 端点。它根据用户定义的允许和拒绝模式检查请求内容,确保只有经过批准的输入才会被转发到上游 LLM。根据其配置,该插件可以仅检查最新消息或整个对话历史,并且可以设置为检查所有角色的提示或仅检查最终用户的提示。 -当同时配置了**允许**和**拒绝**模式时,插件首先确保至少匹配一个允许的模式。如果没有匹配,请求将被拒绝并返回 _"Request doesn't match allow patterns"_ 错误。如果找到允许的模式,它会检查是否存在任何拒绝模式的匹配——如果检测到任何匹配,则拒绝请求并返回 _"Request contains prohibited content"_ 错误。 +当同时配置了 `allow_patterns` 和 `deny_patterns` 时,插件首先确保至少匹配一个 `allow_patterns`。如果没有匹配,请求将被拒绝。如果匹配了允许的模式,它会继续检查是否存在任何拒绝模式的匹配。 ## 插件属性 -| **字段** | **必选项** | **类型** | **描述** | -| ------------------------------ | ---------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| match_all_roles | 否 | boolean | 如果设置为 `true`,插件将检查所有角色的提示消息。否则,它只在角色为 `"user"` 时进行验证。默认值为 `false`。 | -| match_all_conversation_history | 否 | boolean | 启用时,对话历史中的所有消息将被连接并检查。如果为 `false`,则只检查最后一条消息的内容。默认值为 `false`。 | -| allow_patterns | 否 | array | 正则表达式模式列表。提供时,提示必须匹配**至少一个**模式才被认为是有效的。 | -| deny_patterns | 否 | array | 正则表达式模式列表。如果任何这些模式匹配提示内容,请求将被拒绝。 | +| **字段** | **必选** | **类型** | **描述** | +| --- | --- | --- | --- | +| `match_all_roles` | False | boolean | 如果为 `true`,验证所有角色的消息。如果为 `false`,仅验证 `user` 角色的消息。默认值:`false`。 | +| `match_all_conversation_history` | False | boolean | 如果为 `true`,连接并检查对话历史中的所有消息。如果为 `false`,仅检查最后一条消息的内容。默认值:`false`。 | +| `allow_patterns` | False | array | 消息应匹配的正则表达式模式数组。配置后,消息必须至少匹配一个模式才被视为有效。默认值:`[]`。 | +| `deny_patterns` | False | array | 消息不应匹配的正则表达式模式数组。如果消息匹配任何模式,请求将被拒绝。如果同时配置了 `allow_patterns` 和 `deny_patterns`,插件会首先确保至少匹配一个 `allow_patterns`。默认值:`[]`。 | Review Comment: 中文文档的插件属性表中,“必选”一列使用了英文 `False`(例如此处 4 行都是 `False`),这与仓库内中文文档表格普遍使用“是/否”(例如 docs/zh/latest/plugins/ai-proxy.md 的表格)不一致,也会让读者误解为布尔值而非“是否必填”。建议将该列的值改为“否”(并按现有文档习惯统一列名/取值格式)。 ```suggestion | `match_all_roles` | 否 | boolean | 如果为 `true`,验证所有角色的消息。如果为 `false`,仅验证 `user` 角色的消息。默认值:`false`。 | | `match_all_conversation_history` | 否 | boolean | 如果为 `true`,连接并检查对话历史中的所有消息。如果为 `false`,仅检查最后一条消息的内容。默认值:`false`。 | | `allow_patterns` | 否 | array | 消息应匹配的正则表达式模式数组。配置后,消息必须至少匹配一个模式才被视为有效。默认值:`[]`。 | | `deny_patterns` | 否 | array | 消息不应匹配的正则表达式模式数组。如果消息匹配任何模式,请求将被拒绝。如果同时配置了 `allow_patterns` 和 `deny_patterns`,插件会首先确保至少匹配一个 `allow_patterns`。默认值:`[]`。 | ``` ########## docs/zh/latest/plugins/ai-prompt-guard.md: ########## @@ -27,63 +27,606 @@ description: 本文档包含有关 Apache APISIX ai-prompt-guard 插件的信息 # --> +<head> + <link rel="canonical" href="https://docs.api7.ai/hub/ai-prompt-guard" /> +</head> + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + ## 描述 -`ai-prompt-guard` 插件通过检查和验证传入的提示消息来保护您的 AI 端点。它根据用户定义的允许和拒绝模式检查请求内容,确保只有经过批准的输入才会被处理。根据其配置,插件可以检查最新消息或整个对话历史,并且可以设置为检查所有角色的提示或仅检查最终用户的提示。 +`ai-prompt-guard` 插件通过检查和验证传入的提示消息来保护您的 LLM 端点。它根据用户定义的允许和拒绝模式检查请求内容,确保只有经过批准的输入才会被转发到上游 LLM。根据其配置,该插件可以仅检查最新消息或整个对话历史,并且可以设置为检查所有角色的提示或仅检查最终用户的提示。 -当同时配置了**允许**和**拒绝**模式时,插件首先确保至少匹配一个允许的模式。如果没有匹配,请求将被拒绝并返回 _"Request doesn't match allow patterns"_ 错误。如果找到允许的模式,它会检查是否存在任何拒绝模式的匹配——如果检测到任何匹配,则拒绝请求并返回 _"Request contains prohibited content"_ 错误。 +当同时配置了 `allow_patterns` 和 `deny_patterns` 时,插件首先确保至少匹配一个 `allow_patterns`。如果没有匹配,请求将被拒绝。如果匹配了允许的模式,它会继续检查是否存在任何拒绝模式的匹配。 ## 插件属性 -| **字段** | **必选项** | **类型** | **描述** | -| ------------------------------ | ---------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| match_all_roles | 否 | boolean | 如果设置为 `true`,插件将检查所有角色的提示消息。否则,它只在角色为 `"user"` 时进行验证。默认值为 `false`。 | -| match_all_conversation_history | 否 | boolean | 启用时,对话历史中的所有消息将被连接并检查。如果为 `false`,则只检查最后一条消息的内容。默认值为 `false`。 | -| allow_patterns | 否 | array | 正则表达式模式列表。提供时,提示必须匹配**至少一个**模式才被认为是有效的。 | -| deny_patterns | 否 | array | 正则表达式模式列表。如果任何这些模式匹配提示内容,请求将被拒绝。 | +| **字段** | **必选** | **类型** | **描述** | +| --- | --- | --- | --- | +| `match_all_roles` | False | boolean | 如果为 `true`,验证所有角色的消息。如果为 `false`,仅验证 `user` 角色的消息。默认值:`false`。 | +| `match_all_conversation_history` | False | boolean | 如果为 `true`,连接并检查对话历史中的所有消息。如果为 `false`,仅检查最后一条消息的内容。默认值:`false`。 | +| `allow_patterns` | False | array | 消息应匹配的正则表达式模式数组。配置后,消息必须至少匹配一个模式才被视为有效。默认值:`[]`。 | +| `deny_patterns` | False | array | 消息不应匹配的正则表达式模式数组。如果消息匹配任何模式,请求将被拒绝。如果同时配置了 `allow_patterns` 和 `deny_patterns`,插件会首先确保至少匹配一个 `allow_patterns`。默认值:`[]`。 | ## 使用示例 -创建一个带有 `ai-prompt-guard` 插件的路由,如下所示: +以下示例将使用 OpenAI 作为上游服务提供商。在继续之前,请创建一个 [OpenAI 账户](https://openai.com)和一个 [API 密钥](https://openai.com/blog/openai-api)。您可以选择将密钥保存到环境变量中: + +```shell +export OPENAI_API_KEY=<YOUR_OPENAI_API_KEY> +``` + +如果您使用其他 LLM 提供商,请参阅提供商的文档以获取 API 密钥。 + +### 实现允许和拒绝模式 + +以下示例演示了如何使用 `ai-prompt-guard` 插件通过定义允许和拒绝模式来验证用户提示,以及如何理解允许模式的优先级。 + +定义允许和拒绝模式。您可以选择将它们保存到环境变量中以便于转义: + +```shell +# 允许美元金额 +export ALLOW_PATTERN_1='\\$?\\(?\\d{1,3}(,\\d{3})*(\\.\\d{1,2})?\\)?' +# 拒绝美国电话号码格式 +export DENY_PATTERN_1='(\\([0-9]{3}\\)|[0-9]{3}-)[0-9]{3}-[0-9]{4}' +``` + +:::note + +您可以使用以下命令从 `config.yaml` 中获取 `admin_key` 并保存到环境变量中: + +```shell +admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g') +``` + +::: + +<Tabs groupId="api"> +<TabItem value="admin-api" label="Admin API"> + +创建一个路由,使用 [ai-proxy](./ai-proxy.md) 代理到 OpenAI 并使用 `ai-prompt-guard` 检查输入提示: ```shell curl "http://127.0.0.1:9180/apisix/admin/routes/1" -X PUT \ - -H "X-API-KEY: ${ADMIN_API_KEY}" \ + -H "X-API-KEY: ${admin_key}" \ -d '{ - "uri": "/v1/chat/completions", + "uri": "/anything", + "methods": ["POST"], "plugins": { + "ai-proxy": { + "provider": "openai", + "auth": { + "header": { + "Authorization": "Bearer '"$OPENAI_API_KEY"'" + } + }, + "options":{ + "model": "gpt-4" + } + }, "ai-prompt-guard": { - "match_all_roles": true, - "allow_patterns": [ - "goodword" - ], + "allow_patterns": [ + "'"$ALLOW_PATTERN_1"'" + ], "deny_patterns": [ - "badword" + "'"$DENY_PATTERN_1"'" ] - } - }, - "upstream": { - "type": "roundrobin", - "nodes": { - "api.openai.com:443": 1 - }, - "pass_host": "node", - "scheme": "https" + } } }' ``` -现在发送一个请求: +</TabItem> +<TabItem value="adc" label="ADC"> + +创建一个配置了 `ai-prompt-guard` 和 [ai-proxy](./ai-proxy.md) 插件的路由: + +```yaml title="adc.yaml" +services: + - name: prompt-guard-service + routes: + - name: prompt-guard-route + uris: + - /anything + methods: + - POST + plugins: + ai-proxy: + provider: openai + auth: + header: + Authorization: "Bearer ${OPENAI_API_KEY}" + options: + model: gpt-4 + ai-prompt-guard: + allow_patterns: + - '\$?\(?\d{1,3}(,\d{3})*(\.\d{1,2})?\)?' + deny_patterns: + - '(\([0-9]{3}\)|[0-9]{3}-)[0-9]{3}-[0-9]{4}' +``` + +将配置同步到网关: ```shell -curl http://127.0.0.1:9080/v1/chat/completions -i -XPOST -H 'Content-Type: application/json' -d '{ - "model": "gpt-4", - "messages": [{ "role": "user", "content": "badword request" }] -}' -H "Authorization: Bearer <your token here>" +adc sync -f adc.yaml +``` + +</TabItem> +<TabItem value="ingress" label="Ingress Controller"> + +<Tabs groupId="k8s-api"> +<TabItem value="gateway-api" label="Gateway API"> + +创建一个配置了 `ai-prompt-guard` 和 [ai-proxy](./ai-proxy.md) 插件的路由: + +```yaml title="ai-prompt-guard-ic.yaml" +apiVersion: apisix.apache.org/v1alpha1 +kind: PluginConfig +metadata: + namespace: aic + name: ai-prompt-guard-plugin-config +spec: + plugins: + - name: ai-prompt-guard + config: + allow_patterns: + - '\$?\(?\d{1,3}(,\d{3})*(\.\d{1,2})?\)?' + deny_patterns: + - '(\([0-9]{3}\)|[0-9]{3}-)[0-9]{3}-[0-9]{4}' + - name: ai-proxy + config: + provider: openai + auth: + header: + Authorization: "Bearer your-api-key" + options: + model: gpt-4 +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + namespace: aic + name: prompt-guard-route +spec: + parentRefs: + - name: apisix + rules: + - matches: + - path: + type: Exact + value: /anything + method: POST + filters: + - type: ExtensionRef + extensionRef: + group: apisix.apache.org + kind: PluginConfig + name: ai-prompt-guard-plugin-config +``` + +</TabItem> +<TabItem value="ingress" label="APISIX Ingress Controller"> + +创建一个配置了 `ai-prompt-guard` 和 [ai-proxy](./ai-proxy.md) 插件的路由: + +```yaml title="ai-prompt-guard-ic.yaml" +apiVersion: apisix.apache.org/v2 +kind: ApisixRoute +metadata: + namespace: aic + name: prompt-guard-route +spec: + ingressClassName: apisix + http: + - name: prompt-guard-route + match: + paths: + - /anything + methods: + - POST + plugins: + - name: ai-prompt-guard + enable: true + config: + allow_patterns: + - '\$?\(?\d{1,3}(,\d{3})*(\.\d{1,2})?\)?' + deny_patterns: + - '(\([0-9]{3}\)|[0-9]{3}-)[0-9]{3}-[0-9]{4}' + - name: ai-proxy + enable: true + config: + provider: openai + auth: + header: + Authorization: "Bearer your-api-key" + options: + model: gpt-4 ``` -请求将失败并返回 400 错误和以下响应。 +</TabItem> +</Tabs> + +将配置应用到集群: -```bash +```shell +kubectl apply -f ai-prompt-guard-ic.yaml +``` + +</TabItem> +</Tabs> + +向路由发送一个请求,评估购买的公平性: + +```shell +curl -i "http://127.0.0.1:9080/anything" -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "messages": [ + { "role": "system", "content": "Rate if the purchase is at a decent price in USD." }, + { "role": "user", "content": "John paid $12.5 for a hot brewed coffee in El Paso." } + ] + }' +``` + +您应该收到一个 `HTTP/1.1 200 OK` 响应,类似如下: + +```json +{ + ... + "model": "gpt-4-0613", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "The purchase is not at a decent price. Typically, a hot brewed coffee costs anywhere from $1 to $3 in most places in the US, so $12.5 is quite expensive.", + "refusal": null + }, + "logprobs": null, + "finish_reason": "stop" + } + ], + ... +} +``` + +发送另一个不包含任何价格的请求: + +```shell +curl -i "http://127.0.0.1:9080/anything" -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "messages": [ + { "role": "system", "content": "Rate if the purchase is at a decent price in USD." }, + { "role": "user", "content": "John paid a bit for a hot brewed coffee in El Paso." } + ] + }' +``` + +您应该收到一个 `HTTP/1.1 400 Bad Request` 响应,并看到以下消息: + +```text {"message":"Request doesn't match allow patterns"} ``` + +发送第三个包含电话号码的请求: + +```shell +curl -i "http://127.0.0.1:9080/anything" -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "messages": [ + { "role": "system", "content": "Rate if the purchase is at a decent price in USD." }, + { "role": "user", "content": "John (647-200-9393) paid $12.5 for a hot brewed coffee in El Paso." } + ] + }' +``` + +您应该收到一个 `HTTP/1.1 400 Bad Request` 响应,并看到以下消息: + +```text +{"message":"Request contains prohibited content"} +``` + +默认情况下,插件仅检查 `user` 角色的输入和最后一条消息。例如,如果您发送一个在 `system` 提示中包含禁止内容的请求: + +```shell +curl -i "http://127.0.0.1:9080/anything" -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "messages": [ + { "role": "system", "content": "Rate if the purchase from 647-200-9393 is at a decent price in USD." }, + { "role": "user", "content": "John paid $12.5 for a hot brewed coffee in El Paso." } + ] + }' +``` + +您将收到一个 `HTTP/1.1 200 OK` 响应。 + +如果您发送一个在倒数第二条消息中包含禁止内容的请求: + +```shell +curl -i "http://127.0.0.1:9080/anything" -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "messages": [ + { "role": "system", "content": "Rate if the purchase is at a decent price in USD." }, + { "role": "user", "content": "Customer John contact: 647-200-9393" }, + { "role": "user", "content": "John paid $12.5 for a hot brewed coffee in El Paso." } + ] + }' +``` + +您也将收到一个 `HTTP/1.1 200 OK` 响应。 + +参阅[下一个示例](#验证所有角色的消息和对话历史)了解如何检查所有角色和所有消息。 + +### 验证所有角色的消息和对话历史 + +以下示例演示了如何使用 `ai-prompt-guard` 插件验证所有角色(如 `system` 和 `user`)的提示,以及验证整个对话历史而不是仅验证最后一条消息。 + +定义允许和拒绝模式。您可以选择将它们保存到环境变量中以便于转义: + +```shell +export ALLOW_PATTERN_1='\\$?\\(?\\d{1,3}(,\\d{3})*(\\.\\d{1,2})?\\)?' +export DENY_PATTERN_1='(\\([0-9]{3}\\)|[0-9]{3}-)[0-9]{3}-[0-9]{4}' +``` + +<Tabs groupId="api"> +<TabItem value="admin-api" label="Admin API"> + +创建一个路由,使用 [ai-proxy](./ai-proxy.md) 代理到 OpenAI 并使用 `ai-prompt-guard` 检查输入提示。将 `match_all_roles` 和 `match_all_conversation_history` 设置为 `true` 以验证所有角色的消息和整个对话: + +```shell +curl "http://127.0.0.1:9180/apisix/admin/routes/1" -X PUT \ + -H "X-API-KEY: ${admin_key}" \ + -d '{ + "uri": "/anything", + "methods": ["POST"], + "plugins": { + "ai-proxy": { + "provider": "openai", + "auth": { + "header": { + "Authorization": "Bearer '"$OPENAI_API_KEY"'" + } + }, + "options":{ + "model": "gpt-4" + } + }, + "ai-prompt-guard": { + "match_all_roles": true, + "match_all_conversation_history": true, + "allow_patterns": [ + "'"$ALLOW_PATTERN_1"'" + ], + "deny_patterns": [ + "'"$DENY_PATTERN_1"'" + ] + } + } + }' +``` + +</TabItem> +<TabItem value="adc" label="ADC"> + +创建一个配置了 `ai-prompt-guard` 和 [ai-proxy](./ai-proxy.md) 插件的路由。将 `match_all_roles` 和 `match_all_conversation_history` 设置为 `true` 以验证所有角色的消息和整个对话: + +```yaml title="adc.yaml" +services: + - name: prompt-guard-service + routes: + - name: prompt-guard-route + uris: + - /anything + methods: + - POST + plugins: + ai-proxy: + provider: openai + auth: + header: + Authorization: "Bearer ${OPENAI_API_KEY}" + options: + model: gpt-4 + ai-prompt-guard: + match_all_roles: true + match_all_conversation_history: true + allow_patterns: + - '\$?\(?\d{1,3}(,\d{3})*(\.\d{1,2})?\)?' + deny_patterns: + - '(\([0-9]{3}\)|[0-9]{3}-)[0-9]{3}-[0-9]{4}' +``` + +将配置同步到网关: + +```shell +adc sync -f adc.yaml +``` + +</TabItem> +<TabItem value="ingress" label="Ingress Controller"> + +<Tabs groupId="k8s-api"> +<TabItem value="gateway-api" label="Gateway API"> + +创建一个配置了 `ai-prompt-guard` 和 [ai-proxy](./ai-proxy.md) 插件的路由。将 `match_all_roles` 和 `match_all_conversation_history` 设置为 `true` 以验证所有角色的消息和整个对话: + +```yaml title="ai-prompt-guard-history-ic.yaml" +apiVersion: apisix.apache.org/v1alpha1 +kind: PluginConfig +metadata: + namespace: aic + name: ai-prompt-guard-plugin-config +spec: + plugins: + - name: ai-prompt-guard + config: + match_all_roles: true + match_all_conversation_history: true + allow_patterns: + - '\$?\(?\d{1,3}(,\d{3})*(\.\d{1,2})?\)?' + deny_patterns: + - '(\([0-9]{3}\)|[0-9]{3}-)[0-9]{3}-[0-9]{4}' + - name: ai-proxy + config: + provider: openai + auth: + header: + Authorization: "Bearer your-api-key" + options: + model: gpt-4 +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + namespace: aic + name: prompt-guard-route +spec: + parentRefs: + - name: apisix + rules: + - matches: + - path: + type: Exact + value: /anything + method: POST + filters: + - type: ExtensionRef + extensionRef: + group: apisix.apache.org + kind: PluginConfig + name: ai-prompt-guard-plugin-config +``` + +</TabItem> +<TabItem value="ingress" label="APISIX Ingress Controller"> + +创建一个配置了 `ai-prompt-guard` 和 [ai-proxy](./ai-proxy.md) 插件的路由。将 `match_all_roles` 和 `match_all_conversation_history` 设置为 `true` 以验证所有角色的消息和整个对话: + +```yaml title="ai-prompt-guard-history-ic.yaml" +apiVersion: apisix.apache.org/v2 +kind: ApisixRoute +metadata: + namespace: aic + name: prompt-guard-route +spec: + ingressClassName: apisix + http: + - name: prompt-guard-route + match: + paths: + - /anything + methods: + - POST + plugins: + - name: ai-prompt-guard + enable: true + config: + match_all_roles: true + match_all_conversation_history: true + allow_patterns: + - '\$?\(?\d{1,3}(,\d{3})*(\.\d{1,2})?\)?' + deny_patterns: + - '(\([0-9]{3}\)|[0-9]{3}-)[0-9]{3}-[0-9]{4}' + - name: ai-proxy + enable: true + config: + provider: openai + auth: + header: + Authorization: "Bearer your-api-key" + options: + model: gpt-4 +``` + +</TabItem> +</Tabs> + +将配置应用到集群: + +```shell +kubectl apply -f ai-prompt-guard-history-ic.yaml +``` + +</TabItem> +</Tabs> + +发送一个在 `system` 提示中包含禁止内容的请求: + +```shell +curl -i "http://127.0.0.1:9080/anything" -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "messages": [ + { "role": "system", "content": "Rate if the purchase from 647-200-9393 is at a decent price in USD." }, + { "role": "user", "content": "John paid $12.5 for a hot brewed coffee in El Paso." } + ] + }' +``` + +您应该收到一个 `HTTP/1.1 400 Bad Request` 响应,并看到以下消息: + +```text +{"message":"Request contains prohibited content"} +``` + +发送一个来自同一角色的多条包含禁止内容的消息的请求: + +```shell +curl -i "http://127.0.0.1:9080/anything" -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "messages": [ + { "role": "system", "content": "Rate if the purchase is at a decent price in USD." }, + { "role": "user", "content": "Customer John contact: 647-200-9393" }, + { "role": "user", "content": "John paid $12.5 for a hot brewed coffee in El Paso." } + ] + }' +``` + +您应该收到一个 `HTTP/1.1 400 Bad Request` 响应,并看到以下消息: + +```text +{"message":"Request contains prohibited content"} +``` + +发送一个符合模式的请求: + +```shell +curl -i "http://127.0.0.1:9080/anything" -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "messages": [ + { "role": "system", "content": "Rate if the purchase is at a decent price in USD." }, + { "role": "system", "content": "The puchase is made in El Paso." }, Review Comment: 示例请求体里 `The puchase is made in El Paso.` 存在拼写错误(puchase)。这会影响示例的专业性与可搜索性,建议更正为 `purchase`。 ```suggestion { "role": "system", "content": "The purchase is made in El Paso." }, ``` -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
