VectifyAI/PageIndex · error · PageIndexAPIError
messages must contain a user or assistant message.
Error message
messages must contain a user or assistant message.
What it means
After validation, chat_completions requires at least one user or assistant message in the list. A messages array consisting only of system/developer messages (even if non-empty) fails, since there is no conversation to drive.
Source
Thrown at pageindex/local_chat.py:87
"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)
_SENTINEL = object()
def _stream_sync(agen_factory) -> Iterator[Any]:
"""Drive an async generator from a background thread; yield synchronously.
Closing the iterator cancels the run between items: the pump stops, and
the async generator's cleanup cancels the underlying agent task, so no
further model turns or tool executions start. An in-flight backendView on GitHub (pinned to afb5e11976)
Solutions
- Always append the user's question: messages.append({"role":"user","content":query})
- Guard: if not any(m['role'] in ('user','assistant') for m in messages): abort early
- Check upstream filtering logic isn't dropping user turns
Example fix
# before
client.chat_completions(model=m, messages=[system_msg])
# after
client.chat_completions(model=m, messages=[system_msg, {"role":"user","content":question}]) Defensive patterns
Strategy: validation
Validate before calling
if not any(m.get('role') in ('user','assistant') for m in messages):
messages = messages + [{'role':'user','content':query}] Type guard
def has_conversation_turn(msgs) -> bool:
return any(isinstance(m, dict) and m.get('role') in ('user','assistant') for m in msgs) Try / catch
null
Prevention
- Always append the user question after the system prompt
- Guard against filters stripping user turns
- Test prompt-building code with empty inputs
When it happens
Trigger: messages=[{"role":"system","content":"..."}] only; or all user/assistant messages were filtered out by earlier processing.
Common situations: Building system prompt first and forgetting to append the user question, upstream filters stripping user turns (moderation, truncation), replaying config-only transcripts.
Related errors
- messages must be a non-empty list.
- Each message must be a dict with 'role' and 'content'.
- system message content must be a string or a list of text pa
- chat_completions content must be a string; for structured it
- Unsupported role for chat_completions: {role!r}. Tool histor
AI-assisted analysis of VectifyAI/PageIndex@afb5e11976 (2026-08-27).
Data as JSON: /api/errors/864ca37969ffe986.
Report an issue: GitHub.