RAG อ้างเอกสารเก่า? ตรวจ Citation ให้ตรงรุ่นด้วย SHA-256 และ UTF-8

วิศวกรตรวจใบอ้างอิง v1 ที่หยุดเชื่อมกับเอกสารฉบับปัจจุบัน v2
วิศวกรตรวจใบอ้างอิง v1 ที่หยุดเชื่อมกับเอกสารฉบับปัจจุบัน v2
ภาพอธิบายจำลอง: ต้องตรวจรุ่นเอกสารก่อนใช้ข้อความอ้างอิงจาก RAG · ภาพเต็ม

คำตอบสั้น ๆ: ให้ระบบแอปเลือก revision ปัจจุบันจาก manifest ที่แอปควบคุม และเก็บ SHA-256 ที่แอปคำนวณตอน ingest ไว้กับ revision นั้น เมื่อตรวจ citation ให้คำนวณ hash จาก bytes ปัจจุบันแล้วเทียบทั้งกับ manifest และ digest ที่ candidate ส่งมา พร้อมตรวจ span ว่ามี quote ตรงกัน หาก manifest ขยับจาก v1 เป็น v2 ให้ปฏิเสธ v1 แม้ hash ของ v1 ยังถูกต้อง ผลผ่านหมายถึง “พบ quote ใน source revision ปัจจุบัน” ไม่ได้พิสูจน์ความจริงหรือ entailment ของคำตอบ

บทเรียนนี้ใช้ Python 3.12 และ standard library สร้าง gate เล็ก ๆ ที่ผูก citation กับ revision ปัจจุบันของเอกสารที่แอปเชื่อถือ ไม่มี LLM หรือบริการภายนอก

ทำไม quote ที่ดูถูกต้องอาจล้าสมัย

RAG ค้นเอกสารก่อนให้โมเดลร่างคำตอบ แต่ quote ที่ตรงกับเอกสารฉบับหนึ่งอาจมาจาก revision เก่า เช่น นโยบายเดิมว่า “ต้องขออนุมัติก่อนเผยแพร่” เปลี่ยนเป็น “ต้องได้รับอนุมัติก่อนส่งออก” หากดัชนียังคืนข้อความเดิม คำอ้างก็ล้าสมัยได้

gate ตรวจสามอย่าง: source อยู่ใน allowlist, citation ชี้ revision ปัจจุบัน และ quote ตรงกับ byte span ของ revision นั้น ส่วนความน่าเชื่อถือหรือความหมายของเอกสารต้องประเมินแยก

งาน attribution สนับสนุนการตรวจข้อความกับแหล่งอิสระ (Measuring Attribution in Natural Language Generation Models); manifest, hash และ byte-span gate ในบทนี้เป็นแบบออกแบบทางวิศวกรรมของเรา

แฮชของเอกสาร v1 ยังตรงกับสำเนาเก่า แต่ใบอ้างอิง v1 ถูกหยุดเมื่อคลังปัจจุบันเป็น v2
แบบจำลองการออกแบบของบทเรียน: แฮชฉบับเก่าที่ตรงไม่ทำให้ใบอ้างอิงนั้นผ่านนโยบายใช้รุ่นปัจจุบัน · ภาพเต็ม

แบบจำลองอำนาจ: candidate ขอได้ แต่ manifest เป็นผู้ตัดสิน

citation จาก retriever เป็น candidate ที่ไม่น่าเชื่อถือ แม้จะบรรทุก source ID, revision, digest, offsets และ quote มาเองก็ตาม แอปค้น source ใน allowlist แล้วอ่าน current revision, expected SHA-256 และ canonical bytes จาก manifest ที่เชื่อถือได้ จากนั้นคำนวณ hash ซ้ำ

candidate เลือก bytes หรือ hash เองไม่ได้: validator คำนวณ hash จาก bytes แล้วเทียบกับ expected hash ของ manifest ก่อนตรวจ digest ใน candidate การป้องกัน manifest และเส้นทางอัปเดตเป็นเงื่อนไขความเชื่อถือของแอป เพราะ SHA-256 ไม่ได้ยืนยันตัวตนผู้เผยแพร่หรือความจริงของเอกสาร

ค่าผู้กำหนดบทบาท
current revision, expected hash และ bytesแอปauthority ที่ validator ตรวจเทียบ
revision, digest, span และ quotecandidateinput ที่ยังไม่เชื่อถือ
QUOTE_VERIFIED หรือ reasongateยืนยันตำแหน่งหรือบอกเหตุผลปฏิเสธ

ตามนโยบายนี้ v1 stale ทันทีที่ manifest ชี้ v2 แม้ hash ของ v1 ยังถูกต้อง หากต้องอ้างย้อนหลังต้องกำหนด timestamp และกติกา historical snapshot แยกต่างหาก; candidate เลือกอดีตมาแทน current revision เองไม่ได้

ทำไมตำแหน่งต้องนับเป็น UTF-8 bytes

Python str คือข้อความ Unicode ส่วน bytes คือหน่วยข้อมูลทีละ byte; ตัวอักษรไทยใช้หลาย byte ใน UTF-8 จึงทำให้ตำแหน่ง string กับ byte ต่างกัน เอกสาร Python เรื่อง bytes ระบุการค้นโดยดัชนี byte หากระบบนับคนละแบบ quote อาจเลื่อนหรือเริ่มกลางอักขระ

