
บทเรียน Agent Engineering · LangGraph Python 1.2.15 · ตรวจต้นทาง 14 ตุลาคม 2026
Agent ทำรายงานฉบับใหม่จาก checkpoint เก่าได้ แต่คุณตรวจฉบับเดิมกลับมาหรือยัง? ผลทางเลือกที่ดูถูกต้องยังไม่พิสูจน์ว่า state ของเส้นทางเดิมไม่ปนกับเส้นทางใหม่
Quick Answer: ข่าวนี้เปลี่ยนอะไร และเราจะฝึกอะไร
LangGraph 1.2.15 แก้กรณีการรันงาน (run) จาก checkpoint เก่าที่อาจทำให้ State ของ branch เดิมปนกับค่าที่เขียน (writes) จาก run ใหม่ ขอบเขตของบั๊กนี้คือ DeltaChannel ร่วมกับ durability="exit" และการระบุ checkpoint ที่ thread เดินเลยไปแล้ว ไม่ใช่ข้อสรุปว่าการใช้ checkpoint ทุกแบบเสียหาย รุ่นนี้เผยแพร่ 12 ตุลาคม 2026 เวลา 22:38:26 UTC ตามข้อมูล GitHub Release API [1] [2]
ทักษะเดียวในบทเรียนนี้คือ เลือก checkpoint ในอดีต สร้าง fork แล้วอ่าน checkpoint ปลายเส้นทางเดิมเพื่อพิสูจน์ว่าไม่ได้เปลี่ยน ตัวอย่างใช้ workflow Python กับข้อมูลจำลองในเครื่อง ไม่มีการเรียก LLM หรือ API จริง โค้ดและ tests ยัง NOT RUN ส่วน Lab เป็น simulation ผลที่คาดหมายจึงต้องแยกจากหลักฐานการทดสอบที่ผู้อ่านรันเอง
หลังอ่าน คุณต้องส่งมอบอะไรได้
จัดทำ manifest หรือรายการหลักฐานที่มี thread ID, checkpoint ที่เลือก, ปลายเส้นทางเดิม, จุดเริ่มและปลาย fork พร้อมรายงาน A/B และ values เดิมก่อน/หลัง เกณฑ์ตรวจรับคือ fork ใช้ B แต่การอ่านด้วย config ปลาย A ยังคงได้ A และค่าทั้งชุดเท่าเดิม ถ้าหาไม่พบหรือเจอหลายจุดต้องหยุด ไม่เลือกตัวแรกเพื่อให้โค้ดเดินต่อ
จาก LLM chat สู่ระบบที่มีประวัติให้ตรวจ
ใน LLM chat คุณพิมพ์ว่า “เปลี่ยนแหล่งข้อมูลแล้วสรุปใหม่” จากนั้นอ่านสองคำตอบเปรียบเทียบ แต่ Agent มีข้อมูลจากเครื่องมือและงานหลายขั้น จึงต้องระบุว่ากำลังใช้ข้อมูลฉบับใดและเดินต่อจากตรงไหน ป้าย “ล่าสุด” ใช้แทน ID ของฉบับที่ต้องการตรวจไม่ได้
ตัวอย่างนี้เป็น workflow ที่กำหนดขั้นแน่นอน ไม่มีโมเดลเลือกเครื่องมือเอง เราจำกัดโจทย์เพื่อเห็น state ชัดก่อนเพิ่มความไม่แน่นอน อ่านภาพรวมได้ใน สถาปัตยกรรม Agent: Workflow, Single-Agent และ Multi-Agent
คำที่ต้องเข้าใจก่อนเลือกจุดย้อน
State คือข้อมูลที่ส่งต่อ เช่นโจทย์ ผลเครื่องมือและรายงาน Thread คือหน่วยติดตามประวัติงาน Checkpoint คือสถานะพร้อมตำแหน่งการดำเนินงานที่บันทึกไว้ Branch คือเส้นทางที่แยกจากจุดก่อนหน้า
Checkpointer เก็บ graph state ใน thread ส่วน Store เก็บข้อมูลระดับแอปพลิเคชันข้าม thread ตัวอย่างใช้ InMemorySaver ที่เก็บใน RAM และหายเมื่อ process ปิด จึงไม่ใช่ตัวอย่างกู้หลังเครื่องดับ [4]
เมื่ออ่านสถานะ values บอกข้อมูล next บอกขั้นที่จะทำต่อ และ config ระบุ checkpoint ตัวอย่างเลือก next == ("format_report",) เพื่อเปลี่ยนข้อมูลหลัง mock tool แต่ก่อนรายงาน [5]

