Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
af244a4
feat(ai): support configurable request JSON library
nic-6443 May 19, 2026
4f0fe94
chore: satisfy request json lint
nic-6443 May 19, 2026
ff40b98
fix: harden request JSON selector
nic-6443 May 19, 2026
4ed03d7
build: install simdjson dependency from source
nic-6443 May 19, 2026
f0f1614
docs: add JSON library benchmark data
nic-6443 May 19, 2026
162460d
docs: remove private benchmark source link
nic-6443 May 19, 2026
d022938
fix: address JSON library dependency review
nic-6443 May 19, 2026
a16f402
ci: ensure recent Rust for qjson builds
nic-6443 May 19, 2026
5c6a9c4
ci: run rustup-init with expected name
nic-6443 May 19, 2026
024cba3
test: allow parser-specific JSON errors
nic-6443 May 19, 2026
854c919
ci: harden JSON dependency installers
nic-6443 May 19, 2026
71a8775
docs: note simdjson round-trip limits
nic-6443 May 19, 2026
985a1d3
ci: handle rustup symlink privileges
nic-6443 May 19, 2026
8ab89aa
fix: materialize qjson request body decode
nic-6443 May 19, 2026
2b7d77c
chore: tighten rustup and simdjson install helpers
nic-6443 May 19, 2026
8ee86b0
chore: rely on schema for JSON lib config
nic-6443 May 19, 2026
e4bad9e
chore: simplify request JSON dispatch
nic-6443 May 19, 2026
915d46b
docs: explain simdjson encode path
nic-6443 May 19, 2026
c3fbda1
ci: install simdjson for LuaRocks smoke
nic-6443 May 19, 2026
f97c6a4
chore: share Rust toolchain installer
nic-6443 May 19, 2026
802bbe0
chore: share Rust toolchain setup
nic-6443 May 19, 2026
9cc97e1
ci: install simdjson for checkout smoke
nic-6443 May 19, 2026
c9443ba
test: make nacos fallback host deterministic
nic-6443 May 19, 2026
3ddcc21
test: share nacos fallback marker across workers
nic-6443 May 19, 2026
129d575
feat(ai): default request JSON library to simdjson
nic-6443 May 19, 2026
78c02c7
ci: harden Rust toolchain fallback
nic-6443 May 19, 2026
47f3537
ci: persist Rust toolchain selection
nic-6443 May 19, 2026
b960cf0
ci: export Rust toolchain environment
nic-6443 May 19, 2026
36a9043
ci: prefer pinned rustup toolchain on Linux
nic-6443 May 19, 2026
b4f4edb
ci: expose Rust environment in Docker builds
nic-6443 May 19, 2026
2e8e607
ci: pin rustup-init version with checksum
nic-6443 May 19, 2026
cd53052
ci: install Arch build tools for simdjson
nic-6443 May 19, 2026
0670af4
ci: preserve Rust env for dependency builds
nic-6443 May 19, 2026
fa86179
ci: pass Rust env to LuaRocks installs
nic-6443 May 19, 2026
db52531
ci: report outdated Rust packages clearly
nic-6443 May 19, 2026
3a30b9f
test: revert unrelated nacos change
nic-6443 May 19, 2026
8219798
fix: use api7 simdjson package
nic-6443 May 20, 2026
3d70dcb
test: make simdjson array check explicit
nic-6443 May 20, 2026
2604778
build: install simdjson through rockspec
nic-6443 May 20, 2026
1b1d302
build: use simdjson 0.1.1
nic-6443 May 20, 2026
884f954
build: choose writable Rust home for LuaRocks deps
nic-6443 May 20, 2026
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
2 changes: 2 additions & 0 deletions apisix-master-0.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ dependencies = {
"api7-lua-resty-aws == 2.0.2-1",
"multipart = 0.5.11-1",
"luautf8 = 0.2.0-1",
"lua-qjson = 0.1.0-1",
"lua-resty-simdjson = 1.2.0-1",
}

