คำตอบสั้น: บันทึก revision ล่าสุดไว้กับสถานะเอกสาร แม้เอกสารนั้นถูกลบแล้ว ทุก event ต้องผ่านจุดตรวจเดียวกัน: รับ revision ที่สูงกว่า, ปฏิเสธ revision ที่ต่ำกว่าโดยไม่เปลี่ยน state และจัดการกรณี revision เท่ากันตามนโยบายที่ระบุไว้ วิธีนี้ป้องกันงาน ingest หรือ reindex เก่าทำให้เอกสารกลับมาได้ เฉพาะเมื่อ revision มาจากแหล่งที่เชื่อถือได้และข้อมูลลบยังถูกเก็บไว้นานพอเทียบกับ event ที่อาจ replay.
บทเรียนนี้ฝึกทักษะเดียว: ออกแบบ revision gate ต่อเอกสารเพื่อกัน event ที่มาช้าเขียนทับการตัดสินใจใหม่กว่า ผู้อ่านจะไล่ event UPSERT 7 → DELETE 9 → UPSERT 8 แล้วกำหนดผลที่รับได้ใน Python mock แบบไม่เชื่อมบริการจริง.

ปัญหา: ลบแล้ว แต่งานเก่ายังมาถึงได้
งาน reindex อาจอ่านเอกสารก่อนถูกลบ แต่ใช้เวลาประมวลผลนานกว่า ขณะที่คำสั่งลบเดินทางถึง index ก่อน ถ้าปลายทางเชื่อทุก UPSERT ที่มาถึง โดยไม่เทียบกับสถานะล่าสุด งานเก่าอาจเขียนเนื้อหากลับเข้า RAG
ตัวอย่าง: ดัชนีเคยรับ UPSERT revision 7 แล้ว worker อีกงานถือ snapshot revision 8 อยู่ ระหว่างนั้น source ลบเอกสารที่ revision 9 และ DELETE มาถึงก่อน UPSERT revision 8 จากงานเก่า เวลามาถึงบอกเพียงลำดับการส่งถึงปลายทาง ไม่ได้บอกว่า event ใดสะท้อนการตัดสินใจใหม่กว่าของ source หากเก็บเพียงว่า “ไม่มีเอกสารนี้แล้ว” โดยลืม revision 9 ระบบอาจแยก event 8 ที่เก่าจากการสร้างเอกสารใหม่ไม่ออก

แบบจำลอง state และกติกาการตัดสิน
Revision คือหมายเลขลำดับที่ source กำหนดให้การเปลี่ยนแปลงหนึ่งครั้งของเอกสารเดียวกัน โดยสมมติว่าเลขสูงกว่าหมายถึงการตัดสินใจใหม่กว่า Revision gate เป็นจุดตรวจที่เปรียบเทียบ event กับ state ล่าสุดก่อนเขียนข้อมูล Tombstone คือสถานะที่จำว่าเอกสารถูกลบที่ revision ใด แม้เนื้อหาจะไม่ถูกส่งคืนให้การค้นหาแล้วก็ตาม
ตัวอย่างเก็บ state ใน map เดียวต่อ doc_id: revision, operation และข้อความ หากเป็น DELETE ให้ข้อความเป็น None การเก็บ revision กับสถานะลบไว้ด้วยกันทำให้ event เก่าถูกเปรียบเทียบกับ revision 9 แทนการมอง ID ราวกับไม่เคยมีอยู่
| กรณี | ผลของตัวอย่างนี้ | state |
|---|---|---|
| ID ใหม่ | APPLIED | สร้าง state |
| revision ต่ำกว่า | STALE | คงเดิม |
| revision เท่ากัน, operation และข้อความเหมือนกัน | DUPLICATE | คงเดิม |
| revision เท่ากัน, operation หรือข้อความต่างกัน | CONFLICT | คงเดิม รอตรวจสอบ |
| revision สูงกว่า | APPLIED | แทน state ล่าสุด |
การแยก DUPLICATE กับ CONFLICT เป็นนโยบายของ mock นี้: retry ที่เหมือนกันไม่ต้องเปลี่ยน state แต่ข้อมูลต่างกันที่ revision เดียวกันไม่ควรถูกตัดสินด้วย arrival order ส่วน Elasticsearch external versioning กำหนดให้ version ที่ส่งมาต้องมากกว่า version ปัจจุบัน จึงปฏิเสธทั้ง version ที่เท่ากันและต่ำกว่า อย่าอ้างว่า Elasticsearch ใช้กติกา equal-version แบบเดียวกับ mock.

