คู่มือ Pydantic AI: สร้าง LLM Agent แบบปลอดภัยด้วย Type Safety ตั้งแต่โค้ดตัวแรกจนถึงขึ้นระบบจริง
คนที่เคยทำแอปพลิเคชัน LLM ย่อมรู้ดีว่าส่วนที่น่าปวดหัวที่สุดไม่ใช่การต่อ API แต่เป็นผลลัพธ์จากโมเดลที่ไม่เคยหน้าตาเหมือนเดิมเลยสักครั้ง Pydantic AI ได้นำการตรวจสอบประเภทข้อมูล (Type Validation) ของ Pydantic มาใช้ในการพัฒนา Agent ทำให้ผลลัพธ์มีโครงสร้างและเครื่องมือต่างๆ สามารถตรวจสอบแบบ Static ได้ บทความนี้จะพาคุณเรียนรู้ตั้งแต่การติดตั้งจนถึงระดับสูง พร้อมแชร์ข้อผิดพลาดที่ผมเคยเจอมาด้วยตัวเอง
บทนำ: ทำไมผลลัพธ์ที่โมเดลส่งกลับมาถึงไม่เหมือนเดิมสักที
ถ้าคุณเคยเรียกใช้ API ของ LLM คุณน่าจะคุ้นเคยกับภาพนี้ดี: คุณบอกโมเดลว่า "ช่วยส่งคืน JSON ที่มี name กับ score หน่อย" เก้าครั้งแรกมันทำได้ปกติ พอครั้งที่ 10 มันดันแถมประโยคว่า "ได้เลย นี่คือผลลัพธ์:" ไว้หน้า JSON ทำให้โค้ด json.loads() ของคุณพังทันที จากนั้นคุณก็ต้องมานั่งเขียน Regular Expression เป็นพวงเพื่อเคลียร์สตริง แล้วตามด้วย if อีกเพียบเพื่อเช็คว่ามีฟิลด์นั้นอยู่จริงไหม—สุดท้ายแล้ว โค้ด 70% ใน "แอปพลิเคชัน AI" ของคุณหมดไปกับการรับมือกับผลลัพธ์ที่คาดเดาไม่ได้ของโมเดล
ผมเองเคยดูแลเซอร์วิสจัดหมวดหมู่ภายในอยู่ตัวหนึ่ง แค่เรื่องจัดการเคสที่โมเดลลืมส่งฟิลด์ใดฟิลด์หนึ่งมาเป็นครั้งคราว ก็ทำให้ผมต้องอยู่โอทีถึงสามวันแล้ว พอเปลี่ยนมาใช้ Pydantic AI โค้ดป้องกันเหล่านั้นแทบจะถูกลบออกเกือบหมด เพราะงานตรวจสอบความถูกต้องนี้ ทางเฟรมเวิร์กจัดการให้อย่างเบ็ดเสร็จ บทความนี้จะมาเล่าให้ฟังชัดๆ ว่าใช้งานอย่างไรครับ
Pydantic AI คืออะไร
Pydantic AI เป็น Python Agent Framework จากทีม Pydantic คุณอาจจะเคยเห็นชื่อ Pydantic ผ่านตามาบ้าง—เพราะ SDK ทางการของ OpenAI, Anthropic, Google รวมถึง LangChain และ LlamaIndex ต่างก็พึ่งพาตัวช่วยตรวจสอบความถูกต้องของข้อมูล (Data Validation) เบื้องหลังทั้งสิ้น พูดง่ายๆ ก็คือ ในเรื่องของการตรวจสอบความถูกต้อง พวกเขาคือผู้ที่มีคุณสมบัติเหมาะสมที่สุดในอีโคระบบนี้แล้ว
ปรัชญาการออกแบบของมันคล้ายกับ FastAPI มาก: ใช้ Type Hints พื้นฐานของ Python เพื่อกำหนดพฤติกรรมให้ชัดเจน ที่เหลือปล่อยให้เฟรมเวิร์กจัดการต่อ แนวคิดหลักมีอยู่ไม่กี่อย่าง ได้แก่ Agent, Tools, Dependencies (การฉีดดีเพนเดนซี) และ Structured Output (โครงสร้างผลลัพธ์) คุณไม่ต้องไปท่องจำคลาส Abstract ที่มันสร้างขึ้นมาเอง การเขียนก็เหมือนโค้ด Python ทั่วไป แต่ IDE จะช่วยเติมโค้ดอัตโนมัติ (Auto-complete) และเครื่องมือตรวจสอบ Type (เช่น Pyright, mypy) ก็สามารถจับข้อผิดพลาดได้ตั้งแต่ก่อนที่คุณจะรันโปรแกรมด้วยซ้ำ
มันเป็น Model-agnostic นั่นหมายความว่าไม่ได้ผูกติดกับค่ายโมเดลใดค่ายหนึ่ง รองรับทั้ง OpenAI, Anthropic, Google, Groq, Cohere, Mistral, Ollama และอื่นๆ อีกกว่าสิบเจ้า การเปลี่ยนโมเดลจึงทำได้แค่แก้สตริงตัวเดียว หากอยากเข้าใจความแตกต่างระหว่าง Agent กับการเรียก API ทั่วไป สามารถอ่าน AI Agent คืออะไร ก่อนได้ครับ
นำไปทำอะไรได้บ้าง
พูดง่ายๆ คือ ทุกสถานการณ์ที่ "ต้องการให้โมเดลส่งผลลัพธ์ที่เชื่อถือได้กลับมา" ล้วนเหมาะสมทั้งหมด:
- การดึงข้อมูลแบบมีโครงสร้าง (Structured Extraction): โยนจดหมายร้องเรียนเข้าไป แล้วให้มันพ่นฟิลด์
อารมณ์,หมวดหมู่,ระดับความเร่งด่วนออกมา พร้อมการันตีว่า Type ถูกต้องแน่นอน - การจัดหมวดหมู่และการติดป้ายกำกับ (Classification & Labeling): ต้องติดแท็กเอกสารจำนวนมาก โดยจำกัดผลลัพธ์ให้อยู่ใน Enum ที่คุณกำหนดไว้ ถ้าโมเดลตอบมั่วจะถูกบล็อกทันที
- Tool-calling Agent: ให้โมเดลเรียกใช้ฟังก์ชันของคุณได้ เช่น ค้นหาฐานข้อมูล, เรียก Weather API, คำนวณเลข โดยที่เฟรมเวิร์กจะแปลง Type ของฟังก์ชันให้เป็นคำอธิบายเครื่องมือที่โมเดลเข้าใจ
- การตอบคำถามด้วย RAG: ทำงานร่วมกับการค้นหา Vector เพื่อสร้างระบบตอบคำถามที่มีแหล่งอ้างอิง สามารถดู คู่มือการทำ RAG ของเราประกอบได้
เมื่อเทียบกับเฟรมเวิร์กขนาดใหญ่อย่าง LangChain ที่ห่อหุ้มทุกอย่างไว้ Pydantic AI จงใจออกแบบให้มีขนาดบางเบา หากคุณแค่อยากทำให้ผลลัพธ์ของโมเดลมีความน่าเชื่อถือ โดยไม่อยากแบกรับอีโคระบบทั้งชุดเพื่อแลกับฟังก์ชันเล็กๆ ฟังก์ชันเดียว เส้นLearning Curve ของมันจะเป็นมิตรกับคุณมากกว่าเยอะครับ
วิธีใช้งาน: เริ่มต้นครั้งแรก
1. การติดตั้ง
bash
pip install pydantic-ai
แนะนำให้สร้าง Virtual Environment ขึ้นมาใช้ เวอร์ชั่น Python ที่ปลอดภัยควรเป็น 3.9 ขึ้นไป
2. ตั้งค่า API Key
ตัวอย่างเช่น Anthropic ให้ตั้งค่าEnvironment Variable:
bash
export ANTHROPIC_API_KEY=key_ของคุณ
ถ้าใช้ OpenAI ก็ตั้ง OPENAI_API_KEY ไล่ไปตามนั้นครับ
3. เขียน Agent ตัวแรก
python
from pydantic_ai import Agent
agent = Agent('anthropic:claude-sonnet-4-6')
result = agent.run_sync('อธิบายว่า Vector Database คืออะไรในประโยคเดียว')
print(result.output)
พารามิเตอร์ตัวแรกคือชื่อโมเดล ในรูปแบบ ผู้ให้บริการ:โมเดล หากต้องการเปลี่ยนเป็น OpenAI ก็แค่เปลี่ยนเป็น 'openai:gpt-4o' โค้ดส่วนอื่นไม่ต้องแก้เลย—นี่คือข้อดีของความเป็น Model-agnostic
4. ทำให้ผลลัพธ์มีโครงสร้าง
นี่คือจุดสำคัญ คุณกำหนด Pydantic Model ขึ้นมาเป็นฟอร์แมตผลลัพธ์:
python
from pydantic import BaseModel
from pydantic_ai import Agent
class Review(BaseModel):
sentiment: str # positive / negative / neutral
score: int # 1 ถึง 5
summary: str
agent = Agent('anthropic:claude-sonnet-4-6', output_type=Review)
result = agent.run_sync('ร้านนี้ของอร่อยแต่รอเกือบชั่วโมง เกินไปหน่อย')
print(result.output.score) # ได้ตัวเลขจำนวนเต็มทันที ไม่ต้อง parse เอง
print(result.output.sentiment) # ได้สตริงทันที
หากสิ่งที่โมเดลตอบกลับมาไม่ตรงกับ Type ของ Review เฟรมเวิร์กจะส่งข้อความแสดงข้อผิดพลาดกลับไปให้โมเดลลองใหม่โดยอัตโนมัติ เมื่อคุณได้รับ result.output มันจะเป็น Python Object ที่ผ่านการตรวจสอบความถูกต้องเรียบร้อยแล้ว แถม IDE ยังช่วยเติมฟิลด์ให้ด้วย
5. มอบเครื่องมือ (Tool) ให้ Agent
python
from pydantic_ai import Agent
agent = Agent('anthropic:claude-sonnet-4-6')
@agent.tool_plain
def get_weather(city: str) -> str:
"""查询指定城市目前的天气"""
return f'ตอนนี้ที่ {city} อุณหภูมิ 28 องศา ท้องฟ้าโปร่ง'
result = agent.run_sync('ตอนนี้ที่ไทเปอากาศเป็นยังไงบ้าง?')
print(result.output)
Docstring นั้นไม่ได้มีไว้แค่เขียนเท่ๆ แต่มันจะกลายเป็นคำอธิบายเครื่องมือที่โมเดลมองเห็น Type Hints ของฟังก์ชัน (city: str) จะถูกแปลงเป็นสเปกพารามิเตอร์ที่โมเดลเข้าใจ และพารามิเตอร์เหล่านี้จะถูกตรวจสอบความถูกต้องผ่าน Pydantic เช่นกัน
เทคนิคขั้นสูง
Dependency Injection คือฟีเจอร์ที่ถูกประเมินค่าต่ำที่สุดของมัน คุณสามารถส่งการเชื่อมต่อฐานข้อมูล, ข้อมูลผู้ใช้, หรือ API client เข้าไปใน Agent และ Tool ได้อย่างปลอดภัยด้วย Type ผ่าน RunContext:
python
from dataclasses import dataclass
from pydantic_ai import Agent, RunContext
@dataclass
class Deps:
user_id: int
db: object # การเชื่อมต่อฐานข้อมูลของคุณ
agent = Agent('anthropic:claude-sonnet-4-6', deps_type=Deps)
@agent.tool
def get_orders(ctx: RunContext[Deps]) -> str:
return f'ค้นหาคำสั่งซื้อของผู้ใช้ {ctx.deps.user_id}'
ตอนเขียนเทสต์ คุณแค่เปลี่ยน db ให้เป็น Mock Object โดยไม่ต้องแตะฐานข้อมูลจริง ซึ่งสำคัญมากสำหรับการเขียน Unit Test
Streaming: หากต้องการทำเอฟเฟกต์พิมพ์ข้อความแบบเรียลไทม์ ให้ใช้ agent.run_stream() มันจะตรวจสอบความถูกต้องของโครงสร้างผลลัพธ์ไปพร้อมๆ กับการสร้างข้อความ ทำให้ User Experience ดีขึ้นมาก
Observability: Pydantic AI สามารถ integration กับ Logfire ซึ่งเป็นทีมเดียวกันได้อย่างราบรื่น หลังจากเชื่อมต่อแล้ว ทุกๆ การเรียกโมเดล, การทริกเกอร์เครื่องมือ, ปริมาณ Token ที่ใช้, และเวลาที่ใช้ไป จะมองเห็นได้ทั้งหมด สิ่งที่ Debug ยากที่สุดในแอป LLM คือ "ทำไมโมเดลถึงตอบแบบนี้" พอมีสิ่งนี้แล้วก็ไม่ต้องมานั่งเดาสุ่มอีกต่อไป หากต้องการวางแผนระบบ Agent ให้สมบูรณ์ยิ่งขึ้น สามารถอ่านประกอบกับ คู่มือการพัฒนา AI Agent ของเราได้
ข้อผิดพลาดที่พบบ่อยและสิ่งที่ต้องระวัง
- คิดว่าใส่ output_type แล้วจะปลอดภัย 100%: เฟรมเวิร์กจะสั่งให้โมเดลลองใหม่เมื่อตรวจสอบความถูกต้องไม่ผ่าน แต่การลองใหม่มีขีดจำกัด หากยังผิดซ้ำๆ มันจะพ่น Exception ออกมา ซึ่งคุณยังคงต้องใช้ try/except อยู่ดี สิ่งที่ Type Validation ช่วยลดคือ "ข้อมูลสกปรกหลุดรอดเข้าสู่ระบบ" ไม่ใช่ "โมเดลจะไม่ทำผิดพลาดเลย"
- เขียน Docstring ของ Tool แบบขอไปที: โมเดลอาศัย Docstring ในการตัดสินใจว่าจะเรียกใช้เครื่องมือตอนไหน หากเขียนคลุมเครือ โมเดลจะเรียกมั่วหรือไม่ยอมเรียกเลย ให้มองว่ามันคือคู่มือการใช้งานที่เขียนให้โมเดลอ่าน
- ใส่ตรรกะซับซ้อนใน Tool โดยไม่จัดการ Exception: หากโค้ดใน Tool เกิดข้อผิดพลาด ข้อความนั้นจะถูกส่งกลับไปหาโมเดล ซึ่งอาจทำให้โมเดลวนเวียนอยู่กับความผิดพลาดนั้นและเผาผลาญ Token ทิ้งจำนวนมาก ควรดักจับ Exception ที่ควรกันไว้ให้เรียบร้อย
- ละเลยเรื่องต้นทุน: การลองซ้ำของ Structured Output และการเรียก Tool หลายรอบ จะทำให้การบริโภค Token เร็วกว่าที่คุณคิด ก่อนขึ้นระบบจริงต้องต่อระบบ Monitor เพื่อดูค่าใช้จ่ายจริงให้ชัดเจน
- พยายามใช้มันเป็น Framework ขนาดใหญ่: มันตั้งใจออกแบบมาให้มีขนาดบาง หากคุณต้องการการจัดลำดับขั้นตอนที่ซับซ้อนหลายขั้นตอน หรือมี Connector สำเร็จรูปจำนวนมาก LlamaIndex หรือโซลูชันอื่นอาจจะตอบโจทย์และง่ายกว่า อย่าฝืนใช้งาน
บทวิจารณ์จาก TheAI Academy
พูดตามตรง ในตลาดมี Agent Framework เยอะจนเลือกไม่ถูก แต่สิ่งที่ Pydantic AI แก้ไขคือปัญหาเฉพาะเจาะจงที่นักพัฒนา LLM ทุกคนเคยโดนมาแล้ว นั่นก็คือ ผลลัพธ์ที่ไม่น่าเชื่อถือ มันไม่ได้พยายามจะเป็น "เฟรมเวิร์กที่ทรงพลังที่สุดในจักรวาล" แต่มันนำเอาเรื่อง "ความปลอดภัยของ Type (Type Safety)" ที่ชาว Python ให้ความสำคัญอยู่แล้ว ย้ายเข้ามาสู่การพัฒนา AI ได้อย่างสะอาดสะอ้าน สำหรับคนที่คุ้นเคยกับการเขียน FastAPI และ Pydantic แทบจะไม่มีต้นทุนในการเรียนรู้เลย
มันไม่ได้ทำให้โมเดลของคุณฉลาดขึ้น แต่ทำให้โค้ดของคุณน่าเชื่อถือขึ้น—และอย่างหลังต่างหากคือสิ่งที่จะช่วยชีวิตคุณจริงๆ หลังจากขึ้นระบบไปแล้ว
ถ้าคุณทำแค่ Demo หรือเล่นสนุกๆ คุณอาจจะยังไม่รู้สึกอะไร แต่ตราบใดที่คุณกำลังจะเอาโปรเจกต์ขึ้นระบบจริง, มีคนใช้งานจริง, และต้องดูแลรักษาระยะยาว คุณค่าของความปลอดภัยของ Type และ Observability จะยิ่งเด่นชัดขึ้นทุกวัน
แหล่งที่มา
คำถามที่พบบ่อย
Pydantic AI ต่างจาก LangChain ยังไง และควรเลือกใช้อะไร?
ข้อแตกต่างที่ใหญ่ที่สุดคือ "น้ำหนัก" LangChain เป็นระบบนิเวศขนาดใหญ่ที่มี Connector, Integration และ Layer นามธรรมจำนวนมาก เหมาะสำหรับโปรเจกต์ใหญ่ที่ต้องการการจัดการที่ซับซ้อน แต่เส้นทางการเรียนรู้มีความชัน ส่วน Pydantic AI ถูกออกแบบมาให้บางเบา แกนหลักมีแค่แนวคิดเรื่อง Agent, เครื่องมือ, Dependency Injection และ Structured Output โดยเน้นไปที่ Type Safety หากความต้องการของคุณคือ "ทำให้ผลลัพธ์จากโมเดลน่าเชื่อถือ และเขียนโค้ดใกล้เคียงกับ Python ดั้งเดิม" Pydantic AI จะเรียนรู้ได้เร็วกว่ามาก แต่ถ้าต้องการ Integration สำเร็จรูปจำนวนมาก LangChain จะสะดวกกว่า ทั้งสองตัวนี้ไม่ได้ขัดแย้งกัน สามารถเลือกใช้ตามขนาดของโปรเจกต์ได้เลย
จำเป็นต้องใช้โมเดลของ OpenAI เท่านั้นไหม? ต่อโมเดลรันในเครื่อง (Local) ได้หรือเปล่า?
ไม่จำเป็นครับ Pydantic AI เป็นแบบ Model-agnostic รองรับ OpenAI, Anthropic, Google, Groq, Mistral, Cohere, Ollama และอื่นๆ อีกกว่าสิบราย การเปลี่ยนโมเดลโดยทั่วไปแก้ไขแค่สตริงเดียวตอนสร้าง Agent เท่านั้น หากต้องการรันโมเดลในเครื่อง สามารถทำผ่าน Ollama โดยชี้สตริงโมเดลไปที่บริการ Local ของคุณ ส่วนอื่นของโค้ดไม่ต้องแก้ไขเลย
การกำหนด Structured Output จะรับประกันได้จริงไหมว่าโมเดลจะไม่ตอบมั่ว?
ไม่สามารถรับประกันได้ว่าตัวโมเดลจะไม่ทำผิดพลาด แต่รับประกันได้ว่า "ข้อมูลที่ไม่ตรงกับ Type ที่คุณกำหนดจะไม่เล็ดลอดเข้ามาในระบบ" เมื่อโมเดลตอบสิ่งที่ไม่ผ่านการตรวจสอบของ Pydantic เฟรมเวิร์กจะส่งข้อความแสดงข้อผิดพลาดกลับไปให้มันลองใหม่โดยอัตโนมัติ อย่างไรก็ตาม การลองใหม่มีขีดจำกัด หากล้มเหลวต่อเนื่องจะโยน Exception ออกมา ดังนั้นคุณจึงยังต้องใช้ try/except เพื่อจัดการกับสถานการณ์ที่แย่ที่สุด สิ่งที่มันช่วยลดคือความเสี่ยงเรื่องข้อมูลขยะ ไม่ใช่ปัญหาเรื่องความฉลาดของโมเดล
มือใหม่ที่ไม่เคยเขียน Pydantic มาก่อน จะเรียนรู้ยากไหม?
หากคุณพอมีพื้นฐาน Python และ Type Hints อยู่บ้าง เกณฑ์ความยากก็ไม่สูงครับ แกนหลักของ Pydantic คือ "การใช้ class เพื่อกำหนดหน้าตาของข้อมูล" ซึ่งเขียนได้ค่อนข้างเป็นธรรมชาติ ขอแนะนำให้สละเวลาสักสิบนาทีดูวิธีที่ Pydantic กำหนด BaseModel ก่อน แล้วค่อยกลับมาเขียน Agent จะราบรื่นขึ้นมาก อุปสรรคทางความคิดจริงๆ กลับเป็นเรื่องแนวคิดการออกแบบ Agent และ Tool ต่างๆ ซึ่งสามารถศึกษาควบคู่ไปกับคู่มือการพัฒนา Agent ของเราได้