Is there an existing issue for this?
Kong version
Kong 3.14.0.13 (server: kong/3.14.0.13-enterprise-edition)
Current Behavior
Enabling Bedrock explicit prompt caching (cache_control on ChatBedrockConverse, or a native cachePoint block) on a llm_format: bedrock Converse route works for the first model call, then breaks on every call that follows a tool call. Kong returns HTTP 500 instead of forwarding the request to Bedrock — so caching cannot be used at all for any tool-calling agent, which is the normal shape of agentic traffic.
Kong returns its generic error envelope and never calls Bedrock:
{
"message": "An unexpected error occurred",
"request_id": "bc01fe387b59e650a861eb310be46e4b"
}
error_log:
[ai-proxy-advanced] /usr/local/share/lua/5.1/kong/llm/adapters/bedrock.lua:149:
bad argument #1 to 'insert' (table expected, got string)
The access log shows Kong failed before any upstream attempt:
tries=[], upstream_status="", proxy=-1
Controls on the same route isolate the cause:
[toolResult] only → 200
[toolResult, text] (no cachePoint) → 500
- The same
[toolResult, text] body sent directly to bedrock-runtime → 200
So this is not an AWS validation error, and it is not specific to prompt caching. Prompt caching just makes it common: LangChain ChatBedrockConverse emits [toolResult, cachePoint] on post-tool turns when caching is enabled.
Root cause is in bedrock_msg_to_openai_msg (kong/llm/adapters/bedrock.lua; line number varies by version — 149 on 3.14.0.13-enterprise, and the same code is present in current master):
- The
toolResult branch sets new_msg.content to a string (part.content).
- The next content block falls through to the generic
else branch.
- That branch calls
table.insert(new_msg.content, part) on the string, raising:
bad argument #1 to 'insert' (table expected, got string)
There is no cachePoint branch and no type guard on new_msg.content. Because the else branch handles every non-tool block, this reproduces with text and cachePoint (both verified) and by inspection applies to any other block (image, document, …).
parse-request.lua calls adapter:to_kong_req before the upstream request, so even native Bedrock format is parsed/converted rather than passed through untouched.
Reproduced on Converse (non-streaming); ConverseStream not yet verified.
Expected Behavior
Kong should accept a valid Bedrock Converse ContentBlock mix — including a toolResult followed by cachePoint, text, or another block — convert it for analytics without raising, and forward the native request to Bedrock:
[toolResult] → 200
[toolResult, cachePoint] → 200, request reaches Bedrock
[toolResult, text] → 200, request reaches Bedrock
cacheWriteInputTokens / cacheReadInputTokens are preserved when Bedrock returns them
The adapter needs to stop assuming new_msg.content is always a table after a toolResult.
Guarding the table.insert so it only appends when new_msg.content is a table or nil prevents the crash; the native body can then continue unchanged to Bedrock.
Steps To Reproduce
- Configure
ai-proxy-advanced on a Bedrock Converse route:
llm_format: bedrock
route_type: llm/v1/chat
POST /bedrock/model/<claude-model>/converse with:
{
"messages": [
{
"role": "user",
"content": [
{
"text": "Use the probe tool."
}
]
},
{
"role": "assistant",
"content": [
{
"toolUse": {
"toolUseId": "t1",
"name": "probe_tool",
"input": {}
}
}
]
},
{
"role": "user",
"content": [
{
"toolResult": {
"toolUseId": "t1",
"content": [
{
"json": {
"ok": true
}
}
]
}
},
{
"cachePoint": {
"type": "default"
}
}
]
}
],
"toolConfig": {
"tools": [
{
"toolSpec": {
"name": "probe_tool",
"description": "Diagnostic probe",
"inputSchema": {
"json": {
"type": "object",
"properties": {}
}
}
}
}
]
},
"inferenceConfig": {
"maxTokens": 16
}
}
Result: 500
bedrock.lua: bad argument #1 to 'insert' (table expected, got string)
No upstream request is made.
-
Repeat with only the toolResult block in the final message → 200.
-
Repeat step 2 but replace cachePoint with:
{
"text": "Reply only with OK."
}
Result: 500.
- Send the exact body from step 4 directly to AWS Bedrock Converse → 200.
Anything else?
Same defect class as #14837 (Gemini driver drops cachedContent during the OpenAI-shape transformation): in both cases, the adapter's internal analytics conversion corrupts or chokes on a valid native field.
Related:
A general safeguard so adapters never mangle or crash on valid native content during the internal copy would cover all of these.
Is there an existing issue for this?
Kong version
Current Behavior
Enabling Bedrock explicit prompt caching (cache_control on ChatBedrockConverse, or a native cachePoint block) on a llm_format: bedrock Converse route works for the first model call, then breaks on every call that follows a tool call. Kong returns HTTP 500 instead of forwarding the request to Bedrock — so caching cannot be used at all for any tool-calling agent, which is the normal shape of agentic traffic.
Kong returns its generic error envelope and never calls Bedrock:
{ "message": "An unexpected error occurred", "request_id": "bc01fe387b59e650a861eb310be46e4b" }error_log:The access log shows Kong failed before any upstream attempt:
Controls on the same route isolate the cause:
[toolResult]only → 200[toolResult, text](nocachePoint) → 500[toolResult, text]body sent directly tobedrock-runtime→ 200So this is not an AWS validation error, and it is not specific to prompt caching. Prompt caching just makes it common: LangChain
ChatBedrockConverseemits[toolResult, cachePoint]on post-tool turns when caching is enabled.Root cause is in
bedrock_msg_to_openai_msg(kong/llm/adapters/bedrock.lua; line number varies by version — 149 on3.14.0.13-enterprise, and the same code is present in currentmaster):toolResultbranch setsnew_msg.contentto a string (part.content).elsebranch.table.insert(new_msg.content, part)on the string, raising:There is no
cachePointbranch and no type guard onnew_msg.content. Because theelsebranch handles every non-tool block, this reproduces withtextandcachePoint(both verified) and by inspection applies to any other block (image,document, …).parse-request.luacallsadapter:to_kong_reqbefore the upstream request, so even native Bedrock format is parsed/converted rather than passed through untouched.Reproduced on Converse (non-streaming); ConverseStream not yet verified.
Expected Behavior
Kong should accept a valid Bedrock Converse
ContentBlockmix — including atoolResultfollowed bycachePoint,text, or another block — convert it for analytics without raising, and forward the native request to Bedrock:[toolResult]→ 200[toolResult, cachePoint]→ 200, request reaches Bedrock[toolResult, text]→ 200, request reaches BedrockcacheWriteInputTokens/cacheReadInputTokensare preserved when Bedrock returns themThe adapter needs to stop assuming
new_msg.contentis always a table after atoolResult.Guarding the
table.insertso it only appends whennew_msg.contentis a table ornilprevents the crash; the native body can then continue unchanged to Bedrock.Steps To Reproduce
ai-proxy-advancedon a Bedrock Converse route:POST /bedrock/model/<claude-model>/conversewith:{ "messages": [ { "role": "user", "content": [ { "text": "Use the probe tool." } ] }, { "role": "assistant", "content": [ { "toolUse": { "toolUseId": "t1", "name": "probe_tool", "input": {} } } ] }, { "role": "user", "content": [ { "toolResult": { "toolUseId": "t1", "content": [ { "json": { "ok": true } } ] } }, { "cachePoint": { "type": "default" } } ] } ], "toolConfig": { "tools": [ { "toolSpec": { "name": "probe_tool", "description": "Diagnostic probe", "inputSchema": { "json": { "type": "object", "properties": {} } } } } ] }, "inferenceConfig": { "maxTokens": 16 } }Result: 500
No upstream request is made.
Repeat with only the
toolResultblock in the final message → 200.Repeat step 2 but replace
cachePointwith:{ "text": "Reply only with OK." }Result: 500.
Anything else?
Same defect class as #14837 (Gemini driver drops
cachedContentduring the OpenAI-shape transformation): in both cases, the adapter's internal analytics conversion corrupts or chokes on a valid native field.Related:
A general safeguard so adapters never mangle or crash on valid native content during the internal copy would cover all of these.