VectifyAI/PageIndex · error · PageIndexAPIError
chat_completions content must be a string; for structured it
Error message
chat_completions content must be a string; for structured items use responses() or messages().
What it means
On the chat_completions surface, user/assistant message content must be a plain string. Structured content (lists of parts, tool calls) is intentionally rejected — those round-trips belong to the responses() or messages() surfaces which understand item dicts.
Source
Thrown at pageindex/local_chat.py:76
def _split_chat_messages(messages) -> "tuple[list[str], list[dict]]":
"""Validate the chat_completions surface's messages: system/developer
content joins the managed instructions; user/assistant history passes
through. Tool-history round-trips belong to responses()/messages()."""
if not isinstance(messages, list) or not messages:
raise PageIndexAPIError("messages must be a non-empty list.")
system_texts: list[str] = []
history: list[dict] = []
for message in messages:
if not isinstance(message, dict) or "role" not in message:
raise PageIndexAPIError(
"Each message must be a dict with 'role' and 'content'.")
role = message["role"]
if role in ("system", "developer"):
system_texts.append(_system_text(message.get("content")))
elif role in ("user", "assistant"):
content = message.get("content")
if not isinstance(content, str):
raise PageIndexAPIError(
"chat_completions content must be a string; for "
"structured items use responses() or messages()."
)
history.append({"role": role, "content": content})
else:
raise PageIndexAPIError(
f"Unsupported role for chat_completions: {role!r}. Tool "
"history round-trips belong to responses() or messages()."
)
if not history:
raise PageIndexAPIError("messages must contain a user or assistant "
"message.")
return system_texts, history
def _run_sync(coro):
from .utils import run_off_loop
return run_off_loop(asyncio.run, coro)View on GitHub (pinned to afb5e11976)
Solutions
- Flatten content parts to a string: "\n".join(p["text"] for p in content)
- Use client.responses(...) or client.messages(...) when history contains structured items or tool calls
- Strip tool_call entries from history before chat_completions
Example fix
# before
msgs=[{"role":"user","content":[{"type":"text","text":"hi"}]}]
client.chat_completions(model=m, messages=msgs)
# after
msgs=[{"role":"user","content":"hi"}]
client.chat_completions(model=m, messages=msgs) Defensive patterns
Strategy: validation
Validate before calling
def flatten(content):
if isinstance(content, str): return content
if isinstance(content, list): return "\n".join(p.get('text','') for p in content if isinstance(p, dict))
return content
messages = [{**m, 'content': flatten(m['content'])} for m in messages] Type guard
def has_string_content(m) -> bool:
c = m.get('content')
return c is None or isinstance(c, str) Try / catch
null
Prevention
- Flatten content-part arrays to strings for chat_completions
- Route tool-call history to responses()/messages()
- Strip assistant tool_calls before replaying history
When it happens
Trigger: Passing {"role":"user","content":[{"type":"text","text":"hi"}]} or assistant messages containing tool_calls to chat_completions.
Common situations: Replaying captured OpenAI request bodies that use content-part arrays, mixing surfaces when porting between chat_completions and responses, feeding back assistant output with structured parts.
Related errors
- system message content must be a string or a list of text pa
- messages must be a non-empty list.
- Each message must be a dict with 'role' and 'content'.
- Unsupported role for chat_completions: {role!r}. Tool histor
- messages must contain a user or assistant message.
AI-assisted analysis of VectifyAI/PageIndex@afb5e11976 (2026-08-27).
Data as JSON: /api/errors/ff46e9fffed9668e.
Report an issue: GitHub.