
ภาพอธิบายแนวคิด Problem Details และการจำแนกเพื่อทบทวน — ฉากจำลองสำหรับบทเรียน
· คลิกภาพอ่านขนาดเต็มQuick Answer: ให้ trusted API adapter ส่งข้อเท็จจริงจาก transport ได้แก่ HTTP status จริง, content type และ body bytes เข้าฟังก์ชันจำแนก โดยถือ JSON body ว่าไม่น่าเชื่อถือ จำกัดขนาด ตรวจชนิดเนื้อหา แล้วใช้ URI ของ type ที่ตรง allowlist คู่กับ status จริงเท่านั้น ผลเป็นหมวดทบทวน เช่น REVIEW_INPUT, REVIEW_REVISION หรือ REVIEW_GENERIC ข้อความ detail และ title ไม่ใช่คำสั่ง ห้าม classifier แก้ข้อมูล เรียกซ้ำ ส่งงาน หรือออกเครือข่าย
หลังจบบทเรียน คุณทำอะไรได้
คุณจะเขียนตารางจับคู่ชนิดปัญหากับ HTTP status กำหนดทางออกเมื่อข้อมูลไม่พอ และอธิบายได้ว่าทำไมคำตอบให้คนอ่านจึงไม่ควรควบคุม tool จากเดิมที่ใช้ LLM แปลข้อความ error ให้แยกส่วนจำแนกที่ตรวจสอบได้ออกมาก่อน แล้วค่อยส่งผลจำกัดให้คนทบทวน
ปัญหาที่ต้องแก้: error text ไม่ใช่สัญญาควบคุม
สมมติ Agent เตรียม brief คลิปอธิบายการรับมือข้อผิดพลาด แล้วส่งร่างให้ API ตรวจสอบ บริการตอบกลับว่า “แก้ข้อมูลที่ส่งมา” หรือ “มีคนแก้ revision ไปแล้ว” หากโค้ดค้นหาคำในข้อความดังกล่าวแล้วสั่ง Agent ให้แก้และส่งใหม่ เราได้ผูกการควบคุมระบบเข้ากับประโยคที่อาจเปลี่ยนภาษา เปลี่ยนสำนวน หรือมีข้อความที่ผู้โจมตีใส่กลับมา
ปัญหานี้มีสองชั้น ชั้นแรกคือการจำแนกผลตอบกลับ: response นี้หมายถึงข้อมูลไม่ผ่านกติกา หรือ revision ล้าสมัย ชั้นที่สองคือการตัดสินใจทำงาน: ใครมีสิทธิ์แก้ brief จะขอ revision ล่าสุดได้เมื่อใด และการส่งงานต้องผ่านการอนุมัติหรือไม่ บทเรียนนี้ตอบเฉพาะชั้นแรก และให้ผลเป็นหมวดที่คนต้องทบทวน ไม่สวมบทเป็น policy engine ที่ลงมือแทน
HTTP status เพียงอย่างเดียวก็ไม่พอสำหรับการเลือกเหตุผลเชิงธุรกิจ เพราะ status เดียวกันอาจเกิดจากหลายเงื่อนไข ส่วนข้อความอิสระก็ไม่นิ่งพอให้ใช้เป็นรหัส ดังนั้น adapter ต้องมีข้อมูลสองด้าน: status ที่ transport ได้รับจริง และชนิดปัญหาที่ API ระบุด้วยตัวระบุซึ่งระบบเราอนุญาตไว้อย่างจำกัด เมื่อนำไปใช้กับ provider รายใด ต้องตรวจสัญญาของ provider นั้นก่อน ห้ามสมมติว่าทุก API ส่งรูปแบบมาตรฐานเดียวกัน
RFC 9457 นิยามรูปแบบ Problem Details สำหรับ HTTP ด้วย media type application/problem+json และสมาชิกอย่าง type, title, status, detail, instance โดย type ระบุชนิดหลัก; สมาชิกมาตรฐานชนิดผิดและ extension ที่ไม่รู้จักให้ละเลย ส่วน status ใน body เป็นข้อมูลอธิบายและควรตรงกับ status จริง (RFC 9457).
แยกชั้นข้อมูลก่อนเขียน classifier
กำหนดขอบเขตว่า classifier รับค่าอะไร แทนการให้มันรับ response object ขนาดใหญ่จาก HTTP client โดยตรง ชั้นก่อนหน้า ซึ่งเรียกว่า trusted adapter ในตัวอย่าง ต้องเป็นผู้ดึง status จริง, content type และ bytes จาก response แล้วกำหนดเพดานขนาด body ในบทเรียนใช้ 8,192 bytes เป็นค่าควบคุมเฉพาะตัวอย่าง ไม่ใช่ข้อกำหนดของ RFC หรือคำแนะนำสากล
จากนั้น parser ตรวจว่า content type เป็น application/problem+json ตามนโยบายที่ adapter รองรับ จำกัดจำนวน bytes ก่อน decode และ parse JSON อย่างปลอดภัย ผลลัพธ์ parsing ควรบอกสถานะชัด เช่น parsed problem, media type ที่ไม่รองรับ, body ใหญ่เกิน, JSON เสียรูป หรือข้อมูลไม่ได้เป็น object การ parse ล้มเหลวไม่ได้แปลว่า Agent ควรลองใหม่ และไม่ควรกลายเป็น exception ที่ปล่อย raw body ไปปรากฏใน prompt หรือ log โดยไม่จำกัด
ชั้นจำแนกทำงานหลังการ parse แล้วเท่านั้น อินพุตควรมี actual status ที่ผ่านการตรวจช่วงค่า, content type ที่ normalize แล้ว และ object ของ JSON ที่ผ่าน parser โดย parser ไม่ควรทำ network request เพิ่ม ไม่ตาม URI ใน type หรือ instance และไม่แปลง body ให้เป็นคำสั่ง Agent ทุกชั้นควรส่งข้อมูลขั้นต่ำเท่าที่จำเป็น เพื่อให้ตรวจทานได้ว่าข้อมูลมาจากไหน

