BerriAI/litellm · error · Exception

Unable to convert openai tool calls={tool_calls} to bedrock

Error message

Unable to convert openai tool calls={tool_calls} to bedrock tool calls. Received error={e}

What it means

A blanket except in _convert_to_bedrock_tool_call_invoke: any exception raised while mapping OpenAI assistant tool_calls to Bedrock Converse 'toolUse' blocks is re-raised with this message. Root causes are usually malformed tool_call dicts (missing 'id' or 'function', non-string arguments) or invalid JSON in arguments.

Source

Thrown at litellm/litellm_core_utils/prompt_templates/factory.py:3715

                        # Fallback: no objects extracted — use empty dict.
                        arguments_dict = {}

                bedrock_tool = BedrockToolUseBlock(input=arguments_dict, name=name, toolUseId=tool_id)
                bedrock_content_block = BedrockContentBlock(toolUse=bedrock_tool)
                _parts_list.append(bedrock_content_block)

                # Check for cache_control and add a separate cachePoint block
                if tool.get("cache_control", None) is not None:
                    cache_point_block = litellm.AmazonConverseConfig().get_cache_point_block(
                        {"cache_control": tool["cache_control"]},
                        block_type="content_block",
                        model=model,
                    )
                    if cache_point_block is not None:
                        _parts_list.append(cache_point_block)
        return _parts_list
    except Exception as e:
        raise Exception(f"Unable to convert openai tool calls={tool_calls} to bedrock tool calls. Received error={e}")


def _append_bedrock_tool_result_media_block(
    tool_result_content_blocks: list[BedrockToolResultContentBlock],
    processed_block: BedrockContentBlock,
    content: dict,
    content_type: str,
) -> None:
    if "image" in processed_block:
        tool_result_content_blocks.append(BedrockToolResultContentBlock(image=processed_block["image"]))
    elif "document" in processed_block:
        tool_result_content_blocks.append(BedrockToolResultContentBlock(document=processed_block["document"]))
    else:
        verbose_logger.warning(
            "Bedrock Converse: unrecognized BedrockContentBlock keys %s for %s tool-result block %s; dropping.",
            list(processed_block.keys()),
            content_type,
            content,

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Inspect the full exception text after 'Received error=' — it contains the underlying cause.
  2. Ensure each tool_call has string 'id', and 'function' with 'name' and JSON-string 'arguments'.
  3. Normalize provider-specific tool call objects to the OpenAI shape before sending history to Bedrock.
  4. If arguments is already a dict, json.dumps it first.

Example fix

# before
{"role": "assistant", "tool_calls": [{"function": {"name": "get_weather", "arguments": {"city": "SF"}}}]}
# after
import json
{"role": "assistant", "tool_calls": [{"id": "call_1", "type": "function", "function": {"name": "get_weather", "arguments": json.dumps({"city": "SF"})}}]}
Defensive patterns

Strategy: validation

Validate before calling

def valid_openai_tool_call(tc: dict) -> bool:
    return (
        isinstance(tc.get("id"), str)
        and isinstance(tc.get("function"), dict)
        and isinstance(tc["function"].get("name"), str)
        and isinstance(tc["function"].get("arguments"), str)
        and _is_json(tc["function"]["arguments"])
    )

def _is_json(s):
    try:
        json.loads(s); return True
    except Exception:
        return False

Type guard

def is_openai_tool_call(x) -> bool:
    return (
        isinstance(x, dict)
        and set(x) >= {"id", "type", "function"}
        and x["type"] == "function"
        and isinstance(x["function"], dict)
        and {"name", "arguments"} <= set(x["function"])
    )

Try / catch

try:
    resp = litellm.completion(model="bedrock/...", messages=history)
except Exception as e:
    if "Unable to convert openai tool calls" in str(e):
        history = [normalize_tool_calls(m) for m in history]
        resp = litellm.completion(model="bedrock/...", messages=history)

Prevention

When it happens

Trigger: Calling a Bedrock Converse model with an assistant message whose tool_calls entries lack 'id', have 'function' missing 'name'/'arguments', or whose arguments are a dict-with-unserializable-values or a non-JSON string. Also triggered if a tool entry itself has an unexpected shape.

Common situations: Replaying stored/LLM-generated assistant messages into conversation history; hand-built tool_call dicts; tool call objects from another provider's schema (missing id); arguments already parsed as dict with weird types.

Related errors


AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15). Data as JSON: /api/errors/80af02434fffaf86. Report an issue: GitHub.