diff --git a/apisix/plugins/ai-providers/base.lua b/apisix/plugins/ai-providers/base.lua index 3021b89cdd4b..33403329b853 100644 --- a/apisix/plugins/ai-providers/base.lua +++ b/apisix/plugins/ai-providers/base.lua @@ -201,8 +201,20 @@ 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 + core.table.merge(query_params, client_args) + 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 b01e7a513c90..d4b7ad6889ac 100644 --- a/t/plugin/ai-proxy-passthrough.t +++ b/t/plugin/ai-proxy-passthrough.t @@ -278,3 +278,156 @@ 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