ทำความรู้จัก Pydantic: ตัวช่วยจัดการ Data Validation และการสร้าง Structured Output สำหรับ AI

ในการพัฒนาซอฟต์แวร์ด้วยภาษา Python ไม่ว่าจะเป็นการทำ Web Application, การเตรียมข้อมูลสำหรับ Data Science หรือแม้แต่การพัฒนาระบบ Agentic AI สิ่งหนึ่งที่นักพัฒนาต้องเจอเสมอคือ ความไม่แน่นอนของข้อมูล (Data Inconsistency) ข้อมูลที่รับเข้ามาอาจผิดประเภท ขาดหาย หรือไม่ตรงตามรูปแบบที่คาดหวัง ซึ่งนำไปสู่บั๊กที่แก้ไขได้ยากในภายหลัง บทความนี้จะพาทุกคนไปทำความรู้จักกับเครื่องมือยอดฮิตที่เข้ามาแก้ปัญหานี้อย่าง Pydantic พร้อมวิธีการนำไปประยุกต์ใช้กับ Large Language Models (LLMs)

from typing import Annotated, Literal

from annotated_types import Gt

from pydantic import BaseModel


class Fruit(BaseModel):
    name: str  
    color: Literal['red', 'green']  
    weight: Annotated[float, Gt(0)]  
    bazam: dict[str, list[tuple[int, bool, float]]]  


print(
    Fruit(
        name='Apple',
        color='red',
        weight=4.2,
        bazam={'foobar': [(1, True, 0.1)]},
    )
)
#> name='Apple' color='red' weight=4.2 bazam={'foobar': [(1, True, 0.1)]}

Pydantic คืออะไร ทำไมถึงควรใช้?

Pydantic คือไลบรารีของ Python ที่ใช้สำหรับการทำ Data Validation และ Settings Management โดยอาศัยฟีเจอร์ Type Hints ของ Python (ที่ถูกเพิ่มเข้ามาตั้งแต่ Python 3.5) ในการตรวจสอบความถูกต้องของข้อมูล

แทนที่เราจะต้องมานั่งเขียนโค้ด if-else เช็คว่าข้อมูลที่รับมาเป็น String หรือ Integer ไลบรารีตัวนี้จะจัดการตรวจสอบและแปลงประเภทข้อมูล (Type Coercion) ให้โดยอัตโนมัติ

ทำไมถึงควรนำมาใช้งานในโปรเจกต์?

  • ลด Boilerplate Code: ไม่ต้องเขียนโค้ดตรวจสอบข้อมูลเองยาวๆ ช่วยให้โค้ดสะอาดและดูแลรักษาง่ายขึ้น

  • ผสานการทำงานกับ IDE ได้ดีเยี่ยม: เนื่องจากใช้ Type Hints พื้นฐานของ Python ทำให้ Editor อย่าง VS Code หรือ PyCharm สามารถทำ Autocomplete และแจ้งเตือน Error ได้ทันที

  • ประสิทธิภาพสูง: ในเวอร์ชัน V2 Core ของระบบถูกเขียนใหม่ด้วยภาษา Rust ทำให้ความเร็วในการ Validate ข้อมูลเพิ่มขึ้นอย่างก้าวกระโดด

  • เป็นมาตรฐานของ Framework ยุคใหม่: การทำ Web API ไลบรารีนี้คือหัวใจสำคัญเบื้องหลัง FastAPI และยังสามารถนำไปปรับใช้กับสถาปัตยกรรมของ Django หรือ Flask ได้อย่างลงตัว

Concepts การ Define Schema และการ Validate

หัวใจสำคัญของการใช้งานคือคลาส BaseModel การสร้าง Schema หรือโครงสร้างข้อมูลทำได้ง่าย ๆ เพียงแค่สร้างคลาสที่สืบทอดจาก BaseModel และกำหนดตัวแปรพร้อม Type Hints

การทำ Data Validation

เมื่อมีการสร้าง Instance จากคลาสที่ออกแบบไว้ ระบบจะทำหน้าที่ตรวจสอบข้อมูล (Validate) ที่ถูกส่งเข้ามาทันที:

  1. Type Checking: ตรวจสอบว่าประเภทข้อมูลตรงกับที่ระบุไว้หรือไม่

  2. Type Coercion: หากข้อมูลไม่ตรงแต่สามารถแปลงสภาพได้ (เช่น รับค่ามาเป็น String "123" แต่ระบุ Type เป็น int) ระบบจะพยายามแปลงค่าให้โดยอัตโนมัติ

  3. Error Handling: หากข้อมูลไม่ถูกต้องและแปลงไม่ได้ ระบบจะพ่น ValidationError ออกมาพร้อมบอกตำแหน่งและสาเหตุที่ชัดเจน