สามชั้นเชิงแนวคิดเพื่อแยกข้อมูลควบคุมกับข้อความให้คนอ่าน ไม่ใช่รายการสมาชิกทั้งหมดของ RFC 9457
· คลิกภาพอ่านขนาดเต็มสัญญาผลลัพธ์: หมวดเพื่อทบทวน ไม่ใช่คำสั่ง
ฟังก์ชันคืน dictionary ที่มีเพียงสาม key: decision, reason และ http_status ไม่คืน type หรือข้อความจาก server เหตุผลใช้รหัสภายในตัวพิมพ์ใหญ่ เช่น KNOWN_INVALID_BRIEF, KNOWN_STALE_REVISION, UNKNOWN_OR_UNMATCHED_PROBLEM และ BODY_STATUS_MISMATCH ส่วนความผิดพลาดก่อนถึง mapping ใช้รหัสเฉพาะ เช่น INVALID_HTTP_STATUS, BODY_TOO_LARGE, UNEXPECTED_CONTENT_TYPE หรือ INVALID_JSON
เหตุผลต้องเป็นรหัสคงที่ที่ระบบกำหนด เพื่อช่วย debug โดยไม่เปิดเผย implementation detail ให้ client หรือบันทึก body ทั้งชุดลง log อย่าเก็บ prompt, token, brief ฉบับเต็ม หรือข้อมูลส่วนบุคคลเพียงเพื่อให้ทีมเห็นสาเหตุ หากจำเป็นต้องสืบสวน ให้มี diagnostic ที่เข้าถึงจำกัดและนโยบาย retention เหมาะสม แนวทาง Error Handling ของ OWASP สนับสนุนการแยกข้อความทั่วไปที่ client เห็นออกจากรายละเอียดภายใน แต่การเก็บ log ต้องออกแบบกับระบบจริง (OWASP Error Handling Cheat Sheet)
กติกา exact allowlist สำหรับงานนี้
ตัวอย่างรู้จักเพียง type แบบ absolute URI สองค่า: https://api.example.test/problems/invalid-brief และ https://api.example.test/problems/stale-revision โดยจับคู่กับ actual HTTP status ที่คาดไว้ด้วย ค่าจาก RFC ไม่ได้จำกัด type URI ให้ต้องเป็น absolute URI เสมอ; relative URI ใช้ได้ตามมาตรฐาน แต่ adapter แคบนี้ไม่ยอมรับค่าอื่น เว้นแต่ตรงกับ allowlist ที่ประกาศไว้ การไม่รู้จักค่าใน adapter จึงหมายถึง “นโยบายท้องถิ่นไม่รองรับ” ไม่ใช่ “response ผิด RFC”
การจับคู่มีลักษณะนี้: type invalid-brief พร้อม actual status 422 ให้ REVIEW_INPUT; type stale-revision พร้อม actual status 409 ให้ REVIEW_REVISION type ที่รู้จักแต่ status อื่นให้ REVIEW_GENERIC เหตุผลว่า mismatch ส่วน type อื่น หายไป หรือถูกแทนเป็น about:blank ก็ได้ generic เช่นกัน การจับคู่ทั้งคู่ป้องกันการนำรหัส type ที่ถูกต้องในบริบทหนึ่งไปอ้างกับ status ที่ไม่ตรงสัญญาอีกบริบทหนึ่ง
ถ้า body มีสมาชิก status เป็นตัวเลขที่ถูกต้องแต่ไม่ตรงกับ actual status นโยบายตัวอย่างนี้ให้ generic ด้วยเหตุผล mismatch นี่เป็นการเลือกแบบอนุรักษ์นิยมของ adapter สำหรับการสอน ไม่ใช่กฎที่ RFC บังคับให้ classifier ทุกตัวใช้ หาก body status หายไปหรือเป็นชนิดผิด parser สามารถละเลยสมาชิกนั้นตามรูปแบบมาตรฐาน แล้วใช้ actual status กับ type ที่ผ่าน allowlist ต่อได้ ต้องบันทึกเหตุผลที่เลือกนโยบายนี้ไว้ เพื่อให้ทีมไม่เข้าใจว่า RFC สั่งให้ตก generic เสมอ
ส่วนสมาชิกมาตรฐานที่มีชนิดไม่ตรงตามที่กำหนดให้ละเลย ไม่พยายาม “เดา” จากค่าแปลก เช่น object ที่วางใน title หรือ string ที่วางใน status ส่วนสมาชิก extension ที่ classifier ไม่รู้จักก็ไม่ควรเปลี่ยนการตัดสินใจ แม้ extension ชื่อดูเหมือนมีคำสั่ง เช่น next_action ก็ยังเป็นข้อมูลที่ไม่อยู่ใน schema นโยบายนี้ จึงไม่มีอิทธิพลต่อ decision

