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 backend

View on GitHub (pinned to afb5e11976)

Solutions

  1. Always append the user's question: messages.append({"role":"user","content":query})
  2. Guard: if not any(m['role'] in ('user','assistant') for m in messages): abort early
  3. 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

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


AI-assisted analysis of VectifyAI/PageIndex@afb5e11976 (2026-08-27). Data as JSON: /api/errors/864ca37969ffe986. Report an issue: GitHub.