Pydantic กับ Structured Output

จาก 0 ถึง AI Agent — บันทึกการเรียนรู้ผ่านโปรเจกต์จริง

ตอนที่ 5: Pydantic กับ Structured Output


ตอนนี้เราเกือบครบแล้ว เหลือแค่ส่วนสุดท้าย — Pydantic และ entry point ของโปรแกรม

ฟังดูน่ากลัว แต่จริงๆ ไม่ยากเลย มาดูกัน


ปัญหาของการรับข้อมูลจาก AI

สมมติ Claude ส่งคำตอบกลับมา แต่ดันส่งผิด เช่น:

  • confidence ควรเป็น "high", "medium", หรือ "low" แต่ Claude ส่ง "very high" มา
  • found_in_docs ควรเป็น true/false แต่ส่งมาเป็น "yes" แทน
  • ถ้าไม่มีอะไรตรวจสอบ โปรแกรมอาจทำงานผิดพลาดโดยที่เราไม่รู้


    Pydantic: ผู้ตรวจสอบข้อมูล

    Pydantic คือ library ที่ช่วยตรวจสอบว่าข้อมูลถูกต้องตามที่เรากำหนดหรือเปล่า — ถ้าผิดจะ error ทันที แทนที่จะเงียบแล้วทำงานผิดพลาด

    ใน ask.py เรานิยาม model ไว้ตรงนี้:

    from pydantic import BaseModel
    from typing import List
    
    class DocAnswer(BaseModel):
        answer: str
        source_files: List[str]
        confidence: str   # "high" | "medium" | "low"
        found_in_docs: bool

    แค่นี้เลย — บอกว่า DocAnswer ต้องมี 4 field และแต่ละ field เป็น type อะไร

    ตอนที่ Claude เรียก return_answer เราก็แปลงข้อมูลมันเป็น DocAnswer ด้วยบรรทัดนี้:

    return DocAnswer(**block.input)

    **block.input คือการแกะ dictionary แล้วส่งเป็น argument เข้า DocAnswer — ถ้า field ไหนผิด type หรือขาดหายไป Pydantic จะ raise error ทันที


    Entry Point: โค้ดที่รันเมื่อเปิดโปรแกรม

    ส่วนสุดท้ายคือ if __name__ == "__main__" — โค้ดในนี้จะรันเฉพาะตอนที่เราสั่ง python ask.py โดยตรง:

    if __name__ == "__main__":
        print(f"Docs folder: {DOCS_FOLDER.resolve()}")
        print("Type 'quit' to exit\n")
    
        while True:
            question = input("You: ").strip()
            if question.lower() in ("quit", "exit"):
                break
            if not question:
                continue
    
            print("\nThinking...")
            result = ask(question)
    
            if isinstance(result, DocAnswer):
                print(f"\nClaude: {result.answer}")
                print(f"Sources:     {result.source_files}")
                print(f"Confidence:  {result.confidence}")
                print(f"In docs:     {result.found_in_docs}\n")
            else:
                print(f"\nClaude: {result}\n")

    ทำงานอย่างนี้:

    1. วน loop รอรับคำถามจากผู้ใช้

    2. ถ้าพิมพ์ quit หรือ exit ก็หยุด

    3. ส่งคำถามไปที่ ask() แล้วรอผล

    4. ถ้าผลเป็น DocAnswer แสดงแบบ structured, ถ้าเป็น text ธรรมดาก็แสดงตรงๆ


    isinstance() คืออะไร?

    if isinstance(result, DocAnswer):

    isinstance() ตรวจสอบว่า result เป็น object ประเภท DocAnswer ไหม — ถ้าใช่แสดงแบบมีหัวข้อ ถ้าไม่ใช่ก็แสดงเป็น text ธรรมดา

    ทำไมต้องเช็ค? เพราะในบางกรณีที่ agent วน loop แล้วออกมาด้วย end_turn (แทนที่จะเรียก return_answer) ผลที่ได้จะเป็น string ธรรมดา ไม่ใช่ DocAnswer


    ภาพรวมโค้ดทั้งหมดใน ask.py

    imports + load_dotenv
        ↓
    DocAnswer (Pydantic model)
        ↓
    client + DOCS_FOLDER + SYSTEM_PROMPT
        ↓
    tools = [list_files, read_file, return_answer]
        ↓
    run_tool() — Python รัน tool จริง
        ↓
    ask() — Agent Loop
        ↓
    if __name__ == "__main__" — CLI

    แค่นี้เอง ทั้งโปรเจกต์อยู่ในไฟล์เดียว ไม่ถึง 100 บรรทัด


    สรุปตอนที่ 5

    Pydantic ช่วยให้เรามั่นใจว่าข้อมูลที่ได้จาก Claude ถูกต้องตาม format ที่กำหนด และ entry point ก็เป็นแค่ while loop ธรรมดาที่รอรับคำถาม

    ตอนหน้าเป็นตอนสุดท้าย — เราจะรันโปรแกรมจริง และสรุปสิ่งที่ได้เรียนรู้จากโปรเจกต์นี้


    📚 Series: จาก 0 ถึง AI Agent — บันทึกการเรียนรู้ผ่านโปรเจกต์จริง

    Source code ทั้งหมดอยู่ที่ github.com/nipitpongpan/ask-my-docs


    Posted

    in

    ,

    by

    Tags:

    Comments

    Leave a Reply

    Your email address will not be published. Required fields are marked *