build = {
Expand Down
1 change: 1 addition & 0 deletions apisix/cli/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ local _M = {
enable_server_tokens = true,
extra_lua_path = "",
extra_lua_cpath = "",
request_body_json_lib = "qjson",
Comment thread
nic-6443 marked this conversation as resolved.
Outdated
Comment thread
nic-6443 marked this conversation as resolved.
Outdated
proxy_cache = {
cache_ttl = "10s",
zones = {
Expand Down
4 changes: 4 additions & 0 deletions apisix/cli/schema.lua
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ local config_schema = {
},
}
},
request_body_json_lib = {
enum = {"cjson", "simdjson", "qjson"},
default = "qjson",
},
proxy_cache = {
type = "object",
properties = {
Expand Down
4 changes: 2 additions & 2 deletions apisix/core/request.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

local lfs = require("lfs")
local log = require("apisix.core.log")
local json = require("apisix.core.json")
local request_json = require("apisix.core.request_json")
local io = require("apisix.core.io")
local multipart = require("multipart")
local core_str = require("apisix.core.string")
Expand Down Expand Up @@ -371,7 +371,7 @@ local function get_request_body_table(ctx, content_type)
if not body then
return nil, "could not get body: " .. (body_err or "request body is empty")
end
result, err = json.decode(body)
result, err = request_json.decode(body)
if not result then
return nil, "could not parse JSON request body: " .. (err or "invalid JSON")
end
Expand Down
129 changes: 129 additions & 0 deletions apisix/core/request_json.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
--
-- 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 config_local = require("apisix.core.config_local")
local log = require("apisix.core.log")
local core_json = require("apisix.core.json")
local require = require
local pcall = pcall


local DEFAULT_JSON_LIB = "qjson"
local json_libs = {
cjson = true,
qjson = true,
simdjson = true,
}

local qjson
local simdjson_parser

local _M = {}


local function configured_json_lib()
local local_conf = config_local.local_conf()
local name = local_conf and local_conf.apisix
and local_conf.apisix.request_body_json_lib
or DEFAULT_JSON_LIB

if not json_libs[name] then
log.warn("invalid apisix.request_body_json_lib: ", name,
", fallback to ", DEFAULT_JSON_LIB)
return DEFAULT_JSON_LIB
Comment thread
nic-6443 marked this conversation as resolved.
Outdated
end

return name
Comment thread
nic-6443 marked this conversation as resolved.
Outdated
end


local function normalize_result(ok, res, err)
if not ok then
return nil, res
end

return res, err
end


local function qjson_module()
if qjson then
return qjson
end

local ok, mod = pcall(require, "qjson")
if not ok then
return nil, mod
end

qjson = mod
return qjson
Comment thread
nic-6443 marked this conversation as resolved.
Outdated
end


local function simdjson_decode(str)
if not simdjson_parser then
local ok, simdjson = pcall(require, "resty.simdjson")
if not ok then
return nil, simdjson
end

local parser, err = simdjson.new()
if not parser then
return nil, err
end
simdjson_parser = parser
end

return normalize_result(pcall(simdjson_parser.decode, simdjson_parser, str))
Comment thread
nic-6443 marked this conversation as resolved.
Outdated
end


function _M.decode(str)
local name = configured_json_lib()
if name == "cjson" then
return core_json.decode(str)
end

if name == "simdjson" then
return simdjson_decode(str)
end

local mod, err = qjson_module()
if not mod then
return nil, err
end

return normalize_result(pcall(mod.decode, str))
Comment thread
nic-6443 marked this conversation as resolved.
Outdated
end


function _M.encode(data)
if configured_json_lib() == "qjson" then
local mod, err = qjson_module()
if not mod then
return nil, err
end

return normalize_result(pcall(mod.encode, data))
end

return core_json.encode(data)
Comment thread
nic-6443 marked this conversation as resolved.
end


return _M
4 changes: 2 additions & 2 deletions apisix/plugins/ai-transport/auth-aws.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

require("resty.aws.config") -- reads env vars before init
local aws = require("resty.aws")
local core = require("apisix.core")
local request_json = require("apisix.core.request_json")
local signer = require("resty.aws.request.sign")
local ngx = ngx
local ngx_escape_uri = ngx.escape_uri
Expand Down Expand Up @@ -81,7 +81,7 @@ function _M.sign_request(params, aws_conf, region)

-- Serialize body to JSON string (SigV4 signs the exact bytes)
if type(params.body) == "table" then
local body_str, err = core.json.encode(params.body)
local body_str, err = request_json.encode(params.body)
if not body_str then
return "failed to encode body: " .. (err or "")
end
Expand Down
3 changes: 2 additions & 1 deletion apisix/plugins/ai-transport/http.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
-- Provides HTTP client lifecycle management for AI provider requests.

local core = require("apisix.core")
Comment thread
nic-6443 marked this conversation as resolved.
local request_json = require("apisix.core.request_json")
local http = require("resty.http")
local ngx_now = ngx.now
local pairs = pairs
Expand Down Expand Up @@ -107,7 +108,7 @@ function _M.request(params, timeout)
req_json = params.body
else
local err
req_json, err = core.json.encode(params.body)
req_json, err = request_json.encode(params.body)
if not req_json then
httpc:close()
return nil, "encode body: " .. (err or "unknown"), {
Expand Down
2 changes: 2 additions & 0 deletions conf/config.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ apisix:
enable_server_tokens: true # If true, show APISIX version in the `Server` response header.
extra_lua_path: "" # Extend lua_package_path to load third-party code.
extra_lua_cpath: "" # Extend lua_package_cpath to load third-party code.
request_body_json_lib: qjson # JSON library used to decode request bodies parsed by core.request.
# Can be cjson, simdjson, or qjson.
# lua_module_hook: "my_project.my_hook" # Hook module used to inject third-party code into APISIX.

proxy_cache: # Proxy Caching configuration
Expand Down
11 changes: 11 additions & 0 deletions docs/en/latest/plugins/ai-proxy.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ The `ai-proxy` Plugin simplifies access to LLM and embedding models by transform

In addition, the Plugin also supports logging LLM request information in the access log, such as token usage, model, time to the first response, and more. These log entries are also consumed by logging plugins such as `http-logger` and `kafka-logger`. These options do not affect `error.log`.

## Request Body JSON Library

APISIX uses `apisix.request_body_json_lib` to select the JSON library for request body parsing through `core.request.get_request_body_table`, which is shared by `ai-proxy` and other AI Plugins. It also controls JSON encoding for AI upstream request bodies.

```yaml title="conf/config.yaml"
apisix:
request_body_json_lib: qjson
```

Valid values are `cjson`, `simdjson`, and `qjson`. The default is `qjson`. When `simdjson` is configured, APISIX uses `simdjson` to decode request bodies and `cjson` to encode AI upstream request bodies.
Comment thread
nic-6443 marked this conversation as resolved.
Outdated

## Request Format

| Name | Type | Required | Description |
Expand Down
11 changes: 11 additions & 0 deletions docs/zh/latest/plugins/ai-proxy.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ import TabItem from '@theme/TabItem';

此外,该插件还支持在访问日志中记录 LLM 请求信息,如令牌使用量、模型、首次响应时间等。这些日志条目也会被 `http-logger`、`kafka-logger` 等日志插件消费。这些选项不影响 `error.log`。

## 请求体 JSON 库

APISIX 使用 `apisix.request_body_json_lib` 选择 `core.request.get_request_body_table` 解析请求体时使用的 JSON 库。该入口由 `ai-proxy` 和其他 AI 插件共享。该配置也会控制 AI 上游请求体的 JSON 编码。

```yaml title="conf/config.yaml"
apisix:
request_body_json_lib: qjson
```

有效值为 `cjson`、`simdjson` 和 `qjson`,默认值为 `qjson`。当配置为 `simdjson` 时,APISIX 使用 `simdjson` 解码请求体,并使用 `cjson` 编码 AI 上游请求体。

## 请求格式

| 名称 | 类型 | 必选项 | 描述 |
Expand Down
2 changes: 2 additions & 0 deletions t/core/config-default.t
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ __DATA__
local config = require("apisix.core").config.local_conf()

ngx.say("node_listen: ", config.apisix.node_listen)
ngx.say("request_body_json_lib: ", config.apisix.request_body_json_lib)
ngx.say("stream_proxy: ", encode_json(config.apisix.stream_proxy))
ngx.say("admin_key: ", encode_json(config.deployment.admin.admin_key))
}
Expand All @@ -39,6 +40,7 @@ __DATA__
GET /t
--- response_body
node_listen: 1984
request_body_json_lib: qjson
stream_proxy: {"tcp":[9100]}
admin_key: null

Expand Down
Loading
Loading