VectifyAI/PageIndex · error · PageIndexAPIError
Each message must be a dict with 'role' and 'content'.
Error message
Each message must be a dict with 'role' and 'content'.
What it means
Each element of the messages list must be a dict containing at least a 'role' key. Strings, tuples, or dicts without 'role' are rejected — this mirror of the OpenAI chat schema is enforced before the model runs.
Source
Thrown at pageindex/local_chat.py:68
if isinstance(part, dict) and isinstance(part.get("text"), str)]
if texts:
return "\n".join(texts)
raise PageIndexAPIError(
"system message content must be a string or a list of text parts."
)
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:View on GitHub (pinned to afb5e11976)
Solutions
- Use {"role": ..., "content": ...} dicts for every message
- Validate each item: all(isinstance(m, dict) and 'role' in m for m in messages)
- If converting from another SDK's format, write an explicit adapter
Example fix
# before
messages=[{"content": "hi"}]
# after
messages=[{"role": "user", "content": "hi"}] Defensive patterns
Strategy: validation
Validate before calling
for m in messages:
if not isinstance(m, dict) or 'role' not in m:
raise ValueError(f'bad message: {m!r}') Type guard
def is_valid_message(m) -> bool:
return isinstance(m, dict) and 'role' in m and 'content' in m Try / catch
null
Prevention
- Always build messages as {role, content} dicts
- Schema-validate external message payloads (pydantic/jsonschema)
- Watch for role-key typos in JSON
When it happens
Trigger: messages=["hello"], messages=[("user","hi")], or messages=[{"content":"hi"}] (missing role).
Common situations: Hand-building message arrays, JSON payloads with typos ('Role'), converting from Anthropic-style tuples, LLM-generated request bodies with schema drift.
Related errors
- messages must be a non-empty list.
- Unsupported role for chat_completions: {role!r}. Tool histor
- messages must contain a user or assistant message.
- system message content must be a string or a list of text pa
- chat_completions content must be a string; for structured it
AI-assisted analysis of VectifyAI/PageIndex@afb5e11976 (2026-08-27).
Data as JSON: /api/errors/4ef83dfddaccdff7.
Report an issue: GitHub.