นอกจากนี้ยังสามารถใช้ฟังก์ชัน Field เพื่อเพิ่มเงื่อนไขที่ซับซ้อนขึ้นได้ เช่น กำหนดความยาวสูงสุด-ต่ำสุดของตัวอักษร หรือค่าตัวเลขที่อนุญาต เพื่อให้เห็นภาพรวมที่ชัดเจน เราสามารถแบ่งคอนเซปต์สำคัญออกเป็นกลุ่มต่างๆ ได้ดังนี้

กลุ่ม Core Concepts (โครงสร้างหลักและการจัดการฟิลด์)

  • Models: คลาสพื้นฐาน (BaseModel) ที่ใช้สำหรับนิยามโครงสร้างข้อมูล (Schema) เมื่อเราสร้าง Instance ระบบจะทำการตรวจสอบและแปลงข้อมูลให้อัตโนมัติ

  • Fields: ฟังก์ชัน Field() ที่ใช้สำหรับตั้งค่าข้อมูลเฉพาะจุด เช่น การกำหนดค่าเริ่มต้น, การใส่ข้อจำกัด (Constraints อย่าง min_length, ge สำหรับค่ามากกว่าหรือเท่ากับ), หรือการเพิ่มคำอธิบาย (Description)

  • Types: รองรับตั้งแต่ Standard Types ของ Python (str, int, list, dict) ไปจนถึง Extended Types ที่ไลบรารีเตรียมไว้ให้ เช่น EmailStr, HttpUrl, หรือ IPv4Address เพื่อการตรวจสอบที่เฉพาะเจาะจงยิ่งขึ้น

  • Alias: การตั้งชื่อแฝงให้ฟิลด์ มีประโยชน์มากเวลาที่ชื่อคีย์ใน JSON ไม่ตรงกับข้อกำหนดการตั้งชื่อตัวแปรใน Python (เช่น รับข้อมูลมาเป็น first-name แต่อยากให้ตัวแปรในโค้ดชื่อ first_name)

กลุ่ม Data Parsing & Transformation (การแปลงและส่งออกข้อมูล)

  • JSON: รองรับการรับข้อมูลโดยตรงจาก JSON String (ผ่าน model_validate_json()) อย่างรวดเร็วและปลอดภัย

  • JSON Schema: ความสามารถในการแปลง Model ที่เราเขียนให้กลายเป็นมาตรฐาน JSON Schema ได้อัตโนมัติ ซึ่งเป็นหัวใจสำคัญที่ Framework อย่าง FastAPI นำไปใช้สร้าง API Documentation (OpenAPI/Swagger)

  • Serialization: กระบวนการแปลง Model กลับไปเป็นข้อมูลพื้นฐาน (Dictionary หรือ JSON) ผ่านคำสั่งอย่าง model_dump() หรือ model_dump_json() ซึ่งสามารถปรับแต่งได้ว่าจะรวมฟิลด์ไหนบ้าง

  • Conversion Table: กฎเกณฑ์ที่อยู่เบื้องหลังการทำ Type Coercion (การพยายามแปลงข้อมูล) ระบบจะมีตารางการแปลงที่ชัดเจนว่าประเภทข้อมูลใดสามารถแปลงไปเป็นประเภทใดได้บ้างโดยไม่เสียความหมาย

กลุ่ม Advanced Types & Annotations (การจัดการชนิดข้อมูลขั้นสูง)

  • Unions: การรองรับตัวแปรที่เป็นไปได้หลายประเภท (เช่น int | str) โดยมีระบบ Smart Union ที่จะพยายามจับคู่ข้อมูลให้ตรงกับ Type ที่เหมาะสมที่สุดก่อนเสมอ

  • Forward Annotations: การอ้างอิงถึง Model ที่ยังไม่ได้ประกาศ หรือการทำโครงสร้างข้อมูลแบบวนลูป (Recursive Models เช่น โครงสร้างต้นไม้) โดยไม่ต้องเขียนโค้ดแก้ปัญหา Circular Imports ให้ยุ่งยาก

  • Dataclasses: หากคุณชื่นชอบการใช้ @dataclass พื้นฐานของ Python เราสามารถใช้ pydantic.dataclasses เข้ามาแทนที่เพื่อเพิ่มพลังในการ Validate ข้อมูลได้ทันทีโดยไม่ต้องเปลี่ยนไปใช้ BaseModel

