สัปดาห์ที่ 12 · Advanced

APIs + Integration

สัปดาห์นี้คือจุดที่ Python "คุยกับโลก" — ใช้ API ของคนอื่น (Google, LINE, Anthropic Claude, OpenAI, weather, …) · สร้าง API ของตัวเอง ด้วย Flask · เรียก LLM API ใส่ AI ใน app · รู้จัก GCP catalog ของ engineer · ครอบคลุมที่สุดของหลักสูตร

📐 สัปดาห์นี้ = เริ่มมี UI เปิด UX/UI Principles ค้างไว้แท็บข้าง ๆ · โดยเฉพาะ Section 0 — เลือก UI Technology (เลือก Streamlit vs Flask vs HTML/JS ให้ตรงงาน) · Section 3 — 5 States ที่ AI ลืม · Section 5 — Forms · ใช้กับ W12 ทั้งหมด

เป้าหมายสัปดาห์นี้

🔒 ทำไม "ออกแบบ + เลือก API" คือทักษะที่ AI ทำให้คุณไม่ได้ AI "เขียน API call" ได้ใน 5 วินาที · แต่ตัดสินใจว่า "ใช้ API ไหน · เก็บ key ยังไง · จัดการ rate limit · เลือก auth pattern · เลือก LLM ตัวไหนสำหรับงานไหน" = หน้าที่คุณ · นี่คือ ทักษะที่แยกนักศึกษาที่ "ใช้ API ได้" จาก "ใช้ AI ทำของจริงได้"

🌐 API คืออะไร — เริ่มจาก mental model

API (Application Programming Interface) = "ประตูที่ระบบใช้คุยกัน" · ลองคิดเปรียบเทียบ:

โลกจริงAPI คือ
ร้านอาหาร — คนกินไม่ต้องเข้าครัวเมนู + พนักงาน = API · ครัว = internal logic
ATM — ไม่ต้องเข้าธนาคารปุ่ม + จอ = API · ระบบบัญชี = backend
เครื่องชงกาแฟ — กดปุ่มปุ่มกาแฟ-นม-ฟอง = API · มอเตอร์ภายใน = implementation

หลักการ: ระบบ A อยาก "ใช้" ระบบ B โดย "ไม่ต้องรู้ว่า B ทำงานยังไงข้างใน" · B เปิด API เป็น contract · A เรียก API → ได้ผลลัพธ์

🗺 API มีกี่ประเภท?