Replay, Fork และ Resume ให้ตอบคนละคำถาม
| การทำงาน | คำถามที่ใช้เลือก |
|---|---|
| Replay | จะเดินต่อจากข้อมูลเดิมอีกครั้งหรือไม่ |
| Fork | จะเปลี่ยน state ในอดีตเพื่อสำรวจทางเลือกหรือไม่ |
| Resume งานที่หยุด | จะทำงานที่ค้างภายใต้ประวัติที่บันทึกไว้ต่อหรือไม่ |
Time travel ใช้ update_state กับ historical config แล้ว invoke(None, fork_config) เพื่อเดินต่อ มันสร้าง checkpoint ใหม่โดยไม่ลบประวัติเดิม แต่ขั้นหลังจุดย้อน (downstream nodes) รวม LLM/API/interrupt อาจทำงานใหม่ [6]
ข้อวิเคราะห์สำคัญคือ checkpoint ไม่ undo การกระทำภายนอก: อีเมลที่ส่งหรือโพสต์ที่เผยแพร่ไปแล้วไม่ได้ถูกเรียกคืนจากการ fork ตัวอย่างจึงใช้ formatter ที่คืนข้อความ ไม่มีเครื่องมือส่งจริง
Worked example: รายงานข้อมูล A กับทางเลือก B
สถานการณ์สมมติ: รายงานภายในอ่าน fixture A “เอกสารขั้นตอนเดิม” ส่วนทางเลือกใช้ B “เอกสารขั้นตอนทางเลือก” Fixture คือข้อมูลทดสอบที่เราตั้งเอง ไม่ใช่ฐานข้อมูลบริษัท
คำสั่งคือ “ทำรายงาน A แล้วลอง B ที่จุดก่อนรายงาน พร้อมยืนยันว่าฉบับ A เหมือนเดิม” แผนคืออ่านข้อมูล → checkpoint ก่อนรายงาน → รายงาน A → fork จากจุดที่เลือก → รายงาน B → อ่าน checkpoint ปลาย A → ตรวจรับ
Mock tool read_fixture เลือกข้อมูลในเครื่องและคืนสำเนา ไม่เรียก API ส่วน format_report ตรวจ field path และ text ก่อนประกอบรายงาน เราเก็บ config ปลาย A และสำเนา values ก่อน แล้ว patch fixture B ที่จุดก่อน formatter ด้วย as_node="read_fixture" เพราะแก้ผลที่ node นี้เคยส่งออก ไม่ได้เรียก mock tool ซ้ำ
Expected output คือ “เส้นทาง A | เอกสารขั้นตอนเดิม” กับ “เส้นทาง B | เอกสารขั้นตอนทางเลือก” ยังไม่ใช่ผลรันจริง หลักฐานตรวจรับต้องรวมผลอ่านกลับ A/B ด้วย config ปลายแต่ละฉบับ ไม่ใช้รายงานใหม่เพียงบรรทัดเดียว

โค้ด Python: เก็บหลักฐานก่อน แล้วจึงแยกเส้นทาง
ใช้ Python 3.12+ และตรึง langgraph==1.2.15 Package รองรับ Python>=3.10; dependency ตาม tag มี langchain-core, checkpoint, SDK และ prebuilt [8] ไม่ต้อง API key/environment variable ไม่มี model/API billing ในตัวอย่าง แต่ใช้ทรัพยากรเครื่องและการดาวน์โหลด package หากติดตั้งเอง
โครงสร้างคือ START → read_fixture → format_report → END imports ตรวจจาก Graph exports ที่ tag [11] ชุดท้ายบทความมีไฟล์เต็ม ช่วงหลักคือ:
original_final = graph.get_state(thread_config)
original_config = deepcopy(original_final.config)
original_values = deepcopy(original_final.values)
selected = select_before_report(graph, thread_config)
fork_start_config = graph.update_state(
selected.config,
{"scenario": "alternate", "fixture": deepcopy(FIXTURES["alternate"])},
as_node="read_fixture",
)
fork_output = graph.invoke(None, fork_start_config)
original_readback = deepcopy(graph.get_state(original_config).values)
assert original_readback == original_values
deepcopy แยกค่าที่เก็บไว้ตรวจจากข้อมูลที่อาจแก้ภายหลัง ไม่สร้าง transaction ให้ฐานข้อมูล ตัวช่วยเลือกด้วย next ยอมรับหนึ่งจุด; หากมีหลายรอบ/branch ต้องระบุ checkpoint ID จาก history ไม่เดา list index นี่เป็นกติกาตรวจรับที่เราออกแบบ
fork_start_config ชี้จุดก่อน formatter ไม่ใช่ปลาย fork โค้ดเต็มจึงเก็บ fork_final_config เพิ่ม ตรวจว่า terminal และ parent chain กลับถึง fork start แล้วอ่าน B ผ่าน config นั้น การจับ latest หลัง invoke ใช้ได้ใน ตัวอย่างหนึ่ง worker ที่ไม่มีใครเขียน thread ระหว่างนั้น ระบบ concurrent ต้องมี run ownership/correlation ไม่ถือว่าหัวล่าสุดเป็นงานของเราเสมอ ส่วนการตรวจ A ใช้ original final config ที่เก็บไว้เท่านั้น
วิธีติดตั้งและทดสอบ: ให้ผู้อ่านทำในเครื่องที่มีสิทธิ์
ผู้เขียน NOT RUN ไม่ได้ติดตั้ง รัน import หรือ compile ตัวอย่าง คำสั่งนี้เปลี่ยน virtual environment ของผู้อ่านและเข้าถึง package registry ไม่ใช่การทำงานของ Lab บนเว็บ:
py -3.12 -m venv .venv
.venv\Scripts\python.exe -m pip install -r requirements.txt
.venv\Scripts\python.exe code\checkpoint_fork.py
.venv\Scripts\python.exe -m unittest discover -s code -p "test_checkpoint_fork.py" -v
Input คือ scenario="original" แล้ว controlled fork ใช้ "alternate" ผลที่คาดหมายมีรายงาน A/B, original_unchanged: true และ runtime-generated IDs อย่าอ้าง expected output เป็น log จริง
Tests ใช้ standard-library unittest: ตรวจ branch A/B, next/ID, terminal, ID ที่ไม่พบ, ประวัติคลุมเครือ และ fork ที่ fixture ผิด schema แต่ A ต้องเหมือนเดิม ยังไม่รันทุกข้อ มี assertion ไม่เท่ากับ PASS และอย่าใช้ Python -O ซึ่งปิด assert กับ demo นี้
ทำไมเราไม่ใช้ DeltaChannel เป็นพื้นฐานทั้งหมด
ข่าวเกี่ยวกับ DeltaChannel แต่ตัวอย่างหลักใช้ state ธรรมดาเพื่อฝึก fork ก่อนรายละเอียด storage อ่าน source [9] และ regression test ที่ tag [10] สำหรับบั๊กเฉพาะ
DeltaChannel ยัง beta; reducer รับ current state กับ batch ของ writes ต้อง deterministic และ batching-invariant จึงแทนด้วย ordinary binary operator.add โดยไม่ตรวจสัญญาไม่ได้ เราไม่ได้รัน reproduction รายงาน tests ของผู้พัฒนาและ community ใน PR9269 [3] เป็นผลของผู้อื่น ไม่ใช่ Hybrid Mind runtime PASS และไม่พิสูจน์ migration/storage ที่ไม่ได้ตรวจ
จุดที่งานอาจพัง และเงื่อนไขหยุด
- เลือก list index: ตำแหน่งเปลี่ยนเมื่อมี checkpoint เพิ่ม
- เลือกปลายงาน: next ว่างอาจได้ no-op แทนทดลอง formatter
- ตรวจ latest: ไม่ได้ตอบว่า checkpoint ปลาย A เปลี่ยนหรือไม่
- patch field ที่มีกฎรวมค่า (reducer): ค่าอาจสะสมแทนการแทนที่
- เจอหลายจุด: หยุดระบุ checkpoint ID ให้ตรงช่วง ไม่เดา
- มี downstream external write: หยุดก่อนทดลองจนมีอำนาจและแผนกันซ้ำ
รายงานหยุดให้ระบุสิ่งที่ขาด เช่น “มีสอง candidates” หรือ “fixture ไม่มี text” อย่าเติมรายงานสมบูรณ์กลบ error ตัวอย่างไม่มี concurrent workers; หากหลาย Agent เขียนร่วมกัน อ่าน Blackboard Architecture และสถานะร่วมแบบมีเวอร์ชัน เพิ่มก่อนออกแบบสิทธิ์เขียน

