การเปิดดูรายการเดินบัญชีธนาคารไม่ใช่เรื่องสนุกสำหรับใครเลย พวกมันมาในรูปแบบ PDF ที่สแกนมา, ไฟล์ส่งออก CSV หรือไฟล์ XML ที่เต็มไปด้วยตัวย่อที่เข้าใจยากอย่าง OFX สำหรับนักบัญชี ผู้ทำบัญชี และผู้สร้าง fintech การเปลี่ยนเอกสารเหล่านี้ให้เป็นข้อมูลที่สะอาดและมีโครงสร้างคือปัญหาที่น่าปวดหัวอยู่ตลอดเวลา เมื่อ Large Language Models (LLMs) ปรากฏตัวขึ้น พวกมันดูเหมือนจะเป็นทางออกที่ช่วยให้รอดพ้นจากปัญหานี้ได้ แค่ป้อน PDF ให้เครื่องแล้วสั่งให้สร้าง JSON จะมีอะไรผิดพลาดได้ล่ะ?

ผมได้เรียนรู้ว่าอะไรที่สามารถผิดพลาดได้จากการสร้าง StatementDecoder ซึ่งเป็นเครื่องมือที่ออกแบบมาเพื่อแปลงรายการเดินบัญชีธนาคารให้เป็นข้อมูลที่นำไปใช้งานได้ เหมือนกับนักพัฒนาคนอื่นๆ ผมสันนิษฐานว่าส่วนที่ยากที่สุดคือการสอนให้ระบบอ่านรูปแบบเอกสารที่หลากหลาย แต่ผมคิดผิด การอ่านเอกสารนั้นแทบจะเป็นเรื่องง่ายมาก ฝันร้ายที่แท้จริงคือการตรวจจับว่าเมื่อไหร่ที่เครื่องแอบสร้างตัวเลขขึ้นมาเอง หรือสลับตัวเลขสองหลักในจำนวนเงินของรายการธุรกรรม

เดโมที่ทำงานดีเกินไป

ความพยายามครั้งแรกของผมนั้นง่ายจนน่าหลงใหล ผมส่งรายการเดินบัญชีเข้าไปใน LLM โดยตรงและขอผลลัพธ์กลับมาเป็น JSON ที่มีโครงสร้าง ผลลัพธ์ที่ได้ให้ความรู้สึกเหมือนเวทมนตร์ โมเดลจัดการกับรูปแบบที่แตกต่างกันได้อย่างง่ายดาย มันสามารถอ่าน PDF ที่สแกนมาซึ่งตัวแปลงข้อมูล (parsers) มาตรฐานมักจะอ่านไม่ได้ มันดูเหมือนจะเข้าใจตาราง หัวข้อ และรายการเดินบัญชีที่มีหลายหน้าได้โดยไม่ต้องมีคำสั่งที่ชัดเจน ในช่วงไม่กี่ชั่วโมงอันแสนวิเศษนั้น ผมคิดว่าปัญหาได้รับการแก้ไขแล้ว

แต่เมื่อผมทดสอบกับข้อมูลลูกค้าจริง เวทมนตร์นั้นก็มลายหายไป ธนาคารในสหราชอาณาจักรแต่ละแห่งใช้รูปแบบรายการเดินบัญชีของตัวเอง และความแตกต่างนั้นไม่ใช่แค่เรื่องของความสวยงาม รายการเดินบัญชีของ Wise มีรูปแบบเฉพาะตัวที่แปลกประหลาด ไฟล์ส่งออก CSV ของ Revolut ดูเหมือนจะตรงไปตรงมา จนกระทั่งคุณสังเกตเห็นวิธีที่พวกเขาจัดการกับธุรกรรมหลายสกุลเงินและฟิลด์ metadata ไฟล์ OFX รุ่นเก่า ซึ่งเป็นรูปแบบที่ดูเหมือนมาจากยุค 1990 จริงๆ มักจะส่งโครงสร้างแท็กที่ล้าสมัยและปัญหาการเข้ารหัส (encoding) มาให้ตัวแปลงข้อมูลใดก็ตามที่คาดหวัง markup สมัยใหม่

