{"record":{"id":"ac4d6b60425b295b","repo":"HKUDS/DeepTutor","slug":"unexpected-concept-graph-payload-type-type-raw","errorCode":null,"errorMessage":"unexpected concept_graph payload type: {type(raw).__name__}","messagePattern":"unexpected concept_graph payload type: (.+?)","errorType":"exception","errorClass":"GenerationFailure","httpStatus":null,"severity":"error","filePath":"deeptutor/book/blocks/concept_graph.py","lineNumber":100,"sourceCode":"\nclass ConceptGraphGenerator(BlockGenerator):\n    block_type = BlockType.CONCEPT_GRAPH\n\n    async def _generate(\n        self, ctx: BlockContext\n    ) -> tuple[dict[str, Any], list[SourceAnchor], dict[str, Any]]:\n        raw = ctx.extra.get(\"concept_graph\") or ctx.block.params.get(\"concept_graph\")\n        if raw is None:\n            raise GenerationFailure(\"concept_graph payload missing from BlockContext.extra\")\n        if isinstance(raw, ConceptGraph):\n            graph = raw\n        elif isinstance(raw, dict):\n            try:\n                graph = ConceptGraph.model_validate(raw)\n            except Exception as exc:\n                raise GenerationFailure(f\"invalid concept_graph payload: {exc}\") from exc\n        else:\n            raise GenerationFailure(f\"unexpected concept_graph payload type: {type(raw).__name__}\")\n\n        chapters_index = ctx.extra.get(\"chapter_index\") or []\n        if not isinstance(chapters_index, list):\n            chapters_index = []\n\n        mermaid_src = render_mermaid(graph)\n\n        # Build a node→chapter lookup for the interactive sidebar.\n        node_to_chapter: dict[str, str] = {}\n        for n in graph.nodes:\n            if n.chapter_id:\n                node_to_chapter[n.id] = n.chapter_id\n\n        return (\n            {\n                \"render_type\": \"concept_graph\",\n                \"code\": {\"language\": \"mermaid\", \"content\": mermaid_src},\n                \"graph\": graph.model_dump(),","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/HKUDS/DeepTutor/blob/3e82f130422a813cdd73c10b21a44e9325f5821a/deeptutor/book/blocks/concept_graph.py#L82-L118","documentation":"This GenerationFailure is thrown by the concept_graph book block when the raw payload returned by the LLM/generator is neither a ConceptGraph instance (or dict) — i.e. model_validate cannot be applied — because the object is of some unexpected type such as a list, str, or None. It signals that the generator contract (return a ConceptGraph model or a validate-able dict) was violated upstream.","triggerScenarios":"Calling the concept_graph block's _generate when the generator/pipeline returns raw=None, a JSON string, a list of nodes, or any object that is not a ConceptGraph pydantic model and not a dict. Typically happens when an LLM returns a bare JSON string that wasn't parsed, or a tool returns a list wrapper like {'graphs': [...]}.","commonSituations":"LLM output parsing step skipped (raw stays a str); upstream generator changed its return type after a refactor or version bump; mocked/test generators returning fixtures with the wrong shape; None returned when the generator silently fails.","solutions":["Inspect/log the actual type(raw) in the generator pipeline and fix the upstream producer to return a dict or ConceptGraph","If the LLM returns a JSON string, parse it with json.loads (or the project's LLM JSON extractor) before handing it to the block","Wrap list-shaped payloads (e.g. {'graph': {...}} or [ {...} ]) by extracting the inner dict before validation","Upgrade/degrade the generator package to a version whose return contract matches the block's expectations"],"exampleFix":"// before\nraw = await generator.generate(...)  # returns '{\"nodes\": [...]}' as str\n\n// after\nimport json\nif isinstance(raw, str):\n    raw = json.loads(raw)\nif isinstance(raw, list) and raw and isinstance(raw[0], dict):\n    raw = raw[0]","handlingStrategy":"validation","validationCode":"def is_concept_graph_payload(raw) -> bool:\n    return isinstance(raw, dict) and 'nodes' in raw","typeGuard":"from deeptutor.book.blocks.concept_graph import ConceptGraph\n\ndef is_concept_graph_payload(raw) -> bool:\n    if isinstance(raw, ConceptGraph):\n        return True\n    if not isinstance(raw, dict):\n        return False\n    try:\n        ConceptGraph.model_validate(raw)\n        return True\n    except Exception:\n        return False","tryCatchPattern":"try:\n    block.generate(ctx)\nexcept GenerationFailure as exc:\n    if 'unexpected concept_graph payload type' in str(exc):\n        raw = inspect_upstream_generator_output()\n        # parse str/list wrappers then retry once","preventionTips":["Parse LLM JSON strings to dicts before passing payloads between stages","Add contract tests asserting generators return ConceptGraph or dict","Log type(raw) at stage boundaries to catch shape drift early"],"tags":["concept-graph","pydantic","llm-output","generation"],"backgroundTag":"llm-output-schema-mismatch","analyzedSha":"3e82f130422a813cdd73c10b21a44e9325f5821a","analyzedAt":"2026-08-27T06:57:25.364Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}