ai-prompt-decorator re-encodes empty JSON arrays as objects (tools: [] → tools: {})
Is there an existing issue for this?
- I have searched the existing issues
This is not a duplicate of #14431. That issue dropped sibling fields (model, temperature) when rewriting messages. This one keeps fields such as tools, but Lua cjson turns empty arrays into empty objects.
Kong version ($ kong version)
Kong Gateway 3.14.0.x and 3.15.0.5 (Konnect data plane and self-managed). Still reproduces after the 3.15.0.0 native-body decorator fix (AG-976 / FTI-7459).
Current Behavior
Claude Code (e.g. 2.1.247) talks to Anthropic through AI Proxy with llm_format: anthropic. Even with tools disabled it still sends tools: [] instead of omitting the field.
With ai-prompt-decorator on the same service, the upstream Anthropic Messages API returns:
400 tools: Input should be a valid arrayThe body that leaves Kong has "tools": {} instead of "tools": [].
The same rewrite also flips nested empty arrays inside tool JSON Schema, e.g. input_schema.required: [] → {}.
Without the decorator (AI Proxy native passthrough only), tools: [] is forwarded unchanged and the request succeeds.
decorate-prompt.lua decodes/encodes the native body with cjson.safe (no array_mt) and then kong.service.request.set_body(), whose JSON encoder also treats empty Lua tables as objects.
Expected Behavior
ai-prompt-decorator should preserve JSON array vs object identity when it re-serializes the request, including empty arrays:
tools: []stays[](or the field is omitted)- nested schema arrays such as
required: []stay[]
Decorating a shared system prompt must not change Anthropic (or other native) tool payloads.
A practical fix is to decode with decode_array_with_array_mt(true) (or kong.tools.cjson.decode_with_array_mt) and encode with an encoder that honors array_mt, the same pattern request-transformer already uses. Optionally expose a config toggle, but preserving empty arrays by default for native Anthropic requests would be enough.
Steps To Reproduce
- Route Anthropic-native traffic through
ai-proxy(llm_format: anthropic,route_type: llm/v1/chat) plusai-prompt-decorator(llm_format: anthropic, prepend a system prompt). - POST a Messages body that includes an empty tools array:
curl -sS -X POST "$PROXY/v1/messages" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-opus-4",
"max_tokens": 8,
"messages": [{"role": "user", "content": "ping"}],
"tools": []
}'- Upstream sees
"tools": {}and Anthropic (or a mock that validates the Anthropic contract) returns400 tools: Input should be a valid array. - Repeat without
ai-prompt-decorator:"tools": []is preserved and the request succeeds. - Repeat with one tool whose
input_schema.requiredis[]: decorator rewritesrequiredto{}.
Anything else?
Workaround we are using today: drop ai-prompt-decorator and inject the shared org system prompt with pre-function, decoding via cjson.safe.new() + decode_array_with_array_mt(true) and writing back with kong.service.request.set_raw_body. That keeps tools: [] / required: [] and still prepends Anthropic system (string or text-block array).
Related but different:
- #14431 — decorator dropped
model/temperature(fixed) - 3.15.0.0 AG-976 — decorator forwarded an OpenAI-shaped body for native formats (fixed); empty-array encoding was not part of that change
Source: Kong/kong