Skip to content
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

Merged
merged 87 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
b98a48f
feat: ai proxy plugin
shreemaan-abhishek Aug 16, 2024
8188ae4
remove subrequest
shreemaan-abhishek Aug 16, 2024
7b83b3a
fix diff test
shreemaan-abhishek Aug 16, 2024
97cafa5
long line fix
shreemaan-abhishek Aug 16, 2024
35b1787
completions typo in consts
shreemaan-abhishek Aug 16, 2024
e18caef
license
shreemaan-abhishek Aug 16, 2024
28f06ae
plugins.t fix
shreemaan-abhishek Aug 16, 2024
82f9692
handle empty req body problem
shreemaan-abhishek Aug 18, 2024
0577e8e
auth schema fix
shreemaan-abhishek Aug 18, 2024
e5f00f7
scheme and method
shreemaan-abhishek Aug 18, 2024
c307b04
auth and model.name required
shreemaan-abhishek Aug 18, 2024
ef4cf84
scheme in lua code forgot to commit
shreemaan-abhishek Aug 18, 2024
4bf6bd2
tests
shreemaan-abhishek Aug 18, 2024
42adfd1
lint
shreemaan-abhishek Aug 18, 2024
0af00ae
add docs
shreemaan-abhishek Aug 20, 2024
aff56a0
options merger test
shreemaan-abhishek Aug 20, 2024
f25f21a
fix encryption mode comment
shreemaan-abhishek Aug 20, 2024
d2d253e
fix(lint): local ngx
shreemaan-abhishek Aug 20, 2024
58ca8a7
fix lint
shreemaan-abhishek Aug 21, 2024
f146f20
index to json
shreemaan-abhishek Aug 21, 2024
2317aa8
reindex
shreemaan-abhishek Aug 21, 2024
3ac0fe5
unsupported provider
shreemaan-abhishek Aug 22, 2024
6e31cfe
remove , nil
shreemaan-abhishek Aug 22, 2024
e302360
move to core.request
shreemaan-abhishek Aug 22, 2024
83f2197
update empty body test
shreemaan-abhishek Aug 22, 2024
bcc21cb
fix way to set upstream
shreemaan-abhishek Aug 22, 2024
10a07c1
add log
shreemaan-abhishek Aug 22, 2024
7220c08
response_streaming -> stream
shreemaan-abhishek Aug 22, 2024
a4afb30
refactor override schema
shreemaan-abhishek Aug 22, 2024
6248005
content type update
shreemaan-abhishek Aug 22, 2024
e88683c
remove completions
shreemaan-abhishek Aug 22, 2024
7d9c075
source -> type
shreemaan-abhishek Aug 22, 2024
9823570
fix lint
shreemaan-abhishek Aug 22, 2024
94d00f4
or -> and
shreemaan-abhishek Aug 23, 2024
b24e439
core.utils -> resolver
shreemaan-abhishek Aug 23, 2024
5ca70f3
global pcall
shreemaan-abhishek Aug 23, 2024
284ad76
rname test file
shreemaan-abhishek Aug 23, 2024
6baa7d1
use has_prefix
shreemaan-abhishek Aug 23, 2024
bdab563
fix upstream handling
shreemaan-abhishek Aug 23, 2024
2d0a7a1
dont modify tfsp
shreemaan-abhishek Aug 23, 2024
530448f
subrequest
shreemaan-abhishek Aug 27, 2024
ed11fa4
subrequest log check
shreemaan-abhishek Aug 27, 2024
cba307a
unused var
shreemaan-abhishek Aug 27, 2024
3febd29
http version check
shreemaan-abhishek Aug 28, 2024
e566a37
reindex
shreemaan-abhishek Aug 30, 2024
4c13cef
Merge branch 'master' of github.com:apache/apisix into ai-proxy
shreemaan-abhishek Aug 30, 2024
28c9c4d
Revert "subrequest"
shreemaan-abhishek Sep 3, 2024
780561d
Revert "subrequest log check"
shreemaan-abhishek Sep 3, 2024
4ffdd85
use httpc to request LLM
shreemaan-abhishek Sep 3, 2024
0521f89
pass through test
shreemaan-abhishek Sep 3, 2024
bd8309b
clean up
shreemaan-abhishek Sep 3, 2024
6f5d158
don't handle upstream when proxy_buffering
shreemaan-abhishek Sep 4, 2024
d4f3a5a
test scheme fix
shreemaan-abhishek Sep 4, 2024
1bfeac9
test path fix
shreemaan-abhishek Sep 4, 2024
25975c0
support SSE in tests
shreemaan-abhishek Sep 4, 2024
7c77ed6
cleanup
shreemaan-abhishek Sep 4, 2024
fa46abe
CLEANUP
shreemaan-abhishek Sep 4, 2024
45e4f98
cleanup
shreemaan-abhishek Sep 4, 2024
99af867
use `endpoint` instead of several config fields
shreemaan-abhishek Sep 4, 2024
11aef59
cleanup
shreemaan-abhishek Sep 4, 2024
4eb0ff4
`delayed_access` -> `disable_proxy_buffering_access_phase`
shreemaan-abhishek Sep 4, 2024
92ebd9d
cleanup
shreemaan-abhishek Sep 4, 2024
c0dc59e
scalable auth schema
shreemaan-abhishek Sep 4, 2024
99cf3a4
fix lint
shreemaan-abhishek Sep 5, 2024
243b5f5
remove body as auth param
shreemaan-abhishek Sep 5, 2024
217b5af
query param auth test
shreemaan-abhishek Sep 5, 2024
6661a0e
fix test
shreemaan-abhishek Sep 5, 2024
1c00e2c
fix lint
shreemaan-abhishek Sep 5, 2024
37cdd7b
remove forgotten header appender part
shreemaan-abhishek Sep 5, 2024
1062cc2
remove subrequest 😩
shreemaan-abhishek Sep 5, 2024
7b23623
Revert "remove subrequest 😩"
shreemaan-abhishek Sep 5, 2024
4278fd5
use encode_args instead of custom func
shreemaan-abhishek Sep 5, 2024
d915292
cleanup
shreemaan-abhishek Sep 5, 2024
8c3bcb3
clean upstream.lua
shreemaan-abhishek Sep 5, 2024
bcca3f2
optimize
shreemaan-abhishek Sep 5, 2024
929cbb1
fix lint
shreemaan-abhishek Sep 6, 2024
e149170
pass data to upstream in streaming way
shreemaan-abhishek Sep 6, 2024
d346d38
Reapply "remove subrequest 😩"
shreemaan-abhishek Sep 9, 2024
3429e03
update doc
shreemaan-abhishek Sep 9, 2024
7c08290
Apply suggestions from code review
shreemaan-abhishek Sep 10, 2024
977cf68
code review
shreemaan-abhishek Sep 10, 2024
073b3f8
code review
shreemaan-abhishek Sep 11, 2024
5831108
code review
shreemaan-abhishek Sep 11, 2024
ab4c37e
update priority
shreemaan-abhishek Sep 12, 2024
cdd37db
update priority
shreemaan-abhishek Sep 12, 2024
e0e3e15
fix test
shreemaan-abhishek Sep 12, 2024
f6768e4
Merge branch 'master' of github.com:apache/apisix into ai-proxy
shreemaan-abhishek Sep 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,12 @@ install: runtime
$(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/utils
$(ENV_INSTALL) apisix/utils/*.lua $(ENV_INST_LUADIR)/apisix/utils/

$(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/plugins/ai-proxy
$(ENV_INSTALL) apisix/plugins/ai-proxy/*.lua $(ENV_INST_LUADIR)/apisix/plugins/ai-proxy

$(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/plugins/ai-proxy/drivers
$(ENV_INSTALL) apisix/plugins/ai-proxy/drivers/*.lua $(ENV_INST_LUADIR)/apisix/plugins/ai-proxy/drivers

$(ENV_INSTALL) bin/apisix $(ENV_INST_BINDIR)/apisix


Expand Down
1 change: 1 addition & 0 deletions apisix/cli/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ local _M = {
"proxy-rewrite",
"workflow",
"api-breaker",
"ai-proxy",
"limit-conn",
"limit-count",
"limit-req",
Expand Down
58 changes: 58 additions & 0 deletions apisix/cli/ngx_tpl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,64 @@ http {
}
}

location @disable_proxy_buffering {
bzp2010 marked this conversation as resolved.
Show resolved Hide resolved
# http server location configuration snippet starts
{% if http_server_location_configuration_snippet then %}
{* http_server_location_configuration_snippet *}
{% end %}
# http server location configuration snippet ends

proxy_http_version 1.1;
proxy_set_header Host $upstream_host;
proxy_set_header Upgrade $upstream_upgrade;
proxy_set_header Connection $upstream_connection;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass_header Date;

### the following x-forwarded-* headers is to send to upstream server
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $var_x_forwarded_proto;
proxy_set_header X-Forwarded-Host $var_x_forwarded_host;
proxy_set_header X-Forwarded-Port $var_x_forwarded_port;

{% if enabled_plugins["proxy-cache"] then %}
### the following configuration is to cache response content from upstream server
proxy_cache $upstream_cache_zone;
proxy_cache_valid any {% if proxy_cache.cache_ttl then %} {* proxy_cache.cache_ttl *} {% else %} 10s {% end %};
proxy_cache_min_uses 1;
proxy_cache_methods GET HEAD POST;
proxy_cache_lock_timeout 5s;
proxy_cache_use_stale off;
proxy_cache_key $upstream_cache_key;
proxy_no_cache $upstream_no_cache;
proxy_cache_bypass $upstream_cache_bypass;

{% end %}

proxy_pass $upstream_scheme://apisix_backend$upstream_uri;

{% if enabled_plugins["proxy-mirror"] then %}
mirror /proxy_mirror;
{% end %}

header_filter_by_lua_block {
apisix.http_header_filter_phase()
}

body_filter_by_lua_block {
apisix.http_body_filter_phase()
}

log_by_lua_block {
apisix.http_log_phase()
}

proxy_buffering off;
access_by_lua_block {
apisix.disable_proxy_buffering_access_phase()
}
}

location @grpc_pass {

access_by_lua_block {
Expand Down
2 changes: 2 additions & 0 deletions apisix/constants.lua
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,6 @@ return {
["/stream_routes"] = true,
["/plugin_metadata"] = true,
},
CHAT = "llm/chat",
COMPLETION = "llm/completions",
}
26 changes: 24 additions & 2 deletions apisix/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ local tonumber = tonumber
local type = type
local pairs = pairs
local ngx_re_match = ngx.re.match
local balancer = require("ngx.balancer")
local control_api_router

local is_http = false
Expand Down Expand Up @@ -722,7 +723,19 @@ function _M.http_access_phase()
plugin.run_plugin("access", plugins, api_ctx)
end

_M.handle_upstream(api_ctx, route, enable_websocket)
if not api_ctx.custom_upstream_ip then
_M.handle_upstream(api_ctx, route, enable_websocket)
end
bzp2010 marked this conversation as resolved.
Show resolved Hide resolved

if ngx.ctx.disable_proxy_buffering then
stash_ngx_ctx()
return ngx.exec("@disable_proxy_buffering")
end
end


function _M.disable_proxy_buffering_access_phase()
ngx.ctx = fetch_ctx()
end


Copy link
Contributor

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.

Expand Down Expand Up @@ -893,7 +906,16 @@ function _M.http_balancer_phase()
return core.response.exit(500)
end

load_balancer.run(api_ctx.matched_route, api_ctx, common_phase)
if api_ctx.custom_upstream_ip then
local ok, err = balancer.set_current_peer(api_ctx.custom_upstream_ip,
api_ctx.custom_upstream_port)
if not ok then
core.log.error("failed to overwrite upstream for ai_proxy: ", err)
return core.response.exit(500)
end
else
bzp2010 marked this conversation as resolved.
Show resolved Hide resolved
load_balancer.run(api_ctx.matched_route, api_ctx, common_phase)
end
end


Expand Down
103 changes: 103 additions & 0 deletions apisix/plugins/ai-proxy.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
--
-- 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 schema = require("apisix.plugins.ai-proxy.schema")
local constants = require("apisix.constants")
local require = require

local ngx_req = ngx.req
local ngx = ngx

local plugin_name = "ai-proxy"
local _M = {
version = 0.5,
priority = 1004,
name = plugin_name,
schema = schema,
}


function _M.check_schema(conf)
return core.schema.check(schema.plugin_schema, conf)
end


local CONTENT_TYPE_JSON = "application/json"
bzp2010 marked this conversation as resolved.
Show resolved Hide resolved


local function get_request_table()
local req_body, err = core.request.get_body()
if not req_body then
return nil, "failed to get request body: " .. (err or "request body is empty")
end
req_body, err = req_body:gsub("\\\"", "\"") -- remove escaping in JSON
shreemaan-abhishek marked this conversation as resolved.
Show resolved Hide resolved
shreemaan-abhishek marked this conversation as resolved.
Show resolved Hide resolved
bzp2010 marked this conversation as resolved.
Show resolved Hide resolved
if not req_body then
return nil, "failed to remove escaping from body: " .. req_body .. ". err: " .. err
end
return core.json.decode(req_body)
end

function _M.access(conf, ctx)
local route_type = conf.route_type
ctx.ai_proxy = {}

local content_type = core.request.header(ctx, "Content-Type") or CONTENT_TYPE_JSON
if content_type ~= CONTENT_TYPE_JSON then
return 400, "unsupported content-type: " .. content_type
end

local request_table, err = get_request_table()
if not request_table then
return 400, err
end

local req_schema = schema.chat_request_schema
if route_type == constants.COMPLETION then
req_schema = schema.chat_completion_request_schema
end
local ok, err = core.schema.check(req_schema, request_table)
if not ok then
return 400, "request format doesn't match schema: " .. err
end

if conf.model.options and conf.model.options.response_streaming then
request_table.stream = true
ngx.ctx.disable_proxy_buffering = true
shreemaan-abhishek marked this conversation as resolved.
Show resolved Hide resolved
end

if conf.model.name then
request_table.model = conf.model.name
end

local ai_driver = require("apisix.plugins.ai-proxy.drivers." .. conf.model.provider)
shreemaan-abhishek marked this conversation as resolved.
Show resolved Hide resolved
local ok, err = ai_driver.configure_request(conf, request_table, ctx)
if not ok then
core.log.error("failed to configure request for AI service: ", err)
return 500
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

end

if route_type ~= "passthrough" then
local final_body, err = core.json.encode(request_table)
if not final_body then
core.log.error("failed to encode request body to JSON: ", err)
return 500
end
ngx_req.set_body_data(final_body)
end
end

return _M
bzp2010 marked this conversation as resolved.
Show resolved Hide resolved
67 changes: 67 additions & 0 deletions apisix/plugins/ai-proxy/drivers/openai.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
--
-- 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 _M = {}

local core = require("apisix.core")
local test_scheme = os.getenv("AI_PROXY_TEST_SCHEME")
local ngx = ngx
local pairs = pairs

-- globals
local DEFAULT_HOST = "api.openai.com"
local DEFAULT_PORT = 443

local path_mapper = {
["llm/completions"] = "/v1/completions",
bzp2010 marked this conversation as resolved.
Show resolved Hide resolved
["llm/chat"] = "/v1/chat/completions",
}


function _M.configure_request(conf, request_table, ctx)
local ip, err = core.resolver.parse_domain(conf.model.options.upstream_host or DEFAULT_HOST)
if not ip then
core.log.error("failed to resolve ai_proxy upstream host: ", err)
return core.response.exit(500)
end
ctx.custom_upstream_ip = ip
ctx.custom_upstream_port = conf.model.options.upstream_port or DEFAULT_PORT

local ups_path = (conf.model.options and conf.model.options.upstream_path)
or path_mapper[conf.route_type]
ngx.var.upstream_uri = ups_path
ngx.var.upstream_scheme = test_scheme or "https"
ngx.req.set_method(ngx.HTTP_POST)
ngx.var.upstream_host = conf.model.options.upstream_host or DEFAULT_HOST
ctx.custom_balancer_host = conf.model.options.upstream_host or DEFAULT_HOST
ctx.custom_balancer_port = conf.model.options.port or DEFAULT_PORT
bzp2010 marked this conversation as resolved.
Show resolved Hide resolved
bzp2010 marked this conversation as resolved.
Show resolved Hide resolved
if conf.auth.source == "header" then
bzp2010 marked this conversation as resolved.
Show resolved Hide resolved
core.request.set_header(ctx, conf.auth.name, conf.auth.value)
else
local args = core.request.get_uri_args(ctx)
args[conf.auth.name] = conf.auth.value
core.request.set_uri_args(ctx, args)
end

if conf.model.options then
for opt, val in pairs(conf.model.options) do
request_table[opt] = val
end
end
return true, nil
shreemaan-abhishek marked this conversation as resolved.
Show resolved Hide resolved
end

return _M
Loading
Loading