span [start, end) รวม byte ที่ start และไม่รวม end; ขนาด quote คือ end - start ตรวจขอบเขต 0 ≤ start < end ≤ len(source_bytes) และ decode slice แบบ strict เพื่อปฏิเสธ offset ที่ตัดกลาง UTF-8 จากนั้นจึงเทียบ bytes ของ quote ที่แสดงแบบตรงตัว [Python bytes operations]

Hash ต้องคำนวณจาก canonical bytes ชุดเดียวกับที่ใช้ offset ด้วย “canonical” คือรูปแบบ bytes ที่ระบบตกลงว่าจะเป็นตัวแทนเอกสาร เช่น วิธีจัดการ CRLF กับ LF, Unicode normalization, HTML ที่ลบหรือเก็บ และช่องว่างท้ายบรรทัด หากเปลี่ยนกติกาหรือเนื้อหาให้สร้าง revision ใหม่พร้อม digest ใหม่ อย่าคำนวณตำแหน่งจากข้อความก่อนแปลง แต่ไปตรวจกับ bytes หลังแปลง

สี่ด่านตรวจแหล่งและรุ่น แฮช ขอบเขต UTF-8 และข้อความตรง ก่อนให้ผล QUOTE_VERIFIED
แบบจำลองการออกแบบของบทเรียน: ผ่านสี่ด่านหมายถึงข้อความอ้างอิงตรงกับแหล่งฉบับปัจจุบัน ยังต้องตรวจความหมายของคำตอบแยก · ภาพเต็ม

ตัวอย่าง: นโยบายวิดีโอเปลี่ยนจาก v1 เป็น v2

fixture video-policy-demo มี v1 “ต้องขออนุมัติก่อนเผยแพร่” และ v2 “ต้องได้รับอนุมัติก่อนส่งออก” แอปเก็บ revision, expected hash และ bytes ใน manifest; candidate v1 อาจยังค้างใน index หลังอัปเดต

หาก v1 ยัง current, source ผ่าน allowlist, expected hash ตรงกับ bytes, candidate digest ตรงกับ expected hash และ span/quote ตรง byte ต่อ byte ผลคือ quote อยู่ในสำเนา v1 ปัจจุบัน ตัวอย่าง JSON ด้านล่างแสดง v2; start/end นับ UTF-8 bytes จากต้นเอกสาร

เมื่อเลื่อน manifest เป็น v2, candidate v1 ถูกปฏิเสธด้วย stale_revision แม้ hash เก่ายังตรง หากสร้าง candidate ใหม่จาก quote v2 และคำนวณ span/hash ตรงจึงผ่านเป็น QUOTE_VERIFIED

validator ตรวจชนิดและขนาด, allowlist, current revision, source hash, candidate digest, bounds, UTF-8 และ quote ตามลำดับ จึงบอกสาเหตุปฏิเสธได้โดยไม่เปิดเผยเอกสารทั้งหมด

ดู implementation ใน citation_gate.py และ regression cases ใน test_citation_gate.py dataclass เป็น immutable และ validator ตรวจกับ manifest snapshot หนึ่งชุดต่อการเรียก

cd code
python demo.py
python -m unittest -v
# ผล demo คาดหมาย: current-v1 ผ่าน; stale-v1-after-v2 ถูกปฏิเสธ
# ผล unittest คาดหมาย: Ran 10 tests ... OK
# ตัวอย่างและ unittest ในงานเขียนนี้: NOT RUN

สิบกรณีทดสอบครอบคลุม pass, stale, source/digest mismatch, offset ผิดชนิดหรือขอบเขต, UTF-8 แตก, quote ไม่ตรง และ manifest ผิดตัวตน ชุดนี้เป็นเพียงชุดทดสอบที่เขียนไว้; ผล runtime ยังเป็น NOT RUN

สำหรับ fixture นี้ ผลที่คาดคือ v1 ผ่านก่อนอัปเดต จากนั้น v1 ถูกปฏิเสธเมื่อ pointer เป็น v2; quote ใหม่ของ v2 ผ่านได้เมื่อสร้าง candidate และตำแหน่งจาก bytes v2

แยกการตรวจข้อความและรุ่นเอกสารนโยบายวิดีโอจำลอง ออกจากการพิจารณาว่าคำตอบสรุปเกินหลักฐานหรือไม่
ตัวอย่างนโยบายวิดีโอจำลอง: ข้อความและรุ่นตรงกันเป็นการตรวจที่แยกจากความจริงและการรองรับข้อสรุป · ภาพเต็ม

แบบฝึกหัด: มอบหมายงานตรวจ citation ให้ชัด

ก่อนใช้ Video Agent Lab ด้านล่าง ให้ทดลอง brief สำหรับสคริปต์วิดีโอที่ตรวจนโยบายการอนุมัติก่อนส่งออก แล้วสังเกตว่าผู้รับงานระบุ revision, source และจุดหยุดอย่างไร Lab เป็นแบบจำลองเพื่อการเรียนรู้ ไม่ได้ใช้ citation gate ในโค้ดนี้หรือวัดระบบวิดีโอจริง ให้อ้างอิง brief หกส่วนด้านล่างเมื่อตรวจ prompt ที่สร้าง

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 และคำบรรยาย

Task brief ที่คัดลอกไปปรับใช้ได้

