This is an automated email from the ASF dual-hosted git repository.
AlinsRan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new f60432417 fix(ai-proxy): forward client method and query string for
passthrough (#13546)
f60432417 is described below
commit f6043241753ecb0914e3d24ce9903ca52f43c4eb
Author: AlinsRan <[email protected]>
AuthorDate: Mon Jun 15 10:45:42 2026 +0800
fix(ai-proxy): forward client method and query string for passthrough
(#13546)
---
apisix/plugins/ai-providers/base.lua | 20 +++-
t/plugin/ai-proxy-passthrough.t | 208 +++++++++++++++++++++++++++++++++++
2 files changed, 227 insertions(+), 1 deletion(-)
diff --git a/apisix/plugins/ai-providers/base.lua
b/apisix/plugins/ai-providers/base.lua
index 3021b89cd..ba1330952 100644
--- a/apisix/plugins/ai-providers/base.lua
+++ b/apisix/plugins/ai-providers/base.lua
@@ -201,8 +201,26 @@ function _M.build_request(self, ctx, conf, request_body,
opts)
ctx.ai_converter.convert_headers(headers)
end
+ -- For the passthrough protocol the gateway acts as a catch-all proxy, so
+ -- forward the client's HTTP method and original query string unchanged.
+ -- Other protocols always issue a POST with provider-specific query args.
+ local method = "POST"
+ if ctx.ai_target_protocol == "passthrough" then
+ method = core.request.get_method()
+ local client_args = ctx.var.args and
core.string.decode_args(ctx.var.args)
+ if type(client_args) == "table" then
+ -- client query overrides the endpoint query, but configured
+ -- auth.query credentials must stay non-overridable by the caller
+ for k, v in pairs(client_args) do
+ if not (auth.query and auth.query[k] ~= nil) then
+ query_params[k] = v
+ end
+ end
+ end
+ end
+
local params = {
- method = "POST",
+ method = method,
scheme = scheme,
headers = headers,
ssl_verify = conf.ssl_verify,
diff --git a/t/plugin/ai-proxy-passthrough.t b/t/plugin/ai-proxy-passthrough.t
index b01e7a513..76688b436 100644
--- a/t/plugin/ai-proxy-passthrough.t
+++ b/t/plugin/ai-proxy-passthrough.t
@@ -278,3 +278,211 @@ images: passthrough
empty: nil
--- no_error_log
no matching AI protocol
+
+
+
+=== TEST 10: set route for passthrough query/method forwarding
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "uri": "/plugin_proxy_rewrite_args",
+ "plugins": {
+ "ai-proxy": {
+ "provider": "openai",
+ "auth": {
+ "header": {
+ "Authorization": "Bearer token"
+ }
+ },
+ "override": {
+ "endpoint": "http://127.0.0.1:1980"
+ },
+ "ssl_verify": false
+ }
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 11: passthrough forwards the client query string to the upstream
+--- request
+POST /plugin_proxy_rewrite_args?name=foo
+{"prompt":"x"}
+--- more_headers
+Content-Type: application/json
+--- response_body eval
+qr/name: foo/
+--- no_error_log
+no matching AI protocol
+
+
+
+=== TEST 12: set route for passthrough method forwarding
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "uri": "/plugin_proxy_rewrite",
+ "plugins": {
+ "ai-proxy": {
+ "provider": "openai",
+ "auth": {
+ "header": {
+ "Authorization": "Bearer token"
+ }
+ },
+ "override": {
+ "endpoint": "http://127.0.0.1:1980"
+ },
+ "ssl_verify": false
+ }
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 13: passthrough forwards the client HTTP method to the upstream
+--- request
+PUT /plugin_proxy_rewrite
+{"prompt":"x"}
+--- more_headers
+Content-Type: application/json
+--- error_log
+plugin_proxy_rewrite get method: PUT
+--- no_error_log
+no matching AI protocol
+
+
+
+=== TEST 14: set route whose override.endpoint carries its own query args
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "uri": "/plugin_proxy_rewrite_args",
+ "plugins": {
+ "ai-proxy": {
+ "provider": "openai",
+ "auth": {
+ "header": {
+ "Authorization": "Bearer token"
+ }
+ },
+ "override": {
+ "endpoint":
"http://127.0.0.1:1980/plugin_proxy_rewrite_args?name=fromendpoint&ekey=eval"
+ },
+ "ssl_verify": false
+ }
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 15: client query overrides the endpoint query on conflicting keys
+--- request
+POST /plugin_proxy_rewrite_args?name=fromclient&ckey=cval
+{"prompt":"x"}
+--- more_headers
+Content-Type: application/json
+--- response_body
+uri: /plugin_proxy_rewrite_args
+ckey: cval
+ekey: eval
+name: fromclient
+--- no_error_log
+no matching AI protocol
+
+
+
+=== TEST 16: set route whose auth.query seeds a gateway-managed credential
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "uri": "/plugin_proxy_rewrite_args",
+ "plugins": {
+ "ai-proxy": {
+ "provider": "openai",
+ "auth": {
+ "header": {
+ "Authorization": "Bearer token"
+ },
+ "query": {
+ "akey": "secret"
+ }
+ },
+ "override": {
+ "endpoint": "http://127.0.0.1:1980"
+ },
+ "ssl_verify": false
+ }
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 17: client query cannot override the configured auth.query credential
+--- request
+POST /plugin_proxy_rewrite_args?akey=attacker&zname=foo
+{"prompt":"x"}
+--- more_headers
+Content-Type: application/json
+--- response_body
+uri: /plugin_proxy_rewrite_args
+akey: secret
+zname: foo
+--- no_error_log
+no matching AI protocol