กลุ่ม Validation Control (การควบคุมความถูกต้อง)

  • Validators: หากข้อจำกัดพื้นฐานไม่พอ เราสามารถเขียน Custom Logic ขึ้นมาตรวจสอบเองได้ ผ่าน Decorator อย่าง @field_validator (ตรวจสอบทีละฟิลด์) หรือ @model_validator (ตรวจสอบความสัมพันธ์ของข้อมูลทั้ง Model)

  • Strict Mode: โหมดเข้มงวดที่จะ “ปิด” การแปลงประเภทข้อมูล (Type Coercion) เช่น หากฟิลด์ต้องการ int แต่ข้อมูลเข้ามาเป็น String "123" ระบบจะแจ้งเตือน Error ทันที โหมดนี้เหมาะกับระบบที่ต้องการความเป๊ะของ Data สูงสุด

  • Validation Decorator: นอกจากการใช้กับ Model แล้ว ยังมี @validate_call ที่สามารถนำไปครอบฟังก์ชันหรือเมธอดปกติ เพื่อให้ระบบช่วยตรวจสอบ Argument ที่ส่งเข้ามา และ Return Type ที่ส่งออกไปแบบอัตโนมัติ

กลุ่ม Configuration & Utilities (การตั้งค่าและการใช้งานเฉพาะทาง)

  • Configuration: การปรับแต่งพฤติกรรมของ Model ผ่านตัวแปร model_config เช่น การตั้งค่า extra='forbid' เพื่อปฏิเสธข้อมูลที่ส่งเข้ามาเกินกว่าที่ Schema ระบุไว้

  • Type Adapter: เครื่องมือสุดยืดหยุ่น (TypeAdapter) ที่ช่วยให้เราสามารถ Validate ข้อมูลธรรมดาๆ เช่น List[int] หรือ Dict[str, int] ได้โดยตรง ไม่จำเป็นต้องสร้างคลาส BaseModel ขึ้นมาครอบก่อน

  • Settings Management: ส่วนเสริมที่ได้รับความนิยมมาก (ผ่านแพ็กเกจ pydantic-settings) ใช้สำหรับอ่านและ Validate ค่า Configuration ของแอปพลิเคชันจาก Environment Variables (.env) เข้ามาเป็น Object อย่างปลอดภัย

เบื้องหลังความเร็ว (Under the Hood)

  • Performance: ในเวอร์ชันล่าสุด (V2) Core Validation Engine ได้ถูกเขียนขึ้นใหม่ทั้งหมดด้วยภาษา Rust (pydantic-core) ทำให้การทำ Validation ประมวลผลได้รวดเร็วกว่าเวอร์ชันก่อนหน้าถึง 5-50 เท่า และใช้หน่วยความจำน้อยลงอย่างเห็นได้ชัด

ตัวอย่าง Coding การใช้งาน Pydantic

from pydantic import BaseModel, Field, ValidationError
from typing import Optional

# 1. Define Schema
class UserProfile(BaseModel):
    user_id: int
    username: str = Field(..., min_length=3, max_length=50) # ต้องมี 3-50 ตัวอักษร
    email: str
    age: Optional[int] = None # ใส่หรือไม่ใส่ก็ได้
    is_active: bool = True # ค่าเริ่มต้นคือ True

# 2. การรับข้อมูลที่ถูกต้อง (Validation & Coercion)
# สังเกตว่า user_id เป็น string "101" แต่จะถูกแปลงเป็น int อัตโนมัติ
valid_data = {
    "user_id": "101", 
    "username": "developer_th",
    "email": "dev@example.com"
}

user = UserProfile(**valid_data)
print(user.user_id) # Output: 101 (เป็น Integer แล้ว)
print(user.model_dump()) # Output เป็น Dictionary ของข้อมูลทั้งหมด

# 3. การจัดการเมื่อข้อมูลผิดพลาด (Error Handling)
invalid_data = {
    "user_id": "not_a_number", # ผิดประเภทและแปลงเป็นเลขไม่ได้
    "username": "ab", # สั้นกว่า min_length ที่กำหนด
    "email": "invalid_email"
}

try:
    bad_user = UserProfile(**invalid_data)
except ValidationError as e:
    print(e.json()) # แสดงรายละเอียด Error อย่างชัดเจนว่าฟิลด์ไหนผิดพลาด

Output:

101
{'user_id': 101, 'username': 'developer_th', 'email': 'dev@example.com', 'age': None, 'is_active': True}
[{"type":"int_parsing","loc":["user_id"],"msg":"Input should be a valid integer, unable to parse string as an integer","input":"not_a_number","url":"https://errors.pydantic.dev/2.12/v/int_parsing"},{"type":"string_too_short","loc":["username"],"msg":"String should have at least 3 characters","input":"ab","ctx":{"min_length":3},"url":"https://errors.pydantic.dev/2.12/v/string_too_short"}]

