ai-proxy Bedrock: `bedrock.additionalModelRequestFields` is sent as a JSON array (400) and `bedrock.toolConfig.toolChoice` is ignored
Thanks for adding the bedrock passthrough fields to the Converse driver in #13354. They are how llm/v1/chat clients reach Converse options such as reasoning effort, top_k or toolChoice, since the driver maps only max_tokens, stop, temperature and top_p (to_bedrock_generation_config). Both currently reach Bedrock wrapped in an array. Found while setting reasoning effort for the OpenAI GPT-6 models.
Is there an existing issue for this?
- I have searched the existing issues
Kong version ($ kong version)
master ba2c74a (3.10.0-dev); same code in 3.9.3. No Kong node: kong/llm/drivers/bedrock.lua ran under LuaJIT 2.1 + lua-cjson with its Kong/nginx imports stubbed, and the body from to_format went to live Bedrock Converse (us-east-1).
Current Behavior
kong/llm/drivers/bedrock.lua L56-68 wrap the client's object in a new table:
local function to_additional_request_fields(request_table)
return {
request_table.bedrock.additionalModelRequestFields
}
end
-- ...
local function to_tool_config(request_table)
return {
request_table.bedrock.toolConfig
}
end| client sends | Kong sends to Converse | Bedrock |
|---|---|---|
"bedrock":{"additionalModelRequestFields":{"reasoning":{"effort":"high"}}} |
"additionalModelRequestFields":[{"reasoning":{"effort":"high"}}] |
❌ 400 The format of the additionalModelRequestFields field is invalid. Provide a json object for additionalModelRequestFields and try again. |
tools + "bedrock":{"toolConfig":{"toolChoice":{"tool":{"name":"get_weather"}}}} |
"toolConfig":{"1":{"toolChoice":{...}},"tools":[...]} |
⚠️ 200, toolChoice ignored: text reply, no tool_calls |
The 400 does not depend on the model: GPT-6 Sol/Luna/Astra (us. and global.), GPT-5.6 Sol, gpt-oss-120b (reasoning_effort) and Claude Haiku 4.5 (top_k) all return it.
Expected Behavior
Both fields go out as objects, the way to_bedrock_guardrail_config (L50-52) already passes guardrailConfig. Live results with that change:
| case (us-east-1) | Before (master) | After |
|---|---|---|
GPT-6 Sol/Luna, us. + global., effort none / high (8 runs) |
❌ 400 | ✅ 200: none text only (11-12 output tokens), high reasoning block + text |
GPT-6 Astra, us. + global., effort low / high (4 runs) |
❌ 400 | ✅ 200: low text only, high reasoning block + text |
GPT-6 Astra, effort none |
❌ 400 (array) | 400 from Astra itself, as with a direct Converse call: 'none' is not supported ... Supported values are: 'low', 'medium', 'high', 'xhigh', and 'max'. |
Haiku 4.5 top_k, gpt-oss reasoning_effort |
❌ 400 | ✅ 200 |
GPT-6 Sol/Luna/Astra, forced tool via toolConfig.toolChoice (6 runs) |
⚠️ text, 0 tool_calls |
✅ 1 tool_calls, finish tool_calls |
tools without a bedrock key |
✅ toolUse |
✅ unchanged |
no bedrock key (unit/requests/llm-v1-chat.json fixture) |
✅ | ✅ unchanged |
The high replies above start with a reasoning block, and Kong still drops their text (#15006, L429, fixed separately in #15014).
Steps To Reproduce
bedrock.to_format({ messages = {...}, bedrock = { additionalModelRequestFields = { reasoning = { effort = "high" } } } }, model_info, "llm/v1/chat")- POST the encoded body to
/model/us.openai.gpt-6-sol/converse. - 400 as above.
Anything else?
Proposed change (+2/-6) and a spec case local function to_additional_request_fields(request_table)
- return {
- request_table.bedrock.additionalModelRequestFields
- }
+ return request_table.bedrock.additionalModelRequestFields
end
@@
local function to_tool_config(request_table)
- return {
- request_table.bedrock.toolConfig
- }
+ return request_table.bedrock.toolConfig
endA new it in the bedrock tools block of spec/03-plugins/38-ai-proxy/01-unit_spec.lua calls _to_bedrock_chat_openai with both fields plus tools and asserts objects come out. Run through a LuaJIT shim that loads that block as is (busted is not set up here):
master: FAIL bedrock tools passes bedrock additionalModelRequestFields and toolConfig through as objects
spec/03-plugins/38-ai-proxy/01-unit_spec.lua:1385: Expected objects to be the same.
Passed in:
[{"reasoning":{"effort":"high"}}]
Expected:
{"reasoning":{"effort":"high"}}
5 successes / 1 failures
patched: 6 successes / 0 failures
luacheck: 0 warnings / 0 errorsThe change, a changelog entry and that spec case are in #15016.
Source: Kong/kong