โมเดลยังคงดึงข้อมูลได้ดีกว่าระบบเทมเพลตสำเร็จรูปใดๆ มาก แต่คำว่า "ดีกว่ามาก" นั้นยังไม่ดีพอเมื่อเป็นเรื่องของเงินทอง

เมื่อความแม่นยำ 99% คือความล้มเหลว

นี่คือปัญหาพื้นฐานของการใช้ AI ในการดึงข้อมูลทางการเงิน หากโมเดลประมวลผลรายการธุรกรรมสองร้อยแถวและทำถูกต้องหนึ่งร้อยเก้าสิบเก้าแถว ผลลัพธ์ที่ได้จะดูสมบูรณ์แบบมาก JSON มีรูปแบบที่ถูกต้อง คีย์และค่าต่างๆ ตรงกัน การตรวจสอบผ่านๆ อาจไม่พบสิ่งผิดปกติใดๆ แต่ถ้าข้อผิดพลาดเพียงจุดเดียวทำให้ตัวเลขสองหลักในจำนวนเงินสลับกัน เปลี่ยนรายการฝากเป็นรายการถอน หรือเลื่อนจุดทศนิยม การทำบัญชีของคุณก็จะเสียหายทันที คุณจะไม่สามารถตรวจพบมันได้ด้วยการกวาดสายตามองข้อมูลที่มีโครงสร้างจำนวนมหาศาล

มนุษย์ที่ตรวจสอบ JSON ดิบแทบจะไม่สังเกตเห็นตัวเลขที่สลับกันในจำนวนเงินธุรกรรม รูปแบบที่สมบูรณ์แบบกลับทำให้ข้อผิดพลาดนั้นอันตรายยิ่งขึ้นอย่างน่าประหลาด คุณไม่สามารถส่งมอบเครื่องมือทางการเงินที่ "ถูกต้องเป็นส่วนใหญ่" ได้ มันต้องถูกต้อง หรือไม่ก็ต้องประกาศออกมาดังๆ ว่ามันไม่แน่ใจ

ปฏิกิริยาแรกของผมนั้นคาดเดาได้ ผมออกแบบ prompt ให้ดีขึ้น ผมอัปเกรดไปใช้โมเดลที่มีความสามารถสูงขึ้น ผมทดลองใช้การให้เหตุผลแบบ chain-of-thought เพื่อให้โมเดลแสดงขั้นตอนการคิด แต่ไม่มีสิ่งใดแก้ไขปัญหาหลักได้เลย ผมกำลังขอให้ระบบที่ใช้ความน่าจะเป็น (probabilistic system) แบบเดิมสร้างคำตอบขึ้นมา แล้วก็ขอให้ระบบเดิมตัวเดิมนั่นแหละเป็นคนรับรองว่าคำตอบนั้นถูกต้อง นั่นไม่ใช่การตรวจสอบ แต่มันเป็นเพียงแค่การแสดงละครเพื่อสร้างความเชื่อมั่นในความสอดคล้องของตัวเองเท่านั้น

ให้คณิตศาสตร์เป็นตัวตัดสิน

รายการเดินบัญชีธนาคารมีคุณสมบัติที่เอกสารส่วนใหญ่ไม่มี นั่นคือข้อจำกัดทางคณิตศาสตร์ในตัว ยอดเงินยกมาบวกกับผลรวมของธุรกรรมทั้งหมดต้องเท่ากับยอดเงินคงเหลือ ยอดเงินคงเหลือสะสม หากมีอยู่ ก็ต้องตรงกันในแต่ละแถว สิ่งเหล่านี้ไม่ใช่ความชอบส่วนตัวในเชิงสไตล์ แต่มันคือกฎที่ตายตัว