เป้าหมาย: ตรวจว่า quote ที่จะใช้ตอบคำถามเรื่องนโยบายอยู่ใน current revision ของเอกสารที่อนุญาต
ข้อมูลและเครื่องมือ: รับ citation candidate หนึ่งรายการ; อ่าน server-owned manifest snapshot; ไม่มี model/API call
สิทธิ์: อ่านเฉพาะ source ID ที่ allowlist; ห้ามแก้ manifest, เขียน source หรือทำ action จากคำตอบ
สิ่งส่งมอบ: QUOTE_VERIFIED หรือ REJECTED พร้อม reason code และ source ID/revision ที่ตรวจ
เกณฑ์ตรวจรับ: revision ปัจจุบันตรง, digest ตรงกับ canonical bytes, span เป็น UTF-8 byte boundary และ slice เท่ากับ quote
จุดหยุด: หยุดหลังตรวจหนึ่ง citation; ห้ามสรุปว่า claim จริง/มี entailment และห้าม fallback ไปใช้ revision เก่า

RAG · บททดลองจำลอง

ใบอ้างอิงยังชี้ไปฉบับปัจจุบันหรือไม่?

RAG (การสร้างคำตอบโดยค้นคืนข้อมูล) ควรผูกใบอ้างอิงกับแหล่งที่แอปกำหนดว่าเป็นฉบับปัจจุบัน ใช้แผนผังสามมิตินี้ดูเส้นทางจาก manifest ที่เชื่อถือได้ ไปยัง snapshot ของไบต์ และช่วงข้อความที่อ้างอิง

การจำลอง: ใช้เอกสารนโยบายวิดีโอสมมติสองฉบับเท่านั้น ไม่มีการเรียก API หรือโมเดล และไม่ได้ตรวจแหล่งข้อมูลจริง

ผลกติกา

ระบุฉบับเก่ากว่า manifest ปัจจุบัน จึงปฏิเสธว่า stale แม้ digest ของฉบับเก่าจะยังตรงกับไบต์เดิม

เมื่อฉบับปัจจุบันเป็น v2 ใบอ้างอิง v1 จะ stale ตามนโยบายนี้ แม้ hash ของ v1 ยังตรงกับไบต์ v1 ก็ตาม ต้องเทียบ revision กับ manifest ที่แอปเชื่อถือก่อนใช้ใบอ้างอิง

ขอบเขตผล: QUOTE_VERIFIED หมายถึงพบ quote ตรงกับช่วงไบต์ที่อ้างในแหล่งฉบับปัจจุบันเท่านั้น ไม่ยืนยันความจริง ความน่าเชื่อถือของผู้เผยแพร่ หรือว่าแหล่งนั้นสนับสนุนข้อสรุป


เลือกกรณีแล้วอ่านผลตามกติกา

กรณีต่อไปนี้เป็น fixtures ที่กำหนดไว้ล่วงหน้า เพื่อแยกการตรวจแต่ละข้อให้อ่านง่าย ไม่ใช่การทดสอบ API หรือการประเมิน RAG จริง

QUOTE_VERIFIED · ตำแหน่ง quote ตรง

สมมติว่า source_id อยู่ใน manifest ปัจจุบัน, revision ตรง, SHA-256 ที่คำนวณจากไบต์ปัจจุบันตรงกับ manifest, ขอบเขตไบต์ถูกต้อง และไบต์ที่เลือกเท่ากับ UTF-8 ของ quote

ผลนี้ยืนยันตำแหน่งและความตรงของ quote เท่านั้น ไม่ยืนยันความจริงหรือ entailment (การที่หลักฐานรองรับข้อสรุป)

ข้อความสำรอง: ลำดับการตรวจ

  • หา source_id จาก manifest ฝั่งแอปที่เชื่อถือ ห้ามให้ citation เลือก authority เอง
  • revision ใน citation ต้องเท่ากับ revision ปัจจุบัน
  • คำนวณ SHA-256 จาก canonical UTF-8 bytes ปัจจุบัน แล้วเทียบ digest
  • ตรวจขอบเขต [start,end) และถอดรหัสเฉพาะขอบเขต UTF-8 ที่สมบูรณ์
  • เทียบไบต์ช่วงนั้นกับ UTF-8 bytes ของ quote ที่แสดง แล้วหยุดที่ผลการระบุตำแหน่ง

แบบจำลองนี้ไม่กำหนดค่าความแม่นยำหรือคะแนน ไม่โหลดข้อมูล ไม่ใช้ storage และไม่ใช่การทดลอง production ตัวอย่างโค้ด/ปุ่มยังไม่ได้ทดสอบสถานะ: NOT RUN / UI UNKNOWN

สิ่งที่ gate นี้ยืนยันและสิ่งที่ต้องตรวจแยก

ผล QUOTE_VERIFIED หมายถึง source อยู่ใน allowlist, revision ตรง current, hash ของ bytes ตรงกับ expected hash และ digest ของ candidate, span ถูกขอบเขตและถอด UTF-8 ได้ และ slice ตรง quote จึงเหมาะเป็น gate ก่อน render citation

ผลนี้ไม่ยืนยันผู้เผยแพร่ ความจริงหรือความสดบนเว็บไซต์ และไม่ตรวจว่า quote สนับสนุนคำตอบหรือไม่ การยืนยันตัวตนแหล่งและการประเมิน entailment ต้องมีขั้นแยก

ระบบจริงยังต้องกำหนด ACL, cache freshness, การอัปเดต revision แบบ atomic และการเพิกถอน source เอง Manifest ล่าสุดของแอปอาจยังไม่ใช่ข้อมูลล่าสุดจากต้นทาง; snapshot ในตัวอย่างไม่แก้ race ระหว่าง ingest กับ validation และไม่ใช่ distributed lock