Persistence, ความปลอดภัย และต้นทุนที่ต้องรายงานตรง ๆ
Durability exit บันทึกตอน run ออก; async บันทึกระหว่างขั้นถัดไป; sync บันทึกก่อนขั้นถัดไป โหมด exit จึงไม่เก็บสถานะระหว่างทางเพื่อกู้เมื่อ process พังกลาง run [5] ตัวอย่าง RAM ไม่วัด latency/storage และไม่สร้างเปอร์เซ็นต์ความเร็วให้แต่ละโหมด
ในระบบจริง ให้บันทึก data revision กับ tool result ที่ใช้ ไม่ใช่เพียง checkpoint ID และเลือกโหมดจากงานที่ยอมเสียได้กับจุดที่คนตรวจ นี่เป็นข้อเสนอออกแบบ ไม่ใช่ผลทดสอบ production
State/log อาจมีข้อมูลละเอียด ต้องกำหนดผู้เข้าถึง อายุการเก็บ และ secret handling ตัวอย่างนี้มีเพียงข้อความสมมติ ไม่มี tool เปลี่ยนข้อมูลภายนอก การไม่ฝัง secret ไม่แทน security review ของระบบบริษัท ส่วนต้นทุนจริงต้องมาจากจำนวน run/model calls/tool/storage ที่บันทึก ไม่ถือว่าเป็นศูนย์เพราะหน้านี้ไม่มีราคา
Task brief หกส่วนสำหรับนำไปใช้
ข้อความต่อไปนี้เป็น brief สมมติสำหรับฝึกกับไฟล์ตัวอย่าง อ่านหรือคัดลอกได้โดยไม่ต้องใช้ปุ่ม มันมอบหมายงานตรวจ branch เท่านั้น ไม่เพิ่มอำนาจเรียก Agent จริง
1. เป้าหมาย: ทำรายงาน fixture A แล้ว fork จากจุดก่อนรายงาน
เพื่อเปรียบเทียบ fixture B โดยรักษาหลักฐานของ A
2. ข้อมูล/เครื่องมือ: Python 3.12+, LangGraph 1.2.15,
local fixtures และ InMemorySaver; ไม่มี LLM/API/secret
3. ขอบเขตสิทธิ์: อ่านข้อมูลจำลองและทำงานใน RAM เท่านั้น
ห้ามส่งข้อความ เผยแพร่ หรือเปลี่ยนข้อมูลภายนอก
4. สิ่งส่งมอบ: manifest พร้อม thread/checkpoint IDs,
report A/B และ values ของ original ก่อน/หลัง fork
5. เกณฑ์ตรวจรับ: fork ใช้ B, original final config ยังได้ A,
values เดิมเท่าเดิม และระบุจุดที่เลือกจาก next ได้
6. จุดหยุด: checkpoint ไม่พบ/มีหลายจุด/fixture ผิด schema
ให้รายงานสาเหตุ; ถ้าต้องใช้ tool เขียนจริงให้ส่งคนตัดสิน
แบบฝึกหนึ่งข้อ: ทางเลือกถูกต้อง แต่จุดตรวจผิดได้ไหม
ทำ A ให้เสร็จ เก็บ original final config แล้ว fork B ตามตัวอย่าง เพิ่มคอลัมน์ใน manifest ว่าแต่ละการอ่านใช้ config ใด เปรียบเทียบการอ่านด้วย thread ID อย่างเดียวกับการอ่านด้วย original final config โดยไม่แก้โค้ดบังคับผล
ส่ง manifest หนึ่งชุดพร้อมเหตุผลว่าแถวใดตรวจ A และแถวใดบอกหัวประวัติปัจจุบัน Rubric ที่ออกแบบคือเลือกจุดก่อน formatter ได้, B ตรง fixture, A เท่าค่าเดิม และอธิบาย config ได้ หากคลุมเครือ ส่งรายงานหยุดพร้อม candidates แทน ยังแสดงทักษะการไม่เดาจุดย้อน
ห้ามให้คะแนนหรือ PASS จนรันและเก็บหลักฐานจริง แบบฝึกไม่ตรวจโมเดล เครื่องดับ ฐานข้อมูล หรือทุก branch ในระบบอื่น
Lab จำลอง: เห็นเส้นทางแล้วตรวจหลักฐาน
ฝึกเลือกจุดย้อนและตรวจ A/B ใน simulation ด้านล่าง ปุ่มไม่รัน Python/API หรือสร้าง LangGraph checkpoint จริง ต้องทดสอบโค้ดแยก
TOPIC 3D · SIMULATION · ไม่รัน LangGraph
อ่าน branch เดิมผ่าน config เดิม
เลือก C1 ก่อนจัดรูปแบบรายงาน แล้วดูสาขาทดลอง F1 แยกจาก C2 ของเดิม C0, C1, C2 และ F1 เป็นชื่อกับค่าที่ผู้เขียนตั้ง ไม่ใช่ checkpoint ID หรือ log ที่ได้จากการรันจริง ภาพนี้ลดขั้นตอนภายในให้เหลือความสัมพันธ์ที่ต้องตรวจ ไม่ใช่ผังของทุก checkpoint ที่ LangGraph สร้าง
การเลือก “ค่าปน” แสดงผลผิดที่สร้างขึ้นเพื่อฝึกตรวจรับ ไม่ได้จำลอง implementation ของ DeltaChannel หรือพิสูจน์บั๊กของเครื่องคุณ บั๊กในข่าวจำกัดที่ DeltaChannel + durability=”exit” + run ที่อ้าง checkpoint เก่าตาม PR9269 การอัปเกรดไม่ซ่อมค่าที่ปนไปแล้วโดยอัตโนมัติ
C0
รับคำขอ
รายงานจาก fixture A
C1 · จุดในอดีต
อ่าน fixture A แล้ว
ยังเหลือ format_report
C2 · ปลายเดิม
report=A · ไม่มี FORK_ONLY
เก็บ config ของจุดนี้ไว้ก่อน
F1 · สาขาทดลอง
report=B · FORK_ONLY
latest ในแบบฝึกชี้ F1
C1 → F1 เป็นภาพย่อของการอัปเดต state แล้วรัน downstream ต่อ
ผลตามกฎจำลอง: ค่า C2 เดิมคงอยู่
SIMULATION / ผลที่ตั้งไว้ ไม่ใช่ log จริง
selected_config = {thread_id: lesson, checkpoint_id: C1}
saved_original_config = {thread_id: lesson, checkpoint_id: C2}
original_before = {report: A, fork_only: false}
fork_result = {report: B, fork_only: true}
get_state(saved_original_config) = {report: A, fork_only: false}
comparison = unchanged ตามข้อมูลสมมตินี้
latest = F1; อ่าน latest อย่างเดียวไม่ใช่การตรวจ C2
หากปุ่ม ภาพสามมิติ หรือ JavaScript ไม่พร้อม ให้อ่านตารางด้านล่างเป็นผลตามกฎของแบบฝึก การตอบสนองของปุ่มและหน้าจอจริงยัง UNKNOWN การเปลี่ยนมุมภาพไม่เปลี่ยน state ของระบบภายนอก
| เลือก | ขั้นที่จะรันต่อ | อ่าน config C2 เดิม | ข้อสรุป |
|---|---|---|---|
| C1 / แยกสาขา | สาขาทดลองได้ report B | report A, ไม่มี FORK_ONLY | ค่าเดิมไม่เปลี่ยนใน fixture นี้ |
| C1 / ตัวอย่างค่าปน | สาขาทดลองได้ report B | report B, มี FORK_ONLY | พบความต่าง ต้องหยุดตรวจ ไม่รับรอง isolation |
| C2 / replay | next=[] จึงไม่มี downstream node | report A | No-op ไม่ใช่การรัน formatter ใหม่ |
แหล่งกลไก: Checkpointers, Time travel และ PR9269 ค่าของ Lab เป็น SIMULATION; runtime, integration และการตรวจ browser ยังไม่ได้รัน
VISUAL INTERACTIVE แยกจากภาพ 3D · PLAN ONLY
เลือกวิธีกลับมาทำงาน ก่อนสั่งให้รันซ้ำ
Inspect อ่าน snapshot ส่วน Replay รัน downstream ใหม่ และ Fork เพิ่ม state ที่แก้แล้วเดินต่อ แบบฝึกนี้เพิ่มสถานการณ์สมมติของ node อ่าน fixture หรือ node ส่งงานภายนอก เพื่อเห็นว่าจุดเริ่มเดียวกันอาจต้องมีแผนต่างกัน ปุ่มสร้างเพียงคำอธิบาย ไม่เรียก API ไม่ส่งโพสต์ และไม่สร้าง checkpoint จริง
PLAN_ONLY · Fork กับ fixture
- เก็บ config และ values ของ C2 เดิม
- อ่าน C1 ที่ระบุชัด ไม่ใช้ latest แทน
- เตรียม update_state ที่ producer node ตรงกับ state
- รัน downstream กับ fixture ที่แก้ในแผนสมมติ
- อ่าน C2 ผ่าน config เดิมแล้วเทียบค่า
หากดำเนินการตามแผนจริง: downstream จะรันใหม่ ปุ่มนี้ยังไม่รันอะไร
ข้อมูลสมมติอยู่ในหน่วยความจำ ไม่มี network, LLM, credential หรือการส่งงาน
ถ้า interactive ไม่พร้อม ใช้ตารางกฎนี้ได้ทันที คำว่าแผนครบเงื่อนไขไม่ได้ยืนยันสิทธิ์จริง ปลายทางรองรับ idempotency หรือความปลอดภัยใน Production ผลทดสอบ code/runtime NOT RUN; browser UI UNKNOWN
| กรณี | ผลตามกฎ | เหตุผล |
|---|---|---|
| Inspect ทุก checkpoint | READ_ONLY_PLAN | อ่าน values/config/next ไม่เรียก downstream |
| Replay C2 ที่ next=[] | NO_OP | ไม่มี downstream ให้รัน ไม่ปลอม as_node ว่ามีงานค้าง |
| Fork C2 ในแบบฝึกนี้ | HOLD_EXAMPLE_SCOPE | แบบฝึกกำหนด fork ก่อน formatter ที่ C1; ไม่ใช่ข้อห้ามของ LangGraph ทุกกรณี |
| Replay/Fork C1 กับ fixture | PLAN_ONLY | downstream จะรันใหม่หากนำไป execute; ตอนนี้แค่แสดงแผน |
| External write ยังไม่อนุมัติ | STOP_PERMISSION | ความสามารถ replay ไม่ได้เพิ่มสิทธิ์ส่งงาน |
| External write ผลเดิมคลุมเครือ | STOP_RECONCILE | อ่านผลเดิมก่อน ห้ามสร้าง operation ID ใหม่เพื่อกลบความไม่รู้ |
| External write พบผลเดิมสำเร็จ | STOP_DUPLICATE | ใช้หลักฐานผลเดิม ไม่ส่งซ้ำเพื่อทดสอบ replay |
| External write อนุมัติ + มีแผน reconcile/idempotency | READY_FOR_HUMAN_REVIEW | แผนตามกฎสมมติครบ ยังไม่สั่งส่งหรือรับรอง exactly-once |
อ่านต่อ: Replay และ Fork กับ Determinism / Idempotency การอ่านคืน task ที่บันทึกเสร็จระหว่าง resume ต่างจาก deliberate historical replay อย่าเหมารวมว่าการกลับมาทำงานทุกแบบจะใช้ผลเดิมหรือเรียกซ้ำเหมือนกัน
Video Agent 3D Lab: เปรียบเทียบ brief สองฉบับ
Lab วิดีโอที่ติดตั้งใช้เป็นฉากเปรียบเทียบ brief A/B ชี้จุด “หลังข้อมูล ก่อนทำบท” และจด manifest แต่ละฉบับแยก มันไม่ได้เพิ่ม checkpoint/fork ให้ปลั๊กอิน และไม่ทดสอบ persistence ของ LangGraph
Brief หกส่วน: เป้าหมาย:วิดีโออธิบาย A แล้วเสนอ B; ข้อมูล/เครื่องมือ:fixture สองชุดกับภาพ workflow; สิทธิ์:จำลองและร่างเท่านั้น; สิ่งส่งมอบ:brief A/B กับรายการขั้นที่ใช้แต่ละฉบับ; เกณฑ์:A ยังอ้าง A หลังร่าง B; จุดหยุด:ข้อมูลขาดหรือเสนอเผยแพร่จริงให้ส่งคนตรวจ อย่าอ้าง generator เติม brief ครบเอง
Fallback:เขียน A/B คนละคอลัมน์บนกระดาษ จดจุดร่วมและจุดแยก แล้วตรวจ A หลังแก้ B ตัวเลขเวลา/เงินใน Lab เป็นค่าตั้ง simulation ไม่ใช่ผลวัดหรือราคาปัจจุบัน ปุ่มและหน้าจอจริงยัง UNKNOWN
จากคลิปดิบ
สู่จักรวาลคอนเทนต์
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 และคำบรรยาย
Checklist ก่อนเพิ่มระบบจริง
- เลือก replay/fork ให้ตรงคำถาม
- เก็บ config และ values เดิมก่อนทำทางเลือก
- เลือก checkpoint ด้วย next และ ID
- ตรวจ node ของ patch และ parent lineage
- อ่าน A/B ด้วย config ปลายแต่ละฉบับ
- แยก expected กับผลทดสอบจริง
Checkpoint อาจเก็บข้อมูลที่ยังอ่านไม่ครบ ตรวจขอบเขตต้นทางเพิ่มใน Pagination: API ตอบ 200 แต่ Agent อ่านครบหรือยัง ก่อนใช้ข้อมูลเป็น state
FAQ
1. รุ่นนี้แก้ checkpoint ทุกกรณีหรือไม่
ยืนยันเฉพาะ change ที่ตรวจต้นทางได้ ไม่รับรองทุก storage/workload
2. Fork ต่างจากเริ่มงานใหม่อย่างไร
ระบุ state และจุดในอดีตที่ใช้ตั้งต้นได้ ตัวอย่างรักษา A แล้วเปลี่ยนข้อมูลก่อน formatter
3. ทำไมอ่าน latest ไม่พอ
มันตอบตำแหน่งปัจจุบัน ไม่ยืนยันค่า checkpoint ปลาย A ที่ตั้งใจตรวจ
4. อัปเกรดแล้วประวัติผิดจะหายเองไหม
PR9269ระบุว่าข้อมูล branch ที่ปนไปแล้วคงอยู่ ต้องตรวจและออกแบบการกู้แยก
5. เพิ่มเครื่องมือส่งจริงได้เลยไหม
ต้องกำหนดสิทธิ์และการตรวจผลก่อน checkpoint ไม่ undo ผลภายนอก
6. โค้ดและ Lab ผ่านหรือยัง
Code/tests NOT RUN; Lab เป็น simulation; UI UNKNOWN ไม่มีผล live/production ของผู้เขียน
Source code และคำแนะนำทดสอบ
ไฟล์เขียนสำหรับบทเรียนนี้ตาม public API เป็น local synthetic NOT RUN อ่าน README ก่อนติดตั้งเอง
code/checkpoint_fork.py · NOT RUN
"""Synthetic checkpoint/fork lesson. No model, network or external writes.
Editorial test status: NOT RUN. Assertions are acceptance checks for the reader
to execute; their presence is not evidence that this program has been tested.
"""
from __future__ import annotations
from copy import deepcopy
import json
from typing import Any, TypedDict
from uuid import uuid4
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import END, START, StateGraph
FIXTURES = {
"original": {"path": "A", "text": "เอกสารขั้นตอนเดิม"},
"alternate": {"path": "B", "text": "เอกสารขั้นตอนทางเลือก"},
}
class LessonState(TypedDict, total=False):
scenario: str
fixture: dict[str, str]
report: str
def read_fixture(state: LessonState) -> dict[str, Any]:
"""Mock tool node: select local synthetic data; no API call."""
scenario = state.get("scenario")
if scenario not in FIXTURES:
raise ValueError("Unknown synthetic scenario.")
return {"fixture": deepcopy(FIXTURES[scenario])}
def format_report(state: LessonState) -> dict[str, str]:
"""Pure formatter. Reject incomplete mock tool data."""
fixture = state.get("fixture")
if not isinstance(fixture, dict) or set(fixture) != {"path", "text"}:
raise ValueError("Fixture must contain only path and text.")
if any(not isinstance(value, str) or not value.strip()
for value in fixture.values()):
raise ValueError("Fixture values must be nonempty strings.")
return {"report": f"เส้นทาง {fixture['path']} | {fixture['text']}"}
def build_graph() -> Any:
builder = StateGraph(LessonState)
builder.add_node("read_fixture", read_fixture)
builder.add_node("format_report", format_report)
builder.add_edge(START, "read_fixture")
builder.add_edge("read_fixture", "format_report")
builder.add_edge("format_report", END)
return builder.compile(checkpointer=InMemorySaver())
def select_before_report(
graph: Any,
thread_config: dict[str, Any],
checkpoint_id: str | None = None,
) -> Any:
"""Select by execution boundary, never by a history-list index.
This lesson accepts exactly one matching checkpoint. With multiple turns or
branches, the caller must specify an ID from the displayed history.
"""
candidates = [
snapshot
for snapshot in graph.get_state_history(thread_config)
if snapshot.next == ("format_report",)
and (
checkpoint_id is None
or snapshot.config["configurable"]["checkpoint_id"] == checkpoint_id
)
]
if len(candidates) != 1:
raise ValueError(
"Expected one checkpoint before format_report; "
"inspect history and supply its exact checkpoint_id."
)
return candidates[0]
def checkpoint_lineage(
graph: Any, snapshot: Any, ancestor_config: dict[str, Any], limit: int = 12
) -> list[str]:
"""Read a bounded parent chain; stop if the expected ancestor is absent."""
ancestor_id = ancestor_config["configurable"]["checkpoint_id"]
ids: list[str] = []
current = snapshot
for _ in range(limit):
current_id = current.config["configurable"]["checkpoint_id"]
if current_id in ids:
raise RuntimeError("Unexpected cycle in checkpoint lineage.")
ids.append(current_id)
if current_id == ancestor_id:
return ids
if current.parent_config is None:
break
current = graph.get_state(current.parent_config)
raise RuntimeError("Fork start not found in the bounded parent chain.")
def run_demo() -> dict[str, Any]:
graph = build_graph()
thread_config = {"configurable": {"thread_id": str(uuid4())}}
graph.invoke({"scenario": "original"}, thread_config)
original_final = graph.get_state(thread_config)
if original_final.next:
raise RuntimeError("Original branch has not finished.")
original_config = deepcopy(original_final.config)
original_values = deepcopy(original_final.values)
selected = select_before_report(graph, thread_config)
fork_start_config = graph.update_state(
selected.config,
{
"scenario": "alternate",
"fixture": deepcopy(FIXTURES["alternate"]),
},
as_node="read_fixture",
)
if graph.get_state(fork_start_config).next != ("format_report",):
raise RuntimeError("Fork is not at the expected boundary.")
fork_output = graph.invoke(None, fork_start_config)
# ONE WORKER demo: no other invocation can advance this thread between
# invoke and this capture. A concurrent production thread needs its own
# run ownership/correlation; its latest head is not necessarily our fork.
fork_final = graph.get_state(thread_config)
if fork_final.next:
raise RuntimeError("Captured fork is not terminal.")
fork_final_config = deepcopy(fork_final.config)
lineage = checkpoint_lineage(graph, fork_final, fork_start_config)
fork_readback = deepcopy(graph.get_state(fork_final_config).values)
assert fork_readback == fork_output
original_readback = deepcopy(graph.get_state(original_config).values)
assert original_readback == original_values
assert original_readback["report"] == (
"เส้นทาง A | เอกสารขั้นตอนเดิม"
)
assert fork_output["report"] == (
"เส้นทาง B | เอกสารขั้นตอนทางเลือก"
)
assert original_readback["fixture"]["path"] == "A"
return {
"mode": "synthetic_no_model_no_network",
"thread_id": thread_config["configurable"]["thread_id"],
"selected_checkpoint_id": selected.config["configurable"]["checkpoint_id"],
"original_final_checkpoint_id": (
original_config["configurable"]["checkpoint_id"]
),
"fork_start_checkpoint_id": (
fork_start_config["configurable"]["checkpoint_id"]
),
"fork_final_checkpoint_id": (
fork_final_config["configurable"]["checkpoint_id"]
),
"fork_final_parent_checkpoint_id": (
fork_final.parent_config["configurable"]["checkpoint_id"]
if fork_final.parent_config else None
),
"fork_lineage_checkpoint_ids": lineage,
"original_report": original_readback["report"],
"fork_report": fork_readback["report"],
"original_unchanged": original_readback == original_values,
}
if __name__ == "__main__":
print(json.dumps(run_demo(), ensure_ascii=False, indent=2))
code/test_checkpoint_fork.py · NOT RUN
"""Synthetic acceptance tests. Editorial status: NOT RUN."""
from copy import deepcopy
import unittest
from checkpoint_fork import (
FIXTURES,
build_graph,
run_demo,
select_before_report,
)
class CheckpointForkTests(unittest.TestCase):
def completed_graph(self):
graph = build_graph()
config = {"configurable": {"thread_id": self.id()}}
graph.invoke({"scenario": "original"}, config)
return graph, config
def test_fork_returns_alternative_and_original_is_unchanged(self):
result = run_demo()
self.assertTrue(result["original_unchanged"])
self.assertEqual(result["original_report"], "เส้นทาง A | เอกสารขั้นตอนเดิม")
self.assertEqual(result["fork_report"], "เส้นทาง B | เอกสารขั้นตอนทางเลือก")
self.assertNotEqual(
result["original_final_checkpoint_id"],
result["fork_start_checkpoint_id"],
)
self.assertNotEqual(
result["fork_start_checkpoint_id"],
result["fork_final_checkpoint_id"],
)
self.assertEqual(
result["fork_lineage_checkpoint_ids"][0],
result["fork_final_checkpoint_id"],
)
self.assertIn(
result["fork_start_checkpoint_id"],
result["fork_lineage_checkpoint_ids"],
)
def test_selection_requires_boundary_not_history_position(self):
graph, config = self.completed_graph()
selected = select_before_report(graph, config)
self.assertEqual(selected.next, ("format_report",))
selected_id = selected.config["configurable"]["checkpoint_id"]
addressed = select_before_report(graph, config, selected_id)
self.assertEqual(addressed.config, selected.config)
def test_terminal_checkpoint_is_not_a_before_report_checkpoint(self):
graph, config = self.completed_graph()
terminal = graph.get_state(config)
self.assertEqual(terminal.next, ())
terminal_id = terminal.config["configurable"]["checkpoint_id"]
with self.assertRaises(ValueError):
select_before_report(graph, config, terminal_id)
def test_unknown_checkpoint_id_stops_instead_of_guessing(self):
graph, config = self.completed_graph()
with self.assertRaises(ValueError):
select_before_report(graph, config, "missing-checkpoint")
def test_ambiguous_history_requires_explicit_checkpoint_id(self):
graph, config = self.completed_graph()
first = select_before_report(graph, config)
first_id = first.config["configurable"]["checkpoint_id"]
graph.invoke({"scenario": "alternate"}, config)
with self.assertRaises(ValueError):
select_before_report(graph, config)
self.assertEqual(
select_before_report(graph, config, first_id).config,
first.config,
)
def test_failed_fork_leaves_addressed_original_values_unchanged(self):
graph, config = self.completed_graph()
original = graph.get_state(config)
original_config = deepcopy(original.config)
original_values = deepcopy(original.values)
selected = select_before_report(graph, config)
bad_fork = graph.update_state(
selected.config,
{"fixture": {"path": "B"}}, # Missing text: synthetic failure.
as_node="read_fixture",
)
with self.assertRaises(ValueError):
graph.invoke(None, bad_fork)
self.assertEqual(graph.get_state(original_config).values, original_values)
self.assertEqual(FIXTURES["original"]["path"], "A")
if __name__ == "__main__":
unittest.main()
code/README.md · NOT RUN
# Checkpoint fork lesson — synthetic example
Editorial status: **NOT RUN**. The author has not executed, imported, compiled,
installed or tested these files. Expected output and tests describe acceptance
criteria for a reader's own authorized local run.
## Scope
Python 3.12+; LangGraph pinned to1.2.15 (package supports Python>=3.10).
No LLM, API key, environment variables, network requests, files/database writes,
publishing tools or paid calls exist in the example. The mock tool reads two
local fixtures. InMemorySaver stores checkpoints in RAM and loses them at
process exit. This is not a production restart-recovery or live-integration demo.
## Files
- checkpoint_fork.py: independently authored smallStateGraph and fork manifest.
- test_checkpoint_fork.py: six synthetic acceptance/failure tests.
- ../requirements.txt: langgraph==1.2.15; transitive requirements come from its package metadata.
- No pytest dependency: tests use Python standard-library unittest.
## Installation (reader instructions; NOT performed by the writer)
Run these commands in this run directory in a local environment you control.
Package installation accesses the package registry and changes your chosen
virtual environment; it is not part of the automatic editorial run.
Windows PowerShell:
```powershell
py -3.12 -m venv .venv
.venv\Scripts\python.exe -m pip install -r requirements.txt
.venv\Scripts\python.exe code\checkpoint_fork.py
.venv\Scripts\python.exe -m unittest discover -s code -p "test_checkpoint_fork.py" -v
```
Linux/macOS withPython3.12:
```sh
python3.12 -m venv .venv
.venv/bin/python -m pip install -r requirements.txt
.venv/bin/python code/checkpoint_fork.py
.venv/bin/python -m unittest discover -s code -p 'test_checkpoint_fork.py' -v
```
No environment variable configuration is required. There is no model/provider
billing from this example; local resources and package download still exist.
## Input and expected output
Input inside run_demo: scenario="original". The controlled fork replaces the
recorded mockfixture with scenario="alternate" afterread_fixture and before
format_report. You may inspect or alter the fixtures locally.
Expected (NOT observed by the writer), omitting runtime-generated IDs:
```json
{
"mode": "synthetic_no_model_no_network",
"original_report": "เส้นทาง A | เอกสารขั้นตอนเดิม",
"fork_report": "เส้นทาง B | เอกสารขั้นตอนทางเลือก",
"original_unchanged": true
}
```
The executable emits thread, selected, original-final, fork-start, fork-final
and parent/lineage IDs too. Values are generated at a reader's runtime and must
not be copied as claimed logs. fork-start addresses the new checkpoint before
the formatter; fork-final addresses the captured terminal checkpoint.
ONE WORKER assumption: after invoke finishes, this demonstration alone can
advance its thread, so it captures the latest snapshot and stores its full config.
It verifies terminal next, expected parent ancestry through a bounded read chain,
and rereads the fork by that captured config. In a concurrent production thread,
latest cannot be assumed to belong to our fork: use explicit run ownership and
correlation before accepting it.
Acceptance: address the saved original-final config after the fork, compare
deepcopied values, show the alternative output, and record IDs. Selecting a
terminal checkpoint or an unknown/ambiguous checkpoint must stop rather than
guessing an index.
## Design and limits
- StateGraph -> read_fixture mocknode -> format_report pure node -> END.
- select_before_report selects snapshot.next==("format_report",).
- Multiple matches require a checkpoint_id chosen from inspected history.
- update_state usesas_node="read_fixture" because the patch replaces that
node's already-recorded synthetic output; it does not call the mock tool again.
- invoke(None,fork_start_config) runs the downstream formatter.
- Original verification uses original_final.config, never the latest thread head.
- Fork final is captured only under ONE WORKER, then reread by fork_final.config;
terminal next, fork-start lineage, and fork-output equality are checked.
- Standardstate channels are used; this is not a repro ofDeltaChannel's
exit-durability regression and does not prove that bug fixed locally.
- Replay/fork can execute downstream node operations again in a real graph.
Checkpoints do not undo external sends or publications. Do not add such a
tool without a separate authorization/idempotency/reconciliation design.
- It intentionally tests one sequential graph without concurrent workers,
subgraphs, durable storage, migrations, process crashes or model decisions.
- Python assert can be disabled withoptimization(-O); do not use -O for this
acceptance demo, and use explicit validation exceptions in production logic.
## Sources
Official APIs and package contract:
- https://docs.langchain.com/oss/python/langgraph/use-time-travel
- https://docs.langchain.com/oss/python/langgraph/checkpointers
- https://github.com/langchain-ai/langgraph/blob/1.2.15/libs/langgraph/pyproject.toml
- https://github.com/langchain-ai/langgraph/blob/1.2.15/libs/langgraph/langgraph/graph/__init__.py
Original independent demonstration code; uses documented public API patterns.
Not a copied upstream test. No production-ready claim.
requirements.txt · NOT RUN
langgraph==1.2.15
แหล่งอ้างอิงต้นทาง
- LangGraph Python1.2.15 release notes —รายการแก้ไขของรุ่นที่ใช้ตั้งโจทย์
- GitHub Release API metadata —วันเผยแพร่ UTC และ commit ของ tag
- PR9269: isolate exit-run DeltaChannel writes from an existing newer branch —ขอบเขต fix, tradeoff และรายงานทดสอบของผู้อื่น
- LangGraph Persistence —ขอบเขต checkpointer/store และ RAM persistence
- LangGraph Checkpointers —state snapshot, history, update และ durability modes
- LangGraph Use time-travel —replay/fork/interrupt และขั้นตอนเดินต่อ
- Functional API: determinism and idempotency —task results, resume และการทำซ้ำเมื่อ task ไม่เสร็จ
- pyproject.toml ที่ tag1.2.15 —runtime/dependencies
- DeltaChannel source ที่ tag1.2.15 —beta และ reducer contract
- Upstream DeltaChannel exit-mode tests —รูปแบบ regression ต้นทาง ไม่ใช่ผลรันของผู้เขียน
- Graph public exports ที่ tag1.2.15 —ชื่อ imports ของตัวอย่าง
เอกสารปัจจุบันเปิดตรวจวันที่ 14 ตุลาคม 2026 แต่เวลาที่แก้แต่ละหน้าไม่ทราบ ส่วน source ที่เกี่ยวกับตัวอย่างตรึง tag1.2.15 หาก API เปลี่ยน ให้ตรวจรุ่นก่อนทำตาม ไม่ใช้บทความนี้รับรอง compatibility ของรุ่นใหม่โดยอัตโนมัติ

ใส่ความเห็น