Conditional GET สำหรับ AI Agent: ใช้ ETag และ 304 อ่านข้อมูลเดิมอย่างมีเงื่อนไข

ภาพจำลองการตรวจ If-None-Match: เมื่อ ETag v1 ตรงกัน เซิร์ฟเวอร์ตอบ 304 และเอเจนต์นำ body เดิมที่จับคู่ไว้กลับมาใช้

คำตอบสั้น: 304 ให้ใช้ body เดิมได้เมื่อจับคู่ validator ถูกต้อง

Conditional GET คือการอ่าน resource พร้อมส่ง validator ที่ได้จากการอ่านครั้งก่อน เพื่อถาม origin ว่า representation ที่ client เก็บไว้ยังตรงกับปัจจุบันหรือไม่ บทเรียนนี้ใช้ ETag เป็น validator: เมื่อมี entry ที่ key ตรงกัน client ส่ง If-None-Match แล้วรับผลจำลองเป็น 304 จึงนำ body เดิมของ entry นั้นกลับมาใช้ได้ หากได้ 200 ให้แทนที่ body และ ETag เป็นคู่เดียวกัน ส่วน ERROR ยังคงเป็น error และไม่แปลงเป็นผลสำเร็จจาก cache โดยอัตโนมัติ

กฎใช้งานที่ต้องจำมีสามข้อ: เก็บ body กับ ETag ด้วยกันภายใต้ key ของ representation เดียวกัน; 304 ใช้ body ที่ client มีอยู่แล้ว ไม่ได้ส่ง body ใหม่มาให้; และการไม่มี entry ที่ตรงกันทำให้ 304 ใช้ต่อไม่ได้ ตัวอย่างทั้งหมดเป็น hand trace ของ response สังเคราะห์ ไม่ส่ง HTTP request ไม่ได้วัดความเร็ว และไม่ยืนยันว่า origin หรือ SDK ใดรองรับพฤติกรรมนี้

ภาพจำลองการตรวจ If-None-Match: เมื่อ ETag v1 ตรงกัน เซิร์ฟเวอร์ตอบ 304 และเอเจนต์นำ body เดิมที่จับคู่ไว้กลับมาใช้
304 ไม่ได้แปลว่าไม่มีข้อมูล — ใน mock นี้ body เดิมใช้ได้เมื่อ ETag ตรงกับคู่ที่เก็บไว้ — ภาพอธิบายแบบจำลองของผู้เขียน ไม่มี HTTP call หรือผลวัดจริง; exact strong-tag subset มิใช่ HTTP cache เต็มรูปแบบ · คลิกภาพอ่านขนาดเต็ม

ผลลัพธ์การเรียนรู้

เมื่อจบบทเรียน คุณอธิบาย 200, 304 และ ERROR ได้ วาง body กับ validator ใน entry เดียวกัน แยก key ตาม URI, trusted view และภาษา และไล่ trace การแทนที่ ETag โดยระบุสิ่งที่ mock ยังไม่ได้จำลอง

Conditional GET อาจช่วยไม่ต้องรับ body ซ้ำเมื่อ origin รองรับ แต่ไม่มีการวัดปริมาณข้อมูล เวลา หรือค่าใช้จ่ายที่ประหยัดได้

ปัญหา: body ว่างไม่ได้แปลว่าผลลัพธ์ว่าง

สมมติ agent อ่านคู่มือภาษาไทยได้ body A และเก็บไว้ จากนั้นต้องอ่าน URL เดิมอีกครั้ง ถ้า adapter มอง response 304 เป็นผลค้นหาว่าง agent อาจสรุปว่าคู่มือไม่มีเนื้อหา ทั้งที่ 304 เป็นสัญญาณให้ใช้ representation ที่ client มีอยู่ตามเงื่อนไข การออกแบบต้องเชื่อม response กลับไปยัง entry ที่ถูกตรวจ ไม่ใช่หยิบ body ใดก็ได้จาก cache

ความผิดอีกแบบคือเก็บ tag แยกจาก body หรือ key ทุกภาษาไว้ช่องเดียวกัน เมื่อคำขอภาษาอังกฤษได้ response 304 ที่มี tag เหมือนกับรายการภาษาไทย ระบบอาจนำข้อความไทยไปตอบคำถามอังกฤษ Tag ที่ดูเหมือนกันไม่ใช่หลักฐานว่า representation เป็นชิ้นเดียวกัน และ ETag เป็นค่าทึบแสงสำหรับระบุ validator ไม่ใช่สิทธิ์เข้าถึง ไม่ใช่ hash ที่ระบบต้องตีความ และไม่ใช่ตัวนับ revision สากล

จากความจำของ LLM สู่ cache ของ adapter

โมเดลภาษาไม่มีความทรงจำที่เชื่อถือได้เกี่ยวกับ response ก่อนหน้าเพียงเพราะข้อความเคยอยู่ในบริบท การจำ body ที่อ่านแล้วเป็นหน้าที่ของ adapter หรือชั้นจัดเก็บที่ระบบออกแบบไว้ Conditional GET เพิ่มขั้นตรวจความใหม่ก่อนนำ representation ที่เก็บไว้มาใช้ จึงเป็นกลไกของเส้นทางข้อมูล ไม่ใช่ความสามารถที่เกิดขึ้นเองจาก prompt

ภาพจำคือบัตรสองใบผูกกัน: ใบหนึ่งเป็น body ที่อ่านได้ อีกใบเป็น ETag ที่ origin เคยให้ ทั้งคู่ต้องอยู่ในซองเดียวกับ key ที่บอกว่าเป็น endpoint, trusted view และ variant ภาษาใด เมื่อขอซ้ำให้ส่ง validator จากซองนั้น หากผลตอบกลับเป็น 304 ที่ตรงกับซอง จึงคืน body จากซอง ถ้าได้ 200 ให้แทนที่สองใบพร้อมกัน หากเกิด error ให้ส่ง error ตาม contract ของ adapter

ศัพท์ HTTP ที่ต้องแยกให้ออก

ETag เป็น validator ที่ผูกกับ selected representation ส่วน If-None-Match เป็น request header ที่ใช้เงื่อนไขว่ามี tag ใดตรงกับ representation ปัจจุบันหรือไม่ ใน HTTP semantics เต็มรูปแบบ If-None-Match ใช้ weak comparison และการตรงกันใน GET หรือ HEAD นำไปสู่ 304 ตามเงื่อนไขของมาตรฐาน เมื่อไม่มีการจับคู่ origin อาจส่ง representation เต็มเป็น 200 บทเรียนนี้สรุปเฉพาะแนวคิดที่จำเป็น; รายละเอียด protocol ให้ยึด RFC 9110 และ RFC 9111

304 ไม่มีเนื้อหา response ให้ใช้แทน body เดิม ฝั่ง cache ต้องหา stored response ที่เหมาะสม และใน HTTP caching มาตรฐานอาจปรับ metadata ของ stored response ด้วยข้อมูล header จาก 304 ตัวอย่างจำลองนี้ตั้งกติกาแคบกว่า: response 304 ต้องมี ETag ใน fixture ซึ่งตรงแบบ exact equality กับ ETag ที่ส่งและกับ entry ปัจจุบันก่อน reuse นี่เป็นข้อกำหนดของ mock ไม่ใช่ข้อบังคับทั่วไปของ HTTP

อย่าสับสน If-None-Match กับ If-Match ตัวแรกเกี่ยวกับการตรวจเงื่อนไขเมื่ออ่านซ้ำ ส่วนตัวหลังเป็น precondition อีกแบบที่ใช้กับการดำเนินการซึ่งต้องตรวจ validator ก่อนทำต่อ เช่นการป้องกันการเขียนทับข้อมูลที่เปลี่ยนไป บทเรียนนี้เป็น read-only GET trace จึงไม่สาธิตการเขียน การอนุมัติ หรือ lost-update workflow