วาง gate ตรงไหนในวงจร RAG

วาง gate หลัง retrieval สร้าง candidate และก่อนแสดง citation: ingest สำเนาและ revision, ค้น chunk, สร้าง citation, ตรวจกับ manifest แล้วส่ง quote ที่ผ่านไปยัง renderer ขั้นนี้ตรวจตำแหน่งเท่านั้น ชั้นคำตอบยังต้องประเมินความหมาย

ตัวอย่างเป็น mock ไม่มี retriever หรือ renderer จริง ระบบที่นำแนวคิดไปใช้ควรส่งต่อ source ID, revision และ byte span จาก chunk ถึงหน้าจอ ไม่ควรเดาตำแหน่งใหม่จาก URL ที่เนื้อหาเปลี่ยนได้

{
  "source_id": "video-policy-demo",
  "revision": "v2",
  "digest": "<sha256 hex ของ canonical bytes จาก manifest>",
  "start": 44,
  "end": 125,
  "quote": "ต้องได้รับอนุมัติก่อนส่งออก"
}
# start/end คือ UTF-8 byte offset สำหรับข้อความ v2 ตัวอย่างนี้เท่านั้น
# validator คืน QUOTE_VERIFIED เมื่อค่าทุกส่วนตรงกับ manifest ปัจจุบัน

ในโค้ด SourceSnapshot บรรทุก expected hash จากฝั่งแอป ส่วน CitationCandidate เก็บค่าที่ต้องตรวจ Validator ไม่เชื่อ digest จาก candidate ลำพัง แต่คำนวณ source hash แล้วเทียบทั้งสองชั้นก่อนอ่าน span

ตัวอย่าง candidate ที่ชี้ v2 และ quote ตรงจะผ่าน; candidate v1 หลัง pointer เลื่อนไป v2 คืน stale_revision ก่อนตรวจ span Caller ควรใช้ status/reason เป็นผลเชิงกล ไม่แปลงเป็นคะแนนความจริงหรือความมั่นใจ

ตัวอย่างรับหนึ่ง citation ต่อครั้งเพื่อให้ผลผูกกับ input เดียว ระบบหลาย citation ต้องกำหนดจำนวนสูงสุดและ policy เมื่อรายการใดไม่ผ่านเพิ่มเติม

หาก vector index ไม่มี revision หรือ span ของ canonical bytes ต้องเพิ่ม metadata ที่ตรวจย้อนกลับได้ การค้นคำคล้ายกันภายหลังอาจพบหลายตำแหน่งและไม่ยืนยันว่าเป็น chunk เดิม

เมื่อ citation stale อย่าลบหลักฐานเงียบ ๆ แล้วส่งคำตอบเดิม Caller อาจค้นใหม่ แจ้งว่าหา citation ปัจจุบันไม่พบ หรือหยุดส่งผู้ตรวจ ตาม policy ที่ระบบกำหนด; ตัวอย่างนี้หยุดหลัง candidate เดียว

ออกแบบ manifest และเหตุผลปฏิเสธให้ตรวจสอบได้

manifest ควรผูก stable source ID, revision ที่แอปสร้างตอน ingest, expected SHA-256 และ bytes (หรือ pointer ไปยัง immutable object) ไว้ด้วยกัน การเปลี่ยนเนื้อหาสร้าง revision ใหม่; validator คำนวณ hash จาก bytes ที่อ่านซ้ำเพื่อยืนยันว่าตรงกับ expected hash

ตัวอย่างใช้ dataclass แบบ frozen และคัดลอก mapping หนึ่งครั้ง โดยผู้เรียกต้องส่ง mapping ที่เชื่อถือได้และคงที่ ขนาดถูกจำกัดก่อนและหลังคัดลอก แต่ระบบจริงยังต้องจัดการ atomic update และความสอดคล้องของ manifest กับ object เอง

reason code แยกประเภทปัญหาได้โดยไม่ต้อง log เนื้อหาเต็ม เช่น source_not_allowlisted, stale_revision, source_digest_mismatch, candidate_digest_mismatch, span_out_of_bounds และ quote_mismatch เป็นเบาะแสสำหรับวินิจฉัย ไม่ใช่ข้อยืนยันสาเหตุ

ลำดับนี้ช่วยแยก drift สองแบบ: source_digest_mismatch แปลว่า bytes ที่โหลดไม่ตรงกับ expected hash ที่ manifest เชื่อถือ อาจอ่าน object ผิดรุ่นหรือข้อมูลเสีย; candidate_digest_mismatch แปลว่า candidate อ้าง bytes อีกชุดทั้งที่ source ใน manifest มี hash ที่ยืนยันได้ ส่วน stale_revision จะหยุดก่อน hash เพื่อบอกตรง ๆ ว่า retriever ค้างฉบับเดิม Caller จึงเลือก sync index หรือหยุดตอบได้โดยไม่สรุปว่าข้อความต้นทางเท็จ

อย่าส่ง bytes ทั้งเอกสารกลับใน error response; caller ใช้ reason code ตัดสินใจต่อ ส่วนข้อความต่อผู้ใช้อาจสรุปว่า “ยังยืนยันแหล่งอ้างอิงปัจจุบันไม่ได้” โดยไม่บอกเป็นนัยว่า source เป็นเท็จ

ความปลอดภัย ต้นทุน และสิ่งที่ยังไม่ครอบคลุม

