{"product_id":"json-mode-buộc-claude-trả-về-json-chinh-xac","title":"JSON Mode — Buộc Claude trả về JSON chính xác","description":"\n\u003cp\u003eKhi tích hợp Claude vào ứng dụng, bạn thường cần parse output của Claude thành dữ liệu có cấu trúc. Nếu Claude trả về text tự do, code của bạn sẽ break. Câu hỏi là: làm sao \u003cstrong\u003eđảm bảo\u003c\/strong\u003e Claude luôn trả về JSON hợp lệ?\u003c\/p\u003e\n\n\u003cp\u003eClaude không có \"JSON mode\" như một số model khác, nhưng có nhiều kỹ thuật kết hợp lại cho kết quả \u003cstrong\u003erất đáng tin cậy\u003c\/strong\u003e. Bài này trình bày tất cả các kỹ thuật từ đơn giản đến nâng cao.\u003c\/p\u003e\n\n\u003ch2\u003eKỹ thuật 1: Yêu cầu JSON trong prompt\u003c\/h2\u003e\n\n\u003cp\u003eCách đơn giản nhất — mô tả schema trong prompt:\u003c\/p\u003e\n\n\u003cpre\u003e\u003ccode\u003eimport anthropic\nimport json\n\nclient = anthropic.Anthropic()\n\nresponse = client.messages.create(\n    model=\"claude-haiku-4-5\",\n    max_tokens=500,\n    messages=[{\n        \"role\": \"user\",\n        \"content\": \"\"\"Phân tích review sản phẩm sau và trả về JSON.\n\nReview: \"Điện thoại này pin trâu, camera đẹp, nhưng hơi nặng và đắt.\"\n\nTrả về JSON với format:\n{\n  \"sentiment\": \"positive|negative|neutral|mixed\",\n  \"pros\": [\"điểm tốt 1\", \"điểm tốt 2\"],\n  \"cons\": [\"điểm chưa tốt 1\"],\n  \"score\": 1-10,\n  \"summary\": \"tóm tắt ngắn\"\n}\n\nChỉ trả về JSON, không có text khác.\"\"\"\n    }],\n    temperature=0.0,\n)\n\ntext = response.content[0].text\ndata = json.loads(text)\nprint(data[\"sentiment\"])  # \"mixed\"\nprint(data[\"pros\"])       # [\"pin trâu\", \"camera đẹp\"]\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003cp\u003eCách này hoạt động tốt trong 90% trường hợp. Vấn đề: đôi khi Claude thêm text trước hoặc sau JSON (\"Đây là JSON: {...} Hy vọng hữu ích!\").\u003c\/p\u003e\n\n\u003ch2\u003eKỹ thuật 2: Prefill với \"{\" — Đáng tin cậy nhất\u003c\/h2\u003e\n\n\u003cp\u003eTrick mạnh nhất: \u003cstrong\u003ebắt đầu response bằng ký tự mở JSON\u003c\/strong\u003e. Claude không thể nói gì trước JSON nếu bạn đã điền sẵn ký tự đầu tiên:\u003c\/p\u003e\n\n\u003cpre\u003e\u003ccode\u003eresponse = client.messages.create(\n    model=\"claude-haiku-4-5\",\n    max_tokens=500,\n    messages=[\n        {\n            \"role\": \"user\",\n            \"content\": \"\"\"Phân tích review: \"Điện thoại pin trâu, camera đẹp nhưng hơi nặng.\"\n\nTrả về JSON với: sentiment, pros (array), cons (array), score (1-10)\"\"\"\n        },\n        # Prefill: bắt đầu response của Claude với \"{\"\n        {\n            \"role\": \"assistant\",\n            \"content\": \"{\"\n        }\n    ],\n    temperature=0.0,\n)\n\n# Response chỉ chứa phần còn lại của JSON (sau \"{\")\npartial_json = response.content[0].text\nfull_json = \"{\" + partial_json  # Ghép lại\n\ndata = json.loads(full_json)\nprint(data)\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003cp\u003eKỹ thuật này \u003cstrong\u003eđảm bảo 100%\u003c\/strong\u003e response bắt đầu bằng JSON object. Claude không thể nói gì khác vì đã bị \"lock in\" vào JSON format.\u003c\/p\u003e\n\n\u003ch2\u003eKỹ thuật 3: Stop Sequences\u003c\/h2\u003e\n\n\u003cp\u003eKết hợp với prefill, stop sequences giúp kiểm soát chính xác điểm kết thúc:\u003c\/p\u003e\n\n\u003cpre\u003e\u003ccode\u003eresponse = client.messages.create(\n    model=\"claude-haiku-4-5\",\n    max_tokens=500,\n    messages=[\n        {\"role\": \"user\", \"content\": \"Trả về JSON object có key 'result' với giá trị là số nguyên tố đầu tiên lớn hơn 100.\"},\n        {\"role\": \"assistant\", \"content\": \"{\"}\n    ],\n    stop_sequences=[\"}\"],  # Dừng ngay sau khi đóng JSON object\n    temperature=0.0,\n)\n\n# Cần thêm \"}\" vào cuối vì stop sequence không được include\npartial = response.content[0].text\nfull_json = \"{\" + partial + \"}\"\n\n# Hoặc dùng stop_reason để detect\nif response.stop_reason == \"stop_sequence\":\n    full_json = \"{\" + response.content[0].text + \"}\"\n\ndata = json.loads(full_json)\nprint(data[\"result\"])  # 101\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003cp\u003e\u003cstrong\u003eLưu ý:\u003c\/strong\u003e Stop sequences hữu ích nhất khi JSON object nhỏ và không có nested objects phức tạp (vì \"}\" xuất hiện nhiều lần trong nested JSON).\u003c\/p\u003e\n\n\u003ch2\u003eKỹ thuật 4: XML wrapper + Extraction\u003c\/h2\u003e\n\n\u003cp\u003eCho phép Claude \"suy nghĩ\" trước khi output JSON, tránh hallucination:\u003c\/p\u003e\n\n\u003cpre\u003e\u003ccode\u003eresponse = client.messages.create(\n    model=\"claude-sonnet-4-5\",\n    max_tokens=1000,\n    messages=[{\n        \"role\": \"user\",\n        \"content\": \"\"\"Phân tích báo cáo tài chính sau và extract thông tin key.\n\nBáo cáo: \"Quý 3\/2024: Doanh thu đạt 150 tỷ VND, tăng 23% so với cùng kỳ.\nChi phí hoạt động là 95 tỷ, lợi nhuận ròng 55 tỷ. Số nhân viên: 1.200 người.\"\n\nSuy nghĩ kỹ, sau đó trả về trong tag \u003cjson\u003e:\n\u003cjson\u003e\n{\n  \"quarter\": \"Q3\/2024\",\n  \"revenue_billion_vnd\": 150,\n  \"revenue_growth_percent\": 23,\n  \"operating_cost_billion_vnd\": 95,\n  \"net_profit_billion_vnd\": 55,\n  \"employees\": 1200\n}\n\u003c\/json\u003e\"\"\"\n    }],\n    temperature=0.0,\n)\n\nimport re\ntext = response.content[0].text\n\n# Extract JSON từ XML tag\nmatch = re.search(r\"\u003cjson\u003e(.*?)\u003c\/json\u003e\", text, re.DOTALL)\nif match:\n    json_str = match.group(1).strip()\n    data = json.loads(json_str)\n    print(f\"Revenue: {data['revenue_billion_vnd']}B VND\")\n    print(f\"Growth: {data['revenue_growth_percent']}%\")\u003c\/json\u003e\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eKỹ thuật 5: Robust JSON parsing\u003c\/h2\u003e\n\n\u003cp\u003eDù dùng kỹ thuật nào, production code nên có fallback parsing mạnh:\u003c\/p\u003e\n\n\u003cpre\u003e\u003ccode\u003eimport json\nimport re\n\ndef robust_json_parse(text: str) -\u0026gt; dict:\n    \"\"\"\n    Parse JSON từ text với nhiều fallback strategies.\n    \"\"\"\n    text = text.strip()\n\n    # Strategy 1: Parse trực tiếp\n    try:\n        return json.loads(text)\n    except json.JSONDecodeError:\n        pass\n\n    # Strategy 2: Tìm JSON object đầu tiên bằng regex\n    match = re.search(r\"{.*}\", text, re.DOTALL)\n    if match:\n        try:\n            return json.loads(match.group())\n        except json.JSONDecodeError:\n            pass\n\n    # Strategy 3: Tìm JSON trong code block\n    code_block = re.search(r\"'''(?:json)?\n?(.*?)\n?'''\", text, re.DOTALL)\n    if code_block:\n        try:\n            return json.loads(code_block.group(1).strip())\n        except json.JSONDecodeError:\n            pass\n\n    # Strategy 4: Tìm JSON trong XML tag\n    xml_match = re.search(r\"\u003cjson\u003e(.*?)\u003c\/json\u003e\", text, re.DOTALL)\n    if xml_match:\n        try:\n            return json.loads(xml_match.group(1).strip())\n        except json.JSONDecodeError:\n            pass\n\n    raise ValueError(f\"Không thể parse JSON từ text: {text[:200]}\")\n\n# Test\ntest_cases = [\n    '{\"key\": \"value\"}',\n    'Here is the JSON: {\"key\": \"value\"} Hope this helps!',\n    ''''json\n{\"key\": \"value\"}\n'''',\n    '\u003cjson\u003e{\"key\": \"value\"}\u003c\/json\u003e',\n]\n\nfor tc in test_cases:\n    try:\n        result = robust_json_parse(tc)\n        print(f\"OK: {result}\")\n    except ValueError as e:\n        print(f\"FAIL: {e}\")\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eKỹ thuật 6: Pydantic validation\u003c\/h2\u003e\n\n\u003cp\u003eSau khi parse, validate schema bằng Pydantic để catch lỗi sớm:\u003c\/p\u003e\n\n\u003cpre\u003e\u003ccode\u003efrom pydantic import BaseModel, Field, validator\nfrom typing import List, Optional\n\nclass ProductReview(BaseModel):\n    sentiment: str = Field(..., pattern=\"^(positive|negative|neutral|mixed)$\")\n    pros: List[str] = Field(default_factory=list)\n    cons: List[str] = Field(default_factory=list)\n    score: int = Field(..., ge=1, le=10)\n    summary: Optional[str] = None\n\n    @validator(\"pros\", \"cons\", each_item=True)\n    def not_empty(cls, v):\n        if not v.strip():\n            raise ValueError(\"Items cannot be empty\")\n        return v.strip()\n\ndef get_structured_review(review_text: str) -\u0026gt; ProductReview:\n    response = client.messages.create(\n        model=\"claude-haiku-4-5\",\n        max_tokens=400,\n        messages=[\n            {\n                \"role\": \"user\",\n                \"content\": f\"\"\"Phân tích review: \"{review_text}\"\n\nTrả về JSON với: sentiment (positive\/negative\/neutral\/mixed),\npros (array), cons (array), score (1-10), summary (string)\"\"\"\n            },\n            {\"role\": \"assistant\", \"content\": \"{\"}\n        ],\n        temperature=0.0,\n    )\n\n    json_str = \"{\" + response.content[0].text\n    data = robust_json_parse(json_str)\n\n    # Validate với Pydantic — raises ValidationError nếu sai schema\n    return ProductReview(**data)\n\ntry:\n    review = get_structured_review(\"Sản phẩm ổn nhưng giá hơi cao, giao hàng nhanh.\")\n    print(f\"Sentiment: {review.sentiment}\")\n    print(f\"Score: {review.score}\/10\")\n    print(f\"Pros: {review.pros}\")\nexcept Exception as e:\n    print(f\"Error: {e}\")\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eChọn kỹ thuật nào?\u003c\/h2\u003e\n\n\u003ctable\u003e\n  \u003cthead\u003e\n    \u003ctr\u003e\n\u003cth\u003eKỹ thuật\u003c\/th\u003e\n\u003cth\u003eĐộ tin cậy\u003c\/th\u003e\n\u003cth\u003ePhức tạp\u003c\/th\u003e\n\u003cth\u003eKhi nào dùng\u003c\/th\u003e\n\u003c\/tr\u003e\n  \u003c\/thead\u003e\n  \u003ctbody\u003e\n    \u003ctr\u003e\n\u003ctd\u003ePrompt đơn giản\u003c\/td\u003e\n\u003ctd\u003e90%\u003c\/td\u003e\n\u003ctd\u003eThấp\u003c\/td\u003e\n\u003ctd\u003ePrototype, testing\u003c\/td\u003e\n\u003c\/tr\u003e\n    \u003ctr\u003e\n\u003ctd\u003ePrefill \"{\"\u003c\/td\u003e\n\u003ctd\u003e99%\u003c\/td\u003e\n\u003ctd\u003eThấp\u003c\/td\u003e\n\u003ctd\u003eProduction, JSON objects\u003c\/td\u003e\n\u003c\/tr\u003e\n    \u003ctr\u003e\n\u003ctd\u003eXML + Extraction\u003c\/td\u003e\n\u003ctd\u003e98%\u003c\/td\u003e\n\u003ctd\u003eTrung bình\u003c\/td\u003e\n\u003ctd\u003eKhi cần Claude suy nghĩ trước\u003c\/td\u003e\n\u003c\/tr\u003e\n    \u003ctr\u003e\n\u003ctd\u003ePrefill + Stop seq\u003c\/td\u003e\n\u003ctd\u003e99%\u003c\/td\u003e\n\u003ctd\u003eTrung bình\u003c\/td\u003e\n\u003ctd\u003eJSON nhỏ, đơn giản\u003c\/td\u003e\n\u003c\/tr\u003e\n    \u003ctr\u003e\n\u003ctd\u003eRobust parsing + Pydantic\u003c\/td\u003e\n\u003ctd\u003e99.9%\u003c\/td\u003e\n\u003ctd\u003eCao\u003c\/td\u003e\n\u003ctd\u003eProduction critical\u003c\/td\u003e\n\u003c\/tr\u003e\n  \u003c\/tbody\u003e\n\u003c\/table\u003e\n\n\u003cp\u003e\u003cstrong\u003eKhuyến nghị cho production:\u003c\/strong\u003e Kết hợp \u003cstrong\u003ePrefill \"{\"\u003c\/strong\u003e + \u003cstrong\u003eRobust parsing\u003c\/strong\u003e + \u003cstrong\u003ePydantic validation\u003c\/strong\u003e. Ba lớp bảo vệ này đảm bảo hệ thống của bạn không bao giờ crash vì JSON malformed.\u003c\/p\u003e\n\n\u003cp\u003eKết hợp JSON output với \u003ca href=\"\/collections\/nang-cao\"\u003ePrompt Caching\u003c\/a\u003e để giảm latency khi schema phức tạp được reuse nhiều lần.\u003c\/p\u003e\n","brand":"Minh Tuấn","offers":[{"title":"Default Title","offer_id":47721830056148,"sku":null,"price":0.0,"currency_code":"VND","in_stock":true}],"thumbnail_url":"\/\/cdn.shopify.com\/s\/files\/1\/0821\/0264\/9044\/files\/json-mode-bu_c-claude-tr_-v_-json-chinh-xac.jpg?v=1774521611","url":"https:\/\/claude.vn\/products\/json-mode-bu%e1%bb%99c-claude-tr%e1%ba%a3-v%e1%bb%81-json-chinh-xac","provider":"CLAUDE.VN","version":"1.0","type":"link"}