คู่ type และ HTTP status ต้องตรงรายการที่กำหนด จึงได้หมวดทบทวนเฉพาะ — เป็นนโยบายของตัวอย่าง ไม่มีการเรียก API
· คลิกภาพอ่านขนาดเต็มWorked Example: brief วิดีโอผิดรูปแบบกับ revision เก่า
เริ่มจากเหตุการณ์แรก: Agent ส่ง mock video brief ไปตรวจ แล้ว trusted adapter ส่ง actual status 422, content type application/problem+json และ JSON object ที่มี type ตรงกับ URI invalid-brief Body อาจมี title ที่อ่านง่ายและ detail อธิบายช่องที่ต้องตรวจ แต่ classifier ไม่ได้นำประโยคนั้นมาเป็น policy สิ่งที่ทำให้ได้ REVIEW_INPUT คือคู่ที่ตรงกันระหว่างตัวระบุ type ที่อนุญาตกับ status จริง
เหตุการณ์ที่สอง: API mock ตอบ actual status 409 และ type stale-revision ผลเป็น REVIEW_REVISION ระบบปลายทางอาจแสดงหมายเหตุให้คนตรวจ revision ในหน้าจอ แต่ classifier หยุดที่การบอกหมวด ไม่ดึงเอกสารล่าสุด ไม่คัดลอกข้อมูลจาก revision ใหม่ ไม่ผสานการแก้ไข และไม่กดส่ง brief อีกครั้ง การโหลดข้อมูลถือเป็น I/O และการเขียนถือเป็น side effect จึงต้องอยู่นอกฟังก์ชันนี้
Python ที่แสดงกติกา ไม่ทำ HTTP
ตัวอย่าง Python 3.12 ใช้ฟังก์ชัน classify(actual_status, content_type, body) โดยรับ body เป็น bytes ฟังก์ชันตรวจ status ให้อยู่ในช่วง HTTP, ตรวจชนิด bytes และเพดาน 8,192 bytes, ตรวจ media type, decode และ parse JSON เอง แล้วคืนเฉพาะ decision, reason, http_status ไม่รับ parsed object จาก caller และไม่มี HTTP client, credential, network, retry หรือ dispatch
บันทึกโค้ดเป็น problem_example.py ใช้ Python 3.12 ขึ้นไปและ standard library ไม่ต้องติดตั้งแพ็กเกจ ตั้ง environment variable หรือ API key ไม่มีค่าบริการ API ใน mock นี้ แต่ยังใช้ทรัพยากรเครื่องตามปกติ Sandbox และ Live integration ยังไม่ได้ทำ สิทธิ์จำกัดเฉพาะข้อมูลจำลองในหน่วยความจำและพิมพ์ผล
"""Finite mock classifier for RFC 9457-style HTTP problem responses."""
import json
import math
from typing import Any
MAX_BODY_BYTES = 8192
INVALID_BRIEF = "https://api.example.test/problems/invalid-brief"
STALE_REVISION = "https://api.example.test/problems/stale-revision"
def _reject_constant(value: str) -> None:
raise ValueError("non-finite JSON number")
def _finite_float(value: str) -> float:
number = float(value)
if not math.isfinite(number):
raise ValueError("non-finite JSON number")
return number
def _unique_object(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
result: dict[str, Any] = {}
for key, value in pairs:
if key in result:
raise ValueError("duplicate JSON member")
result[key] = value
return result
def _generic(reason: str, status: int | None = None) -> dict[str, Any]:
return {"decision": "REVIEW_GENERIC", "reason": reason, "http_status": status}
def classify(actual_status: int, content_type: str | None, body: bytes) -> dict[str, Any]:
"""Return a review label only; never perform an action."""
if type(actual_status) is not int or not 100 <= actual_status <= 599:
return _generic("INVALID_HTTP_STATUS")
if not isinstance(body, bytes):
return _generic("INVALID_BODY_BYTES", actual_status)
if len(body) > MAX_BODY_BYTES:
return _generic("BODY_TOO_LARGE", actual_status)
if not isinstance(content_type, str):
return _generic("MISSING_OR_INVALID_CONTENT_TYPE", actual_status)
media_type = content_type.split(";", 1)[0].strip().lower()
if media_type != "application/problem+json":
return _generic("UNEXPECTED_CONTENT_TYPE", actual_status)
try:
document = json.loads(
body.decode("utf-8"), object_pairs_hook=_unique_object,
parse_constant=_reject_constant, parse_float=_finite_float,
)
except (UnicodeDecodeError, json.JSONDecodeError, ValueError, RecursionError):
return _generic("INVALID_JSON", actual_status)
if not isinstance(document, dict):
return _generic("PROBLEM_BODY_NOT_OBJECT", actual_status)
problem_type = document.get("type")
if not isinstance(problem_type, str):
problem_type = "about:blank"
body_status = document.get("status")
# bool subclasses int in Python, but is not an HTTP status number here.
if isinstance(body_status, int) and not isinstance(body_status, bool):
if body_status != actual_status:
return _generic("BODY_STATUS_MISMATCH", actual_status)
elif isinstance(body_status, float):
if not math.isfinite(body_status) or body_status != actual_status:
return _generic("BODY_STATUS_MISMATCH", actual_status)
if actual_status == 422 and problem_type == INVALID_BRIEF:
decision, reason = "REVIEW_INPUT", "KNOWN_INVALID_BRIEF"
elif actual_status == 409 and problem_type == STALE_REVISION:
decision, reason = "REVIEW_REVISION", "KNOWN_STALE_REVISION"
else:
decision, reason = "REVIEW_GENERIC", "UNKNOWN_OR_UNMATCHED_PROBLEM"
return {"decision": decision, "reason": reason, "http_status": actual_status}
def _self_tests() -> None:
def response(status: int, data: str, media: str | None = "application/problem+json"):
return classify(status, media, data.encode("utf-8"))
assert response(422, '{"type":"' + INVALID_BRIEF + '"}')['decision'] == "REVIEW_INPUT"
assert response(409, '{"type":"' + STALE_REVISION + '","status":409}')['decision'] == "REVIEW_REVISION"
assert response(422, '{"type":"' + STALE_REVISION + '"}')['decision'] == "REVIEW_GENERIC"
assert response(422, '{"type":7}')['decision'] == "REVIEW_GENERIC"
assert response(422, '{"type":"' + INVALID_BRIEF + '","status":"422"}')['decision'] == "REVIEW_INPUT"
assert response(422, '{"type":"' + INVALID_BRIEF + '","status":409}')['reason'] == "BODY_STATUS_MISMATCH"
huge_status = ('{"type":"' + INVALID_BRIEF + '","status":' + "9" * 4000 + '}').encode("utf-8")
assert classify(422, "application/problem+json", huge_status)['reason'] == "BODY_STATUS_MISMATCH"
assert response(422, '{"type":"' + INVALID_BRIEF + '","detail":"secret"}').keys() == {"decision", "reason", "http_status"}
assert response(422, '{"type":"' + INVALID_BRIEF + '","x-new":true}')['decision'] == "REVIEW_INPUT"
assert response(422, '{"type":"' + INVALID_BRIEF + '","type":"x"}')['reason'] == "INVALID_JSON"
assert classify(422, None, b"{}")['reason'] == "MISSING_OR_INVALID_CONTENT_TYPE"
assert classify(422, "application/problem+json", b"x" * (MAX_BODY_BYTES + 1))['reason'] == "BODY_TOO_LARGE"
assert response(422, '{"type":"' + INVALID_BRIEF + '","n":NaN}')['reason'] == "INVALID_JSON"
if __name__ == "__main__":
# Three mock responses only. No HTTP calls are made.
examples = [
(422, "application/problem+json", b'{"type":"https://api.example.test/problems/invalid-brief"}'),
(409, "application/problem+json", b'{"type":"https://api.example.test/problems/stale-revision","status":409}'),
(503, "text/plain", b"upstream unavailable"),
]
for status, media, body in examples:
print(json.dumps(classify(status, media, body), sort_keys=True))
คำสั่งตัวอย่างสำหรับผู้เรียน: python problem_example.py ผลสามบรรทัดที่คาดจากการอ่านโค้ดอยู่ด้านล่าง ยังไม่ได้รันเพื่อยืนยัน
{"decision": "REVIEW_INPUT", "http_status": 422, "reason": "KNOWN_INVALID_BRIEF"}
{"decision": "REVIEW_REVISION", "http_status": 409, "reason": "KNOWN_STALE_REVISION"}
{"decision": "REVIEW_GENERIC", "http_status": 503, "reason": "UNEXPECTED_CONTENT_TYPE"}
ไฟล์มี self-tests ที่เขียนไว้ 13 กรณี รวมตัวเลข status ขนาดใหญ่ body เกินเพดาน key ซ้ำ และ detail ไม่รั่วในผล ใช้คำสั่งต่อไปนี้เพื่อรันด้วยตนเอง ผลที่คาดหากผ่านคือ 13 self-tests passed สถานะบทความนี้ยังเป็น NOT RUN ทั้งตัวอย่างและ tests ไม่มีผล integration หรือ production
python -c "import problem_example; problem_example._self_tests(); print('13 self-tests passed')"
ส่วนอ่าน JSON อ้างอิง เอกสาร Python json ตัวอย่างเพิ่มการปฏิเสธ key ซ้ำและตัวเลข non-finite เอง รวมทั้งจับ parse error และจำกัดขนาดก่อน decode เพื่อไม่ส่ง exception หรือ body ดิบเป็นผลลัพธ์ ไม่มีการพิสูจน์ว่าเพียงเท่านี้เพียงพอสำหรับ parser ในระบบจริง
ห้องทดลอง: อ่าน Problem Details แล้วตัดสินใจอย่างระมัดระวัง
สองส่วนเป็นแบบจำลองข้อมูลจำลอง: ส่วนแรกแยกชั้นคำตอบ HTTP ส่วนที่สองจัดประเภทตามกฎบทเรียน ไม่มีการเรียก API หรือสั่ง Agent ทำงานต่อ
1) ชั้นข้อมูล: จากคำตอบถึงหมวดทบทวน
เลือกบัตรเพื่อเน้นชั้นที่กำลังพิจารณา การแสดงผลตั้งต้นอ่านได้โดยไม่ต้องกดปุ่ม
ชั้น 1 · HTTP
status = 422สถานะจริงจาก adapter เป็นฐานเปรียบเทียบ
ชั้น 2 · Content-Type
application/problem+jsonตรวจชนิดสื่อก่อนเชื่อโครงสร้าง
ชั้น 3 · problem type
https://api.example.test/problems/invalid-briefรหัสชนิดที่รู้จักต้องตรงรายการ exact
ชั้น 4 · local decision
REVIEW_INPUTเป็นหมวดให้คนทบทวน ไม่ใช่คำสั่งส่งงาน
กำลังดูชั้น HTTP: ใช้ status ที่รับจริงจาก adapter เป็นค่าหลักในการตรวจสอบ
2) ตัวจำแนกจำลอง: เปลี่ยนอินพุตแล้วดูผล
ชุดตัวอย่างหรือ status/body type เปลี่ยนได้ ข้อความ detail เปลี่ยนภาษาได้แต่ไม่กำหนดหมวด
อินพุตจำลอง
ผลนโยบายท้องถิ่น
ตารางกฎอ้างอิงคงที่
| อินพุตสำคัญ | ผล | เหตุผล |
|---|---|---|
| 422 + invalid-brief | REVIEW_INPUT | known type/status ตรง |
| 409 + stale-revision | REVIEW_REVISION | known type/status ตรง |
| type หรือ status ไม่ตรง/ไม่รู้จัก | REVIEW_GENERIC | ไม่มีคู่ exact |
| media ผิด, JSON ผิดรูป, หรือไม่ใช่ object | REVIEW_GENERIC | ไม่ผ่านรูปแบบที่ยอมรับ |
| body.status ชนิดผิด | ละเว้น field แล้วจำแนกต่อ | ชนิดสมาชิกมาตรฐานผิดถูกละเว้น |
| title/detail/extension เปลี่ยน | หมวดเดิม | ไม่ใช้ route |
ตัวอย่างนี้ไม่ใช่ production parser: ไม่ตรวจ byte limit, encoding, HTTP transport หรือ MIME parsing จริง และไม่พิสูจน์ความปลอดภัย/ความถูกต้องของระบบจริง
ต้นทุนและเหตุผลที่ต้องทำงานน้อย
การจำแนกแบบ deterministic ใช้ CPU และหน่วยความจำเพื่ออ่าน body และ parse JSON; เพดาน 8,192 bytes จำกัดขนาดอินพุตที่ฟังก์ชันต้องประมวลผล แต่ไม่ได้รับประกันต้นทุนรวมของบริการ การไม่เรียก LLM เพื่อตีความประโยค error หมายถึงเส้นทางนี้ไม่มีค่า token inference เพิ่ม และการไม่ fetch URI หรือ retry หมายถึงไม่มี request ขาออกเพิ่มจาก classifier ตัวอย่างนี้ อย่างไรก็ตาม ยังมีต้นทุนของ transport ที่เรียก API มาก่อนหน้า, การเก็บ log, พื้นที่เก็บข้อมูล และเวลาคนตรวจผล ซึ่งขึ้นกับจำนวนเหตุการณ์และระบบจริง เราไม่มีตัวเลข latency, token หรือราคา จึงไม่อ้างอัตราประหยัดหรือ performance gain
การตั้งเพดาน body มี tradeoff: ค่ายิ่งต่ำยิ่งลดงาน decode/parse และ memory ต่อ response แต่หากต่ำกว่าขนาด problem document ที่บริการส่งตามปกติ ก็จะเพิ่มผล BODY_TOO_LARGE และการทบทวนแบบ generic โดยไม่ช่วยแก้ต้นเหตุ เลือกค่าจาก contract และขนาด response ที่สังเกตได้ในระบบที่ได้รับอนุญาต แล้วติดตามจำนวนการปฏิเสธโดยไม่เก็บ body ดิบ
การไม่ส่ง detail เข้า LLM ลดทั้ง token ที่ใช้และความเสี่ยงที่ข้อความปนเปื้อนจะมีอิทธิพลต่อ tool แต่แลกกับการไม่สรุปภาษาธรรมชาติให้โดยอัตโนมัติ ผู้ใช้จึงเห็น reason code ที่คงที่และส่งต่อให้คนตรวจ หากภายหลังเพิ่มการสรุป ต้องคิดต้นทุน inference, privacy, latency และการประเมินความถูกต้องเป็นงานแยก อย่ารวมตัวเลขจากตัวอย่าง mock เข้ากับงบ production
Failure cases และการส่งต่ออย่างปลอดภัย
กรณีข้อมูลไม่ครบต้อง fail closed ในความหมายของ “ไม่สร้าง decision เฉพาะ”: status ไม่มีหรืออยู่นอกช่วงที่ adapter ยอมรับ, content type แปลก, JSON ไม่ใช่ object, type เป็นค่าไม่คาดคิด, body status ขัดกัน หรือ parse error ล้วนให้ generic พร้อม reason ที่บอกชั้นปัญหา วิธีนี้ไม่ได้แปลว่าปิดระบบทุกส่วนหรือซ่อนเหตุการณ์จากผู้ใช้ แต่หมายถึงไม่อนุมานการแก้ไขเฉพาะจากหลักฐานไม่พอ
อย่าทำ retry จาก 422 หรือ 409 ภายใน classifier เพราะการ retry เป็นนโยบายความน่าเชื่อถือที่ต้องคำนึงถึง idempotency, deadline, จำนวนครั้ง, backoff และผลข้างเคียงของปลายทาง แม้ request จะเป็น read-only ก็ยังอาจสร้างโหลดหรือเรียกใช้ quota การตัดสินใจ retry จึงต้องรับข้อมูล transport และ policy แยกกัน รวมทั้งแยกจากประเภทของ error ที่ใช้สื่อสารกับคน