candidate เป็น input ไม่น่าเชื่อถือ จึงตรวจชนิดและขนาดก่อน slice ใน Python bool เป็น subclass ของ int; การเช็ก type(value) is int ป้องกัน True ถูกใช้เป็น byte offset 1 ตัวอย่างจำกัด manifest, source และ quote เพื่อควบคุมขอบเขต

การคำนวณ SHA-256 อ่าน bytes ทั้ง source; ตัวอย่างไม่มี network หรือ token cost แต่ไม่ได้ benchmark latency หากปรับระบบใหญ่ให้คำนวณ hash ตอน ingest ต้องผูก digest กับ immutable bytes และตรวจว่า slice มาจาก object version เดียวกัน

gate ไม่ยืนยันแหล่งจริง, entailment, paraphrase, ACL, ความสดจาก origin, race, audit หรือการป้องกันผู้ดูแลแก้ manifest ผิดพลาด คุณสมบัติเหล่านั้นต้องมี controls และเกณฑ์ตรวจแยก

QUOTE_VERIFIED ไม่อนุญาตให้ agent เรียก tool หรือเปลี่ยนข้อมูล; action ภายนอกยังต้องผ่านสิทธิ์และ approval ของตน

เกณฑ์ตรวจรับและจุดหยุด

  • เมื่อ citation อ้าง current v1, digest ถูกต้อง, span ถูกต้อง และ quote ตรง ให้ผล QUOTE_VERIFIED
  • เมื่อ manifest เลื่อนไป v2 ให้ citation v1 ถูกปฏิเสธ แม้ hash ของ v1 ยังถูกต้อง
  • candidate เลือก historical revision หรือกำหนด hash authority เองไม่ได้
  • bytes เปลี่ยน, digest ผิด, source ไม่อนุญาต, offset ผิด, quote ผิด หรือ boundary ตัดกลาง UTF-8 ต้องไม่ผ่าน
  • ผลสำเร็จใช้ถ้อยคำ “current source quote located” เท่านั้น ห้ามเปลี่ยนเป็นคำกล่าวว่า truth หรือ entailment verified
  • หยุดหลังเช็กหนึ่งรายการ ไม่มี model, live retrieval, API, database, deployment หรือ production validation

หากจะขยายตัวอย่าง ให้เริ่มจากสร้าง canonicalization contract และทดสอบไฟล์ที่มี CRLF/LF กับ Unicode normalization ต่างกัน แล้วกำหนดว่าแต่ละการเปลี่ยนสร้าง revision อย่างไร อย่าเพิ่มการตรวจความหมายเข้าใน reason QUOTE_VERIFIED เพราะจะทำให้ผู้อ่านเข้าใจผลเกินหลักฐานที่ gate มี

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

ทำไมไม่เก็บตำแหน่งตัวอักษรแทน byte offset?

ทำได้หากทุกส่วนตกลงใช้ representation เดียวกัน ตัวอย่างนี้ hash ไฟล์ UTF-8 จึงบันทึก offset เป็น byte และระวังตัวอักษรไทยที่ใช้หลาย byte

QUOTE_VERIFIED พิสูจน์คำตอบของโมเดลหรือไม่?

ไม่ ผลนี้ตรวจตำแหน่งและ quote เท่านั้น; คำตอบอาจตีความผิดหรือใช้แหล่งไม่เหมาะ ต้องตรวจ claim กับบริบทแยก

ถ้าต้องอ้างเอกสารในอดีตล่ะ?

กำหนด policy และ timestamp สำหรับ historical snapshot แยกต่างหาก ตัวอย่างนี้ตรวจ current revision เท่านั้น

ตัวอย่างนี้ได้ทดสอบแล้วหรือยัง?

ยังไม่ได้รัน โค้ดและ unittest จึงเป็น NOT RUN; ตัวอย่างใช้ Python 3.12+ กับ standard library เท่านั้น

แบบฝึกหัดพร้อมเฉลย

โจทย์: manifest ปัจจุบันชี้ video-policy-demo revision v2 และ bytes ระบุ “ต้องได้รับอนุมัติก่อนส่งออก” แต่ retriever ส่ง candidate revision v1, quote “ต้องขออนุมัติก่อนเผยแพร่” และ digest ของ v1 ที่ยังถูกต้อง คุณควรแสดง quote หรือไม่?

เฉลย: ไม่แสดง citation และคืน REJECTED ด้วย reason stale_revision เพราะ revision ของ candidate ไม่ตรงกับ manifest ปัจจุบัน การตรวจหยุดที่นโยบาย freshness ไม่ต้องนำ hash เก่ามาเทียบเพื่ออนุญาต การแก้ที่เหมาะสมคือดึงหรือสร้าง citation ใหม่จาก v2 แล้วตรวจ digest และช่วง byte กับ v2 ถ้า quote ใหม่ตรง ระบบจึงส่ง QUOTE_VERIFIED ได้ แต่ยังต้องให้ชั้นคำตอบประเมินว่าข้อความใหม่ตอบคำถามได้ตรงจุดหรือไม่

อ่านต่อและแหล่งอ้างอิง

ตรวจแหล่งและเขียนตัวอย่าง ณ 16 ตุลาคม 2026 แหล่ง Python ใช้ยืนยันพฤติกรรมของไลบรารีเท่านั้น ส่วน revision manifest, strict freshness, byte-span gate และ acceptance criteria เป็นการออกแบบทางวิศวกรรมของบทเรียน ไม่ใช่ผล benchmark หรือข้อพิสูจน์ด้านความจริงของแหล่งข้อมูล ตัวอย่างและ unittest: NOT RUN