แผนภาพ GET แบบมีเงื่อนไข: 200 เก็บ body กับ ETag เป็นคู่, 304 ที่ตรงกับคู่เดิมใช้ body เดิม, ส่วน ERROR หรือ 304 ที่จับคู่ไม่ได้คืน error
สรุปทางแยกของ mock: ต้องตรวจคู่ body/ETag ก่อน reuse และห้ามใช้ body เก่าเมื่อจับคู่ไม่ได้ — ภาพอธิบายแบบจำลองของผู้เขียน ไม่มี HTTP call หรือผลวัดจริง; exact strong-tag subset มิใช่ HTTP cache เต็มรูปแบบ · คลิกภาพอ่านขนาดเต็ม

สถาปัตยกรรมตัวอย่างและขอบเขต

fixture ใช้ alias a_th, a_en, b_th แทน key ที่ประกอบจาก GET, URI ตัวอย่าง, trusted view A/B และภาษาไทย/อังกฤษ alias เป็นเพียง scope สำหรับสาธิต ไม่ใช่หลักฐานยืนยันตัวตนหรือสิทธิ์ ในระบบจริง trusted scope ต้องมาจาก context ที่ตรวจสอบแล้ว

input เป็น JSON array 1–32 event แต่ละตัวมี key, status, etag, body และต้องตรวจ schema ทั้งชุดก่อน mutation key ต้องเป็น alias ที่กำหนด; status คือ 200, 304 หรือ ERROR; 200 ต้องมี strong quoted ASCII ETag ตาม subset และ body ไม่เกิน 256 ตัวอักษร; 304 มี tag และ body null; ERROR มี etag/body null การตรวจล่วงหน้าป้องกัน trace ครึ่งชุดจาก input ที่ผิดท้ายรายการ

ก่อนแต่ละ event ให้อ่าน snapshot ของ entry ตาม key แล้วใช้ tag ใน snapshot เป็น sent_if_none_match หรือ null เมื่อไม่มี entry response เป็น fixture ไม่ใช่ server จริง 200 แทน entry เป็น body+ETag คู่ใหม่; 304 ต้องมี snapshot และ tag ตรงกับที่ส่ง จึงคืน REUSED พร้อม body เดิม มิฉะนั้นเป็น CACHE_STATE_ERROR; ERROR คืน body null คง entry แต่ไม่ serve stale โดยปริยาย

key นี้ชี้ความเสี่ยงจากการปะปน representation ไม่ใช่อัลกอริทึม cache key สมบูรณ์ ระบบจริงต้องกำหนด URI normalization, header ที่เปลี่ยน representation เช่น Vary, authorization และ cache directives ตาม contract ของตน บทเรียนไม่จำลอง parser หรือกลไกเหล่านั้น

Worked example: ไล่ response สังเคราะห์ 8 เหตุการณ์

ก่อนเริ่ม ให้มอง cache ว่าง แล้วพิจารณา event ตามลำดับ ตารางนี้เป็นผลคาดหมายจาก architecture fixture: ไม่มี server comparison, network, clock หรือ concurrency ทุกแถวอาศัย state ที่เกิดจากแถวก่อนหน้า และชื่อ A/B เป็น label สำหรับ key ไม่ใช่ identity credential

เหตุการณ์ 1 อ่าน a_th ได้ 200, ETag "v1" และ body คู่มือ A รุ่น 1 จึง STORED เป็นคู่ใน key ภาษาไทยของ view A เหตุการณ์ 2 ใช้ key เดิมและ fixture ตอบ 304 กับ "v1"; request ส่ง If-None-Match: "v1" จึง REUSED body รุ่น 1 จาก entry ที่มีอยู่ โดย response ไม่มี body ใหม่

เหตุการณ์ 3 ใช้ key เดิมแต่ได้ 200 กับ "v2" และ body คู่มือ A รุ่น 2 จึง STORED โดยแทน body และ tag พร้อมกัน การแทนที่เป็นคู่สำคัญ: ถ้าเขียน tag ใหม่แต่เหลือ body เก่า หรือกลับกัน entry จะบอกความสัมพันธ์ระหว่าง representation กับ validator ผิด เหตุการณ์ 4 ใช้ key a_en ซึ่งยังไม่มี entry แม้ fixture ให้ 304 กับ "v2"; request จึงไม่มี tag ที่ส่งและไม่มี body ที่ยืนยันไว้ ผลคือ CACHE_STATE_ERROR ไม่ยืม body ภาษาไทยมาเติม

เหตุการณ์ 5 ได้ 200 สำหรับ b_th พร้อม tag "v1" และ body ของ view B จึง STORED แยกจาก A การที่ข้อความ tag เท่ากันไม่ได้รวม entry คนละ key เหตุการณ์ 6 กลับมาที่ a_th แล้วได้ ERROR; ส่ง tag ปัจจุบัน "v2" ในคำขอจำลอง คืนผล ERROR และ body null โดยคงคู่รุ่น 2 ใน cache แต่ไม่ส่ง body เดิมให้ผู้เรียกเป็นความสำเร็จ

เหตุการณ์ 7 กลับมาที่ a_th แล้ว fixture ตอบ 304 พร้อม "v1" แม้ entry ปัจจุบันเป็นรุ่น 2 และ request ส่ง "v2" จึง CACHE_STATE_ERROR; ห้ามคืนรุ่น 1 ที่ไม่ได้ผูกกับ validator ปัจจุบัน เหตุการณ์ 8 ตอบ 304 กับ "v2" ตรงกับ entry และ tag ที่ส่ง จึง REUSED body รุ่น 2 ผลเรียงตาม fixture คือ STORED, REUSED, STORED, CACHE_STATE_ERROR, STORED, ERROR, CACHE_STATE_ERROR, REUSED

การ trace นี้แสดงคุณสมบัติที่ตรวจด้วยมือได้สามอย่าง: 304 ต้องมี body ที่เก็บตรง key อยู่ก่อน; 200 ทำให้ body กับ tag เปลี่ยนเป็นรุ่นเดียวกัน; และ error ไม่ใช่ 304 และไม่กลายเป็น stale hit ส่วนรายละเอียด header ของ response, weak comparison, tag list, freshness หรือการเลือก stored response แบบ RFC ไม่ได้ปรากฏใน trace นี้

ลำดับkeyresponse fixtureผลbody ที่ออกได้
1a_th200 / "v1"STOREDคู่มือ A รุ่น 1
2a_th304 / "v1"REUSEDคู่มือ A รุ่น 1
3a_th200 / "v2"STOREDคู่มือ A รุ่น 2
4a_en304 / "v2"CACHE_STATE_ERRORnull
5b_th200 / "v1"STOREDคู่มือ B รุ่น 1
6a_thERRORERRORnull
7a_th304 / "v1"CACHE_STATE_ERRORnull
8a_th304 / "v2"REUSEDคู่มือ A รุ่น 2
แผนภาพ 8 เหตุการณ์สังเคราะห์: a_th เก็บ v1, reuse v1, แทนที่เป็น v2; a_en ที่ไม่มีคู่เกิด CACHE_STATE_ERROR; b_th เก็บ v1; ERROR ไม่เสิร์ฟ body; v1 ที่ไม่ตรงถูกปฏิเสธ และ v2 ที่ตรง reuse ได้
ไทม์ไลน์นี้ถอดลำดับ fixture ทั้ง 8 เหตุการณ์ ไม่มีค่าประสิทธิภาพหรือการวัดเวลา — ภาพอธิบายแบบจำลองของผู้เขียน ไม่มี HTTP call หรือผลวัดจริง; exact strong-tag subset มิใช่ HTTP cache เต็มรูปแบบ · คลิกภาพอ่านขนาดเต็ม