flowchart TB api["🌐 API
(ประตูคุยกับระบบ)"] api --> web["💻 Web API
(ผ่าน internet)"] api --> lib["📦 Library API
(ใน Python)"] api --> os["🖥 OS API
(เรียก system)"] api --> hw["🔌 Hardware API
(สื่อสารกับอุปกรณ์)"] web --> rest["REST
HTTP + JSON · ปกติที่สุด"] web --> graphql["GraphQL
1 endpoint · query ที่อยาก"] web --> ws["WebSocket
real-time bi-directional"] web --> rpc["gRPC
เร็ว · ใน datacenter"] lib --> pylib["pandas · numpy · requests
import แล้วใช้"] os --> file["file system
open() / read() / write()"] os --> proc["subprocess
เรียกโปรแกรมอื่น"] hw --> serial["Serial / UART
Arduino · sensor"] hw --> modbus["Modbus / OPC-UA
industrial"] hw --> mqtt["MQTT
IoT pub/sub"] classDef cat fill:#1e3a5f,stroke:#3776ab,color:#fff classDef impl fill:#3d2c1a,stroke:#ffd43b,color:#fff class api,web,lib,os,hw cat class rest,graphql,ws,rpc,pylib,file,proc,serial,modbus,mqtt impl

📊 เปรียบเทียบประเภท API

ประเภทตัวอย่างใช้เมื่อเรียกจาก Python ด้วย
REST APITwitter, LINE, GitHub, Claude, IQAirปกติทั่วไป · public servicesrequests
GraphQLGitHub v4, Shopifyดึง data ที่อยากเฉพาะ fieldrequests + query string
WebSocketchat, live dashboard, gamereal-time 2 ทางwebsockets
gRPCGoogle internal servicesservice-to-service ภายในgrpcio
Library APIpandas, numpyทำงานใน Python ปกติimport
OS APIfile system, processคุยกับเครื่องos, subprocess
HardwareModbus PLC, MQTT sensor, Serial Arduinoคุยกับอุปกรณ์pyserial, pymodbus, paho-mqtt
📌 สัปดาห์นี้เน้น REST API + LLM API เพราะ 95% ของ API ที่นักศึกษาจะเจอใน 5 ปีข้างหน้า = REST + LLM · แต่รู้ว่ามีประเภทอื่นไว้ — เผื่อเจอในงาน

🔌 REST API — พื้นฐานที่ต้องรู้

API = "ประตู" ระหว่างระบบ · REST = แบบที่ใช้บ่อยที่สุด · เป็น HTTP requests ปกติ

HTTP Methodหมายความตัวอย่าง
GETดึงข้อมูลGET /students — ขอรายชื่อ
POSTเพิ่มPOST /students — เพิ่มคน
PUT/PATCHแก้PATCH /students/5 — แก้คน id 5
DELETEลบDELETE /students/5

Status code ที่เจอบ่อย

200 OKสำเร็จ
201 Createdสร้างแล้ว
400 Bad Requestส่งข้อมูลผิด
401 Unauthorizedไม่ได้ login / API key ผิด
404 Not Foundไม่พบ
429 Too Many Requestsเกิน rate limit
500 Server Errorserver พัง — ไม่ใช่ความผิดเรา

📡 เรียก API ด้วย requests

ติดตั้ง: pip install requests

GET — ดึงข้อมูล

import requests

# ตัวอย่าง 1: Public API ไม่ต้อง auth
r = requests.get("https://api.coindesk.com/v1/bpi/currentprice.json")
print(r.status_code)         # 200
print(r.json())              # เป็น dict

# ตัวอย่าง 2: GET with parameter
r = requests.get(
    "https://api.airvisual.com/v2/city",
    params={
        "city": "Ubon Ratchathani",
        "state": "Ubon Ratchathani",
        "country": "Thailand",
        "key": "YOUR_API_KEY",
    }
)
data = r.json()
print(data["data"]["current"]["pollution"]["aqius"])

POST — ส่งข้อมูล (เช่นส่ง LINE)

import requests

LINE_TOKEN = "your_channel_access_token"
USER_ID = "Uxxxxxxxxxxxxxxxx"

r = requests.post(
    "https://api.line.me/v2/bot/message/push",
    headers={
        "Authorization": f"Bearer {LINE_TOKEN}",
        "Content-Type": "application/json",
    },
    json={
        "to": USER_ID,
        "messages": [{"type": "text", "text": "สวัสดีจาก Python 🎉"}],
    }
)
print(r.status_code)        # 200 ถ้าสำเร็จ

🔑 เก็บ API Key ปลอดภัย — .env

ห้าม commit API key ใน Git! ถ้า key หลุดบน GitHub — โดน abuse ภายในไม่กี่ชั่วโมง · ค่าธรรมเนียมอาจหลายพัน บาท

ติดตั้ง: pip install python-dotenv

โครงสร้างที่ถูก

my_project/
├── .env                ← เก็บ secrets (อย่าลืม gitignore)
├── .env.example        ← ตัวอย่างที่ commit ได้ (ไม่มีค่าจริง)
├── .gitignore          ← มี .env
└── main.py

.env:

IQAIR_KEY=abc123_real_key_here
LINE_TOKEN=Bearer_real_token_here

.env.example:

IQAIR_KEY=your_iqair_key_here
LINE_TOKEN=your_line_token_here

main.py:

import os
from dotenv import load_dotenv

load_dotenv()                # โหลด .env

key = os.getenv("IQAIR_KEY")
token = os.getenv("LINE_TOKEN")

# ใช้ตามปกติ
r = requests.get(url, params={"key": key, ...})

🔐 Auth Patterns — 4 แบบที่จะเจอ

"API key" เป็นแค่ 1 แบบ ของ authentication · ในงานจริงเจอหลายแบบ · AI ใช้แต่ละแบบไม่เหมือนกัน — ต้องดู doc ของ API ที่ใช้

1️⃣ API Key (ในย่อ params หรือ header)

ง่ายที่สุด · key เป็น string คงที่ · เหมาะกับ public API ทั่วไป

# แบบ A: ใส่ใน query params
r = requests.get(url, params={"api_key": KEY})

# แบบ B: ใส่ใน custom header
r = requests.get(url, headers={"X-API-Key": KEY})

ตัวอย่าง: IQAir, OpenWeather, NewsAPI, public APIs ส่วนใหญ่

2️⃣ Bearer Token (มาตรฐาน OAuth-style)

ใช้ Authorization: Bearer ... header · มาตรฐานของ REST API สมัยใหม่

r = requests.post(
    "https://api.line.me/v2/bot/message/push",
    headers={
        "Authorization": f"Bearer {LINE_TOKEN}",
        "Content-Type": "application/json",
    },
    json=payload,
)

ตัวอย่าง: LINE, Anthropic Claude, OpenAI, GitHub

3️⃣ OAuth 2.0 (login กับ provider · ขอ permission)

ซับซ้อนกว่า · ใช้เมื่อต้องการสิทธิ์ของ user (ไม่ใช่ของเรา) · เช่น "login ด้วย Google"

# Flow: user → ไป google.com/auth → approve → กลับมาที่ app เรา
# ได้ access_token + refresh_token
# ใช้ library: authlib, requests-oauthlib
from authlib.integrations.requests_client import OAuth2Session

oauth = OAuth2Session(CLIENT_ID, CLIENT_SECRET, scope="openid email")
auth_url, state = oauth.create_authorization_url("https://accounts.google.com/o/oauth2/auth")
# → redirect user → callback → exchange code for token

ตัวอย่าง: Google Sign-In, GitHub OAuth, "Connect with Facebook"

4️⃣ Service Account (server-to-server)

App ของเราติดต่อ Google ในนาม "ตัวเอง" — ไม่มี user · ใช้ JSON credentials file

import gspread
# credentials.json = key file ที่ download จาก GCP Console
gc = gspread.service_account(filename="credentials.json")
sheet = gc.open("MySheet").sheet1

ตัวอย่าง: Google Sheets API, GCS, BigQuery — เมื่อ app ทำงานเอง (cron job)

📊 เลือก auth แบบไหน

สถานการณ์Auth ที่ใช้
Public API ของบุคคล (weather, news)API Key
API ของบริการที่คุณ registerBearer Token
App ต้องใช้ข้อมูลของ user (email, photos)OAuth 2.0
Server background job (cron)Service Account

🤖 LLM APIs — ใส่ AI ใน App ของคุณ

หลัง W14 เราจะ deploy app · ก่อนหน้านั้น app บางอันต้อง เรียก LLM เพื่อ: สรุปข้อความ · แปลภาษา · ตอบคำถาม · จัดหมวด · สร้าง content · OCR

📊 เปรียบเทียบ Cloud LLM (ปี 2026)

ProviderModel หลักStrengthราคา input/output (per 1M tokens)
Anthropic Claudeclaude-opus-4-7, claude-sonnet-4-6, claude-haiku-4-5เก่งสุดที่ reasoning + code · ใส่ภาษาไทยได้ดี$3-15 / $15-75 (opus) · $1 / $5 (sonnet) · $0.25 / $1.25 (haiku)
OpenAI GPTgpt-4o, gpt-4o-mini, o-seriesecosystem ใหญ่ · function calling เก่ง$2.50 / $10 (4o) · $0.15 / $0.60 (mini)
Google Geminigemini-2-flash, gemini-2-procontext window ใหญ่ · เชื่อม Google services$0.10 / $0.40 (flash) · $1.25 / $5 (pro)
Local (Ollama)llama-3, qwen-2.5, phi-3ฟรี · ข้อมูลไม่ออกเครื่อง · ช้ากว่า · ต้อง GPU$0 (ค่าไฟ + เครื่อง)
📌 เลือกตัวไหน?
  • เรียน + ทำ demo: Claude Haiku หรือ GPT-4o-mini หรือ Gemini Flash — ถูก + เร็ว
  • งานยากต้อง reasoning / code: Claude Opus หรือ Sonnet
  • Final Project: เริ่มที่ Haiku/mini · upgrade เมื่อจำเป็น
  • ข้อมูล sensitive: Local LLM (Ollama)

🐾 Pattern 1: Basic Completion (Claude SDK)

# pip install anthropic
import os
from anthropic import Anthropic
from dotenv import load_dotenv

load_dotenv()
client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))

