Skip to content

Commit e775640

Browse files
feat: ai-prompt-template plugin (#11517)
1 parent 9c81c93 commit e775640

File tree

7 files changed

+655
-0
lines changed

7 files changed

+655
-0
lines changed

apisix/cli/config.lua

+1
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ local _M = {
213213
"authz-keycloak",
214214
"proxy-cache",
215215
"body-transformer",
216+
"ai-prompt-template",
216217
"proxy-mirror",
217218
"proxy-rewrite",
218219
"workflow",

apisix/plugins/ai-prompt-template.lua

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
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 body_transformer = require("apisix.plugins.body-transformer")
19+
local ipairs = ipairs
20+
21+
local prompt_schema = {
22+
properties = {
23+
role = {
24+
type = "string",
25+
enum = { "system", "user", "assistant" }
26+
},
27+
content = {
28+
type = "string",
29+
minLength = 1,
30+
}
31+
},
32+
required = { "role", "content" }
33+
}
34+
35+
local prompts = {
36+
type = "array",
37+
minItems = 1,
38+
items = prompt_schema
39+
}
40+
41+
local schema = {
42+
type = "object",
43+
properties = {
44+
templates = {
45+
type = "array",
46+
minItems = 1,
47+
items = {
48+
type = "object",
49+
properties = {
50+
name = {
51+
type = "string",
52+
minLength = 1,
53+
},
54+
template = {
55+
type = "object",
56+
properties = {
57+
model = {
58+
type = "string",
59+
minLength = 1,
60+
},
61+
messages = prompts
62+
}
63+
}
64+
},
65+
required = {"name", "template"}
66+
}
67+
},
68+
},
69+
required = {"templates"},
70+
}
71+
72+
73+
local _M = {
74+
version = 0.1,
75+
priority = 1060,
76+
name = "ai-prompt-template",
77+
schema = schema,
78+
}
79+
80+
local templates_lrucache = core.lrucache.new({
81+
ttl = 300, count = 256
82+
})
83+
84+
local templates_json_lrucache = core.lrucache.new({
85+
ttl = 300, count = 256
86+
})
87+
88+
function _M.check_schema(conf)
89+
return core.schema.check(schema, conf)
90+
end
91+
92+
93+
local function get_request_body_table()
94+
local body, err = core.request.get_body()
95+
if not body then
96+
return nil, { message = "could not get body: " .. err }
97+
end
98+
99+
local body_tab, err = core.json.decode(body)
100+
if not body_tab then
101+
return nil, { message = "could not get parse JSON request body: ", err }
102+
end
103+
104+
return body_tab
105+
end
106+
107+
108+
local function find_template(conf, template_name)
109+
for _, template in ipairs(conf.templates) do
110+
if template.name == template_name then
111+
return template.template
112+
end
113+
end
114+
return nil
115+
end
116+
117+
function _M.rewrite(conf, ctx)
118+
local body_tab, err = get_request_body_table()
119+
if not body_tab then
120+
return 400, err
121+
end
122+
local template_name = body_tab.template_name
123+
if not template_name then
124+
return 400, { message = "template name is missing in request." }
125+
end
126+
127+
local template = templates_lrucache(template_name, conf, find_template, conf, template_name)
128+
if not template then
129+
return 400, { message = "template: " .. template_name .. " not configured." }
130+
end
131+
132+
local template_json = templates_json_lrucache(template, template, core.json.encode, template)
133+
core.log.info("sending template to body_transformer: ", template_json)
134+
return body_transformer.rewrite(
135+
{
136+
request = {
137+
template = template_json,
138+
input_format = "json"
139+
}
140+
},
141+
ctx
142+
)
143+
end
144+
145+
146+
return _M

conf/config.yaml.example

+1
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,7 @@ 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
479480
- proxy-mirror # priority: 1010
480481
- proxy-rewrite # priority: 1008
481482
- workflow # priority: 1006

docs/en/latest/config.json

+1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
"plugins/proxy-rewrite",
9292
"plugins/grpc-transcode",
9393
"plugins/grpc-web",
94+
"plugins/ai-prompt-template",
9495
"plugins/fault-injection",
9596
"plugins/mocking",
9697
"plugins/degraphql",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
---
2+
title: ai-prompt-template
3+
keywords:
4+
- Apache APISIX
5+
- API Gateway
6+
- Plugin
7+
- ai-prompt-template
8+
description: This document contains information about the Apache APISIX ai-prompt-template 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-template` plugin simplifies access to LLM providers, such as OpenAI and Anthropic, and their models by predefining the request format
33+
using a template, which only allows users to pass customized values into template variables.
34+
35+
## Plugin Attributes
36+
37+
| **Field** | **Required** | **Type** | **Description** |
38+
| ------------------------------------- | ------------ | -------- | --------------------------------------------------------------------------------------------------------------------------- |
39+
| `templates` | Yes | Array | An array of template objects |
40+
| `templates.name` | Yes | String | Name of the template. |
41+
| `templates.template.model` | Yes | String | Model of the AI Model, for example `gpt-4` or `gpt-3.5`. See your LLM provider API documentation for more available models. |
42+
| `templates.template.messages.role` | Yes | String | Role of the message (`system`, `user`, `assistant`) |
43+
| `templates.template.messages.content` | Yes | String | Content of the message. |
44+
45+
## Example usage
46+
47+
Create a route with the `ai-prompt-template` plugin like so:
48+
49+
```shell
50+
curl "http://127.0.0.1:9180/apisix/admin/routes/1" -X PUT \
51+
-H "X-API-KEY: ${ADMIN_API_KEY}" \
52+
-d '{
53+
"uri": "/v1/chat/completions",
54+
"upstream": {
55+
"type": "roundrobin",
56+
"nodes": {
57+
"api.openai.com:443": 1
58+
},
59+
"scheme": "https",
60+
"pass_host": "node"
61+
},
62+
"plugins": {
63+
"ai-prompt-template": {
64+
"templates": [
65+
{
66+
"name": "level of detail",
67+
"template": {
68+
"model": "gpt-4",
69+
"messages": [
70+
{
71+
"role": "user",
72+
"content": "Explain about {{ topic }} in {{ level }}."
73+
}
74+
]
75+
}
76+
}
77+
]
78+
}
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+
"template_name": "level of detail",
88+
"topic": "psychology",
89+
"level": "brief"
90+
}' -H "Authorization: Bearer <your token here>"
91+
```
92+
93+
Then the request body will be modified to something like this:
94+
95+
```json
96+
{
97+
"model": "some model",
98+
"messages": [
99+
{ "role": "user", "content": "Explain about psychology in brief." }
100+
]
101+
}
102+
```

t/admin/plugins.t

+1
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ opa
9393
authz-keycloak
9494
proxy-cache
9595
body-transformer
96+
ai-prompt-template
9697
proxy-mirror
9798
proxy-rewrite
9899
workflow

0 commit comments

Comments
 (0)