จุดพลาดและสิ่งที่ตัวอย่างไม่ครอบคลุม

อย่าตีความ 304 ว่าเป็นผลค้นหาว่างหรือ 200 ที่มี body ว่าง: ต้องมี stored representation ที่ถูกตรวจแล้วจึง reuse ได้ อย่าค้นด้วย key กว้างจนข้ามภาษา, URI หรือ trusted view และอย่าแยกการอัปเดต body กับ validator จน entry มี state ปะปน

ตัวอย่างเลือกให้ ERROR คง entry แต่ไม่คืน stale body นี่เป็น policy ของบทเรียน ไม่ใช่กฎ HTTP หรือข้อห้าม stale-if-error หากระบบต้องการ fallback ต้องกำหนด freshness, sensitivity, authorization และการสื่อสารความเก่าเป็นนโยบายแยก

mock ไม่ครอบคลุม Cache-Control, freshness, Age, Vary algorithm, authorization cache directives, weak ETag, tag list, metadata merge ของ 304, concurrency, race, origin support หรือ SDK compatibility จึงช่วยฝึก invariant ได้ แต่ไม่ใช่หลักฐานว่า adapter จริงทำงานถูกต้อง

แผนภาพขอบเขต cache key ที่ประกอบด้วย GET, URI, trusted view และภาษา พร้อมช่องแยก a_th v2, a_en ที่ไม่มีคู่ และ b_th v1; ETag ใช้ตรวจ representation ไม่ใช่สิทธิ์เข้าถึง
key ต้องแยก representation ตาม method, URI, trusted view และภาษา; ETag ไม่ใช่หลักฐานอนุญาตเข้าถึง — ภาพอธิบายแบบจำลองของผู้เขียน ไม่มี HTTP call หรือผลวัดจริง; exact strong-tag subset มิใช่ HTTP cache เต็มรูปแบบ · คลิกภาพอ่านขนาดเต็ม

แนวทางแยก pure decision logic ออกจาก transport

แยก logic ที่รับ key, snapshot และ typed response ออกจาก transport ที่ส่ง GET จริง ตรวจสิทธิ์ และแปลง response ตามมาตรฐาน การแยกช่วยให้ hand trace ไม่แอบอ้างการทดสอบ HTTP client

ตรวจ schema ทั้งชุดและ invariant body+ETag ก่อนเปลี่ยน state; 200 แทน entry ทั้งก้อน; 304 คืน body เมื่อมี snapshot ที่ส่ง validator นั้นและ fixture ตรง contract เท่านั้น Exact equality ทำให้ mock deterministic แต่ไม่ใช่ parser ของ If-None-Match เต็มตาม RFC

ในระบบจริงอย่าเชื่อ trusted view ที่ผู้เรียกส่งเอง ต้องตรวจ authorization context และแยก variant ตาม representation ที่ server เลือก URI normalization หรือ key ที่ไม่ครบอาจรวม resource/ภาษาเข้าด้วยกัน รายละเอียดเหล่านี้อยู่นอก mock และต้องระบุ contract ก่อนเลือก client หรือ storage

มี source code, input, expected output, README และ acceptance tests ให้คัดลอกด้านล่าง ใช้ Python 3.11+ standard library โดยไม่ติดตั้ง dependency และไม่มี environment variable/secret ตัวอย่างอ่าน JSON จาก stdin แล้วเขียนผล stdout; ไม่เขียนไฟล์หรือส่ง network request โค้ดและ tests เขียนแล้วแต่ยัง NOT RUN ไม่ได้ import หรือ compile และยังไม่ใช่ production adapter

conditional_get_mock.py — Mock / NOT RUN

"""Deterministic synthetic model of one narrow conditional-GET cache flow.

This is an educational mock, not an HTTP client or a complete HTTP cache.
"""
from __future__ import annotations

from dataclasses import dataclass
import json
import re
import sys
from typing import Any


MAX_INPUT_BYTES = 64 * 1024
MAX_EVENTS = 32
MAX_BODY_CHARS = 256
TAG_PATTERN = re.compile(r'"[A-Za-z0-9._-]{1,32}"', re.ASCII)
EVENT_KEYS = frozenset({"key", "status", "etag", "body"})

# These fixed aliases stand for exact representation keys:
# (GET, fixed URI, trusted-view partition, language variant).
# The aliases are examples only and do not establish real authorization.
KEYS = {
    "a_th": ("GET", "https://example.invalid/assets/guide", "A", "th"),
    "a_en": ("GET", "https://example.invalid/assets/guide", "A", "en"),
    "b_th": ("GET", "https://example.invalid/assets/guide", "B", "th"),
}


class InvalidInput(ValueError):
    """Input does not satisfy the mock's deliberately narrow schema."""


@dataclass(frozen=True)
class Entry:
    body: str
    etag: str


def _valid_tag(value: Any) -> bool:
    return type(value) is str and TAG_PATTERN.fullmatch(value) is not None


def _validate_events(events: Any) -> list[dict[str, Any]]:
    """Validate the whole array before simulate creates or mutates cache state."""
    if type(events) is not list or not 1 <= len(events) <= MAX_EVENTS:
        raise InvalidInput("events must be an array with 1 to 32 items")

    validated: list[dict[str, Any]] = []
    for index, event in enumerate(events):
        if type(event) is not dict or frozenset(event.keys()) != EVENT_KEYS:
            raise InvalidInput(f"event {index} must have exactly key,status,etag,body")
        if type(event["key"]) is not str or event["key"] not in KEYS:
            raise InvalidInput(f"event {index} has an unknown key alias")

        status = event["status"]
        etag = event["etag"]
        body = event["body"]

        # bool is an int subclass in Python; require an exact integer status.
        if type(status) is int and status == 200:
            if not _valid_tag(etag):
                raise InvalidInput(f"event {index} has an invalid strong ASCII ETag")
            if type(body) is not str or len(body) > MAX_BODY_CHARS:
                raise InvalidInput(f"event {index} body must be text of at most 256 characters")
        elif type(status) is int and status == 304:
            if not _valid_tag(etag) or body is not None:
                raise InvalidInput(f"event {index} has an invalid 304 shape")
        elif type(status) is str and status == "ERROR":
            if etag is not None or body is not None:
                raise InvalidInput(f"event {index} has an invalid ERROR shape")
        else:
            raise InvalidInput(f"event {index} status must be integer 200, integer 304, or ERROR")

        # Copy the validated mapping so later processing uses only checked values.
        validated.append({
            "key": event["key"],
            "status": status,
            "etag": etag,
            "body": body,
        })
    return validated


def simulate(events: Any) -> list[dict[str, Any]]:
    """Return one output record per event; all events are validated first."""
    validated = _validate_events(events)
    cache: dict[str, Entry] = {}
    output: list[dict[str, Any]] = []

    for event in validated:
        key = event["key"]
        status = event["status"]
        response_etag = event["etag"]
        response_body = event["body"]
        previous = cache.get(key)
        sent_if_none_match = previous.etag if previous is not None else None

        if status == 200:
            # Replace body and validator together as one immutable entry.
            cache[key] = Entry(body=response_body, etag=response_etag)
            outcome = "STORED"
            body = response_body
        elif status == 304:
            # A 304 is bodyless. Reuse only the matching stored representation.
            if previous is None or sent_if_none_match is None or response_etag != sent_if_none_match:
                outcome = "CACHE_STATE_ERROR"
                body = None
            else:
                outcome = "REUSED"
                body = previous.body
        else:
            # Errors never become 304 and never serve stale content implicitly.
            outcome = "ERROR"
            body = None

        output.append({
            "key": key,
            "sent_if_none_match": sent_if_none_match,
            "status": status,
            "outcome": outcome,
            "body": body,
        })
    return output