msg = client.messages.create(
    model="claude-haiku-4-5-20251001",
    max_tokens=300,
    messages=[
        {"role": "user", "content": "สรุปย่อหน้านี้เป็น 1 ประโยค: ..."}
    ],
)

print(msg.content[0].text)
# Anthropic Python SDK: เอกสาร https://docs.claude.com

🐾 Pattern 2: System Prompt + User Prompt

msg = client.messages.create(
    model="claude-haiku-4-5-20251001",
    max_tokens=500,
    system="คุณคือ TA วิชา Computer Programming · ตอบเป็นภาษาไทย · กระชับ · ไม่ verbose",
    messages=[
        {"role": "user", "content": "อธิบาย concept 'self' ใน Python ในแบบนักศึกษาปี 1"}
    ],
)

🐾 Pattern 3: Multi-turn Conversation

history = []

def chat(user_msg):
    history.append({"role": "user", "content": user_msg})
    resp = client.messages.create(
        model="claude-haiku-4-5-20251001",
        max_tokens=500,
        messages=history,
    )
    answer = resp.content[0].text
    history.append({"role": "assistant", "content": answer})
    return answer

print(chat("เครื่อง CNC คืออะไร?"))
print(chat("แล้ว 3-axis vs 5-axis ต่างกันยังไง?"))   # AI จำ context

🐾 Pattern 4: Streaming (ตอบทีละคำ · ใช้กับ chat UI)

with client.messages.stream(
    model="claude-haiku-4-5-20251001",
    max_tokens=500,
    messages=[{"role": "user", "content": "เล่าเรื่องสั้น ๆ"}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)
print()  # newline

🐾 Pattern 5: Structured Output (JSON ที่ parse ได้)

import json

prompt = """
สกัด field 3 อย่างจากประโยคนี้: 'อยากจอง CNC วันศุกร์ 10 โมง'
ตอบเป็น JSON มี keys: machine, day, time
ห้ามใส่ markdown · ห้ามมีข้อความอื่น
"""

msg = client.messages.create(
    model="claude-haiku-4-5-20251001",
    max_tokens=200,
    messages=[{"role": "user", "content": prompt}],
)

data = json.loads(msg.content[0].text)
print(data)
# → {"machine": "CNC", "day": "ศุกร์", "time": "10:00"}

🐾 Pattern 6: Function / Tool Calling

# ให้ LLM เรียก function ของเราเองได้ — สำหรับ agent
tools = [{
    "name": "get_weather",
    "description": "ดู PM2.5 ของเมือง",
    "input_schema": {
        "type": "object",
        "properties": {"city": {"type": "string"}},
        "required": ["city"],
    },
}]

msg = client.messages.create(
    model="claude-haiku-4-5-20251001",
    max_tokens=500,
    tools=tools,
    messages=[{"role": "user", "content": "PM2.5 อุบลฯ เท่าไหร่?"}],
)

# LLM จะตอบ tool_use block — เราต้อง execute function เอง · ส่งผลกลับ
# (ดู docs.claude.com/agents-and-tools/tool-use)

🐾 Pattern 7: Prompt Caching (ลด cost 90% เมื่อ context ซ้ำ)

# ใส่ cache_control ใน large system prompt (≥ 1024 tokens)
# ครั้งแรก: เสีย token + cost · ครั้งที่ 2-4 ภายใน 5 นาที: cache hit → ลด 90%
msg = client.messages.create(
    model="claude-haiku-4-5-20251001",
    max_tokens=500,
    system=[
        {
            "type": "text",
            "text": LONG_TEXTBOOK_CONTENT,  # ≥ 1024 tokens
            "cache_control": {"type": "ephemeral"},
        }
    ],
    messages=[{"role": "user", "content": "ในหนังสือ section 3 พูดถึงอะไร?"}],
)
⚠️ Cost gotchas
  • Output token แพงกว่า input 3-5 เท่า — limit max_tokens ให้ตรง
  • Streaming = cost เหมือนกัน — แค่ UX ดีขึ้น
  • Function calling = หลาย round trips — คูณ cost
  • ตั้ง budget alert ใน dashboard · ครั้งหนึ่งเขียน loop ผิด = bill 1000 บาทใน 10 นาที

🔄 OpenAI / Gemini — เหมือนกันมาก

# OpenAI
# pip install openai
from openai import OpenAI
client = OpenAI(api_key=OPENAI_KEY)
resp = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Hello"}],
)
print(resp.choices[0].message.content)