Source Code และวิธีลองด้วยตนเอง · NOT RUN

code/citation_gate.py · ยังไม่ได้รัน
"""ตรวจตำแหน่งคำอ้างอิงกับ revision ปัจจุบันที่แอปเชื่อถือ (Python 3.12+)."""

from __future__ import annotations

from dataclasses import dataclass
from hashlib import sha256
from typing import Mapping


@dataclass(frozen=True)
class SourceSnapshot:
    source_id: str
    revision: str
    canonical_utf8: bytes
    expected_sha256: str


@dataclass(frozen=True)
class CitationCandidate:
    source_id: str
    revision: str
    digest: str
    start: int
    end: int
    quote: str


@dataclass(frozen=True)
class GateResult:
    status: str
    reason: str


def validate_citation(
    candidate: CitationCandidate,
    trusted_manifest: Mapping[str, SourceSnapshot],
) -> GateResult:
    """ตรวจ citation เดียวกับสำเนา manifest ที่ผู้เรียกตรึงไว้ตลอดการตรวจหนึ่งครั้ง."""
    if type(candidate) is not CitationCandidate:
        return GateResult("REJECTED", "invalid_candidate_type")
    if not all(type(value) is str and value for value in
               (candidate.source_id, candidate.revision, candidate.digest, candidate.quote)):
        return GateResult("REJECTED", "invalid_text_field")
    if len(candidate.quote) > 1_000_000:
        return GateResult("REJECTED", "quote_too_large")
    try:
        quote_bytes = candidate.quote.encode("utf-8", errors="strict")
    except UnicodeError:
        return GateResult("REJECTED", "invalid_quote_utf8")
    if len(quote_bytes) > 1_000_000:
        return GateResult("REJECTED", "quote_too_large")
    if type(candidate.start) is not int or type(candidate.end) is not int:
        return GateResult("REJECTED", "invalid_offset_type")
    # manifest เป็น mapping ที่แอปเชื่อถือและตรึงไว้ตลอดการตรวจหนึ่งครั้ง
    try:
        if len(trusted_manifest) > 1000:
            return GateResult("REJECTED", "manifest_too_large")
        manifest = dict(trusted_manifest)
    except (TypeError, ValueError):
        return GateResult("REJECTED", "invalid_trusted_manifest")
    if len(manifest) > 1000:
        return GateResult("REJECTED", "manifest_too_large")
    source = manifest.get(candidate.source_id)
    if type(source) is not SourceSnapshot:
        return GateResult("REJECTED", "source_not_allowlisted")
    if type(source.source_id) is not str or type(source.revision) is not str:
        return GateResult("REJECTED", "invalid_manifest_entry")
    if source.source_id != candidate.source_id:
        return GateResult("REJECTED", "manifest_source_mismatch")
    if type(source.canonical_utf8) is not bytes:
        return GateResult("REJECTED", "invalid_source_bytes")
    if (type(source.expected_sha256) is not str or len(source.expected_sha256) != 64
            or any(char not in "0123456789abcdef" for char in source.expected_sha256)):
        return GateResult("REJECTED", "invalid_manifest_digest")
    if len(source.canonical_utf8) > 1_000_000:
        return GateResult("REJECTED", "source_too_large")
    try:
        source.canonical_utf8.decode("utf-8", errors="strict")
    except UnicodeError:
        return GateResult("REJECTED", "invalid_source_utf8")
    if candidate.revision != source.revision:
        return GateResult("REJECTED", "stale_revision")
    actual_digest = sha256(source.canonical_utf8).hexdigest()
    if actual_digest != source.expected_sha256:
        return GateResult("REJECTED", "source_digest_mismatch")
    if candidate.digest != source.expected_sha256:
        return GateResult("REJECTED", "candidate_digest_mismatch")
    data = source.canonical_utf8
    if not (0 <= candidate.start < candidate.end <= len(data)):
        return GateResult("REJECTED", "span_out_of_bounds")
    selected = data[candidate.start:candidate.end]
    try:
        selected.decode("utf-8", errors="strict")
    except UnicodeError:
        return GateResult("REJECTED", "invalid_utf8_boundary_or_quote")
    if selected != quote_bytes:
        return GateResult("REJECTED", "quote_mismatch")
    return GateResult("QUOTE_VERIFIED", "current_source_quote_located")
code/demo.py · ยังไม่ได้รัน
"""แสดง fixture สังเคราะห์สำหรับ current และ stale citation; Python 3.12+ only."""

import json
from hashlib import sha256

from citation_gate import CitationCandidate, SourceSnapshot, validate_citation


V1_TEXT = "ข้อกำหนดวิดีโอ: ต้องขออนุมัติก่อนเผยแพร่"
V2_TEXT = "ข้อกำหนดวิดีโอ: ต้องได้รับอนุมัติก่อนส่งออก"
SOURCE_ID = "video-policy-demo"


def source_snapshot(text: str, revision: str) -> SourceSnapshot:
    raw = text.encode("utf-8")
    return SourceSnapshot(SOURCE_ID, revision, raw, sha256(raw).hexdigest())


def citation_for(source: SourceSnapshot, quote: str) -> CitationCandidate:
    raw_quote = quote.encode("utf-8")
    start = source.canonical_utf8.index(raw_quote)
    return CitationCandidate(
        source_id=source.source_id,
        revision=source.revision,
        digest=sha256(source.canonical_utf8).hexdigest(),
        start=start,
        end=start + len(raw_quote),
        quote=quote,
    )


