feat(ai): support configurable request JSON library#13386
Conversation
There was a problem hiding this comment.
Pull request overview
Adds an APISIX-level setting apisix.request_body_json_lib (default qjson, options cjson/simdjson/qjson) controlling the JSON library used by core.request.get_request_body_table and by AI transport upstream body encoding. Introduces a new module apisix.core.request_json that lazily loads the selected library, plus rockspec dependencies on lua-qjson and lua-resty-simdjson.
Changes:
- New module
apisix/core/request_json.luaselecting between cjson/qjson/simdjson based on local config, wired intoapisix/core/request.luaand AI transport encoders (ai-transport/http.lua,ai-transport/auth-aws.lua). - Schema + default config additions for
request_body_json_lib, and new rockspec depslua-qjsonandlua-resty-simdjson. - Docs (en/zh) and tests covering selector behavior, AI encoder routing, and the new default in
config-default.t.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| apisix/core/request_json.lua | New selector module with lazy load of qjson/simdjson and cjson fallback. |
| apisix/core/request.lua | Replaces core.json.decode with request_json.decode for request body parsing. |
| apisix/plugins/ai-transport/http.lua | Switches upstream body encode to request_json.encode. |
| apisix/plugins/ai-transport/auth-aws.lua | Switches SigV4 body encode to request_json.encode; drops unused core import. |
| apisix/cli/schema.lua | Adds enum schema for request_body_json_lib. |
| apisix/cli/config.lua | Adds default request_body_json_lib = "qjson". |
| conf/config.yaml.example | Documents new option in example config. |
| apisix-master-0.rockspec | Adds lua-qjson and lua-resty-simdjson dependencies. |
| docs/en/latest/plugins/ai-proxy.md | English docs for the new option. |
| docs/zh/latest/plugins/ai-proxy.md | Chinese docs for the new option. |
| t/core/request.t | Adds tests for selector behavior and AI transport encoder routing. |
| t/core/config-default.t | Adds assertion on the new default value. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
8219798
| git \ | ||
| openldap-dev \ | ||
| pcre-dev \ | ||
| rust \ |
There was a problem hiding this comment.
https://github.com/api7/lua-qjson based on rust.
membphis
left a comment
There was a problem hiding this comment.
Low-priority issue, can be fixed in a follow-up PR.
I noticed that the qjson path in request_json.lua calls qjson.decode() / qjson.encode() directly. However, qjson’s lazy table API raises with error() for malformed JSON, non-object/non-array top-level values, or unsupported value types during encoding, instead of returning nil, err like the existing core.json path.
This makes error handling inconsistent when users explicitly configure apisix.request_body_json_lib: qjson. For example, core.request.get_request_body_table() expects the decoder to return nil, err so it can turn parse failures into a controlled 400 response. The AI transport encoding path also expects nil, err. If qjson throws, user input errors may become uncaught exceptions / 500s.
Since this only affects users who explicitly opt into qjson and does not affect the default simdjson path, I do not think it needs to block this PR. A follow-up PR could wrap qjson decode/encode with pcall, normalize failures to nil, err, and add tests for malformed JSON and encode failures in qjson mode.
This adds an APISIX-level JSON library selector for request body parsing used by core.request.get_request_body_table, and reuses the same selector for AI upstream request body encoding.
The supported values are cjson, simdjson, and qjson. The default is simdjson. qjson is available as an experimental option for users who explicitly opt in after evaluating compatibility. The config schema owns the enum/default validation, and request_json caches the validated value from local_conf. The selected decode/encode behavior is fixed by the configured value: cjson uses cjson decode and encode, simdjson uses simdjson decode and cjson encode, and qjson uses qjson decode and encode. qjson decode results are materialized before returning from core.request so existing table consumers such as JSONPath matching can keep using normal Lua table traversal.
lua-qjson and api7-lua-resty-simdjson are installed through LuaRocks dependencies. The simdjson package keeps cjson array metadata for decoded arrays, so empty arrays still round-trip as arrays when encoded by cjson. The Rust toolchain bootstrap logic is shared by CI and install-dependencies to keep the minimum rustc/rustup setup in one place. Docker build paths that run make deps also install or carry the Rust/helper prerequisites needed to build simdjson.
Docs include benchmark data for cjson, simdjson, and qjson on large AI request bodies.
Tests added/updated:
Checks run: