BerriAI/litellm · error · OCIError
Prop `image_url` must be a string or an object with a `url`
Error message
Prop `image_url` must be a string or an object with a `url` property
What it means
For image_url content parts, the adapter accepts either a plain string ('image_url': 'https://...') or an OpenAI-style object ('image_url': {'url': '...'}). If after unwrapping the value is not a string, OCIError(400) "Prop `image_url` must be a string or an object with a `url` property" is raised. Notably, an object whose 'url' key is missing or None also fails, because the unwrapped value is not a str.
Source
Thrown at litellm/llms/oci/chat/generic.py:100
status_code=400,
message=f"Content type `{item_type}` is not supported by OCI",
)
if item_type == "text":
text = content_item.get("text")
if not isinstance(text, str):
raise OCIError(
status_code=400,
message="Content item of type `text` must have a string `text` field",
)
new_content.append(OCITextContentPart(text=text))
elif item_type == "image_url":
image_url = content_item.get("image_url")
if isinstance(image_url, dict):
image_url = image_url.get("url")
if not isinstance(image_url, str):
raise OCIError(
status_code=400,
message="Prop `image_url` must be a string or an object with a `url` property",
)
new_content.append(OCIImageContentPart(imageUrl=OCIImageUrl(url=image_url)))
return OCIMessage(
role=open_ai_to_generic_oci_role_map[role],
content=new_content,
toolCalls=None,
toolCallId=None,
)
def adapt_messages_to_generic_oci_standard_tool_call(role: str, tool_calls: list) -> OCIMessage:
"""Convert an assistant tool-call message to OCI format."""
tool_calls_formatted: Final = []
for tool_call in tool_calls:
if not isinstance(tool_call, dict):View on GitHub (pinned to 6c2dcb801b)
Solutions
- Use the OpenAI shape: {'type':'image_url','image_url':{'url':'https://...'}} or the shorthand string form.
- Ensure the url key exists and is a non-empty string; str() any URL-like objects before building the part.
- For base64 images, use a data URL string ('data:image/png;base64,....') as the url value.
Example fix
# before
{'type':'image_url','image_url':{'data': b64_bytes}}
# after
{'type':'image_url','image_url':{'url': f'data:image/png;base64,{b64_str}'}} Defensive patterns
Strategy: type-guard
Validate before calling
def to_image_part(url_or_obj) -> dict:
url = url_or_obj.get('url') if isinstance(url_or_obj, dict) else url_or_obj
assert isinstance(url, str) and url, 'image_url must be a string or {"url": str}'
return {'type': 'image_url', 'image_url': {'url': url}} Type guard
def is_valid_image_part(part: object) -> bool:
if not (isinstance(part, dict) and part.get('type') == 'image_url'):
return False
iu = part.get('image_url')
url = iu.get('url') if isinstance(iu, dict) else iu
return isinstance(url, str) and bool(url) Prevention
- Standardize on the OpenAI nested form {'image_url': {'url': ...}} across your codebase.
- For base64 images always emit a data: URL string, never raw bytes or a 'data' key.
When it happens
Trigger: Sending {'type':'image_url','image_url':{'data':b64bytes}} (data instead of url), 'image_url': None, or an object where 'url' is missing/None to an oci/ GENERIC model. Also {'image_url': {'url': None}}.
Common situations: Confusing base64 data-field conventions across providers; dicts built from optional values where the url never got populated; passing an httpx.URL or pathlib.Path object instead of a string.
Related errors
- Each content item must have a string `type` field
- Content item of type `text` must have a string `text` field
- Each content item must be a dictionary
- Content type `{item_type}` is not supported by OCI
- Each tool call must be a dictionary
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/970da06530411ef0.
Report an issue: GitHub.