ผมสร้างสถาปัตยกรรมใหม่โดยยึดตามความเข้าใจนี้ ตอนนี้ ทุกการดึงข้อมูล ไม่ว่าจะมาจากแหล่งใด จะต้องผ่านเลเยอร์การตรวจสอบ (validation layer) ก่อนที่ผู้ใช้จะเห็น ไม่สำคัญว่าข้อมูลจะมาจาก LLM ที่กำลังตีความ PDF ที่ไม่ชัดเจน, เครื่องยนต์ OCR ที่กำลังอ่านหน้าที่สแกนมา หรือการ parse ไฟล์ CSV โดยตรง ตัวตรวจสอบจะปฏิบัติกับทุกแหล่งข้อมูลด้วยความสงสัยเท่าๆ กัน

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

การเปลี่ยนแปลงเพียงจุดเดียวนี้ได้เปลี่ยนลักษณะของผลิตภัณฑ์ไปโดยสิ้นเชิง โมเดลภาษาไม่จำเป็นต้องสมบูรณ์แบบอีกต่อไป มันแค่ต้องดีพอที่จะสร้างผลลัพธ์ที่สามารถผ่านการทดสอบทางคณิตศาสตร์ได้ ความกดดันได้เปลี่ยนจากการพยายามบรรลุความแม่นยำที่เป็นไปไม่ได้ในขอบเขตที่ไม่มีข้อจำกัด มาเป็นการสร้างวงจรการตอบกลับ (feedback loop) ที่รัดกุมระหว่างการสร้างข้อมูลและการตรวจสอบความถูกต้อง

ตัวตรวจสอบยังเผยให้เห็นรูปแบบของข้อผิดพลาดที่เกิดขึ้นด้วย เอกสารบางประเภทล้มเหลวในการตรวจสอบการคำนวณ (math check) อย่างต่อเนื่อง ซึ่งบอกให้ผมรู้ได้ทันทีว่าควรทุ่มเทความพยายามไปที่จุดไหน แทนที่จะปรับปรุง prompt engineering แบบสุ่มไปทั่ว ผมสามารถมองเห็นได้ว่ารูปแบบ (layout) ของธนาคารบางแห่งเป็นสาเหตุที่ทำให้เกิดข้อผิดพลาดอย่างเป็นระบบ

ใช้ Code ในจุดที่ควรใช้ และใช้ AI ในจุดที่มันโดดเด่น

บทเรียนที่ทำให้ผมต้องถ่อมตัวลงมากที่สุดอาจเป็นการตระหนักได้ว่า มีส่วนประกอบมากมายใน pipeline ที่ไม่จำเป็นต้องใช้ AI เลยด้วยซ้ำ เมื่อผมต้องเจอกับไฟล์ OFX ของออสเตรเลียที่ยุ่งเหยิง สัญชาตญาณของผมคือการโยน tokens ใส่ปัญหา ผมเคยคิดครู่หนึ่งว่าจะป้อน XML ที่เสียเหล่านั้นให้โมเดล และขอให้มันซ่อมแซมโครงสร้างก่อนที่จะทำการ parse แต่ผมกลับเขียนโค้ดแบบ deterministic เพียงยี่สิบบรรทัดแทน มันสามารถแก้ไขปัญหาการเข้ารหัส (encoding) ที่แปลกประหลาดและแท็กที่ผิดรูปแบบได้อย่างทันที โดยไม่มีค่าใช้จ่ายต่อไฟล์ และสามารถทำซ้ำได้อย่างแม่นยำสมบูรณ์แบบ

ประสบการณ์นั้นทำให้ผมเห็นภาพชัดเจนว่า pipeline สำหรับการสกัดข้อมูล (extraction pipelines) ควรจะถูกจัดระเบียบอย่างไร งานมีอยู่สามส่วนที่แตกต่างกันอย่างชัดเจน และไม่ควรนำมาปนกัน

  • โมเดลทำหน้าที่ทำความเข้าใจเอกสารที่ยุ่งเหยิง เช่น ไฟล์ PDF ที่สแกนมาซึ่งมีตารางที่บิดเบี้ยว ฟอนต์ที่ปนกัน และลายมือเขียน