# Gemini
# pip install google-generativeai
import google.generativeai as genai
genai.configure(api_key=GOOGLE_KEY)
model = genai.GenerativeModel("gemini-2-flash")
resp = model.generate_content("Hello")
print(resp.text)

🏠 Local LLM (Ollama)

# 1. install Ollama จาก ollama.com
# 2. terminal: ollama pull llama3.2:3b
# 3. python:
import ollama
resp = ollama.chat(
    model="llama3.2:3b",
    messages=[{"role": "user", "content": "Hello"}],
)
print(resp["message"]["content"])
# ไม่ต้องมี internet · ไม่ต้องมี key · ฟรี · ช้ากว่า cloud

🤖 AI Agents + RAG — ยุค 2026 ของ LLM

LLM ธรรมดา = "ถามตอบครั้งเดียว" · Agent = LLM ที่ ตัดสินใจเรียก tools ของเรา ทำงานหลาย step · RAG = LLM ที่ "ค้นข้อมูลของเราก่อนตอบ" · 2 pattern นี้คือ "ยุคใหม่" ของ LLM

🔄 Pattern A: Agent Loop — LLM เรียก Tool · วน

จาก Pattern 6 (tool calling) ขั้นต่อไป — "loop" · LLM ขอเรียก tool · เรารัน · ผลกลับให้ LLM · LLM อาจขอเรียก tool อีก · จนได้คำตอบ

import os, json
from anthropic import Anthropic

client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))

# ─── 1. Tools ที่ AI ใช้ได้ ───
def get_weather(city: str) -> dict:
    """Mock — ของจริงเรียก IQAir/OpenWeather"""
    return {"city": city, "pm25": 75, "temp": 32}

def book_lab(machine: str, time: str) -> dict:
    """Mock — ของจริงเขียน DB"""
    return {"status": "ok", "booking_id": "B001", "machine": machine, "time": time}

TOOLS_REGISTRY = {"get_weather": get_weather, "book_lab": book_lab}

# ─── 2. Tool schema ที่บอก AI ───
TOOLS = [
    {
        "name": "get_weather",
        "description": "ดูข้อมูลอากาศของเมือง",
        "input_schema": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]},
    },
    {
        "name": "book_lab",
        "description": "จองเครื่องใน lab",
        "input_schema": {
            "type": "object",
            "properties": {"machine": {"type": "string"}, "time": {"type": "string"}},
            "required": ["machine", "time"],
        },
    },
]

# ─── 3. Agent loop ───
def run_agent(user_query: str, max_iterations: int = 5) -> str:
    messages = [{"role": "user", "content": user_query}]

    for i in range(max_iterations):
        resp = client.messages.create(
            model="claude-haiku-4-5-20251001",
            max_tokens=1024,
            tools=TOOLS,
            messages=messages,
        )

        # ถ้า AI หยุดเอง (ตอบเสร็จ) → break
        if resp.stop_reason == "end_turn":
            return resp.content[0].text

        # ถ้า AI ขอเรียก tool → รัน · ส่งผลกลับ
        if resp.stop_reason == "tool_use":
            messages.append({"role": "assistant", "content": resp.content})

            tool_results = []
            for block in resp.content:
                if block.type == "tool_use":
                    tool = TOOLS_REGISTRY[block.name]
                    result = tool(**block.input)
                    tool_results.append({
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": json.dumps(result),
                    })

            messages.append({"role": "user", "content": tool_results})

    return "❌ เกิน max iterations"


# ─── 4. ใช้ ───
answer = run_agent("PM2.5 ที่อุบลฯ ตอนนี้ · ถ้าอากาศดีให้จอง CNC-1 บ่าย 2")
print(answer)
# Agent จะ:
# 1. เรียก get_weather("Ubon")
# 2. ดู PM2.5 → ตัดสินใจว่า "ดี" หรือไม่
# 3. ถ้าดี → เรียก book_lab("CNC-1", "14:00")
# 4. สรุปให้ user เป็นภาษาคน
⚠️ Agent ระวัง 3 อย่าง
  • Infinite loop — ใส่ max_iterations เสมอ · ป้องกัน bill ช็อก
  • Cost ขึ้นเร็ว — ทุก iteration = 1 LLM call · 5 iterations × Sonnet = $$$
  • Tool security — AI เลือกเรียก tool อะไรก็ได้ · ห้ามให้ tool ลบ data/ส่งเงิน โดยไม่มี human approval

