{"record":{"id":"ab3fb5660e13575d","repo":"run-llama/llama_index","slug":"must-provide-either-user-msg-or-chat-history","errorCode":null,"errorMessage":"Must provide either user_msg or chat_history","messagePattern":"Must provide either user_msg or chat_history","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"llama-index-core/llama_index/core/agent/workflow/base_agent.py","lineNumber":429,"sourceCode":"            )\n            await ctx.store.set(\"user_msg_str\", content_str)\n        elif chat_history and not all(\n            message.role == \"system\" for message in chat_history\n        ):\n            # If no user message, use the last message from chat history as user_msg_str\n            user_hist: List[ChatMessage] = [\n                msg for msg in chat_history if msg.role == \"user\"\n            ]\n            content_str = \"\\n\".join(\n                [\n                    block.text\n                    for block in user_hist[-1].blocks\n                    if isinstance(block, TextBlock)\n                ]\n            )\n            await ctx.store.set(\"user_msg_str\", content_str)\n        else:\n            raise ValueError(\"Must provide either user_msg or chat_history\")\n\n        # Get all messages from memory\n        input_messages = await memory.aget()\n\n        # send to the current agent\n        return AgentInput(input=input_messages, current_agent_name=self.name)\n\n    @step\n    async def setup_agent(self, ctx: Context, ev: AgentInput) -> AgentSetup:\n        \"\"\"Main agent handling logic.\"\"\"\n        llm_input = [*ev.input]\n\n        if self.system_prompt:\n            llm_input = [\n                ChatMessage(role=\"system\", content=self.system_prompt),\n                *llm_input,\n            ]\n","sourceCodeStart":411,"sourceCodeEnd":447,"githubUrl":"https://github.com/run-llama/llama_index/blob/afd0fef371831f9bda13e5af7167cf4e981278ab/llama-index-core/llama_index/core/agent/workflow/base_agent.py#L411-L447","documentation":"StorageContext.to_dict (used when serializing an entire index to a JSON/dict payload) requires that every store it holds is the in-memory 'simple' implementation: SimpleDocumentStore, SimpleIndexStore, SimpleGraphStore, SimplePropertyGraphStore (or None), and SimpleVectorStore for all vector stores. Any custom or database-backed store makes all_simple False and raises this ValueError, because only the simple stores have a JSON-serializable format.","triggerScenarios":"Building a StorageContext with e.g. ChromaVectorStore, MongoDocumentStore, or RedisKVStore-backed stores and then calling index.storage_context.to_dict() or index.save_to_string()/'index_json' paths that depend on it; calling to_dict after StorageContext.from_defaults(vector_store=custom_store).","commonSituations":"Starting with default simple stores, later swapping in a production vector DB (Milvus, Qdrant, Weaviate) via storage_context, then still trying index.save_to_dict / save_to_string; attempting to persist the whole context while using PropertyGraphIndex with Neo4j; serialization paths in workflows that assume simple stores.","solutions":["Persist each store through its own backend instead of to_dict: leave data in the vector DB / docstore and persist only index_struct via index_store, or use index.persist(persist_dir) per-store.","If you need to_dict, construct the StorageContext entirely from simple stores (StorageContext.from_defaults()) and move data into external backends separately.","Serialize only what is serializable: save index structures with SimpleIndexStore and keep documents/vectors in their dedicated stores.","Check isinstance on each store before calling to_dict and raise a clearer domain-specific error in your own code."],"exampleFix":"# before\nsc = StorageContext.from_defaults(vector_store=chroma_store)\nindex.set_index_store(sc.index_store)\npayload = sc.to_dict()  # ValueError: to_dict only available when using simple ...\n\n# after\n# keep Chroma for vectors; persist only the simple index store\nsc = StorageContext.from_defaults()\nindex = VectorStoreIndex(nodes, storage_context=sc, vector_store=chroma_store)\nindex.storage_context.index_store.persist(\"./storage/index_store.json\")","handlingStrategy":"validation","validationCode":"from llama_index.core.storage.docstore import SimpleDocumentStore\nfrom llama_index.core.storage.index_store import SimpleIndexStore\nfrom llama_index.core.storage.graph_store import SimpleGraphStore\nfrom llama_index.core.vector_stores import SimpleVectorStore\n\nsc = storage_context\nok = (isinstance(sc.docstore, SimpleDocumentStore)\n      and isinstance(sc.index_store, SimpleIndexStore)\n      and isinstance(sc.graph_store, SimpleGraphStore)\n      and all(isinstance(v, SimpleVectorStore) for v in sc.vector_stores.values()))\nif ok:\n    payload = sc.to_dict()\nelse:\n    payload = None  # persist per-store instead","typeGuard":"def storage_context_is_simple(sc) -> bool:\n    simple = (SimpleDocumentStore, SimpleIndexStore, SimpleGraphStore)\n    return (isinstance(sc.docstore, SimpleDocumentStore)\n            and isinstance(sc.index_store, SimpleIndexStore)\n            and isinstance(sc.graph_store, SimpleGraphStore)\n            and all(isinstance(v, SimpleVectorStore) for v in sc.vector_stores.values()))","tryCatchPattern":"try:\n    payload = sc.to_dict()\nexcept ValueError as e:\n    if \"simple doc/index/vector stores\" in str(e):\n        # fall back to per-store persistence\n        ...","preventionTips":["Decide serialization strategy before swapping in external vector stores.","Persist index_struct via the index store and data via each backend's own durability.","Assert store types in a startup check when to_dict/save_to_string is part of the pipeline."],"tags":["storage-context","serialization","vector-store","llama-index"],"backgroundTag":null,"analyzedSha":"afd0fef371831f9bda13e5af7167cf4e981278ab","analyzedAt":"2026-08-15T05:42:58.429Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}