Worked example: UPSERT 7 → DELETE 9 → UPSERT 8
เริ่มจาก map ว่าง ส่ง UPSERT(guide-1, 7, “คู่มือเดิม”): ผล APPLIED และ lookup คืนข้อความ ต่อมา DELETE(guide-1, 9): ผล APPLIED; state ยังคงเก็บ ID, revision 9 และ operation DELETE แต่ไม่เก็บข้อความให้ค้นได้ lookup จึงคืน None
เมื่อ UPSERT(guide-1, 8, “งาน reindex ที่มาช้า”) มาถึง gate เปรียบเทียบ 8 กับ 9 แล้วตอบ STALE โดยไม่เขียน state ซ้ำ ผลสุดท้ายยังเป็น revision 9, DELETE และ lookup ยังว่าง นี่คือเกณฑ์สำคัญ: การลบไม่ได้ทำให้ revision ล่าสุดหายไปพร้อมเนื้อหา
หาก source ที่มีอำนาจส่ง UPSERT revision 10 ภายหลัง ตัวอย่างจะรับเพราะใหม่กว่า ซึ่งอาจเป็นการ restore ที่ตั้งใจไว้ แต่ gate ยืนยันได้เพียงลำดับ revision ไม่ได้ยืนยันสิทธิ์ในการกู้คืน ผู้ส่งต้องผ่าน authentication และกฎอนุญาต restore ที่ขอบเขต ingestion ก่อน ห้ามให้ consumer แก้ revision เก่าให้สูงขึ้นเพื่อให้ event ผ่าน.

