VectifyAI/PageIndex · error · PageIndexAPIError

system must be a string or a list of blocks.

Error message

system must be a string or a list of blocks.

What it means

The messages() API accepts extra_system as either a plain string or a list of Anthropic system blocks. _anthropic_system() validates this before building the request; anything else (dict, int, None-as-object, tuple) is rejected with this error.

Source

Thrown at pageindex/local_chat.py:935

def _anthropic_system(client, extra_system, block: Optional[str]) -> list[dict]:
    """System blocks: cache_control marks the stable managed prefix only
    (the API allows 4 breakpoints total — the varying doc block and caller
    blocks must not consume the budget); the doc block and caller system
    content follow as their own blocks."""
    blocks = [{"type": "text",
               "text": CHAT_HEADER + "\n\n" + _base_instructions(client),
               "cache_control": {"type": "ephemeral"}}]
    if block:
        blocks.append({"type": "text", "text": block})
    if extra_system is None:
        return blocks
    if isinstance(extra_system, str):
        if extra_system.strip():
            blocks.append({"type": "text", "text": extra_system})
        return blocks
    if isinstance(extra_system, list):
        return blocks + list(extra_system)
    raise PageIndexAPIError("system must be a string or a list of blocks.")


def _cache_marks(system_blocks, messages) -> int:
    """Breakpoints already on the request. The API allows 4 total; the
    top-level moving breakpoint is only added when it fits."""
    blocks = list(system_blocks)
    for message in messages:
        content = message.get("content")
        if isinstance(content, list):
            blocks += [b for b in content if isinstance(b, dict)]
    return sum(1 for b in blocks
               if isinstance(b, dict) and b.get("cache_control"))


def _dump_block(block) -> Any:
    """A content block as a plain JSON dict, minus SDK-internal fields the
    API rejects (ParsedBetaTextBlock.__api_exclude__, e.g. parsed_output)
    and unset response-only defaults (exclude_unset, like the SDK's own

View on GitHub (pinned to afb5e11976)

Solutions

  1. Pass a plain string: extra_system="Always answer in French"
  2. Or a list of blocks: extra_system=[{'type': 'text', 'text': '...'}]
  3. If the value comes from config, validate its shape before the call

Example fix

# before
client.messages("hi", extra_system={"type": "text", "text": "Be brief"})
# after
client.messages("hi", extra_system=[{"type": "text", "text": "Be brief"}])
Defensive patterns

Strategy: type-guard

Validate before calling

if extra_system is not None and not (isinstance(extra_system, str) or isinstance(extra_system, list)):
    raise TypeError('extra_system must be str or list of blocks')

Type guard

def is_valid_system(v) -> bool:
    return v is None or isinstance(v, str) or (isinstance(v, list) and all(isinstance(b, dict) for b in v))

Prevention

When it happens

Trigger: Calling client.messages(messages, extra_system={'type': 'text', ...}) (a single block dict instead of a list), or passing a non-string/non-list value such as an int or tuple.

Common situations: Assuming a single block dict is accepted like in raw Anthropic SDK calls; passing JSON-decoded data whose shape changed; forgetting to wrap one block in [ ].

Understand the failure class

Background: Invalid argument type errors: "must be of type string", "expected X, got Y", and ERR_INVALID_ARG_TYPE explained — this error's family across 15 libraries.

Related errors


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