BerriAI/litellm · error · OCIError
Tool call `function.arguments` must be a JSON string
Error message
Tool call `function.arguments` must be a JSON string
What it means
Tool call arguments must be a JSON-encoded string (defaulting to '{}' when absent), matching the OpenAI wire format. Passing arguments as a dict/object instead of a serialized string raises OCIError(400) "Tool call `function.arguments` must be a JSON string" in the OCI GENERIC adapter.
Source
Thrown at litellm/llms/oci/chat/generic.py:137
raise OCIError(status_code=400, message="Each tool call must be a dictionary")
if tool_call.get("type") != "function":
raise OCIError(status_code=400, message="OCI only supports function tool calls")
tool_call_id = tool_call.get("id")
if not isinstance(tool_call_id, str):
raise OCIError(status_code=400, message="Tool call `id` must be a string")
tool_function = tool_call.get("function")
if not isinstance(tool_function, dict):
raise OCIError(status_code=400, message="Tool call `function` must be a dictionary")
function_name = tool_function.get("name")
if not isinstance(function_name, str):
raise OCIError(status_code=400, message="Tool call `function.name` must be a string")
arguments = tool_call["function"].get("arguments", "{}")
if not isinstance(arguments, str):
raise OCIError(
status_code=400,
message="Tool call `function.arguments` must be a JSON string",
)
tool_calls_formatted.append(
OCIToolCall(
id=tool_call_id,
type="FUNCTION",
name=function_name,
arguments=arguments,
)
)
return OCIMessage(
role=open_ai_to_generic_oci_role_map[role],
content=None,
toolCalls=tool_calls_formatted,
toolCallId=None,View on GitHub (pinned to 6c2dcb801b)
Solutions
- Serialize: arguments=json.dumps({...}).
- Echo tool calls back verbatim from the model's own response instead of reconstructing them by hand.
- Pre-flight: coerce with json.dumps if not isinstance(arguments, str).
Example fix
# before
{'id':'c1','type':'function','function':{'name':'get_weather','arguments':{'city':'SF'}}}
# after
import json
{'id':'c1','type':'function','function':{'name':'get_weather','arguments':json.dumps({'city':'SF'})}} Defensive patterns
Strategy: validation
Validate before calling
import json
for tc in msg.get('tool_calls') or []:
args = tc.get('function', {}).get('arguments', '{}')
if not isinstance(args, str):
tc['function']['arguments'] = json.dumps(args) Type guard
def tool_call_args_are_json_string(tc: object) -> bool:
args = tc.get('function', {}).get('arguments', '{}') if isinstance(tc, dict) else None
if not isinstance(args, str):
return False
try:
json.loads(args)
return True
except (TypeError, ValueError):
return False Prevention
- Remember the asymmetry: tools.parameters is a dict, tool_calls.function.arguments is a JSON string.
- json.dumps at the boundary where tool results/history is built, never rely on the provider to coerce.
When it happens
Trigger: Sending {'function': {'name':'f','arguments': {'city':'SF'}}} — a live dict — in assistant tool-call history to an oci/ GENERIC model. This is the classic mistake when hand-building history instead of echoing back what the model produced.
Common situations: Developers writing arguments as objects because it reads nicer; frameworks that auto-serialize dicts and stop doing so after an upgrade; mixing conventions between request 'tools' (where parameters is a dict) and history 'tool_calls' (where arguments is a string).
Related errors
- Each tool call must be a dictionary
- OCI only supports function tool calls
- Tool call `id` must be a string
- Tool call `function` must be a dictionary
- Tool call `function.name` must be a string
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/8554522c47658361.
Report an issue: GitHub.