📚 Pattern B: RAG — Retrieval-Augmented Generation

LLM ไม่รู้เรื่อง ในบ้านของคุณ (เอกสารภาควิชา · spec product · chat history) · RAG = "ค้นข้อมูลของเราก่อน · แล้วให้ LLM ตอบโดยใช้ข้อมูลนั้น"

flowchart LR docs[("📚 docs
(PDF, MD, web)")] --> embed1[Embed
(text → vector)] embed1 --> vdb[(🌸 Vector DB
Chroma/Qdrant)] user(["👤 user question"]) --> embed2[Embed] embed2 --> search{Search
nearest vectors} vdb --> search search --> chunks[Top-K chunks] chunks --> llm[LLM
+ context] user --> llm llm --> answer([Answer
with citation]) classDef store fill:#3b2962,stroke:#8b5cf6,color:#fff classDef embed fill:#1e3a5f,stroke:#3776ab,color:#fff classDef ai fill:#5c2618,stroke:#ff7a18,color:#fff class docs,vdb store class embed1,embed2,search,chunks embed class llm,answer ai

🐾 RAG ใน Python (ขั้นต่ำ)

# pip install chromadb anthropic sentence-transformers
import chromadb
from anthropic import Anthropic

client = Anthropic()
chroma = chromadb.Client()
collection = chroma.create_collection("lab_docs")

# ─── 1. Index (ทำครั้งเดียว) ───
docs = [
    "เครื่อง CNC-1 มี travel 800x500x400 mm · spindle 12000 rpm",
    "เครื่อง CNC-2 มี travel 600x400x300 mm · spindle 8000 rpm",
    "PLC FX5U รุ่นใหม่ใช้ GX Works3 · ไม่ใช่ Works2",
    "Lab เปิด 9:00-17:00 จันทร์-ศุกร์ · ห้ามใช้ตอนกลางคืน",
]

collection.add(
    documents=docs,
    ids=[f"doc-{i}" for i in range(len(docs))],
)  # Chroma จะ embed ให้อัตโนมัติ

# ─── 2. Query (ทุกครั้งที่ user ถาม) ───
def rag_answer(question: str) -> str:
    # 2a. หา chunks ที่เกี่ยวข้องที่สุด
    results = collection.query(query_texts=[question], n_results=2)
    context = "\n".join(results["documents"][0])

    # 2b. ใส่ context ใน prompt
    prompt = f"""ข้อมูลจาก lab:
{context}

คำถาม: {question}

ตอบเฉพาะจากข้อมูลข้างบน · ถ้าไม่มีคำตอบให้บอกว่า "ไม่พบในเอกสาร" """

    resp = client.messages.create(
        model="claude-haiku-4-5-20251001",
        max_tokens=500,
        messages=[{"role": "user", "content": prompt}],
    )
    return resp.content[0].text


# ─── 3. ใช้ ───
print(rag_answer("CNC-1 spindle หมุนเร็วแค่ไหน?"))
# → "12,000 rpm" — มาจาก doc 0

print(rag_answer("Lab เปิดเสาร์อาทิตย์มั้ย?"))
# → "ไม่เปิด · จันทร์-ศุกร์เท่านั้น" — มาจาก doc 3
📌 ทำไม RAG ดีกว่า "ใส่ทุกอย่างใน prompt"
  • Cost — ส่งเฉพาะ chunks ที่เกี่ยว · ไม่ใช่หนังสือทั้งเล่ม
  • Accuracy — context ตรงประเด็น · hallucination ลดลง
  • Update — เพิ่ม doc ใหม่ → reindex · ไม่ต้อง retrain LLM
  • Citation — บอกได้ว่าคำตอบมาจาก doc ไหน

🌸 Vector DB ตัวเลือก

Toolดีเหมาะ
Chromaฟรี · ใน Python · เริ่มง่ายprototype · < 100K docs
Qdrantฟรี · Rust · เร็ว · self-hostproduction · open-source
Pineconemanaged · scale ใหญ่production · มี budget
pgvectorextension ของ Postgresใช้ Postgres อยู่แล้ว
Weaviatebuilt-in modulesเน้น semantic search

🚀 Agent + RAG = AI App สมัยใหม่

App แบบ Cursor, ChatGPT (ที่ search web ได้), Notion AI = Agent + RAG รวมกัน


☁️ Google Cloud APIs — Catalog สำหรับ Engineer

GCP มี API หลายร้อยตัว — ที่นี่คือตัว ที่นักศึกษาวิศวะใช้บ่อยที่สุด · ทุกตัวมี free tier · ใช้ Service Account auth · เปิดใน console.cloud.google.com

APIทำอะไรEngineer ใช้ทำอะไร
Vision APIOCR · object detection · face detectionอ่าน label / nameplate จากรูปอุปกรณ์ · QA visual inspection · อ่านลายมือ lab note
Translation APIแปล 100+ ภาษาแปล manual จาก JP/CN เป็นไทย · แปล error message · แปล spec ของ vendor
Speech-to-Textเสียง → ข้อความ (Thai support ดี)ถอด lecture · transcript meeting · voice command บนเครื่องมือ
Text-to-Speechข้อความ → เสียงwarning audio บนเครื่อง · accessibility
Maps Platformmap · directions · places · geocodingrouting สำหรับ delivery · location-based booking · indoor maps
Sheets APIread/write Google Sheetsใช้ Sheets เป็น database ฟรี (W08)
Drive APIupload/download/share filesเก็บ lab report · เก็บ CSV จาก sensor
Gmail APIส่ง emailauto-notify · weekly report
Calendar APIread/write eventsauto-book lab · check availability
Cloud Runhost Python containerdeploy Flask API (W14)
Cloud Storage (GCS)เก็บไฟล์ขนาดใหญ่image archive · sensor data backup
BigQuerySQL บน data ใหญ่ (TB+)analyze sensor data หลายปี · cross-machine analytics
Pub/Submessage queueIoT events · async job
Vertex AIML platform · host Geminiใช้ Gemini API · host custom ML model
เคล็ดลับ — เริ่มต้นด้วย Vision + Translation สำหรับ Final Project ถ้าอยากใช้ AI กับรูป/ภาษา · 2 อันนี้ "ทำงานได้ทันที" · ไม่ต้องเรียน ML · มี free tier 1000 calls/month

ตัวอย่าง: ใช้ Vision API อ่าน serial number จากรูปเครื่อง

# pip install google-cloud-vision
from google.cloud import vision

client = vision.ImageAnnotatorClient()  # ใช้ Service Account จาก GOOGLE_APPLICATION_CREDENTIALS

with open("machine_photo.jpg", "rb") as f:
    image = vision.Image(content=f.read())

# OCR
response = client.text_detection(image=image)
for text in response.text_annotations[:5]:
    print(text.description)
# → "Mitsubishi FX5U" "Serial: 12345678" ...

📊 Integration จริง — 3 ตัวอย่าง

1) Google Sheets เป็น "Database" ฟรี

