Skip to content

Commit 3387916

Browse files
feat: ai-prompt-decorator plugin (apache#11515)
1 parent e775640 commit 3387916

File tree

8 files changed

+525
-2
lines changed

8 files changed

+525
-2
lines changed

apisix/cli/config.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ local _M = {
214214
"proxy-cache",
215215
"body-transformer",
216216
"ai-prompt-template",
217+
"ai-prompt-decorator",
217218
"proxy-mirror",
218219
"proxy-rewrite",
219220
"workflow",
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
--
2+
-- Licensed to the Apache Software Foundation (ASF) under one or more
3+
-- contributor license agreements. See the NOTICE file distributed with
4+
-- this work for additional information regarding copyright ownership.
5+
-- The ASF licenses this file to You under the Apache License, Version 2.0
6+
-- (the "License"); you may not use this file except in compliance with
7+
-- the License. You may obtain a copy of the License at
8+
--
9+
-- http://www.apache.org/licenses/LICENSE-2.0
10+
--
11+
-- Unless required by applicable law or agreed to in writing, software
12+
-- distributed under the License is distributed on an "AS IS" BASIS,
13+
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
-- See the License for the specific language governing permissions and
15+
-- limitations under the License.
16+
--
17+
local core = require("apisix.core")
18+
local ngx = ngx
19+
local pairs = pairs
20+
local EMPTY = {}
21+
22+
local prompt_schema = {
23+
properties = {
24+
role = {
25+
type = "string",
26+
enum = { "system", "user", "assistant" }
27+
},
28+
content = {
29+
type = "string",
30+
minLength = 1,
31+
}
32+
},
33+
required = { "role", "content" }
34+
}
35+
36+
local prompts = {
37+
type = "array",
38+
items = prompt_schema
39+
}
40+
41+
local schema = {
42+
type = "object",
43+
properties = {
44+
prepend = prompts,
45+
append = prompts,
46+
},
47+
anyOf = {
48+
{ required = { "prepend" } },
49+
{ required = { "append" } },
50+
{ required = { "append", "prepend" } },
51+
},
52+
}
53+
54+
55+
local _M = {
56+
version = 0.1,
57+
priority = 1070,
58+
name = "ai-prompt-decorator",
59+
schema = schema,
60+
}
61+
62+
63+
function _M.check_schema(conf)
64+
return core.schema.check(schema, conf)
65+
end
66+
67+
68+
local function get_request_body_table()
69+
local body, err = core.request.get_body()
70+
if not body then
71+
return nil, { message = "could not get body: " .. err }
72+
end
73+
74+
local body_tab, err = core.json.decode(body)
75+
if not body_tab then
76+
return nil, { message = "could not get parse JSON request body: " .. err }
77+
end
78+
79+
return body_tab
80+
end
81+
82+
83+
local function decorate(conf, body_tab)
84+
local new_messages = conf.prepend or EMPTY
85+
for _, message in pairs(body_tab.messages) do
86+
core.table.insert_tail(new_messages, message)
87+
end
88+
89+
for _, message in pairs(conf.append or EMPTY) do
90+
core.table.insert_tail(new_messages, message)
91+
end
92+
93+
body_tab.messages = new_messages
94+
end
95+
96+
97+
function _M.rewrite(conf, ctx)
98+
local body_tab, err = get_request_body_table()
99+
if not body_tab then
100+
return 400, err
101+
end
102+
103+
if not body_tab.messages then
104+
return 400, "messages missing from request body"
105+
end
106+
decorate(conf, body_tab) -- will decorate body_tab in place
107+
108+
local new_jbody, err = core.json.encode(body_tab)
109+
if not new_jbody then
110+
return 500, { message = "failed to parse modified JSON request body: " .. err }
111+
end
112+
113+
ngx.req.set_body_data(new_jbody)
114+
end
115+
116+
117+
return _M

apisix/plugins/ai-prompt-template.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ local schema = {
7272

7373
local _M = {
7474
version = 0.1,
75-
priority = 1060,
75+
priority = 1071,
7676
name = "ai-prompt-template",
7777
schema = schema,
7878
}

conf/config.yaml.example

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,8 @@ plugins: # plugin list (sorted by priority)
476476
#- error-log-logger # priority: 1091
477477
- proxy-cache # priority: 1085
478478
- body-transformer # priority: 1080
479-
- ai-prompt-template # priority: 1060
479+
- ai-prompt-template # priority: 1071
480+
- ai-prompt-decorator # priority: 1070
480481
- proxy-mirror # priority: 1010
481482
- proxy-rewrite # priority: 1008
482483
- workflow # priority: 1006

docs/en/latest/config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
"type": "category",
8888
"label": "Transformation",
8989
"items": [
90+
"plugins/ai-prompt-decorator",
9091
"plugins/response-rewrite",
9192
"plugins/proxy-rewrite",
9293
"plugins/grpc-transcode",
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
---
2+
title: ai-prompt-decorator
3+
keywords:
4+
- Apache APISIX
5+
- API Gateway
6+
- Plugin
7+
- ai-prompt-decorator
8+
description: This document contains information about the Apache APISIX ai-prompt-decorator Plugin.
9+
---
10+
11+
<!--
12+
#
13+
# Licensed to the Apache Software Foundation (ASF) under one or more
14+
# contributor license agreements. See the NOTICE file distributed with
15+
# this work for additional information regarding copyright ownership.
16+
# The ASF licenses this file to You under the Apache License, Version 2.0
17+
# (the "License"); you may not use this file except in compliance with
18+
# the License. You may obtain a copy of the License at
19+
#
20+
# http://www.apache.org/licenses/LICENSE-2.0
21+
#
22+
# Unless required by applicable law or agreed to in writing, software
23+
# distributed under the License is distributed on an "AS IS" BASIS,
24+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25+
# See the License for the specific language governing permissions and
26+
# limitations under the License.
27+
#
28+
-->
29+
30+
## Description
31+
32+
The `ai-prompt-decorator` plugin simplifies access to LLM providers, such as OpenAI and Anthropic, and their models by appending or prepending prompts into the request.
33+
34+
## Plugin Attributes
35+
36+
| **Field** | **Required** | **Type** | **Description** |
37+
| ----------------- | --------------- | -------- | --------------------------------------------------- |
38+
| `prepend` | Conditionally\* | Array | An array of prompt objects to be prepended |
39+
| `prepend.role` | Yes | String | Role of the message (`system`, `user`, `assistant`) |
40+
| `prepend.content` | Yes | String | Content of the message. Minimum length: 1 |
41+
| `append` | Conditionally\* | Array | An array of prompt objects to be appended |
42+
| `append.role` | Yes | String | Role of the message (`system`, `user`, `assistant`) |
43+
| `append.content` | Yes | String | Content of the message. Minimum length: 1 |
44+
45+
\* **Conditionally Required**: At least one of `prepend` or `append` must be provided.
46+
47+
## Example usage
48+
49+
Create a route with the `ai-prompt-decorator` plugin like so:
50+
51+
```shell
52+
curl "http://127.0.0.1:9180/apisix/admin/routes/1" -X PUT \
53+
-H "X-API-KEY: ${ADMIN_API_KEY}" \
54+
-d '{
55+
"uri": "/v1/chat/completions",
56+
"plugins": {
57+
"ai-prompt-decorator": {
58+
"prepend":[
59+
{
60+
"role": "system",
61+
"content": "I have exams tomorrow so explain conceptually and briefly"
62+
}
63+
],
64+
"append":[
65+
{
66+
"role": "system",
67+
"content": "End the response with an analogy."
68+
}
69+
]
70+
}
71+
},
72+
"upstream": {
73+
"type": "roundrobin",
74+
"nodes": {
75+
"api.openai.com:443": 1
76+
},
77+
"pass_host": "node",
78+
"scheme": "https"
79+
}
80+
}'
81+
```
82+
83+
Now send a request:
84+
85+
```shell
86+
curl http://127.0.0.1:9080/v1/chat/completions -i -XPOST -H 'Content-Type: application/json' -d '{
87+
"model": "gpt-4",
88+
"messages": [{ "role": "user", "content": "What is TLS Handshake?" }]
89+
}' -H "Authorization: Bearer <your token here>"
90+
```
91+
92+
Then the request body will be modified to something like this:
93+
94+
```json
95+
{
96+
"model": "gpt-4",
97+
"messages": [
98+
{
99+
"role": "system",
100+
"content": "I have exams tomorrow so explain conceptually and briefly"
101+
},
102+
{ "role": "user", "content": "What is TLS Handshake?" },
103+
{
104+
"role": "system",
105+
"content": "End the response with an analogy."
106+
}
107+
]
108+
}
109+
```

t/admin/plugins.t

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ authz-keycloak
9494
proxy-cache
9595
body-transformer
9696
ai-prompt-template
97+
ai-prompt-decorator
9798
proxy-mirror
9899
proxy-rewrite
99100
workflow

0 commit comments

Comments
 (0)