def main() -> None:
    v1 = source_snapshot(V1_TEXT, "v1")
    v2 = source_snapshot(V2_TEXT, "v2")
    old_citation = citation_for(v1, "ต้องขออนุมัติก่อนเผยแพร่")
    cases = (
        ("current-v1", {SOURCE_ID: v1}),
        ("stale-v1-after-v2", {SOURCE_ID: v2}),
    )
    output = []
    for label, manifest in cases:
        result = validate_citation(old_citation, manifest)
        output.append({"case": label, "status": result.status, "reason": result.reason})
    print(json.dumps(output, ensure_ascii=False, separators=(",", ":")))


if __name__ == "__main__":
    main()
code/test_citation_gate.py · ยังไม่ได้รัน
"""Regression cases for the bounded mechanical citation gate."""

import unittest
from dataclasses import FrozenInstanceError
from hashlib import sha256

from citation_gate import CitationCandidate, SourceSnapshot, validate_citation


V1 = "ข้อกำหนดวิดีโอ: ต้องขออนุมัติก่อนเผยแพร่"
V2 = "ข้อกำหนดวิดีโอ: ต้องได้รับอนุมัติก่อนส่งออก"


def snapshot(text: str, revision: str = "v1") -> SourceSnapshot:
    raw = text.encode("utf-8")
    return SourceSnapshot("video-policy-demo", revision, raw, sha256(raw).hexdigest())


def candidate(src: SourceSnapshot, quote: str, start: int | None = None) -> CitationCandidate:
    raw = src.canonical_utf8
    q = quote.encode("utf-8")
    offset = raw.index(q) if start is None else start
    return CitationCandidate(src.source_id, src.revision, sha256(raw).hexdigest(),
                             offset, offset + len(q), quote)


class CitationGateTests(unittest.TestCase):
    def test_accepts_current_exact_thai_quote(self):
        src = snapshot(V2, "v2")
        self.assertEqual(validate_citation(candidate(src, "ต้องได้รับอนุมัติก่อนส่งออก"), {src.source_id: src}).status,
                         "QUOTE_VERIFIED")

    def test_rejects_old_revision_after_pointer_advances_even_if_old_hash_matches(self):
        old = snapshot(V1, "v1")
        current = snapshot(V2, "v2")
        result = validate_citation(candidate(old, "ต้องขออนุมัติก่อนเผยแพร่"), {current.source_id: current})
        self.assertEqual(result.reason, "stale_revision")

    def test_unallowlisted_source_is_rejected(self):
        src = snapshot(V1)
        item = candidate(src, "ต้องขออนุมัติก่อนเผยแพร่")
        item = CitationCandidate("other", item.revision, item.digest, item.start, item.end, item.quote)
        self.assertEqual(validate_citation(item, {src.source_id: src}).reason,
                         "source_not_allowlisted")

    def test_modified_source_bytes_fail_digest(self):
        src = snapshot(V1)
        item = candidate(src, "ต้องขออนุมัติก่อนเผยแพร่")
        changed = SourceSnapshot(src.source_id, src.revision, V2.encode("utf-8"),
                                 src.expected_sha256)
        self.assertEqual(validate_citation(item, {changed.source_id: changed}).reason,
                         "source_digest_mismatch")

    def test_candidate_cannot_choose_a_different_digest(self):
        src = snapshot(V1)
        item = candidate(src, "ต้องขออนุมัติก่อนเผยแพร่")
        forged = CitationCandidate(item.source_id, item.revision, "0" * 64,
                                   item.start, item.end, item.quote)
        self.assertEqual(validate_citation(forged, {src.source_id: src}).reason,
                         "candidate_digest_mismatch")

    def test_invalid_or_boolean_offsets_are_rejected(self):
        src = snapshot(V1)
        item = candidate(src, "ต้องขออนุมัติก่อนเผยแพร่")
        for start, end in ((True, item.end), (-1, item.end), (item.end, item.end),
                           (item.start, len(src.canonical_utf8) + 1)):
            bad = CitationCandidate(item.source_id, item.revision, item.digest,
                                    start, end, item.quote)
            self.assertEqual(validate_citation(bad, {src.source_id: src}).status, "REJECTED")

    def test_byte_offset_splitting_thai_utf8_sequence_is_rejected(self):
        src = snapshot(V1)
        item = candidate(src, "ข้อกำหนด")
        bad = CitationCandidate(item.source_id, item.revision, item.digest,
                                item.start + 1, item.end, item.quote)
        self.assertEqual(validate_citation(bad, {src.source_id: src}).reason,
                         "invalid_utf8_boundary_or_quote")

    def test_wrong_display_quote_is_rejected(self):
        src = snapshot(V1)
        item = candidate(src, "ต้องขออนุมัติก่อนเผยแพร่")
        bad = CitationCandidate(item.source_id, item.revision, item.digest,
                                item.start, item.end, "ส่งออก")
        self.assertEqual(validate_citation(bad, {src.source_id: src}).reason, "quote_mismatch")

    def test_manifest_source_identity_must_match_lookup_key(self):
        src = snapshot(V1)
        misbound = SourceSnapshot("different-id", src.revision, src.canonical_utf8,
                                  src.expected_sha256)
        self.assertEqual(validate_citation(candidate(src, "ต้องขออนุมัติก่อนเผยแพร่"),
                                           {src.source_id: misbound}).reason,
                         "manifest_source_mismatch")

    def test_snapshot_records_are_immutable(self):
        src = snapshot(V1)
        with self.assertRaises(FrozenInstanceError):
            src.revision = "v2"  # type: ignore[misc]


