blob: 76688b43680af299f50ffec8d38b678364cf5c20 [file]
#
# 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.
#
=encoding utf-8
Tests for the passthrough protocol adapter. Verifies that requests whose
body does not match any known AI protocol (e.g. OpenAI Images Generation)
are proxied to the upstream without protocol-specific transformation.
=cut
BEGIN {
$ENV{TEST_ENABLE_CONTROL_API_V1} = "0";
}
use t::APISIX 'no_plan';
log_level("info");
repeat_each(1);
no_long_string();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests();
__DATA__
=== TEST 1: set route for images generation (passthrough)
--- 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": "/v1/images/generations",
"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 2: images generation request goes through passthrough protocol
--- request
POST /v1/images/generations
{"model":"dall-e-3","prompt":"A cute baby sea otter","n":1,"size":"1024x1024"}
--- more_headers
X-AI-Fixture: openai/images-generation.json
--- response_body eval
qr/baby sea otter/
--- no_error_log
no matching AI protocol
=== TEST 3: passthrough protocol is detected last (chat still matches)
--- 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": "/anything",
"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 4: request with messages field matches openai-chat, not passthrough
--- request
POST /anything
{"messages":[{"role":"user","content":"hello"}]}
--- more_headers
X-AI-Fixture: openai/chat-basic.json
--- response_body eval
qr/1 \+ 1 = 2\./
--- no_error_log
no matching AI protocol
=== TEST 5: passthrough uses override.endpoint path
--- 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": "/v1/images/generations",
"plugins": {
"ai-proxy": {
"provider": "openai",
"auth": {
"header": {
"Authorization": "Bearer token"
}
},
"override": {
"endpoint": "http://127.0.0.1:1980/v1/chat/completions"
},
"ssl_verify": false
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 6: passthrough with override.endpoint uses endpoint path not request URI
--- request
POST /v1/images/generations
{"model":"gpt-4o","prompt":"test"}
--- more_headers
X-AI-Fixture: openai/chat-basic.json
--- response_body eval
qr/1 \+ 1 = 2\./
--- no_error_log
no matching AI protocol
=== TEST 7: set route with model rewrite for passthrough
--- 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": "/v1/images/generations",
"plugins": {
"ai-proxy": {
"provider": "openai",
"auth": {
"header": {
"Authorization": "Bearer token"
}
},
"options": {
"model": "dall-e-3-override"
},
"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 8: passthrough rewrites model from options
--- request
POST /v1/images/generations
{"model":"dall-e-2","prompt":"test","n":1}
--- response_body eval
qr/dall-e-3-override/
--- no_error_log
no matching AI protocol
=== TEST 9: protocol detection unit test
--- config
location /t {
content_by_lua_block {
local protocols = require("apisix.plugins.ai-protocols")
-- chat body matches openai-chat
local name = protocols.detect({messages = {{role = "user", content = "hi"}}}, {})
ngx.say("chat: ", name)
-- embeddings body matches openai-embeddings
name = protocols.detect({input = "hello"}, {})
ngx.say("embeddings: ", name)
-- images body matches passthrough
name = protocols.detect({prompt = "a cat", model = "dall-e-3"}, {})
ngx.say("images: ", name)
-- empty body does NOT match passthrough (requires at least one key)
name = protocols.detect({}, {})
ngx.say("empty: ", name)
}
}
--- response_body
chat: openai-chat
embeddings: openai-embeddings
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