ทำความรู้จัก 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) ที่ถูกส่งเข้ามาทันที:
Type Checking: ตรวจสอบว่าประเภทข้อมูลตรงกับที่ระบุไว้หรือไม่
Type Coercion: หากข้อมูลไม่ตรงแต่สามารถแปลงสภาพได้ (เช่น รับค่ามาเป็น String
"123"แต่ระบุ Type เป็นint) ระบบจะพยายามแปลงค่าให้โดยอัตโนมัติ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 จะช่วยลดเวลาในการจัดการบั๊ก และทำให้โครงสร้างซอฟต์แวร์ของคุณแข็งแกร่งขึ้นอย่างมหาศาล