การใช้งานกับ LLM เพื่อสร้าง Structured Output

ในยุคของ Generative AI ปัญหาคลาสสิกเวลาเราสั่งงาน LLM (เช่น GPT-4 หรือ Claude) คือคำตอบที่ได้มักจะเป็นข้อความ (Unstructured Text) ซึ่งยากต่อการนำไปประมวลผลต่อในระบบซอฟต์แวร์ หรือการนำไปใช้ในเวิร์กโฟลว์ของ Agentic AI ที่ต้องการข้อมูลแบบตายตัว

เครื่องมือตัวนี้จึงกลายมาเป็นกุญแจสำคัญในการบังคับให้ LLM คายผลลัพธ์ออกมาเป็น Structured Output (JSON) ที่มีโครงสร้างตรงตามที่เราต้องการแบบ 100%

ตัวอย่างเช่น ใน Framework อย่าง LangChain หรือการเรียกใช้ OpenAI API โดยตรง เราสามารถโยน BaseModel เข้าไปเป็น Schema บังคับให้ AI ทำงานได้เลย

from pydantic import BaseModel, Field
from langchain_ollama import ChatOllama

# 1. กำหนด Schema ด้วย Pydantic (เหมือนเดิม)
class RecipeInfo(BaseModel):
    recipe_name: str = Field(description="ชื่อเมนูอาหาร")
    ingredients: list[str] = Field(description="รายการวัตถุดิบ")
    estimated_calories: int = Field(description="ปริมาณแคลอรี่โดยประมาณ")

# 2. ตั้งค่า Model Ollama
# แนะนำให้ใช้โมเดลที่เก่งเรื่อง Tools/JSON เช่น llama3.1, llama3.2 หรือ mistral
llm = ChatOllama(
    model="qwen3:1.7b",
    temperature=0,
    base_url="x.x.x.x:xxxx",
    format="json" # บังคับ output เป็น json
)

# 3. สร้างระบบ Structured Output
# LangChain จะจัดการเรื่อง Prompt และการ Parse ข้อมูลให้เอง
structured_llm = llm.with_structured_output(RecipeInfo)

# 4. เรียกใช้งาน
text_input = "วันนี้ทำข้าวต้มหมูสับใส่เห็ดหอม ใช้น้ำซุปกระดูกหมู, ข้าวหอมมะลิ, เห็ดหอม, หมูสับ, แคลอรี่น่าจะราวๆ 350 kcal"

# ส่งข้อความเข้าไปตรงๆ หรือจะทำเป็น Prompt Template ก็ได้
result = structured_llm.invoke(f"Extract the recipe information from this text: {text_input}")

# 5. แสดงผล (ได้เป็น Pydantic Object เหมือนต้นฉบับ)
print(result)
print(f"เมนู: {result.recipe_name}")
print(f"วัตถุดิบ: {result.ingredients}")
print(f"แคลอรี่: {result.estimated_calories}")

Output:

recipe_name='ข้าวต้มหมูสับใส่เห็ดหอม' ingredients=['น้ำซุปกระดูกหมู', 'ข้าวหอมมะลิ', 'เห็ดหอม', 'หมูสับ'] estimated_calories=350
เมนู: ข้าวต้มหมูสับใส่เห็ดหอม
วัตถุดิบ: ['น้ำซุปกระดูกหมู', 'ข้าวหอมมะลิ', 'เห็ดหอม', 'หมูสับ']
แคลอรี่: 350

Conclusion

Pydantic ไม่ได้เป็นเพียงแค่ไลบรารีสำหรับตรวจสอบข้อมูลใน Web Backend อีกต่อไป แต่ได้ก้าวขึ้นมาเป็นมาตรฐานสำคัญในวงการ Python Ecosystem ไม่ว่าคุณจะกำลังพัฒนาระบบ API ธรรมดา, ทำ Data Pipeline, หรือกำลังสร้าง Agentic AI ที่มีความซับซ้อน การนำไลบรารีนี้มาใช้เป็นรากฐานในการกำหนด Schema จะช่วยลดเวลาในการจัดการบั๊ก และทำให้โครงสร้างซอฟต์แวร์ของคุณแข็งแกร่งขึ้นอย่างมหาศาล

ติดตามบทความอื่น ๆ เพิ่มเติมได้ที่ SBC Blog

LINE OA: SUBBRAIN

Facebook: SUBBRAIN

Categories: Data&IT