VectifyAI/PageIndex · error · PageIndexAPIError

doc_id must be a string or a list of strings.

Error message

doc_id must be a string or a list of strings.

What it means

In the local chat surfaces, doc_id used for document targeting must be a plain string or a list of strings. Other types (int, dict, tuple, nested lists) are rejected before name resolution because the targeting block builder can only handle scalar IDs.

Source

Thrown at pageindex/local_chat.py:35

    "You are PageIndex by Vectify AI, a document-focused assistant. "
    "Be concise, never use emojis, and do not expose tool names."
)


# ── shared: prompt, doc targeting, validation, sync bridges ──

def _managed_instructions(client, extra_system: list[str]) -> str:
    # Local: the built-in subset guidance. Own-model chat over cloud
    # documents: the live instructions the MCP server serves.
    base: str = _base_instructions(client)
    return "\n\n".join([CHAT_HEADER, base, *extra_system])


def _doc_block(client, doc_id, scoped: bool) -> Optional[str]:
    if doc_id is None:
        return None
    if not isinstance(doc_id, (str, list)):
        raise PageIndexAPIError("doc_id must be a string or a list of "
                                "strings.")
    # scoped: local surfaces also pass doc_id into the tool layer, so name
    # resolution happens inside the allowlist — only a duplicate name
    # within the targeted set shadows. Cloud tools take no allowlist
    # (targeting is prompt-level), so the whole library shadows.
    return doc_targeting_block(client, doc_id, scoped=scoped)


def _system_text(content: Any) -> str:
    """Text of a system/developer message: a string, or text parts joined."""
    if isinstance(content, str):
        return content
    if isinstance(content, list):
        texts = [part.get("text") for part in content
                 if isinstance(part, dict) and isinstance(part.get("text"), str)]
        if texts:
            return "\n".join(texts)
    raise PageIndexAPIError(

View on GitHub (pinned to afb5e11976)

Solutions

  1. Coerce to string: doc_id=str(doc_id) for scalars
  2. Flatten lists: doc_id=[str(d) for d in doc_ids]
  3. Validate doc_id shape at your API boundary

Example fix

# before
client.chat_completions(model=..., messages=..., doc_id=doc["pk"])

# after
client.chat_completions(model=..., messages=..., doc_id=str(doc["pk"]))
Defensive patterns

Strategy: type-guard

Validate before calling

if isinstance(doc_id, (list, tuple)):
    doc_id = [str(d) for d in doc_id]
elif doc_id is not None:
    doc_id = str(doc_id)

Type guard

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

Try / catch

null

Prevention

When it happens

Trigger: Passing doc_id=123 (numeric DB key), doc_id=("a","b") tuple, doc_id=["a", ["b"]] nested list, or a single dict to chat_completions/responses/messages.

Common situations: IDs coming from a database as integers, JSON payloads where the ID is occasionally an object, copying cloud-API request bodies with structured selectors.

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/b417a453a928f827. Report an issue: GitHub.