{"record":{"id":"b417a453a928f827","repo":"VectifyAI/PageIndex","slug":"doc-id-must-be-a-string-or-a-list-of-strings","errorCode":null,"errorMessage":"doc_id must be a string or a list of strings.","messagePattern":"doc_id must be a string or a list of strings\\.","errorType":"exception","errorClass":"PageIndexAPIError","httpStatus":null,"severity":"error","filePath":"pageindex/local_chat.py","lineNumber":35,"sourceCode":"    \"You are PageIndex by Vectify AI, a document-focused assistant. \"\n    \"Be concise, never use emojis, and do not expose tool names.\"\n)\n\n\n# ── shared: prompt, doc targeting, validation, sync bridges ──\n\ndef _managed_instructions(client, extra_system: list[str]) -> str:\n    # Local: the built-in subset guidance. Own-model chat over cloud\n    # documents: the live instructions the MCP server serves.\n    base: str = _base_instructions(client)\n    return \"\\n\\n\".join([CHAT_HEADER, base, *extra_system])\n\n\ndef _doc_block(client, doc_id, scoped: bool) -> Optional[str]:\n    if doc_id is None:\n        return None\n    if not isinstance(doc_id, (str, list)):\n        raise PageIndexAPIError(\"doc_id must be a string or a list of \"\n                                \"strings.\")\n    # scoped: local surfaces also pass doc_id into the tool layer, so name\n    # resolution happens inside the allowlist — only a duplicate name\n    # within the targeted set shadows. Cloud tools take no allowlist\n    # (targeting is prompt-level), so the whole library shadows.\n    return doc_targeting_block(client, doc_id, scoped=scoped)\n\n\ndef _system_text(content: Any) -> str:\n    \"\"\"Text of a system/developer message: a string, or text parts joined.\"\"\"\n    if isinstance(content, str):\n        return content\n    if isinstance(content, list):\n        texts = [part.get(\"text\") for part in content\n                 if isinstance(part, dict) and isinstance(part.get(\"text\"), str)]\n        if texts:\n            return \"\\n\".join(texts)\n    raise PageIndexAPIError(","sourceCodeStart":17,"sourceCodeEnd":53,"githubUrl":"https://github.com/VectifyAI/PageIndex/blob/afb5e119766630af6014b04fe8b53357527bc05e/pageindex/local_chat.py#L17-L53","documentation":"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.","triggerScenarios":"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.","commonSituations":"IDs coming from a database as integers, JSON payloads where the ID is occasionally an object, copying cloud-API request bodies with structured selectors.","solutions":["Coerce to string: doc_id=str(doc_id) for scalars","Flatten lists: doc_id=[str(d) for d in doc_ids]","Validate doc_id shape at your API boundary"],"exampleFix":"# before\nclient.chat_completions(model=..., messages=..., doc_id=doc[\"pk\"])\n\n# after\nclient.chat_completions(model=..., messages=..., doc_id=str(doc[\"pk\"]))","handlingStrategy":"type-guard","validationCode":"if isinstance(doc_id, (list, tuple)):\n    doc_id = [str(d) for d in doc_id]\nelif doc_id is not None:\n    doc_id = str(doc_id)","typeGuard":"def is_valid_doc_id(v) -> bool:\n    return v is None or isinstance(v, str) or (isinstance(v, list) and all(isinstance(x, str) for x in v))","tryCatchPattern":"null","preventionTips":["Coerce numeric DB keys to str before chat calls","Flatten tuples/nested lists to a flat str list","Validate at API boundary"],"tags":["pageindex","chat","doc-id","type-validation"],"backgroundTag":"invalid-argument-type","analyzedSha":"afb5e119766630af6014b04fe8b53357527bc05e","analyzedAt":"2026-08-27T11:20:48.519Z","schemaVersion":2},"datasetVersion":"2026-08-27T13:17:12.746Z"}