{"record":{"id":"a88b805b2b4edb9f","repo":"rohitg00/ai-engineering-from-scratch","slug":"every-content-block-needs-a-type","errorCode":null,"errorMessage":"every content block needs a type","messagePattern":"every content block needs a type","errorType":"exception","errorClass":"ProtocolError","httpStatus":null,"severity":"error","filePath":"certifications/claude/lessons/08-messages-api-and-application-lifecycle/code/main.py","lineNumber":151,"sourceCode":"                \"type\": \"tool_result\",\n                \"tool_use_id\": tool_id,\n                \"content\": json.dumps(value, sort_keys=True),\n            }\n        except Exception as exc:  # Tool failures become model-visible results.\n            return {\n                \"type\": \"tool_result\",\n                \"tool_use_id\": tool_id,\n                \"content\": f\"{type(exc).__name__}: {exc}\",\n                \"is_error\": True,\n            }\n\n\ndef _validated_blocks(response: dict[str, Any]) -> list[dict[str, Any]]:\n    blocks = response.get(\"content\")\n    if not isinstance(blocks, list) or not blocks:\n        raise ProtocolError(\"response content must be a non-empty block list\")\n    if not all(isinstance(block, dict) and isinstance(block.get(\"type\"), str) for block in blocks):\n        raise ProtocolError(\"every content block needs a type\")\n    return blocks\n\n\ndef _text_from_blocks(blocks: list[dict[str, Any]]) -> str:\n    return \"\".join(str(block.get(\"text\", \"\")) for block in blocks if block[\"type\"] == \"text\")\n\n\ndef collect_stream_text(events: Iterable[dict[str, Any]]) -> str:\n    \"\"\"Collect only text deltas while checking that a stream terminates.\"\"\"\n    chunks: list[str] = []\n    stopped = False\n    for event in events:\n        event_type = event.get(\"type\")\n        if stopped:\n            raise ProtocolError(\"event arrived after message_stop\")\n        if event_type == \"content_block_delta\":\n            delta = event.get(\"delta\", {})\n            if delta.get(\"type\") == \"text_delta\":","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/rohitg00/ai-engineering-from-scratch/blob/39ea8a1c6d0b61f071226eff7ede4d4105fed820/certifications/claude/lessons/08-messages-api-and-application-lifecycle/code/main.py#L133-L169","documentation":"Raised by _validated_blocks when any element of the response content list is not a dict or lacks a string 'type' field. Every Messages API content block (text, tool_use, etc.) must be an object with a discriminating 'type' string; this check runs before any downstream text extraction.","triggerScenarios":"A content list containing a bare string (e.g. [\"hello\"]) or a block like {\"text\":\"hi\"} with no 'type' key, passed into run().","commonSituations":"Hand-written mocks that put plain strings in content, schema drift where 'type' was renamed, or concatenating already-joined text back into the list.","solutions":["Wrap every element as an object with a string 'type' (\"text\", \"tool_use\", ...)","Check for typos like \"types\" or \"block_type\" in mock blocks","Validate fixtures once in a shared helper so all tests emit well-formed blocks"],"exampleFix":"// before\n{\"content\":[\"hello\"]}\n// after\n{\"content\":[{\"type\":\"text\",\"text\":\"hello\"}]}","handlingStrategy":"type-guard","validationCode":"def blocks_are_typed(blocks):\n    return all(isinstance(b, dict) and isinstance(b.get(\"type\"), str) for b in blocks)","typeGuard":"def are_typed_blocks(content: object) -> bool:\n    return isinstance(content, list) and all(\n        isinstance(b, dict) and isinstance(b.get(\"type\"), str) for b in content\n    )","tryCatchPattern":"try:\n    text = _text_from_blocks(_validated_blocks(response))\nexcept ProtocolError as exc:\n    if \"needs a type\" in str(exc):\n        normalize_blocks_or_reject()","preventionTips":["Use one fixture factory that emits typed block objects","Diff mock block shapes against a real captured response"],"tags":["messages-api","content-blocks","validation","python"],"backgroundTag":"content-block-type-missing","analyzedSha":"39ea8a1c6d0b61f071226eff7ede4d4105fed820","analyzedAt":"2026-08-26T03:13:46.626Z","schemaVersion":2},"datasetVersion":"2026-08-26T07:17:17.940Z"}