ขอบเขตของ revision
ตัวอย่างสมมติว่า source ที่เชื่อถือได้เป็นผู้กำหนด revision บวกที่เพิ่มขึ้นต่อเอกสาร โค้ดตรวจรูปแบบและช่วงตัวเลข แต่ยืนยันไม่ได้ว่า producer มีสิทธิ์ออก revision ดังกล่าวหรือไม่ ต้องยืนยันตัวตนและ authorization ก่อนเรียก gate.
ตัวอย่างมี source เดียวและไม่มีการ reset counter, epoch หรือหลาย writer หากเลข revision เริ่มใหม่หลังย้ายระบบ ต้องกำหนด generation หรือกติกา ordering เพิ่มเติม อย่าใช้เวลาที่รับ event เป็นลำดับแทนโดยไม่จัดการ clock skew, timestamp ซ้ำ และผู้เขียนหลายราย.
โครง mock และ acceptance cases
code/revision_gate.py ใช้ Python standard library เก็บ Event, State แบบ immutable และ RevisionIndex ใน dictionary เดียว ก่อนเปลี่ยน state จะตรวจ event ให้ครบ: ID เป็น ASCII 1–80 ตัว เริ่มด้วยตัวอักษรหรือตัวเลข และอาจมี ., _, :, - ในตำแหน่งถัดไป; revision เป็น integer บวกไม่เกิน 263−1 (ปฏิเสธ Boolean); operation เป็น UPSERT หรือ DELETE; ข้อความ UPSERT เป็น UTF-8 ที่ไม่ว่างและไม่เกิน 65,536 bytes; DELETE ต้องไม่มีข้อความ
การตรวจทั้งหมดก่อน mutation หมายความว่า event ผิดรูปแบบจะไม่แก้ state แม้เลขสูงกว่าเดิม โค้ดคืน APPLIED, STALE, DUPLICATE หรือ CONFLICT ให้ผู้เรียกแยกผลได้ ชุดทดสอบที่เขียนไว้ครอบคลุมลำดับหลัก, equal-version, delete ก่อนพบ ID, restore, ID แยก, ชนิดข้อมูลผิด, ขอบเขต ID/revision และ UTF-8 กรณีเหล่านั้นยังไม่ถูกรัน
ตัวอย่างเป็น process เดียวใน memory: ไม่มี persistence, transaction, locking, หรือการรับประกันเมื่อหลาย worker ทำงานพร้อมกัน การเขียน dictionary ครั้งเดียวเป็นภาพประกอบของ state transition ไม่ใช่ atomic compare-and-set ข้ามเครื่อง ระบบจริงต้องอ่าน revision ล่าสุด เปรียบเทียบ และ commit revision, operation และเนื้อหาที่เกี่ยวข้องภายใต้ transaction หรือ conditional-write เดียว การเขียนเนื้อหาและ marker แยกกันโดยไม่มี atomicity อาจเปิดช่องให้ event เก่าแทรก
ผลลัพธ์แต่ละแบบควรมีการจัดการต่างกัน: APPLIED หมายถึง mock แทน state แล้ว ไม่ใช่สิทธิ์ให้ worker เก่าเขียน vector ภายหลังโดยไม่ตรวจ revision อีก; STALE ปกติไม่ควร retry event เดิม เพราะ revision ก็ยังเก่า; DUPLICATE ยืนยันการรับซ้ำได้โดยไม่เขียนซ้ำ; CONFLICT ควรแยกตรวจสอบแทนการสุ่มเลือก payload หนึ่ง ค่าเหล่านี้เป็นผลเชิงนโยบายของตัวอย่าง ไม่ใช่การออกแบบ queue acknowledgment ให้ระบบจริง
ระบบที่นำแนวคิดนี้ไปต่อควรบันทึก execution ID, document ID ที่ไม่เปิดข้อมูลเกินจำเป็น, revision, operation และผลตัดสิน เพื่อไล่เหตุขัดแย้งได้ พร้อมกำหนดอายุ log ให้สอดคล้องกับนโยบายข้อมูลที่ลบ ไม่ควรเก็บข้อความเอกสารใน log เพื่อช่วย debug โดยไม่มีเหตุผลและสิทธิ์ชัดเจน การวัดอัตรา STALE หรือ CONFLICT ช่วยสังเกตคุณภาพ pipeline แต่ไม่ได้แทนการยืนยันว่าข้อมูลทุกสำเนาถูกลบแล้ว
Retention, restart และ replay
mock ไม่มี garbage collection: marker อยู่ตลอดอายุ process แต่เมื่อ process หยุด state ทั้งหมดก็หาย นี่แสดงข้อจำกัดสองด้านพร้อมกัน—เก็บ marker นานช่วยปฏิเสธ event เก่า แต่ state ในหน่วยความจำไม่ทนต่อ restart จึงไม่ใช่รูปแบบ production.
ระบบจริงต้องกำหนด marker retention จากอายุสูงสุดของ event ที่อาจ replay, snapshot consumer, backup และ full reindex หาก marker หมดอายุขณะที่ event เก่ายัง replay ได้ UPSERT เก่าอาจดูเหมือน event แรกของ ID นั้น เอกสาร Elasticsearch ระบุว่าข้อมูล version ของเอกสารที่ลบเก็บไว้ช่วงสั้นตาม index.gc_deletes; จึงไม่ควรกล่าวว่าจำเวอร์ชันลบตลอดไป เอกสาร Kafka 4.2 ระบุ retention ของ delete marker ใน compacted topic และให้ snapshot consumer สแกนให้เสร็จก่อน marker ถูกเก็บกวาด ระบบเหล่านี้มีขอบเขตของตนเอง ไม่ใช่ค่า retention สำเร็จรูปสำหรับทุก RAG
ก่อนกำหนด retention ให้ตอบว่า event เก่าที่สุด replay ได้เมื่อไร, snapshot ใช้เวลาสูงสุดเท่าไร, backup คืน event เดิมได้หรือไม่ และผู้ดูแลเริ่ม reindex จาก snapshot รุ่นเก่าได้หรือไม่ หากไม่รู้ขอบเขตเหล่านี้ อย่าลบ marker เพียงเพื่อประหยัดพื้นที่โดยไม่แก้สัญญา recovery ไปพร้อมกัน
การเชื่อมต่อ RAG เป็นงานถัดไป
ในระบบจริง ทุกทางที่เปลี่ยนเอกสาร—สร้าง, แก้, ลบ, retry และ reindex—ต้องผ่านกติกา ordering เดียวกันก่อนเขียน index หรือเวกเตอร์ หากบาง path ลัด gate การรับประกันก็ไม่ครอบคลุม path นั้น การลบจาก retrieval, ป้องกัน ingestion เก่า, และจัดการสำเนาใน raw storage/cache/backup เป็นคนละภาระงาน marker ไม่ได้ลบทุกสำเนาหรือพิสูจน์การปฏิบัติตามนโยบายข้อมูล
ก่อนเลือก datastore ให้ยืนยันว่ามี conditional write หรือ transaction ที่รองรับ state transition นี้จริง และทดสอบ adapter ใน sandbox แยกข้อมูล อย่าอนุมานพฤติกรรมของ vector database จาก Python mock นี้ บทเรียนที่เกี่ยวข้องกับการผูก citation กับ revision และ byte span คือ RAG Citation Gate; ส่วนการตรวจสิทธิ์ก่อนคืน cache อ่าน RAG Answer Cache สองเรื่องนั้นครอบคลุมคนละส่วนของ pipeline.
Copyable task brief
เป้าหมาย: สร้าง revision gate ต่อ document ID เพื่อป้องกัน stale ingestion/reindex เขียนทับการตัดสินใจใหม่กว่า
เครื่องมือ: Python 3.12 standard library; in-memory mock; ไม่มี API หรือฐานข้อมูล
ขอบเขต: ใช้ synthetic fixtures เท่านั้น; ห้าม network, credentials และข้อมูลจริง; Event มี doc_id, revision, op, text; สมมติ source revision เชื่อถือได้; state ล่าสุดอยู่ใน map เดียว
สิ่งส่งมอบ: apply/lookup, expected example, acceptance cases และ README ระบุข้อจำกัด
เกณฑ์รับงาน: revision สูงกว่าแทน state; ต่ำกว่าคง state; DELETE เก็บ revision; lookup ของ DELETE ว่าง; equal same แยกจาก conflict
จุดหยุด: UPSERT 7, DELETE 9, UPSERT 8 จบที่ DELETE 9; ไม่เพิ่ม persistence หรือ integration
ก่อนนำแนวคิดไปต่อ ให้ตรวจรายการนี้: ทุก producer แนบ revision จาก source เดียวกัน; DELETE ไม่ลบ revision state; ทุก retry ใช้ revision เดิม; restore ผ่าน authorization แยก; retention ครอบคลุม replay ที่ยังอนุญาต; และการเขียน state ทำแบบ atomic ใน datastore จริง รายการนี้เป็นจุดเริ่ม review ไม่ใช่หลักฐานว่าระบบผ่านการทดสอบหรือพร้อมใช้งาน
Acceptance criteria และจุดหยุด
- Validation เกิดก่อน mutation; event ผิดรูปแบบไม่แก้ state
- revision ต่ำกว่าคืน STALE และคง state; revision สูงกว่าจึงแทน state
- DELETE เก็บ revision ใน state เดียวกัน แต่ lookup ไม่คืนข้อความ
- equal revision ที่เหมือนกันคืน DUPLICATE; operation/ข้อความต่างคืน CONFLICT โดยไม่เปลี่ยน state
- ลำดับ 7 UPSERT → 9 DELETE → 8 UPSERT จบด้วย DELETE 9 และ lookup ว่าง
- README บอก runtime, dependencies, input, expected output, permissions และสถานะรัน
จุดหยุดของงานคือครบ acceptance ข้างต้นและมีกรณีทดสอบเขียนไว้ ไม่เพิ่ม adapter, database, retention policy สำหรับ production หรือ deploy ไม่มีผล code test, integration test หรือ runtime test ที่สังเกตได้ใน deliverable นี้; UI QA เป็น UNKNOWN.
ห้องทดลองแบบจำลอง · Memory / RAG
เก็บ Tombstone ไว้ เพื่อกันข้อมูลเก่ากลับมา
เลือกชุดเหตุการณ์และวิธีเก็บสถานะ แล้วดูว่า UPSERT ที่มาช้าจะเปลี่ยนเอกสารในมุมมองค้นคืนหรือไม่
ขอบเขตการจำลอง: นี่คือ browser simulation ของกติกาเดียวกับ Python mock ที่อธิบายในบทความ ไม่ได้รัน Python เก็บสถานะต่อเอกสาร และสมมติว่าแหล่งเหตุการณ์ที่เชื่อถือได้ส่ง revision เพิ่มขึ้นต่อเอกสาร การเก็บ tombstone ใช้ปฏิเสธ revision เก่า ส่วนการลบ marker ทำให้แบบจำลองลืม revision เดิม ตัวเลขเป็น revision สมมติ ไม่ใช่ผลวัดหรือการทดสอบระบบจริง
{rev: 9, op: DELETE, text: null}
จำลอง: r8 ต่ำกว่า r9 จึงไม่เปลี่ยนสถานะ เอกสารยังไม่อยู่ในมุมมองค้นคืน
เมื่อเก็บ tombstone revision 9 ไว้ ระบบเปรียบเทียบ UPSERT r8 กับสถานะล่าสุดและปฏิเสธว่าเก่ากว่า
operation ที่เท่ากัน: เหมือนกัน = DUPLICATE; ต่างกัน = CONFLICT
เกิดข้อผิดพลาดในตัวอย่างจำลอง โปรดใช้ตารางผลลัพธ์คาดหมายด้านล่าง
ทดลองเปรียบเทียบ · สถานะที่จำไว้มีผล
เหตุการณ์เดียวกัน แต่ความจำต่างกัน
ใช้ลำดับ UPSERT r7 → DELETE r9 → UPSERT r8 มาช้า แล้วเปรียบเทียบสองกรณี ไม่มี vector database หรือการเรียกบริการจริง
UPSERT r8 → STALE · ไม่มีการเปลี่ยนสถานะ · doc-A ไม่อยู่ในมุมมอง
UPSERT r8 เป็น event แรกที่พบ · รับตามกติกาตัวอย่าง · doc-A กลับเข้ามุมมอง
ทั้งสองผลใช้ incoming event เดียวกัน ตารางด้านล่างเป็นผลคาดหมายแบบคงที่สำหรับอ่านได้เมื่อปิด JavaScript
| เหตุการณ์เดียวกันหลัง DELETE r9 | เก็บ tombstone r9 | ลืมสถานะ |
|---|---|---|
| UPSERT r8 มาช้า | STALE · ไม่มี | รับเป็น event แรก · มี |
| DELETE r9 ซ้ำแบบเดิม | DUPLICATE · ไม่มี | รับ DELETE เป็น event แรก · ไม่มี |
| UPSERT r9 เนื้อหาต่างกัน | CONFLICT · ไม่มี | รับ UPSERT เป็น event แรก · มี |
| UPSERT r10 ใหม่กว่า | APPLIED · มี | รับ UPSERT เป็น event แรก · มี |
การคืนข้อมูลโดยตั้งใจ: หากมี tombstone r9 แล้ว UPSERT r10 ใหม่กว่า จะรับเป็นการคืนข้อมูลตามกติกาตัวอย่าง การคืนข้อมูลนี้ไม่ทำให้ tombstone หมดอายุเอง
กติกาและข้อจำกัดของแบบจำลอง
revision ต่ำกว่า: STALE และไม่เปลี่ยน; revision เท่ากันพร้อม operation/text เหมือนเดิม: DUPLICATE และไม่เปลี่ยน; revision เท่ากันแต่เนื้อหาต่าง: CONFLICT และไม่เปลี่ยน; revision สูงกว่า: รับสถานะใหม่ UPSERT เก็บข้อความ ส่วน DELETE เก็บ tombstone และ text เป็น null การลบก่อนมี UPSERT ก็สร้าง tombstone ได้
การปฏิบัติต่อ revision เท่ากันเป็นทางเลือกเชิงออกแบบของตัวอย่าง ไม่ใช่ข้ออ้างว่า Elastic external versioning ใช้กติกาเดียวกัน
ตัวอย่างนี้ไม่ครอบคลุมการหมดอายุของ tombstone, การ restart, ความเป็น atomic ในระบบกระจาย, พฤติกรรม vector database, การลบสำเนาทุกแห่ง หรือการยืนยันตัวตนของแหล่งข้อมูล ใช้ source authorization และ retention policy จริงก่อนนำแนวคิดไปออกแบบระบบ
Video Lab: อธิบายงานเก่าที่มาช้าให้เห็นภาพ
ใช้ Lab วิดีโอด้านล่างวางแผนคลิปอธิบายลำดับ UPSERT 7 → DELETE 9 → UPSERT 8 เลือกหัวข้อและรูปแบบวิดีโอ แล้วนำ prompt ที่ได้มาเทียบ brief หกส่วนนี้และเพิ่มส่วนที่ยังขาด Lab นี้จำลองงานวิดีโอ ไม่ได้รัน Python หรือใช้ revision gate ให้ index จริง ค่าเวลาและเงินที่ปรับเป็นสมมติฐาน ไม่ใช่ผลวัดหรือราคาผู้ให้บริการ
เป้าหมาย: วางแผนคลิปอธิบายว่า revision 8 ต่ำกว่า deletion revision 9 จึงถูกปฏิเสธ
เครื่องมือ/ข้อมูล: Video Lab จำลองและเหตุการณ์สมมติ 7 → 9 → 8 เท่านั้น
ขอบเขต: สร้าง brief/storyboard/prompt ไม่เรียก API ไม่ลบหรือสร้างเอกสารจริง
สิ่งส่งมอบ: แผนคลิปที่เห็นเอกสารหายแต่ยังเก็บ marker 9
เกณฑ์รับงาน: แยกลำดับ arrival จาก revision และกล่าวถึง replay/retention
จุดหยุด: เมื่อ brief ครบ หยุดก่อนผลิตหรือเผยแพร่วิดีโอจริง
ตรวจด้วยว่าภาพหรือคำบรรยายไม่ทำให้ผู้ชมเข้าใจว่า marker พิสูจน์การลบทุกสำเนา หรือว่าตัวเลขสูงขึ้นเองแปลว่ามีสิทธิ์กู้คืน การ generate/copy prompt และ UI จริงยังไม่ได้ทดสอบ
จากคลิปดิบ
สู่จักรวาลคอนเทนต์
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 และคำบรรยาย
แบบฝึกหัด
ทำนายผลและ lookup หลังลำดับนี้: UPSERT(manual-a, 4, “A”); UPSERT revision 6 “B”; DELETE revision 5; UPSERT revision 6 “อื่น”; DELETE revision 7; UPSERT revision 6 “rebuild เก่า” ระบุด้วยว่า event ไหนเปลี่ยน state และเพราะเหตุใด arrival time จึงไม่ใช้เลือกข้อมูล.
เฉลย: APPLIED A; APPLIED B; STALE คง B; CONFLICT คง B; APPLIED DELETE 7 และ lookup ว่าง; STALE คง DELETE 7. เกณฑ์ประเมิน: state ไม่เปลี่ยนหลัง rejection, แยก STALE กับ CONFLICT และอธิบายว่า consumer ห้ามเพิ่ม revision เอง.
Quick Answer
เก็บ revision ล่าสุดคู่กับ state รวมทั้งสถานะลบ รับเฉพาะ revision ที่สูงกว่า และปฏิเสธ revision ต่ำกว่าโดยไม่เขียนข้อมูลกลับ ตรวจแหล่ง revision และสิทธิ์ก่อนถึง gate; กำหนด retention จาก replay และ recovery ของระบบ ตัวอย่างเป็น mock ที่ยังไม่ได้รัน ไม่ใช่ integration หรือ production guarantee.
FAQ
ลบเวกเตอร์แล้วพอหรือไม่?
ไม่พอถ้างานเก่ายังเขียนกลับได้ ต้องมี state ลบที่ gate ใช้ปฏิเสธ event เก่า และจัดการสำเนาตามนโยบายข้อมูลแยกกัน.
revision เท่ากันแต่ข้อมูลต่างกันทำอย่างไร?
อย่าเลือกตามเวลามาถึง Mock คืน CONFLICT และคง state เดิม; ระบบจริงต้องกำหนด reconciliation หรือ quarantine.
เก็บ marker นานเท่าไร?
สัมพันธ์กับ event replay, snapshot, backup และ reindex ที่ยังเกิดได้ ไม่มีตัวเลขสากลจากตัวอย่างนี้.
ใช้หลาย worker ได้ไหม?
ไม่ได้ตามที่พิสูจน์ใน mock นี้; ต้องมี atomic compare-and-set หรือ transaction และทดสอบ adapter จริงใน sandbox.
แหล่งอ้างอิง
- Elasticsearch Index API — external version ต้องสูงกว่า version ปัจจุบัน
- Elasticsearch Delete API — deleted version เก็บช่วงสั้นตามการตั้งค่า
index.gc_deletes - Apache Kafka 4.2 Topic Configs — retention ของ delete marker และกรอบเวลาสแกน snapshot
ข้อกำหนด API ข้างต้นเป็น documented behavior ของแต่ละผลิตภัณฑ์; revision gate แบบ map และผลตัวอย่างเป็น engineering analysis/simulation เท่านั้น ไม่ได้ทดสอบกับ Elasticsearch, Kafka หรือ vector database.
โค้ดตัวอย่างและคำสั่งใช้งาน: code/revision_gate.py · code/example_revision_gate.py · code/test_revision_gate.py · README-code.md · ทั้งหมดยัง NOT RUN
Source Code และวิธีลองด้วยตนเอง · NOT RUN
code/revision_gate.py · ยังไม่ได้รัน
"""Bounded, in-memory revision gate mock for per-document RAG ingestion."""
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
import re
_MAX_REVISION = 2**63 - 1
_MAX_TEXT_BYTES = 65_536
_DOC_ID = re.compile(r"[A-Za-z0-9][A-Za-z0-9._:-]{0,79}\Z", re.ASCII)
class Operation(str, Enum):
UPSERT = "UPSERT"
DELETE = "DELETE"
class Result(str, Enum):
APPLIED = "APPLIED"
STALE = "STALE"
DUPLICATE = "DUPLICATE"
CONFLICT = "CONFLICT"
@dataclass(frozen=True)
class Event:
doc_id: str
revision: int
op: str
text: str | None = None
@dataclass(frozen=True)
class State:
revision: int
op: Operation
text: str | None
class RevisionIndex:
"""One-process model; not persistent, distributed, or concurrency-safe."""
def __init__(self) -> None:
self._states: dict[str, State] = {}
@staticmethod
def _validate(event: Event) -> tuple[str, int, Operation, str | None]:
if type(event) is not Event:
raise TypeError("event must be an Event")
if type(event.doc_id) is not str or _DOC_ID.fullmatch(event.doc_id) is None:
raise ValueError("doc_id must start with ASCII letter/digit; following characters may use ASCII letters, digits, . _ : or - (max 80)")
if type(event.revision) is not int or not (1 <= event.revision <= _MAX_REVISION):
raise ValueError("revision must be a positive signed 64-bit integer")
if type(event.op) is not str or event.op not in ("UPSERT", "DELETE"):
raise ValueError("op must be exactly UPSERT or DELETE")
op = Operation(event.op)
if op is Operation.DELETE:
if event.text is not None:
raise ValueError("DELETE must not include text")
return event.doc_id, event.revision, op, None
if type(event.text) is not str or event.text == "":
raise ValueError("UPSERT text must be a non-empty string")
try:
encoded = event.text.encode("utf-8", errors="strict")
except UnicodeEncodeError as exc:
raise ValueError("UPSERT text must be valid UTF-8") from exc
if len(encoded) > _MAX_TEXT_BYTES:
raise ValueError("UPSERT text must be at most 65536 UTF-8 bytes")
return event.doc_id, event.revision, op, event.text
def apply(self, event: Event) -> Result:
# Validate the complete event before reading or changing state.
doc_id, revision, op, text = self._validate(event)
current = self._states.get(doc_id)
if current is None or revision > current.revision:
self._states[doc_id] = State(revision=revision, op=op, text=text)
return Result.APPLIED
if revision < current.revision:
return Result.STALE
if current.op is op and current.text == text:
return Result.DUPLICATE
return Result.CONFLICT
def lookup(self, doc_id: str) -> str | None:
if type(doc_id) is not str or _DOC_ID.fullmatch(doc_id) is None:
raise ValueError("doc_id must be 1-80 ASCII letters, digits, . _ : or -")
state = self._states.get(doc_id)
if state is None or state.op is Operation.DELETE:
return None
return state.text
def state(self, doc_id: str) -> State | None:
if type(doc_id) is not str or _DOC_ID.fullmatch(doc_id) is None:
raise ValueError("doc_id must be 1-80 ASCII letters, digits, . _ : or -")
return self._states.get(doc_id)
code/example_revision_gate.py · ยังไม่ได้รัน
"""Print the expected stale-ingestion example; authored but NOT RUN."""
from revision_gate import Event, RevisionIndex
def main() -> None:
index = RevisionIndex()
events = (
Event("guide-1", 7, "UPSERT", "คู่มือฉบับเก่า"),
Event("guide-1", 9, "DELETE"),
Event("guide-1", 8, "UPSERT", "งาน reindex ที่มาช้า"),
)
for event in events:
print(index.apply(event).value)
print(index.lookup("guide-1"))
state = index.state("guide-1")
assert state is not None
print(f"{state.revision} {state.op.value}")
if __name__ == "__main__":
main()
code/test_revision_gate.py · ยังไม่ได้รัน
"""Authored acceptance cases for revision_gate; NOT RUN."""
import unittest
from revision_gate import Event, Operation, Result, RevisionIndex
class RevisionIndexTests(unittest.TestCase):
def setUp(self) -> None:
self.index = RevisionIndex()
def test_example_accepts_newer_delete_and_rejects_late_upsert(self) -> None:
self.assertEqual(self.index.apply(Event("guide-1", 7, "UPSERT", "old")), Result.APPLIED)
self.assertEqual(self.index.apply(Event("guide-1", 9, "DELETE")), Result.APPLIED)
self.assertEqual(self.index.apply(Event("guide-1", 8, "UPSERT", "late")), Result.STALE)
self.assertIsNone(self.index.lookup("guide-1"))
self.assertEqual(self.index.state("guide-1").revision, 9)
self.assertIs(self.index.state("guide-1").op, Operation.DELETE)
def test_same_revision_same_event_is_duplicate(self) -> None:
self.index.apply(Event("doc", 3, "UPSERT", "text"))
self.assertEqual(self.index.apply(Event("doc", 3, "UPSERT", "text")), Result.DUPLICATE)
def test_same_revision_different_text_is_conflict(self) -> None:
self.index.apply(Event("doc", 3, "UPSERT", "first"))
self.assertEqual(self.index.apply(Event("doc", 3, "UPSERT", "other")), Result.CONFLICT)
self.assertEqual(self.index.lookup("doc"), "first")
def test_same_revision_different_operation_is_conflict(self) -> None:
self.index.apply(Event("doc", 3, "UPSERT", "first"))
self.assertEqual(self.index.apply(Event("doc", 3, "DELETE")), Result.CONFLICT)
self.assertEqual(self.index.lookup("doc"), "first")
def test_delete_for_unseen_id_creates_marker(self) -> None:
self.assertEqual(self.index.apply(Event("doc", 4, "DELETE")), Result.APPLIED)
self.assertEqual(self.index.state("doc").revision, 4)
self.assertIsNone(self.index.lookup("doc"))
def test_later_higher_revision_can_restore_content(self) -> None:
self.index.apply(Event("doc", 4, "DELETE"))
self.assertEqual(self.index.apply(Event("doc", 5, "UPSERT", "restored")), Result.APPLIED)
self.assertEqual(self.index.lookup("doc"), "restored")
def test_documents_advance_independently(self) -> None:
self.index.apply(Event("a", 10, "DELETE"))
self.assertEqual(self.index.apply(Event("b", 1, "UPSERT", "live")), Result.APPLIED)
self.assertEqual(self.index.lookup("b"), "live")
def test_boolean_revision_is_rejected_without_mutation(self) -> None:
with self.assertRaises(ValueError):
self.index.apply(Event("doc", True, "DELETE"))
self.assertIsNone(self.index.state("doc"))
def test_non_ascii_or_invalid_id_is_rejected_without_mutation(self) -> None:
for doc_id in ("", "ไทย", "has space", "x" * 81):
with self.subTest(doc_id=doc_id), self.assertRaises(ValueError):
self.index.apply(Event(doc_id, 1, "DELETE"))
self.assertEqual(self.index.state("valid"), None)
def test_invalid_operation_is_rejected(self) -> None:
with self.assertRaises(ValueError):
self.index.apply(Event("doc", 1, "delete"))
self.assertIsNone(self.index.state("doc"))
def test_delete_with_text_is_rejected_without_mutation(self) -> None:
with self.assertRaises(ValueError):
self.index.apply(Event("doc", 1, "DELETE", "unexpected"))
self.assertIsNone(self.index.state("doc"))
def test_invalid_utf8_surrogate_is_rejected_without_mutation(self) -> None:
with self.assertRaises(ValueError):
self.index.apply(Event("doc", 1, "UPSERT", "bad\ud800"))
self.assertIsNone(self.index.state("doc"))
def test_text_limit_uses_utf8_bytes(self) -> None:
self.index.apply(Event("doc", 1, "UPSERT", "ก" * 21_845))
with self.assertRaises(ValueError):
self.index.apply(Event("other", 1, "UPSERT", "ก" * 21_846))
self.assertIsNone(self.index.state("other"))
def test_revision_must_be_positive_and_bounded(self) -> None:
for revision in (0, -1, 2**63, 1.5):
with self.subTest(revision=revision), self.assertRaises(ValueError):
self.index.apply(Event("doc", revision, "DELETE"))
self.assertIsNone(self.index.state("doc"))
if __name__ == "__main__":
unittest.main()
README-code.md · ยังไม่ได้รัน
# Revision gate mock
This run contains a standard-library-only Python 3.12 example for per-document RAG ingestion ordering. It is an in-memory teaching mock and does not connect to Elasticsearch, Kafka, a vector database, WordPress, or any API.
## Runtime and dependencies
- Runtime target: Python 3.12 or newer.
- Dependencies: Python standard library only (`dataclasses`, `enum`, `re`, `unittest`).
- Environment variables: none. Installation: none.
- Network, API keys, GPU, deployment, or external services: not used. No API or GPU costs are incurred; no cost measurement was performed.
## Example input and expected output
The driver submits UPSERT revision 7, DELETE revision 9, then late UPSERT revision 8 for `guide-1`.
Command from this run directory:
`python code/example_revision_gate.py`
Expected output by inspection of authored code (NOT OBSERVED):
`APPLIED`
`APPLIED`
`STALE`
`None`
`9 DELETE`
The last two lines show that retrieval yields no text while the retained state remembers the deletion revision. This is synthetic input, not a live document.
## Authored acceptance suite
A developer could run the authored suite from this run directory with:
`python -m unittest discover -s code -p "test_*.py" -v`
This command is documentation only. Tests were NOT RUN. The example driver was NOT RUN. No syntax check, import, compile, lint, or runtime check was performed.
## Limits and permissions
- The state map is in memory and disappears when the process stops; there is no persistence or garbage collection policy.
- The mock is single-process and makes no claim about concurrency, transactions, multiple workers, or replicas.
- Source revision is assumed authoritative. Shape validation does not authenticate or authorize a producer.
- Higher-revision UPSERT may restore content; the mock does not decide whether a restore is allowed.
- No database, queue, file, OS, external API, or production write is performed.
Mock means authored local logic with synthetic inputs. Sandbox would mean an isolated integration environment with disposable data and test permissions. Live means a real external service and real permissions. This deliverable is Mock only; Sandbox and Live behavior are not established.
Authored code, driver, and tests: NOT RUN. Integration: NOT RUN. Optional UI/browser QA: UNKNOWN.

ใส่ความเห็น