if __name__ == "__main__":
    unittest.main()
README-code.md · ยังไม่ได้รัน
# ตัวอย่าง Citation Gate

ตัวอย่างจำลองสำหรับ Python 3.12 ขึ้นไป ใช้ standard library เท่านั้น ไม่มี environment variable และไม่ติดตั้งแพ็กเกจ ไม่เรียก LLM/API และไม่ต่อฐานข้อมูล ค่าใช้จ่าย API เป็นศูนย์; การรันใช้ CPU ในเครื่องตามปกติและไม่มีการวัดต้นทุนหรือ latency

## วิธีลองด้วยตนเอง

ติดตั้ง Python 3.12 หรือใหม่กว่าโดยใช้ runtime ที่เชื่อถือได้ แล้วจากโฟลเดอร์นี้รันคำสั่ง:

```text
cd code
python demo.py
python -m unittest -v
```

ไม่มี `pip install` หรือ dependency ภายนอก input ของ demo เป็น fixture สมมติภายในไฟล์เท่านั้น ไม่รับข้อมูลผู้ใช้ ไม่เชื่อม network และไม่แก้ระบบภายนอก ผลตัวอย่างด้านล่างเป็น **ผลคาดหมาย ไม่ใช่ผลรันจริง**:

```json
[{"case":"current-v1","status":"QUOTE_VERIFIED","reason":"current_source_quote_located"},{"case":"stale-v1-after-v2","status":"REJECTED","reason":"stale_revision"}]
```

เมื่อรันทดสอบคาดว่าจะพบ 10 กรณีและจบด้วย `OK` ตัวอย่างและชุดทดสอบยังไม่ได้รันในการจัดทำบทความ สถานะจึงเป็น **NOT RUN**

เพื่อดูผลหนึ่งรายการ ให้สร้าง `SourceSnapshot` สำหรับ `video-policy-demo`, revision `v2`, ข้อความ UTF-8 `ข้อกำหนดวิดีโอ: ต้องได้รับอนุมัติก่อนส่งออก` และ `expected_sha256` ที่แอปคำนวณขณะ ingest จาก bytes ชุดนั้น จากนั้นสร้าง candidate ที่มี digest เดียวกัน, `start=44`/`end=125` ซึ่งเป็น byte span ครึ่งเปิดของ quote `ต้องได้รับอนุมัติก่อนส่งออก`, และใส่ quote เดียวกัน ผลคาดหมายคือ `GateResult(status='QUOTE_VERIFIED', reason='current_source_quote_located')` หาก manifest เปลี่ยนเป็น v3 แล้วส่ง candidate v2 ผลจะเป็น `stale_revision` แม้ hash ของ v2 ยังถูกต้อง

## สัญญาและขอบเขต

แอปเป็นเจ้าของ manifest ที่เชื่อถือได้และเป็นผู้เลือก revision ปัจจุบัน ส่วน candidate เป็นข้อมูลไม่น่าเชื่อถือ จึงไม่สามารถกำหนด hash authority หรือขอเลือก historical revision เองได้ manifest เก็บ `expected_sha256` ที่แอปคำนวณและผูกกับ bytes ระหว่าง ingest; validator คำนวณ hash จาก bytes ปัจจุบันซ้ำแล้วเทียบกับ manifest ก่อนเทียบ digest ของ candidate โค้ดตรวจหนึ่ง citation ต่อการเรียก จำกัด manifest ไว้ 1,000 รายการและ source กับ quote ไว้ไม่เกิน 1 MB; ระบบจริงควรปรับเพดานตามขนาดรับเข้าและทรัพยากรของตนเอง ผู้เรียกต้องส่ง manifest mapping ที่เชื่อถือได้และคงที่ตลอดการตรวจ

`QUOTE_VERIFIED` หมายถึงพบ quote ตรงกันใน bytes ของ source revision ที่ manifest ระบุว่าเป็นปัจจุบัน พร้อม digest และขอบเขต UTF-8 ที่ถูกต้องเท่านั้น ไม่ยืนยันตัวตน/ความน่าเชื่อถือของผู้เผยแพร่ ไม่ยืนยันว่านโยบายยังเป็นจริงในโลกภายนอก ไม่ตรวจว่า quote สนับสนุนคำกล่าวอ้างทั้งประโยคหรือไม่ และไม่อนุญาตให้ทำ action ใด

ก่อนใช้จริงต้องนิยาม canonicalization ให้ชัด เช่น line ending, Unicode normalization, การดึง HTML และเวลาอัปเดต revision/hash ร่วมกัน กติกาการเปลี่ยนเหล่านี้ต้องสร้าง revision ใหม่ การคัดลอก manifest ระหว่างเรียกเป็น snapshot แบบ in-memory เพื่อให้การตรวจครั้งเดียวใช้ค่าคงที่ ไม่ได้แก้ race condition ระหว่างระบบ ingest หลายโหนด, ACL/สิทธิ์อ่าน, การเลือกแหล่งทางการ, หรือการดึงข้อมูลสด

ไม่มี credential, การติดตั้ง, API, ค่าใช้จ่ายบริการ หรือผลการทดสอบ runtime ในตัวอย่างนี้

Response

  1. […] citation กับ revision และ byte span คือ RAG Citation Gate; ส่วนการตรวจสิทธิ์ก่อนคืน cache อ่าน RAG […]

ใส่ความเห็น

Discover more from HYBRID MIND

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

Continue reading