ใช้ gspread + Google Service Account · เซฟ/อ่านลง Sheet ได้เหมือน database

import gspread
gc = gspread.service_account(filename="credentials.json")
sh = gc.open("StudentBookings").sheet1

# อ่าน
rows = sh.get_all_records()         # list of dicts

# เพิ่ม row
sh.append_row(["2026-05-15", "Ploy", "CNC-1", "10:00"])

2) LINE Messaging API

ส่งแจ้งเตือนเข้า LINE ได้ฟรี · เหมาะกับโปรเจกต์นักศึกษาที่อยาก "demo"

def line_push(text):
    requests.post(
        "https://api.line.me/v2/bot/message/push",
        headers={"Authorization": f"Bearer {LINE_TOKEN}"},
        json={"to": USER_ID, "messages": [{"type": "text", "text": text}]}
    )

# ตัวอย่างใช้งาน
if pm25 > 100:
    line_push(f"⚠️ PM2.5 = {pm25} — สูงผิดปกติ")

3) Weather / Air Quality (IQAir, OpenWeatherMap)

def get_air_quality(city, state, country):
    r = requests.get(
        "https://api.airvisual.com/v2/city",
        params={"city": city, "state": state, "country": country, "key": IQAIR_KEY},
        timeout=10,
    )
    r.raise_for_status()    # error ถ้า status >= 400
    return r.json()["data"]["current"]["pollution"]["aqius"]

🔒 Web Security — ก่อน Deploy ขึ้น Internet

เมื่อ app ของเรา "เปิดให้คนอื่นเรียก" = ตกเป้าของ attacker ทั่วโลก · bot scraper · script kiddie · script ทดลอง · พวกนี้ทดสอบ "จุดอ่อนพื้นฐาน" ก่อนเสมอ · ทุก app ต้องป้องกัน 4 อย่างขั้นต่ำ

🛡 1. SQL Injection — ทำไมเราใช้ ? placeholder

ถ้าคุณ concat user input ใส่ SQL ตรง ๆ → attacker ใส่ SQL แทนข้อมูล → ลบ table หมด

# ❌ NEVER DO THIS — SQL injection vulnerability @app.route("/student/<name>") def get_student(name): # ❌ concat = bug sql = f"SELECT * FROM students WHERE name = '{name}'" cur.execute(sql) return cur.fetchall() # Attacker ใส่ใน URL: # /student/Ploy' OR '1'='1 # # → SELECT * FROM students WHERE name = 'Ploy' OR '1'='1' # → คืน "ทุกคน" · leak ข้อมูล! # # /student/'; DROP TABLE students; -- # → ลบ table หมด!
# ✅ ALWAYS — parameterized query @app.route("/student/<name>") def get_student(name): # ✅ ? placeholder — DB driver escape ให้ cur.execute( "SELECT * FROM students WHERE name = ?", (name,) ) return cur.fetchall() # Attacker ใส่: # /student/Ploy' OR '1'='1 # # → SQL ถูก escape: # WHERE name = 'Ploy'' OR ''1''=''1' # → คืน 0 row (ไม่มีคนชื่อนี้) — ปลอดภัย ✅
กฎเหล็ก — ใช้ ? placeholder เสมอ cur.execute(sql, params) ✅ · ไม่ใช่ cur.execute(f"... {x} ...") ❌ · ORM (SQLAlchemy/Django) ก็ทำให้อัตโนมัติ

🛡 2. XSS (Cross-Site Scripting) — Escape User Output

ถ้าคุณแสดง user input ใน HTML ตรง ๆ → attacker ใส่ <script> → รัน JS ในเครื่อง user คนอื่น