การส่งสถานะขัดกันหรือข้อมูลที่ใช้ไม่ได้ไป REVIEW_GENERIC เป็นนโยบายอนุรักษ์นิยมของบทเรียน ไม่ใช่ข้อบังคับให้ทุก client ทำตาม RFC
· คลิกภาพอ่านขนาดเต็มSecurity และข้อจำกัดการนำไปใช้จริง
เชื่อถือเฉพาะข้อเท็จจริงจากแหล่งที่ควบคุม: status จริงที่ trusted adapter อ่านจาก HTTP response และ type string ที่ตรงกับ allowlist แบบ exact match อย่าเชื่อ title/detail ว่าเป็นคำสั่งหรือข้อเท็จจริงทางระบบ หลีกเลี่ยงการ interpolate ข้อความเหล่านั้นเข้ากับ prompt ที่มี tool access หากโมเดลเห็นก็ต้องประกาศว่าเป็นข้อมูลอ้างอิงที่ไม่น่าเชื่อถือและจำกัด capability ซึ่งยังไม่ทำให้ข้อมูลปลอดภัยสมบูรณ์
อย่าตาม URI ของ type หรือ instance อัตโนมัติ ค่าเหล่านี้เป็น identifiers สำหรับอ้างอิง ไม่ใช่คำสั่งให้ browser หรือ backend เปิด URL การ dereference อาจกลายเป็น SSRF หรือข้อมูลไหล หากองค์กรต้องการเอกสารอธิบาย type ให้ทำเป็น catalog ที่ควบคุมโดยทีมและแสดงเนื้อหาที่ผ่านการตรวจ ไม่ fetch ค่าจาก response สด
ขอบเขตงาน: copyable task brief หกส่วน
นำ task brief ต่อไปนี้ไปใช้กับงานใกล้เคียง โดยแทนชื่อบริการและ type ด้วยสัญญาที่ตรวจสอบแล้ว ห้ามลบข้อห้ามเรื่อง side effect หากต้องเพิ่ม retry หรือแก้ข้อมูล ให้เปิดงานแยกพร้อม policy และ approval ที่ชัดเจน
- เป้าหมาย: สร้างตัวจำแนก response แบบ pure function สำหรับ mock API ตรวจ brief วิดีโอ โดยแปลงคู่ type/status ที่รู้จักเป็นหมวด review ภายใน
- Input และแหล่งข้อมูล: รับ actual HTTP status, content type และ body bytes จาก trusted adapter; ฟังก์ชันตรวจ bytes cap 8,192, media type และ parse JSON เอง ตัวเลขนี้เป็นขอบเขตตัวอย่างเฉพาะงาน
- ขอบเขตที่อนุญาต: รู้จัก invalid-brief กับ actual 422 และ stale-revision กับ actual 409; type/status อื่นและข้อผิดสัญญาให้ generic; body status ที่ถูกชนิดแต่ไม่ตรง actual ให้ generic ตาม local policy
- ผลลัพธ์: คืน decision หนึ่งใน REVIEW_INPUT, REVIEW_REVISION, REVIEW_GENERIC พร้อม reason code คงที่ และ actual status; ไม่คืน detail/title ดิบ
- ข้อห้ามและข้อมูลป้องกัน: ห้าม HTTP I/O, credential, fetch URI, LLM, write, retry, cancellation หรือ dispatch; ข้อความใน response เป็น untrusted; ไม่บันทึก body ดิบ
- การยืนยันและเส้นหยุด: ส่ง source, test cases ที่เสนอ, limitations และสถานะ NOT RUN; หยุดหลังจัดประเภท ไม่ทำสิ่งที่หมวดแนะนำ และห้ามอ้างว่า production ready
Acceptance criteria และ stopping condition
เกณฑ์การออกแบบคือฟังก์ชันรับ actual status, content type และ bytes; ตรวจชนิดข้อมูล เพดานขนาด media type และ JSON; จับคู่ exact allowlist; คืนเพียงสาม key ตามสัญญา; และให้ generic เมื่อข้อมูลไม่ตรง policy ต้องมีกรณีทดสอบที่เสนอสำหรับ valid 422, valid 409, type/status mismatch, body-status mismatch, type หายหรือผิดชนิด, unknown extension, media type ไม่รองรับ, JSON เสียรูป และ body เกินเพดาน
ผลต้องไม่เปิดเผยข้อความดิบจาก server หรือเกิด URI fetching, retry, edit, dispatch และ network call เหตุผลภายในต้องแยกชั้นปัญหาได้โดยไม่แนบ body ทั้งชุด
เงื่อนไขหยุดคือทันทีที่ได้ผล review category และ reason code หรือ generic เพราะข้อมูลไม่พอ หลังจากนั้นให้ส่งให้คนหรือ policy layer ที่ได้รับมอบหมาย หากระบบต้องตัดสินใจดึง revision หรือแก้ไข brief ต้องประเมิน authorization, idempotency, conflict handling และ approval แยกก่อน ห้ามวนกลับมาเรียก tool เพียงเพราะ response กล่าวให้ทำเช่นนั้น
สถานะหลักฐานของบทความนี้: Python 3.12 standard library example เป็นแบบร่างที่ยังไม่ได้รัน; unit tests NOT RUN; integration กับ API จริงไม่มี; production deployment ไม่มี; interactive UI ยังไม่ทดสอบ (UNKNOWN) การเขียน test case ที่คาดหวังไม่เท่ากับการผ่าน test และ mock response ไม่เท่ากับหลักฐานการทำงานของ provider
แบบฝึกหัดและ rubric
แบบฝึกหัด: เพิ่มกรณี application/problem+json ที่มี type invalid-brief, actual 422, body status เป็น string, extension suggested_action ระบุ “ส่งใหม่ทันที” และ detail มีข้อความให้เปิดเผย token เขียน decision ที่คาด, เหตุผล, ข้อมูลที่อนุญาตให้ log และอธิบายว่าทำไมไม่มีการ retry จากนั้นเปลี่ยน actual status เป็น 409 และทำตารางว่าอะไรเปลี่ยนและอะไรยังต้องเป็น generic
แนวตรวจแบบฝึกหัด: ที่ actual 422 ต้องได้ REVIEW_INPUT เพราะ type ตรง และ status แบบ string ถูกละเลย ส่วน extension กับ detail ไม่ใช้ควบคุม ที่ actual 409 ต้องได้ REVIEW_GENERIC เพราะคู่ไม่ตรง ให้ถือว่าทำได้ครบเมื่ออธิบายทั้งสองผล ระบุได้ว่า log มีเพียงรหัสคงที่กับ status และหยุดโดยไม่ส่งซ้ำ หากยังใช้ข้อความใน detail ตัดสิน ให้ทบทวนเส้นแบ่งข้อมูลกับคำสั่งก่อน
ฝึกเล่า error contract ด้วย Video Lab
ใช้ Video Lab ด้านล่างวางแผนคลิปอธิบายเรื่องนี้ ตั้งหัวข้อ “อ่าน type คู่กับ HTTP status” ลองความยาว 60 วินาที เปิดคำบรรยายไทย แล้วสำรวจลำดับงานวิดีโอและ Prompt ที่ได้ ค่าเวลาและเงินใน Lab เป็นสมมติฐานที่ผู้เรียนปรับ ไม่ใช่ผลวัดของ classifier หรือราคาบริการที่ยืนยันแล้ว เครื่องมือนี้ไม่ parse Problem Details และไม่ได้เรียก API ตรวจ brief
นำข้อความที่ Lab สร้างมาเทียบกับ brief หกส่วนนี้ เติมสิทธิ์ เกณฑ์รับงาน และจุดหยุดหากยังขาด ผู้เรียนอ่าน brief นี้ได้แม้ JavaScript หรือ canvas ไม่ทำงาน
- เป้าหมาย: อธิบายการจำแนก error สำหรับ Agent ให้ผู้ชมแยก status จริง, type และข้อความสำหรับคนได้
- ข้อมูลและเครื่องมือ: ใช้ตัวอย่างสมมติ 422/invalid-brief, 409/stale-revision และคู่ที่ไม่ตรง ใช้ Lab เพื่อวางแผนคลิปเท่านั้น
- ขอบเขต: แสดงสามหมวดทบทวน ห้ามใช้ข้อมูลลูกค้า credential หรือ endpoint จริง ห้ามแสดงการ retry หรือแก้ brief อัตโนมัติ
- สิ่งส่งมอบ: สคริปต์ไทยและลำดับภาพ transport → parser → type/status → review พร้อมป้าย “ข้อมูลจำลอง”
- เกณฑ์ตรวจรับ: คนตรวจยืนยันว่าคู่ตัวอย่างถูกต้อง ข้อความ detail ไม่กำหนดการทำงาน และค่าที่ไม่รู้จักเข้าหมวดทั่วไป
- จุดหยุด: หยุดเมื่อได้แผนคลิปให้คนทบทวน ไม่มีการผลิต อัปโหลด หรือส่งคำขอจริงจากแบบฝึกหัดนี้
จากคลิปดิบ
สู่จักรวาลคอนเทนต์
ChatGPT ตัดต่อวิดีโอแทนคนได้จริงแค่ไหน? สำรวจระบบ AI Video Editing Agent ผ่านฉากสามมิติที่โต้ตอบได้ พร้อมแยกข้อเท็จจริง สร้างพรอมป์ต์ และจำลองต้นทุนก่อนลงมือทำจริง
ออกแบบระบบตัดต่อในมุมมอง 3D
ลากบนฉากเพื่อหมุนมุมมอง แตะโหนดเพื่อดูหน้าที่ของ AI ในแต่ละขั้นตอน
จัดระเบียบไฟล์ ตรวจคุณภาพภาพและเสียง และกำหนดสิทธิ์เข้าถึงให้ AI Agent
แยกความสามารถจริงจากคำโฆษณา
กดแต่ละประเด็นเพื่ออ่านรายละเอียดและแหล่งข้อมูล
ChatGPT Work เข้าถึงไฟล์ในเครื่องได้ตรวจสอบแล้ว / มีเงื่อนไข+
Astra ใช้โปรแกรมคอมพิวเตอร์ทำงานได้ตรวจสอบแล้ว / ไม่ใช่โปรแกรมตัดต่อในตัว+
ตัดเสร็จใน 5–10 นาทีทุกกรณียังพิสูจน์เป็นเกณฑ์ทั่วไปไม่ได้+
ถูกกว่าจ้างคน 10 เท่าต้องกำหนดต้นทุนให้เทียบกันได้+
หนึ่งไอเดีย สามรูปแบบคอนเทนต์
ปรับรูปแบบวิดีโอ แล้วดูกรอบภาพตัวอย่างและ Prompt เปลี่ยนตามแบบเรียลไทม์
ช่วยตัดต่อ?
พร้อมนำไปใช้กับ AI Agent
ตัวสร้าง Prompt นี้ไม่อัปโหลดคลิป ไม่เรียก API เสียเงิน และไม่ตัดต่อวิดีโอจริง ต้องใช้กับเครื่องมือที่ผู้ใช้อนุญาตภายหลัง
จำลองเวลาและต้นทุนของคุณเอง
เลื่อนตัวเลขเพื่อเปรียบเทียบสถานการณ์สมมติ ไม่ใช่ผล benchmark ที่ยืนยันแล้ว
ตัวเลขเป็นตัวอย่างเพื่อช่วยตัดสินใจ ไม่ใช่ผลประหยัดที่รับประกัน
AI ที่ตัดคลิปเป็น ไม่ได้ทำงานลำพัง
สิ่งที่น่าสนใจของ GPT-6 Astra และ GPT-6.1 Sol ไม่ได้อยู่ที่ว่าตัวโมเดลมีปุ่ม Render MP4 ซ่อนอยู่ แต่คือความสามารถในการจัดการงานหลายขั้นตอนร่วมกับเครื่องมืออื่น: อ่านไฟล์ วางลำดับเรื่อง ใช้แอป ตรวจผลลัพธ์ และแก้งานตามข้อเสนอแนะของมนุษย์
สำหรับคนทำคอนเทนต์ ความต่างสำคัญคือการเปลี่ยนจาก “ส่งคลิปให้ AI แล้วหวังว่าจะได้ผลงาน” ไปเป็น Workflow ที่ตรวจรับได้ทุกขั้น ตั้งแต่การเลือกช่วงเด็ด การสร้างซับ การตรวจคำผิด จนถึงการเรนเดอร์ไฟล์ตามสัดส่วนของแต่ละแพลตฟอร์ม
ข้อควรระวังคือระบบ Work ที่รันบนเว็บหรือมือถือไม่ได้หมายความว่า AI เข้าถึงคลิปบนคอมพิวเตอร์บ้านได้ทันที ต้องทำงานในสภาพแวดล้อมที่เปิดให้เข้าถึงไฟล์จริง มีเครื่องมือตัดต่อ และมีสิทธิ์ใช้งานอย่างชัดเจน
รวบรวมไฟล์ในโฟลเดอร์ที่เข้าถึงได้ ระบุสิทธิ์และข้อจำกัด
ให้ Agent เสนอ Hook, ช่วงตัด และโครงเรื่องก่อนตัดต่อ
ตรวจชื่อเฉพาะ ตัวเลข ภาษาไทย และความถูกต้องของคำกล่าวอ้าง
ตรวจความละเอียด เสียง ระยะเวลา safe zone และคำบรรยาย
FAQ
ทำไมต้องมี type ถ้า HTTP status มีอยู่แล้ว?
Status สื่อกลุ่มผลลัพธ์ในระดับ HTTP ขณะที่ type ที่อยู่ใน allowlist ช่วยแยกกรณีของสัญญาแอปพลิเคชัน ตัวอย่างใช้ทั้งคู่ร่วมกันเพื่อไม่ผูกการตัดสินใจเฉพาะกับ status อย่างเดียว และไม่ยอมรับ type ที่ผิดบริบท
ถ้า type เป็น relative URI ต้องปฏิเสธว่า response ผิดมาตรฐานหรือไม่?
ไม่ควรสรุปเช่นนั้น RFC 9457 อนุญาต URI-reference แต่ adapter นี้มี allowlist แคบและรู้จักเฉพาะ absolute URI สองค่า จึงจัดค่าอื่นเป็นไม่รู้จักตาม local policy และส่ง generic เพื่อทบทวน
ควรใช้ detail ให้ LLM แนะนำการแก้ไขหรือไม่?
ไม่ใช่ในเส้นทาง classifier นี้ เพราะ detail เป็นข้อความจาก response ที่ไม่ควบคุม และอาจมี instruction ที่ไม่ปลอดภัย แสดงให้คนดูได้เมื่อผ่านการ escape และสิทธิ์เหมาะสม แต่การให้โมเดลวิเคราะห์เป็นอีก feature ที่ต้องมี boundary และ evaluation แยก
ทำไม body status ไม่เป็นตัวตัดสินหลัก?
actual status มาจาก HTTP transport ส่วน status ใน body เป็นสมาชิกอธิบาย response ซึ่งผู้สร้างควรให้ตรงกัน เมื่อนโยบายตัวอย่างพบค่าตัวเลขที่ขัดกันจะใช้ generic; นี่เป็น local conservative policy ไม่ใช่ข้อบังคับทั่วไปจาก RFC
จะเพิ่ม type ใหม่ได้อย่างไร?
เพิ่มหลังตรวจสัญญา provider และกำหนดคู่ status, ผล review, reason code และกรณี mismatch แล้วให้ reviewer อนุมัติการเปลี่ยนนโยบาย เขียน test cases และทบทวน log/ข้อมูลส่วนบุคคลก่อนรวม ห้ามเพิ่ม type เพราะข้อความ error ดูคล้ายกันอย่างเดียว
แหล่งอ้างอิงและบทเรียนต่อเนื่อง
ตรวจแหล่งข้อมูลสำหรับข้อเท็จจริงมาตรฐานและหลักความปลอดภัยตามรายการด้านล่าง รายละเอียด allowlist, mismatch-to-generic, เพดาน 8,192 bytes, taxonomy, worked example, acceptance และ rubric เป็นการออกแบบเฉพาะบทเรียน ไม่ใช่ข้อกำหนดจากแหล่งอ้างอิง
- RFC 9457: Problem Details for HTTP APIs
- OWASP Error Handling Cheat Sheet
- GitHub REST API: Troubleshooting the REST API — ตัวอย่างเอกสาร provider เฉพาะ ไม่ใช่หลักฐานว่าใช้ RFC 9457
- API Pagination สำหรับ Agent: รายงานผลที่ยังขาด
- Circuit Breaker สำหรับ AI Agent
- ป้องกัน Tool Output ก่อน Agent นำไปใช้
