#15015·kong

ai-proxy Bedrock: `bedrock.additionalModelRequestFields` is sent as a JSON array (400) and `bedrock.toolConfig.toolChoice` is ignored

Author: kimnamuCreated Sep 23, 2026Updated Sep 24, 2026

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:

lua
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

  1. bedrock.to_format({ messages = {...}, bedrock = { additionalModelRequestFields = { reasoning = { effort = "high" } } } }, model_info, "llm/v1/chat")
  2. POST the encoded body to /model/us.openai.gpt-6-sol/converse.
  3. 400 as above.

Anything else?

Proposed change (+2/-6) and a spec case
diff
 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
 end

A 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 errors

The change, a changelog entry and that spec case are in #15016.