# ❌ Unsafe — echo input ดิบ
@app.route("/search")
def search():
    q = request.args.get("q")
    return f"<h1>ผลค้น: {q}</h1>"

# Attacker เปิด:
# /search?q=<script>steal_cookie()</script>
# → JS รันในทุก browser ที่เปิด link นี้!


# ✅ Safe — ใช้ template engine
from flask import render_template_string

@app.route("/search")
def search():
    q = request.args.get("q")
    # Jinja2 auto-escape < > & ' "
    return render_template_string("<h1>ผลค้น: {{ q }}</h1>", q=q)

# ✅ หรือ manual escape
from markupsafe import escape
return f"<h1>ผลค้น: {escape(q)}</h1>"

🛡 3. Password Hashing — ห้ามเก็บ Plaintext

ถ้า DB หลุด · ผู้โจมตีเห็น password ของทุก user → ใช้กับ Facebook/email/bank ของ user ทันที · ใช้ bcrypt เสมอ

# pip install bcrypt import bcrypt # ─── Register (เก็บ user) ─── def hash_password(password: str) -> bytes: """Hash password — เก็บใน DB""" salt = bcrypt.gensalt() return bcrypt.hashpw(password.encode(), salt) # ─── Login (ตรวจ password) ─── def verify_password(password: str, hashed: bytes) -> bool: return bcrypt.checkpw(password.encode(), hashed) # ─── ทดสอบ ─── user_password = "my_secret_pw_123" # ตอน register stored_hash = hash_password(user_password) print(f"Stored in DB: {stored_hash}") print(f" type: {type(stored_hash).__name__}, length: {len(stored_hash)}") # ตอน login — ลองหลายแบบ print(f"\nถูก: {verify_password('my_secret_pw_123', stored_hash)}") print(f"ผิด: {verify_password('wrong_password', stored_hash)}") print(f"ผิด: {verify_password('my_secret_pw_124', stored_hash)}") # 💡 สังเกต: ทุกครั้งที่ hash จะได้ output ต่างกัน · เพราะ salt random print(f"\nhash อีกครั้ง: {hash_password(user_password)}") print(f"hash อีกครั้ง: {hash_password(user_password)}") # → ต่างกัน · แต่ verify ทั้งคู่ได้ ✅
⚠️ ห้ามใช้ MD5 / SHA-256 ตรง ๆ สำหรับ password MD5/SHA = fast hash · attacker GPU crack ได้ใน 1 วัน · ใช้ bcrypt (slow by design) · Argon2 ยิ่งดี · libraries: bcrypt, passlib, argon2-cffi

🛡 4. CSRF (Cross-Site Request Forgery)

Attacker หลอก user login อยู่ → click link ที่ส่ง form ไปยัง app เรา → ทำ action ในนาม user (โอนเงิน, ลบ data)

# pip install flask-wtf
from flask_wtf.csrf import CSRFProtect

app = Flask(__name__)
app.config["SECRET_KEY"] = os.getenv("SECRET_KEY")  # random ใน .env
csrf = CSRFProtect(app)

# ตอนนี้ทุก POST/PUT/DELETE form ต้องมี CSRF token
# <form method="post">
#   {{ csrf_token() }}    ← inject token
#   ...
# </form>
# → Flask-WTF ตรวจ token ทุกครั้ง · ปลอม token ไม่ได้

🛡 5. Bonus — Headers ที่ต้องเปิด

from flask_talisman import Talisman

app = Flask(__name__)
Talisman(app)   # auto-set security headers

# Headers ที่ Talisman ตั้งให้:
# - Strict-Transport-Security  → บังคับ HTTPS
# - X-Content-Type-Options     → no MIME sniffing
# - X-Frame-Options            → ป้องกัน clickjacking
# - Content-Security-Policy    → จำกัด script source

📋 Security Checklist ก่อน Deploy

📌 Year 1 ทำไหวกี่ข้อ? ขั้นต่ำ 5 ข้อแรก = ป้องกัน 95% ของ attack ที่นักศึกษาจะเจอ · 5 ข้อหลังเป็น "production-grade" · เรียนเพิ่มเมื่อ deploy public จริง

🌐 สร้าง API ของตัวเอง — Flask

ติดตั้ง: pip install flask

# app.py
from flask import Flask, request, jsonify

app = Flask(__name__)

# in-memory store (เปลี่ยนเป็น SQLite/Postgres ใน production)
students = []
next_id = 1


@app.route("/students", methods=["GET"])
def list_students():
    return jsonify(students)


@app.route("/students", methods=["POST"])
def add_student():
    global next_id
    data = request.json
    if "name" not in data:
        return jsonify({"error": "missing name"}), 400

    new_student = {
        "id": next_id,
        "name": data["name"],
        "year": data.get("year", 1),
    }
    students.append(new_student)
    next_id += 1
    return jsonify(new_student), 201


@app.route("/students/<int:sid>", methods=["DELETE"])
def delete_student(sid):
    global students
    before = len(students)
    students = [s for s in students if s["id"] != sid]
    if len(students) == before:
        return jsonify({"error": "not found"}), 404
    return jsonify({"deleted": sid})


if __name__ == "__main__":
    app.run(debug=True, port=5000)

รัน: python app.py · เปิด browser ไป http://localhost:5000/students หรือใช้ curl:

curl -X POST http://localhost:5000/students \
  -H "Content-Type: application/json" \
  -d '{"name": "Ploy", "year": 1}'

