-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: ai-proxy plugin #11499
feat: ai-proxy plugin #11499
Conversation
f5d902f
to
b98a48f
Compare
0af5364
to
f25f21a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no more comments now ✨
apisix/init.lua
Outdated
@@ -726,6 +726,7 @@ function _M.http_access_phase() | |||
end | |||
|
|||
|
|||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there are no substantive changes, please do not change the code style.
t/plugin/ai-proxy.t
Outdated
if header_auth == "Bearer token" or query_auth == "apikey" then | ||
ngx.req.read_body() | ||
local body, err = ngx.req.get_body_data() | ||
local esc = body:gsub('"\\\""', '\"') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this still necessary? Just a confirmation that if it is then no changes are needed.
}' | ||
``` | ||
|
||
Since `passthrough` is not enabled upstream node can be any arbitrary value because it won't be contacted. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- design question: why do we need the parameter
passthrough
? is it meant to be a toggle? - is this sentence needed here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
design question: why do we need the parameter passthrough? is it meant to be a toggle?
yes, kinda. when it's enabled, the response from LLM will be set as the request body to the upstream.
is this sentence needed here?
I think it's important to let users know, instead to let them be puzzled about it and then figure out later.
@@ -96,7 +96,8 @@ | |||
"plugins/fault-injection", | |||
"plugins/mocking", | |||
"plugins/degraphql", | |||
"plugins/body-transformer" | |||
"plugins/body-transformer", | |||
"plugins/ai-proxy" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The recently added ai-*
plugins should probably all go into a new category. Can be amended in a different PR.
Co-authored-by: Traky Deng <[email protected]>
apisix/core/request.lua
Outdated
@@ -334,6 +335,21 @@ function _M.get_body(max_size, ctx) | |||
end | |||
|
|||
|
|||
function _M.get_request_body_table() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Decode body according content-type or rename the method, e.g. get_json_request_body_table?
apisix/plugins/ai-proxy.lua
Outdated
return 400, "unsupported content-type: " .. ct | ||
end | ||
|
||
local request_table, err = core.request.get_request_body_table() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new naming style?
request_table, count_num, flag_boolean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand what you mean.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although adding a suffix makes it more readable, it is not the style used in the project.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh I got it now 😂 . request_table
makes it more readable and it can be understood that it's not a string.
apisix/plugins/ai-proxy.lua
Outdated
return | ||
end | ||
|
||
if core.table.try_read_attr(conf, "model", "options", "stream") then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if core.table.try_read_attr(conf, "model", "options", "stream") then | |
if request_table.stream then |
apisix/plugins/ai-proxy.lua
Outdated
local res, err, httpc = ai_driver.request(conf, request_table, ctx) | ||
if not res then | ||
core.log.error("failed to send request to AI service: ", err) | ||
return 500 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use the predefined Ngx constants?
ngx.HTTP_NOT_ACCEPTABLE = 406
ngx.HTTP_REQUEST_TIMEOUT = 408
ngx.HTTP_CONFLICT = 409
ngx.HTTP_GONE = 410
ngx.HTTP_UPGRADE_REQUIRED = 426
ngx.HTTP_TOO_MANY_REQUESTS = 429
ngx.HTTP_CLOSE = 444
ngx.HTTP_ILLEGAL = 451
ngx.HTTP_INTERNAL_SERVER_ERROR = 500
ngx.HTTP_METHOD_NOT_IMPLEMENTED = 501
ngx.HTTP_BAD_GATEWAY = 502
ngx.HTTP_SERVICE_UNAVAILABLE = 503
ngx.HTTP_GATEWAY_TIMEOUT = 504
ngx.HTTP_VERSION_NOT_SUPPORTED = 505
ngx.HTTP_INSUFFICIENT_STORAGE = 507
apisix/plugins/ai-proxy.lua
Outdated
local body_reader = res.body_reader | ||
if not body_reader then | ||
core.log.error("LLM sent no response body") | ||
return 500 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
end | ||
|
||
local query_params = "" | ||
if conf.auth.query and type(conf.auth.query) == "table" then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if conf.auth.query and type(conf.auth.query) == "table" then | |
if type(conf.auth.query) == "table" then |
end | ||
end | ||
|
||
local path = (parsed_url.path or "/v1/chat/completions") .. query_params |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
local path = (parsed_url.path or "/v1/chat/completions") .. query_params | |
local path = (parsed_url.path or DEFAULT_URI) .. query_params |
|
||
local query_params = "" | ||
if conf.auth.query and type(conf.auth.query) == "table" then | ||
query_params = core.string.encode_args(conf.auth.query) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, from lua-resty-http, params.query
already support table type
local function _format_request(self, params)
local version = params.version
local headers = params.headers or {}
local query = params.query or ""
if type(query) == "table" then
query = "?" .. ngx_encode_args(query)
elseif query ~= "" and str_sub(query, 1, 1) ~= "?" then
query = "?" .. query
end
-- Initialize request
local req = {
str_upper(params.method),
" ",
self.path_prefix or "",
params.path,
query,
HTTP[version],
-- Pre-allocate slots for minimum headers and carriage return.
"",
"",
"",
}
local c = 7 -- req table index it's faster to do this inline vs table.insert
-- Append headers
for key, values in pairs(headers) do
key = tostring(key)
if type(values) == "table" then
for _, value in pairs(values) do
req[c] = key .. ": " .. tostring(value) .. "\r\n"
c = c + 1
end
else
req[c] = key .. ": " .. tostring(values) .. "\r\n"
c = c + 1
end
end
-- Close headers
req[c] = "\r\n"
return tbl_concat(req)
end
t/sse_server_example/main.go
Outdated
fmt.Fprintf(w, "data: %s\n\n", time.Now().Format(time.RFC3339)) | ||
|
||
// Flush the data immediately to the client | ||
if f, ok := w.(http.Flusher); ok { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do type assertion outside the loop? :D
|
||
local res, err = httpc:request(params) | ||
if not res then | ||
return 500, "failed to send request to LLM server: " .. err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return 500, "failed to send request to LLM server: " .. err | |
return internal_server_error, "failed to send request to LLM server: " .. err |
t/sse_server_example/main.go
Outdated
fmt.Fprintf(w, "[ERROR]") | ||
return | ||
} | ||
// A simple loop that sends a message every 2 seconds |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// A simple loop that sends a message every 2 seconds | |
// A simple loop that sends a message every 500ms |
Parameterize it?
Description
Implement the AI-Proxy Plugin.
Checklist