def _unique_object(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
    """Reject duplicate JSON object keys instead of silently taking the last value."""
    result: dict[str, Any] = {}
    for key, value in pairs:
        if key in result:
            raise InvalidInput("duplicate JSON object key")
        result[key] = value
    return result


def main() -> int:
    """Read one bounded JSON document from stdin and emit JSON to stdout."""
    raw = sys.stdin.buffer.read(MAX_INPUT_BYTES + 1)
    if len(raw) > MAX_INPUT_BYTES:
        print("Invalid input; expected a valid conditional GET event array.", file=sys.stderr)
        return 2
    try:
        events = json.loads(raw.decode("utf-8"), object_pairs_hook=_unique_object)
        result = simulate(events)
    except (UnicodeDecodeError, json.JSONDecodeError, InvalidInput, RecursionError):
        print("Invalid input; expected a valid conditional GET event array.", file=sys.stderr)
        return 2

    sys.stdout.write(json.dumps(result, ensure_ascii=False, separators=(",", ":")) + "\n")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

เกณฑ์ตรวจด้วย hand trace และสถานะการทดสอบ

ตรวจแต่ละ event ด้วย state ก่อนหน้า, key, tag ที่ส่ง, fixture, outcome, body และ state หลัง event: 1 สร้าง a_th; 2 reuse รุ่น 1; 3 แทนเป็นรุ่น 2; 4 ปฏิเสธ 304 ของ a_en ที่ไม่มี entry; 5 เก็บ b_th แยก; 6 ERROR ไม่ serve stale; 7 ปฏิเสธ tag รุ่น 1; 8 reuse รุ่น 2

เกณฑ์ schema: key ไม่รู้จัก, status/field ผิด, tag ผิดรูป, body เกินขนาดหรือชนิดไม่ถูกต้อง ต้องปฏิเสธทั้ง array ก่อน mutation ไม่สร้างผลบางแถวแล้วค่อยตรวจพบความผิดท้ายชุด นี่เป็นเกณฑ์ให้เขียน tests ในอนาคต ไม่ใช่ผลรัน test มี unit tests ที่เขียนไว้ 11 กรณี ครอบคลุมแกนการเก็บ/ใช้ซ้ำ/แทนคู่ การแยก key ข้อผิดพลาดและ schema บางขอบเขต แต่ยังไม่มี CLI integration test หรือการครอบคลุมทุกค่าขอบบน อย่าอ้างว่าจำนวนกรณีแปลว่าระบบผ่านแล้ว

สถานะ: fixture สังเคราะห์และ trace เขียนด้วยมือ ไม่มี HTTP/API, import, compile, execute หรือ test run; code/tests = NOT RUN และ Lab UI = UNKNOWN จนกว่าจะตรวจจริง การอ่าน RFC ไม่เปลี่ยนสถานะทดสอบ implementation

Input จำลอง

[
  {"key":"a_th","status":200,"etag":"\"v1\"","body":"คู่มือ A รุ่น 1"},
  {"key":"a_th","status":304,"etag":"\"v1\"","body":null},
  {"key":"a_th","status":200,"etag":"\"v2\"","body":"คู่มือ A รุ่น 2"},
  {"key":"a_en","status":304,"etag":"\"v2\"","body":null},
  {"key":"b_th","status":200,"etag":"\"v1\"","body":"คู่มือ B รุ่น 1"},
  {"key":"a_th","status":"ERROR","etag":null,"body":null},
  {"key":"a_th","status":304,"etag":"\"v1\"","body":null},
  {"key":"a_th","status":304,"etag":"\"v2\"","body":null}
]

Expected output — คำนวณด้วยมือ ไม่ใช่ผลรัน

[
  {"key":"a_th","sent_if_none_match":null,"status":200,"outcome":"STORED","body":"คู่มือ A รุ่น 1"},
  {"key":"a_th","sent_if_none_match":"\"v1\"","status":304,"outcome":"REUSED","body":"คู่มือ A รุ่น 1"},
  {"key":"a_th","sent_if_none_match":"\"v1\"","status":200,"outcome":"STORED","body":"คู่มือ A รุ่น 2"},
  {"key":"a_en","sent_if_none_match":null,"status":304,"outcome":"CACHE_STATE_ERROR","body":null},
  {"key":"b_th","sent_if_none_match":null,"status":200,"outcome":"STORED","body":"คู่มือ B รุ่น 1"},
  {"key":"a_th","sent_if_none_match":"\"v2\"","status":"ERROR","outcome":"ERROR","body":null},
  {"key":"a_th","sent_if_none_match":"\"v2\"","status":304,"outcome":"CACHE_STATE_ERROR","body":null},
  {"key":"a_th","sent_if_none_match":"\"v2\"","status":304,"outcome":"REUSED","body":"คู่มือ A รุ่น 2"}
]

Acceptance tests — เขียนไว้ ยัง NOT RUN

"""Meaningful contract tests for the local conditional-GET mock.

These tests are authored but were NOT RUN for this task.
"""
import unittest

from conditional_get_mock import InvalidInput, _unique_object, simulate


class ConditionalGetMockTests(unittest.TestCase):
    def test_initial_200_stores_representation_and_has_no_validator(self):
        result = simulate([{
            "key": "a_th", "status": 200, "etag": '"v1"', "body": "guide v1"
        }])
        self.assertEqual(result, [{
            "key": "a_th", "sent_if_none_match": None, "status": 200,
            "outcome": "STORED", "body": "guide v1"
        }])

    def test_valid_304_reuses_body_and_sends_stored_validator(self):
        result = simulate([
            {"key": "a_th", "status": 200, "etag": '"v1"', "body": "guide v1"},
            {"key": "a_th", "status": 304, "etag": '"v1"', "body": None},
        ])
        self.assertEqual(result[1]["sent_if_none_match"], '"v1"')
        self.assertEqual(result[1]["outcome"], "REUSED")
        self.assertEqual(result[1]["body"], "guide v1")

    def test_changed_200_replaces_body_and_validator(self):
        result = simulate([
            {"key": "a_th", "status": 200, "etag": '"v1"', "body": "old"},
            {"key": "a_th", "status": 200, "etag": '"v2"', "body": "new"},
            {"key": "a_th", "status": 304, "etag": '"v2"', "body": None},
        ])
        self.assertEqual(result[2]["sent_if_none_match"], '"v2"')
        self.assertEqual(result[2]["body"], "new")

    def test_representation_keys_are_isolated_by_view_and_language(self):
        result = simulate([
            {"key": "a_th", "status": 200, "etag": '"v1"', "body": "Thai A"},
            {"key": "a_en", "status": 304, "etag": '"v1"', "body": None},
            {"key": "b_th", "status": 304, "etag": '"v1"', "body": None},
        ])
        self.assertIsNone(result[1]["sent_if_none_match"])
        self.assertIsNone(result[2]["sent_if_none_match"])
        self.assertEqual([result[1]["outcome"], result[2]["outcome"]],
                         ["CACHE_STATE_ERROR", "CACHE_STATE_ERROR"])

    def test_error_is_not_304_and_does_not_serve_cached_body(self):
        result = simulate([
            {"key": "a_th", "status": 200, "etag": '"v1"', "body": "cached"},
            {"key": "a_th", "status": "ERROR", "etag": None, "body": None},
            {"key": "a_th", "status": 304, "etag": '"v1"', "body": None},
        ])
        self.assertEqual(result[1]["outcome"], "ERROR")
        self.assertIsNone(result[1]["body"])
        self.assertEqual(result[2]["outcome"], "REUSED")

    def test_304_without_cache_or_with_mismatched_validator_fails_closed(self):
        result = simulate([
            {"key": "a_th", "status": 304, "etag": '"v1"', "body": None},
            {"key": "a_th", "status": 200, "etag": '"v1"', "body": "body"},
            {"key": "a_th", "status": 304, "etag": '"v2"', "body": None},
            {"key": "a_th", "status": 304, "etag": '"v1"', "body": None},
        ])
        self.assertEqual(result[0]["outcome"], "CACHE_STATE_ERROR")
        self.assertEqual(result[2]["outcome"], "CACHE_STATE_ERROR")
        self.assertIsNone(result[2]["body"])
        self.assertEqual(result[3]["outcome"], "REUSED")
        self.assertEqual(result[3]["body"], "body")

    def test_schema_is_validated_for_entire_array_before_results_are_returned(self):
        events = [
            {"key": "a_th", "status": 200, "etag": '"v1"', "body": "valid prefix"},
            {"key": "a_th", "status": 200, "etag": "W/\"weak\"", "body": "invalid final event"},
        ]
        with self.assertRaises(InvalidInput):
            simulate(events)

    def test_rejects_duplicate_json_object_keys(self):
        with self.assertRaises(InvalidInput):
            _unique_object([("status", 200), ("status", 304)])

    def test_rejects_bool_status_and_unknown_or_extra_fields(self):
        for event in [
            {"key": "a_th", "status": True, "etag": '"v1"', "body": "x"},
            {"key": "a_th", "status": 200, "etag": '"v1"', "body": "x", "extra": 1},
            {"key": "unknown", "status": 200, "etag": '"v1"', "body": "x"},
        ]:
            with self.subTest(event=event):
                with self.assertRaises(InvalidInput):
                    simulate([event])

    def test_input_array_and_body_bounds(self):
        valid = {"key": "a_th", "status": 200, "etag": '"v1"', "body": "x"}
        with self.assertRaises(InvalidInput):
            simulate([])
        with self.assertRaises(InvalidInput):
            simulate([valid] * 33)
        oversized = dict(valid, body="x" * 257)
        with self.assertRaises(InvalidInput):
            simulate([oversized])

    def test_rejects_non_ascii_or_overlong_tags(self):
        for tag in ['"ไทย"', '"' + ("a" * 33) + '"', 'W/"v1"']:
            event = {"key": "a_th", "status": 200, "etag": tag, "body": "x"}
            with self.subTest(tag=tag):
                with self.assertRaises(InvalidInput):
                    simulate([event])


if __name__ == "__main__":
    unittest.main()

README — Runtime / วิธีใช้ / สิทธิ์ / ข้อจำกัด

# Conditional GET mock

This folder contains a small, deterministic Python mock for one read-only HTTP representation-validation flow. It is an educational state trace. It does not send HTTP requests, implement an Agent, or measure transfer savings or performance. The mock is implemented; provider Sandbox and Live modes are not implemented.

## Requirements

- Python 3.11 or newer.
- Python standard library only; no package installation or network access.
- Runtime permissions are limited to local stdin and stdout: the example reads one JSON document and writes one JSON result. It does not write files or access the network.
- No environment variables, API keys, credentials, or service accounts.
- Resource and security protections are limited to the stated input bounds and schema checks; this small mock is not a complete security boundary.

## Input and output

The fixed aliases are:

- `a_th`: GET `https://example.invalid/assets/guide`, trusted-view partition A, Thai variant.
- `a_en`: the same resource and view, English variant.
- `b_th`: the same resource and language, trusted-view partition B.

These labels illustrate representation scoping only. They are not authentication or authorization. A production adapter must derive identity and permission from trusted state and authorize the caller independently.

Each input event has exactly four fields: `key`, `status`, `etag`, and `body`. The input is a JSON array of 1–32 events. Status is integer 200, integer 304, or string `ERROR`; booleans are not accepted as integers. The whole array is validated before the mock creates or changes cache state. The CLI also rejects duplicate JSON object keys rather than silently choosing one. Body text is limited to 256 characters; ETags are limited to a strong quoted ASCII subset.

For a first synthetic 200, the mock stores body and ETag together. A later 304 is bodyless and may reuse a body only if the exact alias already has the same cached ETag. A 200 replaces the pair. An error returns `ERROR`, keeps any cache entry, and does not serve stale data. A 304 without a matching stored representation returns `CACHE_STATE_ERROR`.

The mock uses exact tag equality for a deliberately narrow strong-tag subset. RFC 9110 specifies weak comparison for `If-None-Match`, so the mock is not a general entity-tag parser and does not model weak validators or tag lists. It also does not implement `Cache-Control`, `Vary` matching, freshness age, authentication cache rules, metadata merging, concurrency, or stale-if-error. The server responses in the fixture are supplied inputs; there is no server comparison or origin behavior.

## Run the example

From this directory, using Python 3.11+ and UTF-8 mode:

POSIX shell:

```sh
python3 -X utf8 conditional_get_mock.py < example-input.json
```

Windows PowerShell (the explicit cmd invocation provides native shell input redirection):

```powershell
cmd /d /c "python -X utf8 conditional_get_mock.py < example-input.json"
```

The output should match `expected-output.json`. The CLI reads at most 64 KiB plus one byte to detect an oversized document; invalid JSON or schema returns exit code 2 and a generic message on stderr. Valid input produces compact UTF-8 JSON on stdout. -X utf8 makes the console encoding explicit for Thai text.

## Run the authored tests

```sh
python -X utf8 -m unittest discover -s . -p "test_conditional_get_mock.py" -v
```

The 11 authored unit-test cases cover initial storage, valid reuse, replacement, representation-key separation, errors, malformed 304 state, whole-array validation, duplicate-key and schema/type rejection, and representative input bounds. They are source-level unit tests, not CLI integration tests or exhaustive boundary testing.

## Verification record

Code and tests were authored but **NOT RUN**. No import, compile, execution, installation, CLI integration, live HTTP request, SDK check, or performance measurement was performed. Local CPU cost is unmeasured. Runtime behavior remains unverified until the owner chooses to run the example and tests.

Copyable task brief: ออกแบบ conditional GET mock

คัดลอก brief หกส่วนนี้เพื่อมอบหมายงานออกแบบและตรวจ hand trace ของ Mock โดยคงขอบเขตและจุดหยุดไว้ครบ

1. เป้าหมาย: ออกแบบ mock แบบ sequential ให้ read-only Agent adapter ตรวจ representation ที่เก็บไว้ด้วย ETag/If-None-Match และ reuse body ได้เฉพาะเมื่อ 304 ตรงกับ entry ของ key เดียวกัน
2. ข้อมูลและเครื่องมือ: Python 3.11+ และ standard library เท่านั้น พร้อม JSON fixture แบบ offline; ไม่มี package ที่ต้องติดตั้ง ไม่มี environment variable, credential หรือ API ใช้ -X utf8 ตาม README เพื่อรับและส่ง UTF-8
3. ขอบเขตสิทธิ์: จำลอง input JSON array 1–32 event, alias a_th/a_en/b_th, exact key สำหรับ GET/URI/view/language, status 200/304/ERROR, validation ทั้งชุดก่อน mutation, snapshot ก่อน event, และ outcome STORED/REUSED/CACHE_STATE_ERROR/ERROR; ไม่ทำ full HTTP cache หรือ authorization จริง
4. สิ่งส่งมอบ: README อธิบาย contract และข้อจำกัด พร้อม trace fixture 8 event และ expected output fields key, sent_if_none_match, status, outcome, body; เก็บ body กับ ETag เป็น entry เดียวและแทนที่พร้อมกันเมื่อ 200
5. เกณฑ์ตรวจรับ: ยืนยันผลทั้ง 8 event ตามตาราง; แสดงว่า a_en ที่ไม่มี entry reuse 304 ไม่ได้; key A/B และภาษาไม่ปะปน; ERROR คง entry แต่คืน body null; 304 tag mismatch ไม่คืน body; input ผิดตำแหน่งใดก็ตามถูกปฏิเสธก่อน state เปลี่ยน; ระบุ exact strong-tag subset เทียบกับ RFC weak comparison
6. จุดหยุด: หยุดเมื่อ hand trace ทั้ง 8 เหตุการณ์และกรณี schema-invalid/key separation ผ่านเกณฑ์คำอธิบาย ห้ามส่ง HTTP/API/LLM request, execute/import/compile/test code, install dependency, deploy หรือแก้ production state ในขอบเขตบทเรียนนี้

ขอบเขตข้อมูลและความปลอดภัย

การแบ่ง cache key คือความถูกต้อง ไม่ใช่ authorization หาก response ขึ้นกับสิทธิ์ ต้องตรวจสิทธิ์ตามนโยบายแม้มี body ใน cache Alias A/B ไม่ใช่ผู้ใช้จริง ETag ไม่ใช่ secret, bearer token หรือใบรับรองว่าเนื้อหาปลอดภัย

ก่อนใช้งานจริงระบุว่าอะไร cache ได้, key ผูกกับ principal/tenant หรือไม่, variant ใดมีผล, ข้อมูลอ่อนไหวมีอายุเท่าใด และจัดการ invalidation/metadata จาก 304 อย่างไร บทเรียนไม่ตัดสินใจแทนระบบและไม่อนุญาตให้เชื่อม tool หรือแก้ infrastructure

3D Lab และ Visual Interactive: อ่าน state ทีละเหตุการณ์

3D Lab แสดง key, body และ ETag เป็นสามชั้นที่ผูกกัน เลือกดูสี่ช่วงของ a_th: 200 เก็บคู่รุ่นแรก, 304 ใช้ body เดิม, 200 แทนทั้งคู่ และ ERROR ที่ยังเก็บคู่แต่ไม่คืนเป็นผลสำเร็จ ปรับมุมด้วยแถบเลื่อนและปุ่มลูกศรบนคีย์บอร์ด ความลึกเป็นภาพอธิบายความสัมพันธ์ ไม่ใช่โครงสร้างหน่วยความจำจริง

Visual Interactive แยกต่างหากให้เลือกแถวทั้งแปดของ fixture แล้วเทียบ key, validator ที่ส่ง, response และ outcome ตารางคงอยู่ให้อ่านทุกแถวเมื่อ JavaScript ไม่ทำงาน จึงตรวจ a_en ที่ไม่มีคู่และ tag mismatch ได้จากข้อความ ปุ่มและ select มี labels/focus/live output และออกแบบ reduced motion ไว้ การออกแบบยังไม่ใช่ผลทดสอบ UI จริง

ทั้งสองเครื่องมือเป็น simulation ของ state ที่ผู้เขียนกำหนด ไม่ส่ง HTTP/API/LLM และไม่มีคะแนนความเร็วหรือตัวเลขประหยัดเงิน ข้อความใต้ภาพและตารางเป็น fallback สำหรับอ่านกลไก เจ้าของจะตรวจพฤติกรรมคีย์บอร์ด จอเล็ก และการแสดงผลจริงได้ภายหลัง; สถานะเหล่านี้ยัง UNKNOWN

3D Lab: body กับ ETag ต้องอยู่เป็นคู่

SIMULATION · ไม่มี HTTP/LLM/API เลือกเหตุการณ์เพื่ออ่านสถานะคู่ข้อมูล ปรับมุมด้วยแถบเลื่อนหรือปุ่มลูกศรบนคีย์บอร์ด ความลึกแสดงการผูกข้อมูลสามชั้น ไม่ใช่โครงสร้างหน่วยความจำจริง

Key คงเดิม: GET /assets/guide · View A · th
body: คู่มือ A รุ่น 1
ETag: "v1"
200 + body + ETag · เก็บ body และ ETag เป็นคู่
อ่านแทนภาพ 3D หรือเมื่อ JavaScript ไม่ทำงาน
  1. 1 · 200 เก็บคู่แรก: 200 + body + ETag · เก็บ body และ ETag ใน Entry เดียว ภายใต้ GET /assets/guide · View A · th
  2. 2 · 304 ใช้ body เดิม: If-None-Match: “v1” → 304 · ผลจำลองไม่มี body; tag ตรงกับคู่ใน key เดิม จึง REUSED body ที่เก็บไว้
  3. 3 · 200 แทนทั้งคู่: If-None-Match: “v1” → 200 · ตอบ full body รุ่น 2 และ tag v2 จึงแทนทั้งคู่ ห้ามคง body รุ่น 1 คู่กับ tag ใหม่
  4. 4 · ERROR ส่งข้อผิดพลาด: If-None-Match: “v2” → ERROR · คู่เดิมยังอยู่ แต่ผลที่ส่งให้ผู้เรียกเป็น ERROR และ body=null ตามนโยบายตัวอย่าง

ตัวอย่างใช้ strong tag ชุดย่อยและ equality แบบตรงตัว ไม่จำลอง weak comparison ตาม HTTP เต็มรูปแบบ การหมุนและผลเปลี่ยนสถานะบนเว็บจริงยัง UNKNOWN

Visual Interactive: ตรวจหลักฐานก่อน REUSED

SIMULATION · ตารางคำนวณด้วยมือ เลือกแถวเพื่อเทียบ key, tag ที่ส่ง และผลคืน ไม่เรียกเซิร์ฟเวอร์และไม่สร้างตัวเลขผลวัด ทุกแถวอ่านได้แม้ไม่มี JavaScript

แถว 1 · a_th · ไม่มี tag เดิม → 200 / "v1" → STORED · body คู่มือ A รุ่น 1
Expected output จำลอง ไม่ใช่ผลรันโค้ด; a_th=A/th, a_en=A/en, b_th=B/th ของ URI เดียว
แถวkey aliasIf-None-Matchresponseoutcomebody ที่ส่งให้ผู้เรียกเหตุผล
1a_thไม่มี200 / “v1”STOREDคู่มือ A รุ่น 1เก็บคู่ A/th รุ่นแรก
2a_th“v1”304 / “v1”REUSEDคู่มือ A รุ่น 1key/tag ตรงและ body ของ 304 เป็น null
3a_th“v1”200 / “v2”STOREDคู่มือ A รุ่น 2แทนทั้ง body และ tag
4a_enไม่มี304 / “v2”CACHE_STATE_ERRORnullไม่มีคู่ของ A/en ห้ามยืมจาก A/th
5b_thไม่มี200 / “v1”STOREDคู่มือ B รุ่น 1tag v1 ซ้ำข้าม key ได้ ไม่ทำให้เนื้อหาเหมือนกัน
6a_th“v2”ERRORERRORnullเก็บคู่เดิมแต่ไม่ส่งเป็นผลสำเร็จ
7a_th“v2”304 / “v1”CACHE_STATE_ERRORnulltag ตอบกลับไม่ตรง sent tag ใน subset นี้
8a_th“v2”304 / “v2”REUSEDคู่มือ A รุ่น 2ข้อผิดพลาดก่อนหน้าไม่ได้ทำลายคู่ที่เก็บ

ทุก 304 ใน fixture มี body=null; แถว 4 กับ 7 ถูกปฏิเสธโดยไม่แก้ cache ส่วน ERROR ไม่ใช่ NOT_FOUND และไม่ใช่หลักฐานว่าเนื้อหาเดิมยังใช้ได้ การแบ่ง View เป็นบริบทจำลอง มิใช่การตรวจสิทธิ์

Video Lab: วางแผนสื่อสอน conditional read

ใช้ Hybrid Mind Video Lab เพื่อวางแผนวิดีโอสอนงานอ่าน asset guide ซ้ำของ Agent เลือกหัวข้อ ความยาว และรูปแบบสื่อ แล้วนำ prompt ที่ได้มาตรวจเทียบ brief หกส่วนข้างล่าง: ต้องแสดงฉากอ่าน 200 แล้วเก็บ body/tag และฉาก conditional read ได้ 304 ซึ่งใช้ body ที่เก็บอยู่ ปลั๊กอินช่วยสำรวจ workflow และร่าง prompt; กลไก ETag อยู่ในตัวอย่างแยกของบทเรียน

ค่าเวลาและเงินใน Video Lab เป็นสมมติฐานที่ผู้เรียนปรับ ไม่ใช่ผลวัดจริง ไม่เรียกบัญชีผลิตวิดีโอหรือเผยแพร่คลิปจากบทเรียนนี้ การอ่าน markup ที่ server render ยืนยันเพียงการฝัง Lab รุ่น active ไม่ได้ยืนยันว่าปุ่ม การคัดลอก prompt หรือการเข้าถึงด้วยคีย์บอร์ดทำงานผ่านแล้ว

1. เป้าหมาย: ร่างคลิปสอนให้ผู้เริ่มสร้าง Agent แยก 304 ออกจากผลข้อมูลว่าง โดยใช้สถานการณ์อ่าน asset guide ซ้ำ
2. ข้อมูลและเครื่องมือ: สำรวจ workflow และร่าง prompt ใน Video Lab; อ้าง fixture a_th และตารางทั้งแปดเหตุการณ์ของบทเรียน
3. ขอบเขต: ทุกฉากเป็น simulation ไม่อ่าน HTTP/API จริง ไม่เชื่อมบัญชี ไม่ผลิตหรือเผยแพร่คลิปจริง ค่าเวลาและเงินเป็นสมมติฐาน; ปลั๊กอินไม่ได้ทำ conditional GET ให้
4. สิ่งส่งมอบ: prompt แผนฉาก200เก็บbody+ETag และ304ใช้bodyเดิม พร้อมคำบรรยาย key/view/language ที่ถูกตรวจ
5. เกณฑ์ตรวจรับ: แสดงbody/tagเป็นคู่;304ไม่มีbodyใหม่; a_enไม่มีคู่ต้องปฏิเสธ; ERRORไม่กลายเป็นผลสำเร็จ; tagไม่ใช่สิทธิ์; ไม่อ้างว่าได้วัดความเร็วหรือประหยัดเงิน
6. จุดหยุด: จบที่promptและhandtraceที่อธิบายได้ครบ หยุดเมื่อไม่รู้สิทธิ์หรือสัญญาorigin ห้ามเรียกบริการจริง ติดตั้ง หรืออ้างผลruntime/UI
H✦HYBRID MINDINTERACTIVE EDITION LIVE EXPERIENCE
HYBRID MIND ORIGINAL / AI AGENT WORKFLOW / 2026

จากคลิปดิบ
สู่จักรวาลคอนเทนต์

ChatGPT ตัดต่อวิดีโอแทนคนได้จริงแค่ไหน? สำรวจระบบ AI Video Editing Agent ผ่านฉากสามมิติที่โต้ตอบได้ พร้อมแยกข้อเท็จจริง สร้างพรอมป์ต์ และจำลองต้นทุนก่อนลงมือทำจริง

06 EDITING STAGES03 CONTENT FORMATS01 INTERACTIVE LAB

ออกแบบระบบตัดต่อในมุมมอง 3D

ลากบนฉากเพื่อหมุนมุมมอง แตะโหนดเพื่อดูหน้าที่ของ AI ในแต่ละขั้นตอน

แผนภาพ 6 ขั้นตอน: Footage, Story, Edit, Captions, Graphics, Render
AGENT PIPELINE / VIEWPORT ORBIT 3D
DRAG / SWIPE TO ROTATE
SELECT NODE TO INSPECT
01 / 06นำเข้าคลิปดิบ

จัดระเบียบไฟล์ ตรวจคุณภาพภาพและเสียง และกำหนดสิทธิ์เข้าถึงให้ AI Agent

● SOURCE
จุดสำคัญ: AI Agent ทำหน้าที่วางแผนและควบคุมเครื่องมือ ส่วนการตัดต่อจริงต้องมีซอฟต์แวร์หรือระบบเรนเดอร์รองรับ ฉากนี้เป็น แบบจำลองเชิงอธิบาย ไม่ใช่ระบบอัปโหลดหรือตัดต่อวิดีโอ

แยกความสามารถจริงจากคำโฆษณา

กดแต่ละประเด็นเพื่ออ่านรายละเอียดและแหล่งข้อมูล

EVIDENCE-FIRST
✓ChatGPT Work เข้าถึงไฟล์ในเครื่องได้ตรวจสอบแล้ว / มีเงื่อนไข+
รองรับการทำงานกับโฟลเดอร์หรือแอปบนคอมพิวเตอร์เดสก์ท็อป เมื่อแผนและการตั้งค่าอนุญาตและผู้ใช้ให้สิทธิ์ ส่วน Work บนมือถือหรือเว็บไม่ได้เข้าถึงโฟลเดอร์ในคอมพิวเตอร์ของคุณโดยตรง ที่มา: OpenAI Help ↗
✓Astra ใช้โปรแกรมคอมพิวเตอร์ทำงานได้ตรวจสอบแล้ว / ไม่ใช่โปรแกรมตัดต่อในตัว+
OpenAI อธิบายความสามารถด้าน computer use ของ Astra รวมถึงงานสื่อที่ใช้เครื่องมือ แต่ต้องแยกบทบาท AI ผู้ควบคุมออกจากซอฟต์แวร์ประมวลผลวิดีโอและการเรนเดอร์ไฟล์ ที่มา: OpenAI ↗
?ตัดเสร็จใน 5–10 นาทีทุกกรณียังพิสูจน์เป็นเกณฑ์ทั่วไปไม่ได้+
เวลาขึ้นกับความยาวคลิป จำนวนทรานซิชัน คุณภาพซับ เครื่องมือ และประสิทธิภาพคอมพิวเตอร์ ควรใช้การจับเวลางานจริงแบบต้นทางเดียวกัน ไม่ใช้ตัวเลขจากโพสต์เป็นเกณฑ์อ้างอิง
?ถูกกว่าจ้างคน 10 เท่าต้องกำหนดต้นทุนให้เทียบกันได้+
ต้องรวมเวลาตรวจแก้ ลิขสิทธิ์เสียง เครื่องมือที่จ่ายเพิ่ม และต้นทุนโมเดล ยังไม่มีหลักฐานว่าประหยัด 10 เท่าสำหรับทุกงาน ทดลองสมมติฐานส่วนตัวได้ใน Cost Simulator ด้านล่าง

หนึ่งไอเดีย สามรูปแบบคอนเทนต์

ปรับรูปแบบวิดีโอ แล้วดูกรอบภาพตัวอย่างและ Prompt เปลี่ยนตามแบบเรียลไทม์

WORKING INTERACTIVE
15 SEC180 SEC
LIVE CREATIVE PREVIEW9:16 · 45 SEC
HYBRID MIND / AI
AI EDITOR / EXPLAINEDAI Agent
ช่วยตัดต่อ?
ใส่ซับตามประโยคได้อัตโนมัติ
▶◉↗
CONCEPT PREVIEW · NOT RENDERED VIDEO
VERTICAL · SHORT FORMVisual Mockup

พร้อมนำไปใช้กับ AI Agent

ตัวสร้าง Prompt นี้ไม่อัปโหลดคลิป ไม่เรียก API เสียเงิน และไม่ตัดต่อวิดีโอจริง ต้องใช้กับเครื่องมือที่ผู้ใช้อนุญาตภายหลัง

จำลองเวลาและต้นทุนของคุณเอง

เลื่อนตัวเลขเพื่อเปรียบเทียบสถานการณ์สมมติ ไม่ใช่ผล benchmark ที่ยืนยันแล้ว

SCENARIO · NOT BENCHMARK
ESTIMATED TIME SAVED11.0 ชม./เดือน
18.0 ชม.
7.0 ชม.
ส่วนต่างต้นทุนเวลา + เครื่องมือ (สมมติ)฿3,920

ตัวเลขเป็นตัวอย่างเพื่อช่วยตัดสินใจ ไม่ใช่ผลประหยัดที่รับประกัน

AI ที่ตัดคลิปเป็น ไม่ได้ทำงานลำพัง

สิ่งที่น่าสนใจของ GPT-6 Astra และ GPT-6.1 Sol ไม่ได้อยู่ที่ว่าตัวโมเดลมีปุ่ม Render MP4 ซ่อนอยู่ แต่คือความสามารถในการจัดการงานหลายขั้นตอนร่วมกับเครื่องมืออื่น: อ่านไฟล์ วางลำดับเรื่อง ใช้แอป ตรวจผลลัพธ์ และแก้งานตามข้อเสนอแนะของมนุษย์

สำหรับคนทำคอนเทนต์ ความต่างสำคัญคือการเปลี่ยนจาก “ส่งคลิปให้ AI แล้วหวังว่าจะได้ผลงาน” ไปเป็น Workflow ที่ตรวจรับได้ทุกขั้น ตั้งแต่การเลือกช่วงเด็ด การสร้างซับ การตรวจคำผิด จนถึงการเรนเดอร์ไฟล์ตามสัดส่วนของแต่ละแพลตฟอร์ม

ข้อควรระวังคือระบบ Work ที่รันบนเว็บหรือมือถือไม่ได้หมายความว่า AI เข้าถึงคลิปบนคอมพิวเตอร์บ้านได้ทันที ต้องทำงานในสภาพแวดล้อมที่เปิดให้เข้าถึงไฟล์จริง มีเครื่องมือตัดต่อ และมีสิทธิ์ใช้งานอย่างชัดเจน

01จัดคลิปให้พร้อม

รวบรวมไฟล์ในโฟลเดอร์ที่เข้าถึงได้ ระบุสิทธิ์และข้อจำกัด

02ตรวจสคริปต์และไทม์ไลน์

ให้ Agent เสนอ Hook, ช่วงตัด และโครงเรื่องก่อนตัดต่อ

03สร้างซับ + กราฟิก

ตรวจชื่อเฉพาะ ตัวเลข ภาษาไทย และความถูกต้องของคำกล่าวอ้าง

04ตรวจไฟล์ MP4 จริง

ตรวจความละเอียด เสียง ระยะเวลา safe zone และคำบรรยาย

แบบฝึกหัด: แก้ trace ที่คืน body ผิด key

เริ่มจาก fixture หลัก แล้วเพิ่มการตรวจเชิงแนวคิด: หลัง event 1 และ 2 ให้เพิ่ม event จาก a_en ที่ตอบ 304 ด้วย "v1" ขณะที่ไม่มี a_en entry แล้วบอกว่าคำขอส่ง validator ใดและผลต้องเป็นอะไร จากนั้นเพิ่ม 200 สำหรับ a_en เป็น body ภาษาอังกฤษกับ "v1" แล้วลอง 304 อีกครั้งของ key เดิม อธิบายว่าทำไมการเท่ากันของ tag ไม่ทำให้ a_th และ a_en รวมเป็น entry เดียว

rubric ตรวจสี่ข้อ: (1) ระบุ sent_if_none_match จาก snapshot ก่อนเหตุการณ์ ไม่ใช่จาก response ที่กำลังจะได้; (2) ตอบ CACHE_STATE_ERROR เมื่อ key ยังไม่มี stored representation; (3) หลัง 200 ภาษาอังกฤษ เก็บ body/tag เป็นคู่ใต้ a_en; (4) reuse ได้เฉพาะเมื่อ event ถัดไปของ key เดิมได้ 304 ที่ตรงตาม mock contract พร้อมบอกว่า exact match เป็นข้อจำกัดของตัวอย่าง ไม่ใช่กฎเปรียบเทียบ tag เต็มของ RFC

จากนั้นเขียน failure case ที่ event ท้าย array ผิด schema แต่ event ก่อนหน้าเป็น 200 แล้วระบุว่าทำไมต้อง reject ทั้ง array ก่อน mutation สุดท้ายเขียน stop condition: หยุดเมื่อ trace หลักทั้ง 8 แถวและกรณี key ภาษาอังกฤษอธิบายได้ครบ โดยไม่เพิ่มการเรียก HTTP, API, LLM, test execution หรือ deploy แบบฝึกหัดนี้เป็น hand trace ไม่ใช่ runtime test

คำถามที่พบบ่อย

304 หมายถึงไม่มีเนื้อหาหรือไม่? ไม่ใช่ 304 ไม่มี body ใหม่และอาศัย stored representation ที่ตรงกัน หากไม่มี entry ตัวอย่างคืน CACHE_STATE_ERROR

ทำไมเก็บ ETag กับ body ด้วยกัน? เพื่อผูก validator กับ representation ที่มันตรวจ หากแยกอัปเดตอาจใช้ tag ใหม่กับ body เก่าหรือหยิบ body ผิด key

ETag เหมือนกันข้าม key ใช้ body ร่วมกันได้ไหม? ไม่ได้จากค่า tag เพียงอย่างเดียว Key และ selected representation ยังสำคัญ

mock เปรียบเทียบ tag ตาม HTTP ครบหรือไม่? ไม่ครบ ใช้ strong quoted ASCII subset กับ exact equality; RFC 9110 ระบุ weak comparison สำหรับ If-None-Match

timeout ควรคืน stale body ไหม? บทเรียนเลือก ERROR และไม่ serve stale โดยปริยาย; stale-if-error ต้องเป็น policy แยกที่กำหนดเงื่อนไขและการสื่อสาร freshness

นี่คือ cache พร้อม production หรือยัง? ไม่ใช่ ไม่มี Cache-Control, Vary, metadata merge, tag list/weak parsing, concurrency, authorization implementation หรือ origin จริง

โค้ดและ UI ผ่านทดสอบหรือยัง? ไม่มี runtime/test; code/tests NOT RUN และ Lab UI UNKNOWN

แหล่งอ้างอิงและบทเรียนต่อเนื่อง

RFC ทั้งสองเผยแพร่เดือนมิถุนายน 2022 ตรวจต้นทางเมื่อ 22 ตุลาคม 2026; นโยบาย Mock โค้ด ตาราง ภาพและ Lab เป็นการออกแบบเพื่อเรียนรู้ ไม่มีผลรันหรือผลวัดประสิทธิภาพ

Discover more from HYBRID MIND

Subscribe now to keep reading and get access to the full archive.

Continue reading