curl http://localhost:5000/students
Streamlit เป็นทางเลือกที่ "ง่ายกว่า" ถ้าอยากแค่ทำ UI สำหรับ data analysis → Streamlit (W14 จะลงลึก) · ถ้าอยากให้ระบบอื่นเรียก → Flask

🐘 PostgreSQL — เมื่อ SQLite ไม่พอ

เมื่อไหร่ต้องเปลี่ยนจาก SQLite

ติดตั้ง PostgreSQL ใน Docker (ง่ายที่สุด)

docker run --name pg -e POSTGRES_PASSWORD=mysecret -p 5432:5432 -d postgres:16

เชื่อมจาก Python — psycopg2 หรือ SQLAlchemy

import psycopg2

conn = psycopg2.connect(
    host="localhost",
    port=5432,
    database="mydb",
    user="postgres",
    password=os.getenv("PG_PASSWORD"),
)
cur = conn.cursor()

cur.execute("SELECT * FROM students WHERE year = %s", (1,))
for row in cur.fetchall():
    print(row)

conn.close()

หรือใช้ SQLAlchemy (ORM) — เขียน Python class แทน SQL · จะลึกใน W14

SQLite → PostgreSQL ใช้ pandas ก็ได้

import pandas as pd
from sqlalchemy import create_engine

# อ่านจาก SQLite
src = create_engine("sqlite:///students.db")
df = pd.read_sql("SELECT * FROM students", src)

# เขียนไป Postgres
dst = create_engine("postgresql://postgres:mysecret@localhost:5432/mydb")
df.to_sql("students", dst, if_exists="replace", index=False)

🔁 Webhook vs Polling

2 แบบของการ "ฟังเหตุการณ์จากภายนอก":

PollingWebhook
วิธีเคาะถามทุก X วินาทีexternal ส่งมาเมื่อมี event
เร็วช้า (delay = interval)real-time
เปลืองเปลือง resource (เคาะแม้ไม่มีอะไร)ประหยัด
ทำง่ายง่าย (loop)ต้องมี public URL
ตัวอย่างดึง weather ทุก 5 นาทีLINE Webhook (user ส่งมา → server ได้ทันที)

🧪 Workshop — สร้าง "Smart Lab Monitor + AI Summarizer"

Tool ที่ใช้ 3 ประเภท API + LLM:

flowchart LR iqair[/IQAir API/] -->|GET พ.5/30 นาที| poller[poller.py
cron loop] poller --> db[(SQLite
history)] poller -->|ถ้า PM > 100| line[/LINE API/] db -->|ดึงล่าสุด| flask[Flask app
/status, /summary] db -->|รวม 24 ชม.| claude[/Claude API
Anthropic/] claude -->|สรุป + แนะนำ| flask user(["👤 user"]) -->|browser| flask classDef external fill:#3b2962,stroke:#8b5cf6,color:#fff classDef internal fill:#1e3a5f,stroke:#3776ab,color:#fff classDef db fill:#3d2c1a,stroke:#ffd43b,color:#fff class iqair,line,claude external class poller,flask internal class db db
  1. สมัคร 3 บริการ + เก็บ key ใน .env — IQAir · LINE Messaging · Anthropic Claude · gitignore .env เสมอ
  2. เขียน fetch_aqi(city) — GET IQAir · timeout=10 · raise_for_status · คืน dict
  3. SQLite schema + save_reading(data) — table reading(ts, aqi, temp, humidity)
  4. line_alert(text) — POST LINE Messaging API
  5. เขียน poller.py — loop: fetch → save → if > 100 alert · sleep 1800
  6. 🤖 เขียน summarize_today() — query 24 ชม. ล่าสุด → ส่งให้ Claude พร้อม prompt: "สรุป PM2.5 24 ชม. + แนะนำ ภาษาไทย กระชับ" · cache 1 ชั่วโมง
  7. 🌐 Flask app/status = JSON ล่าสุด · /summary = AI summary ของวันนี้
  8. requirements.txt + .env.example + README — sequence diagram (W03) อธิบาย flow
  9. commit + push GitHub — ห้าม commit .env
💸 ระวัง LLM cost — ใช้ Haiku + cache ใช้ claude-haiku-4-5-20251001 ($0.25/M input) · เรียก /summary แค่ 1-2 ครั้ง/วัน · cache ผลลัพธ์ใน SQLite · cost < $0.01/เดือน

💡 ข้อควรระวังกับ API

ไม่ใส่ timeout — โปรแกรมแฮงค์ ถ้า API ค้าง ไม่มี timeout= = แฮงค์ตลอดไป · ใส่ timeout=10 ทุกครั้ง
ไม่ handle error — crash API down ได้ตลอด · ครอบด้วย try/except + retry logic
เกิน rate limit (429) ส่วนใหญ่ free tier จำกัด 100-1000 request/วัน · เก็บ cache, ไม่เรียกถี่
Commit .env — ครั้งหนึ่งใน git history = ตลอดกาล · ต้อง rotate key ทันที + ใช้ git-secrets ป้องกัน

ส่งงานสัปดาห์นี้

Reference จาก slide เดิม

เนื้อหานี้ ไม่อยู่ใน slide เดิม — เป็น Mainidea Foundation 17 (Industrial Communication) + 13 (System Composition) ที่ขยายเข้าสู่งานจริง