{"product_id":"tool-use-với-pydantic-type-safe-tools-cho-claude","title":"Tool Use với Pydantic — Type-safe tools cho Claude","description":"\n\u003cp\u003eClaude's tool use là tính năng mạnh, nhưng trong production bạn không thể tin tưởng mù quáng vào output. Điều gì xảy ra nếu Claude trả về \u003ccode\u003eauthor_id\u003c\/code\u003e là string thay vì integer? Hay thiếu required field? \u003cstrong\u003ePydantic\u003c\/strong\u003e — thư viện validation phổ biến nhất trong Python ecosystem — cung cấp một lớp bảo vệ type-safe cho mọi tool call response.\u003c\/p\u003e\n\n\u003ch2\u003eTại sao cần validate tool call responses?\u003c\/h2\u003e\n\n\u003cp\u003eKhi Claude gọi một tool, nó phải cung cấp JSON arguments theo schema bạn định nghĩa. Trong hầu hết các trường hợp, Claude làm đúng. Nhưng:\u003c\/p\u003e\n\n\u003cul\u003e\n  \u003cli\u003eClaude có thể hiểu nhầm schema, đặc biệt với complex nested objects\u003c\/li\u003e\n  \u003cli\u003eInteger fields đôi khi được trả về dưới dạng string\u003c\/li\u003e\n  \u003cli\u003eOptional fields có thể bị bỏ qua hoặc điền sai giá trị\u003c\/li\u003e\n  \u003cli\u003eTrong production với thousands of calls\/day, edge cases sẽ xuất hiện\u003c\/li\u003e\n\u003c\/ul\u003e\n\n\u003cp\u003ePydantic validation bắt các lỗi này trước khi chúng propagate vào database hoặc downstream systems — tiết kiệm debugging time và ngăn data corruption.\u003c\/p\u003e\n\n\u003ch2\u003eSetup: Install và import\u003c\/h2\u003e\n\n\u003cpre\u003e\u003ccode\u003epip install anthropic pydantic\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003cpre\u003e\u003ccode\u003eimport anthropic\nimport json\nfrom pydantic import BaseModel, Field, validator\nfrom typing import Optional, List\nfrom datetime import datetime\n\nclient = anthropic.Anthropic()\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eBước 1: Define Pydantic models\u003c\/h2\u003e\n\n\u003cp\u003eChúng ta sẽ xây dựng một note-taking system với ba models: Author, Note, và SaveNoteResponse.\u003c\/p\u003e\n\n\u003cpre\u003e\u003ccode\u003eclass Author(BaseModel):\n    \"\"\"Thong tin tac gia cua note.\"\"\"\n    id: int = Field(..., gt=0, description=\"Author ID, must be positive integer\")\n    name: str = Field(..., min_length=1, max_length=100)\n    email: Optional[str] = Field(None, pattern=r\"^[w.-]+@[w.-]+.w+$\")\n\n    @validator('name')\n    def name_must_not_be_empty(cls, v):\n        if not v.strip():\n            raise ValueError('Name cannot be whitespace only')\n        return v.strip()\n\n\nclass Note(BaseModel):\n    \"\"\"Mot ghi chu trong he thong.\"\"\"\n    title: str = Field(..., min_length=3, max_length=200)\n    content: str = Field(..., min_length=10)\n    author: Author\n    tags: List[str] = Field(default_factory=list)\n    priority: str = Field(default=\"normal\", pattern=r\"^(low|normal|high|urgent)$\")\n    is_public: bool = Field(default=False)\n\n    @validator('tags')\n    def tags_must_be_lowercase(cls, v):\n        return [tag.lower().strip() for tag in v if tag.strip()]\n\n    @validator('content')\n    def content_must_have_substance(cls, v):\n        if len(v.split()) \u0026lt; 3:\n            raise ValueError('Content must have at least 3 words')\n        return v\n\n\nclass SaveNoteResponse(BaseModel):\n    \"\"\"Response sau khi luu note thanh cong.\"\"\"\n    note_id: str = Field(..., pattern=r\"^NOTE-d{6}$\")\n    saved_at: str  # ISO datetime string\n    word_count: int = Field(..., ge=0)\n    estimated_read_time: int = Field(..., ge=1, description=\"Minutes to read\")\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eBước 2: Define tool từ Pydantic model\u003c\/h2\u003e\n\n\u003cp\u003eThay vì viết tool schema thủ công, chúng ta generate từ Pydantic models:\u003c\/p\u003e\n\n\u003cpre\u003e\u003ccode\u003edef pydantic_to_tool_schema(model_class, tool_name, description):\n    \"\"\"Chuyen Pydantic model thanh Anthropic tool schema.\"\"\"\n    schema = model_class.model_json_schema()\n\n    # Clean up Pydantic-specific metadata\n    def clean_schema(s):\n        if isinstance(s, dict):\n            # Remove 'title' fields (Pydantic adds them, Anthropic doesn't need)\n            s.pop('title', None)\n            for key, value in s.items():\n                if isinstance(value, (dict, list)):\n                    clean_schema(value)\n        elif isinstance(s, list):\n            for item in s:\n                clean_schema(item)\n        return s\n\n    cleaned = clean_schema(schema)\n\n    return {\n        \"name\": tool_name,\n        \"description\": description,\n        \"input_schema\": cleaned\n    }\n\n# Define tools\nSAVE_NOTE_TOOL = pydantic_to_tool_schema(\n    Note,\n    \"save_note\",\n    \"Luu mot ghi chu moi vao he thong. Yeu cau title, content, va thong tin author.\"\n)\n\nSEARCH_NOTES_TOOL = {\n    \"name\": \"search_notes\",\n    \"description\": \"Tim kiem notes theo keyword hoac tags\",\n    \"input_schema\": {\n        \"type\": \"object\",\n        \"properties\": {\n            \"query\": {\"type\": \"string\", \"description\": \"Keyword tim kiem\"},\n            \"tags\": {\n                \"type\": \"array\",\n                \"items\": {\"type\": \"string\"},\n                \"description\": \"Filter theo tags\"\n            },\n            \"limit\": {\n                \"type\": \"integer\",\n                \"minimum\": 1,\n                \"maximum\": 50,\n                \"default\": 10\n            }\n        },\n        \"required\": [\"query\"]\n    }\n}\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eBước 3: Validate tool call input với Pydantic\u003c\/h2\u003e\n\n\u003cpre\u003e\u003ccode\u003eimport random\n\ndef handle_save_note(raw_input: dict) -\u0026gt; dict:\n    \"\"\"\n    Xu ly save_note tool call voi Pydantic validation.\n    raw_input: JSON dict tu Claude's tool call\n    Returns: dict response\n    \"\"\"\n    # VALIDATE — day la buoc quan trong nhat\n    try:\n        note = Note(**raw_input)\n    except Exception as e:\n        # Pydantic validation failed\n        return {\n            \"success\": False,\n            \"error\": f\"Validation failed: {str(e)}\",\n            \"fields_with_errors\": [\n                err[\"loc\"][0] for err in e.errors()\n            ] if hasattr(e, 'errors') else []\n        }\n\n    # Sau khi validate thanh cong, xu ly business logic\n    note_id = f\"NOTE-{random.randint(100000, 999999)}\"\n    word_count = len(note.content.split())\n    read_time = max(1, word_count \/\/ 200)  # ~200 words\/minute\n\n    # Build response va validate no cung voi Pydantic\n    try:\n        response = SaveNoteResponse(\n            note_id=note_id,\n            saved_at=datetime.now().isoformat(),\n            word_count=word_count,\n            estimated_read_time=read_time\n        )\n        return {\n            \"success\": True,\n            \"data\": response.model_dump()\n        }\n    except Exception as e:\n        return {\n            \"success\": False,\n            \"error\": f\"Response validation failed: {str(e)}\"\n        }\n\n\ndef handle_search_notes(raw_input: dict) -\u0026gt; dict:\n    \"\"\"Simulate search — in production would query database.\"\"\"\n    query = raw_input.get(\"query\", \"\")\n    tags = raw_input.get(\"tags\", [])\n    limit = raw_input.get(\"limit\", 10)\n\n    # Mock results\n    mock_notes = [\n        {\n            \"note_id\": f\"NOTE-{i:06d}\",\n            \"title\": f\"Note ve '{query}' so {i}\",\n            \"excerpt\": f\"Day la excerpt cua note lien quan den {query}...\",\n            \"tags\": tags[:2] if tags else [\"general\"],\n            \"author_name\": \"Nguyen Van A\"\n        }\n        for i in range(1, min(limit + 1, 6))\n    ]\n\n    return {\"results\": mock_notes, \"total\": len(mock_notes)}\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eBước 4: Complete flow — từ user request đến validated response\u003c\/h2\u003e\n\n\u003cpre\u003e\u003ccode\u003edef run_note_taking_agent(user_request: str):\n    \"\"\"\n    Complete flow:\n    1. User request\n    2. Claude decides to use tool\n    3. Validate tool call input with Pydantic\n    4. Execute tool\n    5. Validate response with Pydantic\n    6. Return to Claude\n    \"\"\"\n    messages = [{\"role\": \"user\", \"content\": user_request}]\n    tools = [SAVE_NOTE_TOOL, SEARCH_NOTES_TOOL]\n\n    print(f\"User: {user_request}\n\")\n\n    while True:\n        response = client.messages.create(\n            model=\"claude-haiku-4-5\",\n            max_tokens=2048,\n            system=\"\"\"Ban la assistant giup nguoi dung quan ly ghi chu.\n            Khi nguoi dung muon luu note, hay dung save_note tool.\n            Khi ho muon tim kiem, hay dung search_notes tool.\n            Author mac dinh: {id: 1, name: \"Default User\", email: \"user@example.com\"}\"\"\",\n            tools=tools,\n            messages=messages\n        )\n\n        if response.stop_reason == \"end_turn\":\n            for block in response.content:\n                if hasattr(block, 'text'):\n                    print(f\"Claude: {block.text}\")\n            break\n\n        elif response.stop_reason == \"tool_use\":\n            messages.append({\"role\": \"assistant\", \"content\": response.content})\n            tool_results = []\n\n            for block in response.content:\n                if block.type == \"tool_use\":\n                    print(f\"[Tool Call] {block.name}\")\n                    print(f\"[Input] {json.dumps(block.input, ensure_ascii=False, indent=2)}\")\n\n                    # Dispatch to appropriate handler\n                    if block.name == \"save_note\":\n                        result = handle_save_note(block.input)\n                    elif block.name == \"search_notes\":\n                        result = handle_search_notes(block.input)\n                    else:\n                        result = {\"error\": f\"Unknown tool: {block.name}\"}\n\n                    print(f\"[Result] {json.dumps(result, ensure_ascii=False, indent=2)}\n\")\n\n                    tool_results.append({\n                        \"type\": \"tool_result\",\n                        \"tool_use_id\": block.id,\n                        \"content\": json.dumps(result, ensure_ascii=False),\n                        # Nếu validation failed, đánh dấu là error\n                        \"is_error\": not result.get(\"success\", True)\n                    })\n\n            messages.append({\"role\": \"user\", \"content\": tool_results})\n        else:\n            break\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eTest: Các scenarios validation\u003c\/h2\u003e\n\n\u003cpre\u003e\u003ccode\u003e# Test 1: Valid note\nrun_note_taking_agent(\n    \"Luu note nay: 'Hoc Pydantic' voi noi dung 'Pydantic la thu vien validation manh nhat cho Python, rat phu hop voi FastAPI va Claude tool use.' Tags: python, validation. Priority: high.\"\n)\n\n# Test 2: Invalid data — Claude se nhan error feedback\nrun_note_taking_agent(\n    \"Luu note voi title la 'X' (qua ngan) va content la 'Hi' (qua ngan)\"\n)\n\n# Test 3: Search\nrun_note_taking_agent(\n    \"Tim kiem cac notes lien quan den Python\"\n)\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch3\u003eOutput mẫu cho valid note:\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003e[Tool Call] save_note\n[Input] {\n  \"title\": \"Hoc Pydantic\",\n  \"content\": \"Pydantic la thu vien validation manh nhat...\",\n  \"author\": {\"id\": 1, \"name\": \"Default User\", \"email\": \"user@example.com\"},\n  \"tags\": [\"python\", \"validation\"],\n  \"priority\": \"high\",\n  \"is_public\": false\n}\n[Result] {\n  \"success\": true,\n  \"data\": {\n    \"note_id\": \"NOTE-847293\",\n    \"saved_at\": \"2024-12-15T10:30:00.123456\",\n    \"word_count\": 18,\n    \"estimated_read_time\": 1\n  }\n}\n\nClaude: Da luu note \"Hoc Pydantic\" thanh cong! Note ID: NOTE-847293,\nuoc tinh doc trong 1 phut.\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch3\u003eOutput mẫu cho invalid data:\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003e[Tool Call] save_note\n[Input] {\"title\": \"X\", \"content\": \"Hi\", \"author\": {...}}\n[Result] {\n  \"success\": false,\n  \"error\": \"Validation failed: 2 validation errors...\",\n  \"fields_with_errors\": [\"title\", \"content\"]\n}\n\nClaude: Co loi khi luu note: title phai co it nhat 3 ky tu\n(hien tai chi co 1), va content phai co it nhat 10 ky tu va 3 tu.\nVui long cung cap thong tin day du hon.\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eAdvanced: Custom validators cho business logic\u003c\/h2\u003e\n\n\u003cpre\u003e\u003ccode\u003efrom pydantic import model_validator\n\nclass ExpenseNote(BaseModel):\n    \"\"\"Note cho expense report voi business rules.\"\"\"\n    title: str = Field(..., min_length=3)\n    amount: float = Field(..., gt=0)\n    currency: str = Field(default=\"VND\", pattern=r\"^(VND|USD|EUR)$\")\n    category: str\n    receipt_url: Optional[str] = None\n\n    @model_validator(mode='after')\n    def high_amount_requires_receipt(self):\n        \"\"\"Chi phi cao phai co receipt.\"\"\"\n        if self.currency == \"VND\" and self.amount \u0026gt; 1000000:\n            if not self.receipt_url:\n                raise ValueError(\n                    f\"Chi phi {self.amount:,.0f} VND vuot 1M can dinh kem receipt URL\"\n                )\n        elif self.currency == \"USD\" and self.amount \u0026gt; 50:\n            if not self.receipt_url:\n                raise ValueError(\n                    f\"Chi phi {self.amount} USD vuot 50 USD can dinh kem receipt URL\"\n                )\n        return self\n\n    @validator('category')\n    def validate_category(cls, v):\n        valid_categories = ['travel', 'meals', 'software', 'office', 'training', 'other']\n        if v.lower() not in valid_categories:\n            raise ValueError(f\"Category phai la mot trong: {valid_categories}\")\n        return v.lower()\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eTổng kết: Pydantic integration checklist\u003c\/h2\u003e\n\n\u003ctable\u003e\n  \u003cthead\u003e\n    \u003ctr\u003e\n      \u003cth\u003eBước\u003c\/th\u003e\n      \u003cth\u003eAction\u003c\/th\u003e\n      \u003cth\u003eLợi ích\u003c\/th\u003e\n    \u003c\/tr\u003e\n  \u003c\/thead\u003e\n  \u003ctbody\u003e\n    \u003ctr\u003e\n      \u003ctd\u003e1. Define models\u003c\/td\u003e\n      \u003ctd\u003eBaseModel cho input và response\u003c\/td\u003e\n      \u003ctd\u003eType safety, auto-documentation\u003c\/td\u003e\n    \u003c\/tr\u003e\n    \u003ctr\u003e\n      \u003ctd\u003e2. Generate schema\u003c\/td\u003e\n      \u003ctd\u003emodel_json_schema() → tool definition\u003c\/td\u003e\n      \u003ctd\u003eConsistent schema, không copy-paste\u003c\/td\u003e\n    \u003c\/tr\u003e\n    \u003ctr\u003e\n      \u003ctd\u003e3. Validate input\u003c\/td\u003e\n      \u003ctd\u003eModel(**raw_input) trước khi xử lý\u003c\/td\u003e\n      \u003ctd\u003eBắt lỗi sớm, rõ ràng\u003c\/td\u003e\n    \u003c\/tr\u003e\n    \u003ctr\u003e\n      \u003ctd\u003e4. Return errors\u003c\/td\u003e\n      \u003ctd\u003eis_error=True cho tool_result\u003c\/td\u003e\n      \u003ctd\u003eClaude tự retry với data đúng\u003c\/td\u003e\n    \u003c\/tr\u003e\n    \u003ctr\u003e\n      \u003ctd\u003e5. Validate response\u003c\/td\u003e\n      \u003ctd\u003eValidate data trước khi return\u003c\/td\u003e\n      \u003ctd\u003eĐảm bảo downstream systems nhận data sạch\u003c\/td\u003e\n    \u003c\/tr\u003e\n  \u003c\/tbody\u003e\n\u003c\/table\u003e\n\n\u003cp\u003ePydantic + Claude Tool Use là combination hoàn hảo cho production: Claude linh hoạt trong natural language understanding, Pydantic strict về data integrity. Kết hợp hai điểm mạnh này cho ứng dụng vừa thông minh vừa reliable.\u003c\/p\u003e\n\n\u003cp\u003eBước tiếp theo: Tìm hiểu \u003ca href=\"\/collections\/nang-cao\"\u003eTool Search với Embeddings\u003c\/a\u003e — khi bạn có hàng nghìn tools và cần tìm đúng tool cho từng query mà không tốn hết context window.\u003c\/p\u003e\n","brand":"Minh Tuấn","offers":[{"title":"Default Title","offer_id":47721769009364,"sku":null,"price":0.0,"currency_code":"VND","in_stock":true}],"thumbnail_url":"\/\/cdn.shopify.com\/s\/files\/1\/0821\/0264\/9044\/files\/tool-use-v_i-pydantic-type-safe-tools-cho-claude.jpg?v=1774506607","url":"https:\/\/claude.vn\/products\/tool-use-v%e1%bb%9bi-pydantic-type-safe-tools-cho-claude","provider":"CLAUDE.VN","version":"1.0","type":"link"}