29/06/2026 03:13น.

Golang The Series EP.155: Chunking Strategies เทคนิคตัดแบ่งข้อมูลส่งให้ AI
#Golang AI
#Golang
#Go
#Chunking คืออะไร
#RAG
#Overlap
#Data Pipeline
ยินดีต้อนรับเข้าสู่ Golang The Series SS5: AI Awaken EP.155 ครับ! ในตอนที่แล้วเราได้ลุยวิธีสร้าง Collection บน Qdrant กันไปเรียบร้อยแล้ว คราวนี้ลองจินตนาการว่าคุณมีไฟล์คู่มือพนักงานหนา 100 หน้า หรือไฟล์สเปกสินค้า (Product Manual) ขนาดยาวเหยียดที่ต้องการนำมาทำระบบ RAG (Retrieval-Augmented Generation)
คำถามคลาสสิกที่ Dev สาย AI มือใหม่มักจะถามกันเข้ามาบ่อยๆ ก็คือ:
"เราจะจับข้อความทั้งหมดจากเอกสารนี้ โยนเข้ากระบวนการแปลง Embedding ทีเดียวเลยได้ไหม?"
คำตอบสั้นๆ คือ "ไม่ได้เด็ดขาดครับ" และนี่คือ 2 เหตุผลหลักที่สถาปัตยกรรมระบบ AI ทั่วไปไม่รองรับ:
LLM Context Window Limits (ขีดจำกัดของโมเดล): โมเดลภาษาขนาดใหญ่ (LLM) มีโควตาจำกัดจำนวน Token ที่รับได้ต่อหนึ่งคำขอ (Prompt) และตัวโมเดล Embedding เองก็มีขีดจำกัดในการรับข้อความเพื่อนำไปคำนวณมิติเวกเตอร์เช่นกัน
Diluted Meaning (ปัญหาความหมายเจือจาง): การฝืนแปลงข้อความยาวๆ 10-20 หน้าให้เหลือเวกเตอร์เพียงชุดเดียว จะทำให้ "รายละเอียดเชิงลึก" หรือคีย์เวิร์ดย่อยๆ ในเอกสารจางหายไป จน AI ไม่สามารถจับใจความสำคัญของเนื้อหาเฉพาะจุดได้เลย
นี่จึงเป็นเหตุผลสำคัญที่เราต้องรู้จักและเข้าใจเรื่อง Chunking Strategies หรือเทคนิคการตัดแบ่งข้อความขนาดใหญ่ออกเป็นชิ้นส่วนย่อยๆ ที่มีขนาดพอดี แต่ยังคงบริบท (Context) ไว้อย่างครบถ้วน
ในบทความนี้เราจะมาเจาะลึกยุทธวิธีการแบ่งข้อความ พร้อมลุย โค้ดภาษา Go (Golang) สำหรับสร้าง Chunking Engine ประสิทธิภาพสูงกันครับ!
เจาะลึก 3 รูปแบบการทำ Chunking ที่นิยมใช้ในระบบ RAG
การแบ่งชิ้นส่วนข้อความในระบบ RAG ไม่ใช่เรื่องของการสุ่มหั่น แต่มีตั้งแต่แนวคิดพื้นฐานไปจนถึงการปรับแต่งแบบละเอียด เพื่อให้ AI เข้าใจบริบทได้ดีที่สุด โดยทั่วไปจะแบ่งออกเป็น 3 วิธีหลักๆ ดังนี้ครับ:
เลเวล 1: Character / Token-based Chunking (ตัดตามความยาวคงที่)
วิธีนี้เบสิกที่สุดครับ คือการตั้งค่าดื้อๆ เลยว่าต้องการให้แต่ละชิ้นมีขนาดกี่ตัวอักษรหรือกี่ Token (เช่น กำหนดไว้ชิ้นละ 500 ตัวอักษร) พอนับครบปุ๊บ ระบบก็จะหั่นขาดตรงนั้นทันที
ข้อจำกัดที่ต้องระวัง: ข้อมูลมักจะโดนตัดครึ่งประโยค ครึ่งคำ หรือหั่นคำไทยแยกออกจากกัน (เช่น คำว่า "ประสิทธิภาพ" อาจโดนหั่นเป็น "ประสี" อยู่ Chunk หนึ่ง และ "ทธิภาพ" ไปอยู่อีก Chunk หนึ่ง) ส่งผลให้ตอนแปลง Embedding ความหมายจะเพี้ยนทันที
เลเวล 2: Recursive Character Chunking (หั่นตามโครงสร้างเอกสาร)
ขยับความฉลาดขึ้นมาอีกขั้น วิธีนี้ระบบจะไม่หั่นสุ่มสี่สุมห้า แต่จะพยายามมองหา "จุดตัดตามธรรมชาติ" ของภาษาก่อน โดยไล่ลำดับความสำคัญจากใหญ่ไปเล็ก เช่น มองหาจุดขึ้นบรรทัดใหม่ (\n\n หรือ \n) หรือช่องว่างที่เป็นจุดจบประโยคก่อน ถ้าชิ้นส่วนนั้นยังมีขนาดยาวเกินเกณฑ์ที่ตั้งไว้ ถึงจะยอมขยับไปหั่นตามจำนวนตัวอักษร วิธีนี้จะช่วยรักษาโครงสร้างและความหมายของประโยคไว้ได้เนียนกว่ามาก
เลเวล 3: Sliding Window & Overlap (การตัดแบบคาบเกี่ยว/ซ้อนทับ)
ไม่ว่าคุณจะเลือกใช้สองวิธีข้างต้นแบบไหน สิ่งสำคัญที่จะขาดไม่ได้เลยคือการตั้งค่า Overlap หรือการดึงเนื้อหาส่วนท้ายของ Chunk ก่อนหน้า มาเชื่อมต่อเป็นส่วนหัวของ Chunk ถัดไป
ตัวอย่างเช่น: ถ้าเราตั้งค่า Chunk Size = 500 และ Overlap = 100 หมายความว่า ข้อความ 100 ตัวอักษรสุดท้ายของ Chunk ที่ 1 จะถูกยกไปเป็น 100 ตัวอักษรแรกของ Chunk ที่ 2 ด้วย การทำแบบนี้จะช่วยอุดรอยต่อ ไม่ให้ใจความสำคัญตรงจุดเชื่อมต้องขาดตอนไปครับ
Workshop: เขียน Go ทำ Sliding Window Chunking รองรับภาษาไทย
เรามาลองเขียนฟังก์ชันในภาษา Go เพื่อทำ Text Chunking โดยอิงตามหน่วยอักขระ (rune) เพื่อให้ระบบสามารถตัดแบ่งข้อความภาษาไทยได้อย่างแม่นยำ ไม่เกิดปัญหาอักขระต่างดาว พร้อมกับกำหนดค่าคาบเกี่ยว (Overlap) ไปพร้อมกันครับ
Go
package main
import (
"fmt"
)
// ChunkText ทำการตัดแบ่งข้อความตามขนาดและจุดซ้อนทับที่กำหนด (คิดตามจำนวนอักขระ Rune)
func ChunkText(text string, chunkSize int, overlap int) []string {
runes := []rune(text)
var chunks []string
// ป้องกันปัญหา Edge Cases และ Infinite Loop
if chunkSize <= 0 {
return chunks
}
if overlap >= chunkSize {
overlap = chunkSize - 1 // Overlap ต้องน้อยกว่า Chunk Size เสมอ
}
for i := 0; i < len(runes); {
end := i + chunkSize
if end > len(runes) {
end = len(runes)
}
// ดึงชิ้นส่วนข้อความออกมาเก็บไว้
chunks = append(chunks, string(runes[i:end]))
// หากตรวจสอบว่าประมวลผลถึงปลายทางข้อความแล้วให้จบการทำงาน
if end == len(runes) {
break
}
// เลื่อนตำแหน่ง Index ถัดไป โดยถอยกลับมาเท่ากับระยะ Overlap เพื่อรักษาบริบทรอยต่อ
i += (chunkSize - overlap)
}
return chunks
}
func main() {
longText := "ภาษา Go ออกแบบมาเพื่อจัดการ Concurrency ที่ยอดเยี่ยม ด้วยฟีเจอร์อย่าง Goroutines และ Channels " +
"ทำให้สามารถประมวลผลงานขนาดใหญ่แบบขนานได้อย่างมีประสิทธิภาพสูงและกินทรัพยากรน้อยมาก " +
"เหมาะอย่างยิ่งสำหรับการสร้างระบบ Data Pipeline ในยุคปัญญาประดิษฐ์และ AI-First Architecture"
// สั่งหั่นชิ้นละ 50 ตัวอักษร โดยให้มีส่วนซ้อนทับกันพิกัดละ 15 ตัวอักษร
chunks := ChunkText(longText, 50, 15)
for i, chunk := range chunks {
fmt.Printf("🧩 Chunk %d: [%s]\n", i+1, chunk)
}
}
ทำไม Go ถึงเหมาะสำหรับสร้าง Chunking Engine?
เมื่อระบบ RAG ขยายสเกลใหญ่ขึ้น เอกสารที่ถูกส่งเข้ามาประมวลผลพร้อมกันอาจมีปริมาณมหาศาล (เช่น พนักงานอัปโหลดคู่มือองค์กรพร้อมกันหลายสิบคน) ประสิทธิภาพในระดับ System-level ของ Go จะเข้ามาช่วยจัดการปัญหานี้ได้ทันที:
Goroutine Workers (Concurrency ทรงพลัง): เราสามารถรับไฟล์ข้อความขนาดใหญ่เข้ามา แล้วใช้แนวคิด Worker Pool ของ Goroutines แยกย้ายกันไปหั่นซอย Chunk แบบคู่ขนาน (Concurrent) ได้ทันที โดยไม่บล็อกการทำงานของ Main Thread
Memory Efficiency (จัดการหน่วยความจำดีเยี่ยม): Go จัดการสไลซ์ของสตริง (String Slices) และ
[]runeได้อย่างคุ้มค่า มีกลไกการจองหน่วยความจำที่ต่ำมาก ทำให้เราประมวลผล Text ระดับหลายร้อย Megabytes ได้สบายๆ โดยไม่ต้องกังวลเรื่อง Performance Drop หรือปัญหา Memory Leak
🎯 ท้าให้ลอง (Daily Mission)
ลองคัดลอกซอร์สโค้ดด้านบนไปรันบนเครื่องของคุณ แล้วสังเกตผลลัพธ์ในแต่ละ Chunk ดูครับว่า ข้อความส่วนรอยต่อที่เป็น Overlap มีการซ้อนทับกันตามที่เราตั้งโจทย์ไว้จริงไหม?
💡 การบ้านชวนคิด: จากโค้ดตัวอย่างด้านบน หากเราต้องการเปลี่ยนเงื่อนไขจากการนับจำนวนตัวอักษรตรงๆ ไปเป็นการสแกนหาช่องว่าง (Space) หรือตัวขึ้นบรรทัดใหม่ (
\n) เพื่อป้องกันไม่ให้คำภาษาไทยถูกหั่นครึ่งกลางคัน คุณคิดว่าเราควรเขียน Logic เพื่อค้นหาตำแหน่งปลายทาง (end) เพิ่มเติมอย่างไร? ลองฝึกสมองปรับปรุงโค้ดกันดูนะครับ!
❓ FAQ (คำถามที่พบบ่อยประจำตอน)
ควรตั้งค่า Chunk Size และ Overlap เท่าไหร่ดีที่สุดสำหรับภาษาไทย?
ไม่มีตัวเลขที่ตายตัวครับ แต่จุดเริ่มต้นที่แนะนำ (Baseline) สำหรับโมเดลยอดนิยมอย่าง OpenAI Embedding คือการตั้งค่า Chunk Size ประมาณ 400–800 ตัวอักษร (คิดเป็น Rune) และตั้งค่า Overlap ไว้ที่ประมาณ 10%–20% ของขนาด Chunk (ประมาณ 50–150 ตัวอักษร) เพื่อช่วยรักษาความต่อเนื่องของบริบทในประโยคภาษาไทยครับ
ทำไมเราถึงต้องแปลง String ให้เป็นสไลซ์ของ []rune ก่อนทำการ Chunking ในภาษา Go?
เพราะในภาษา Go ตัวสตริงปกติ (String) จะถูกเก็บและนับหน่วยความยาวตามขนาด ไบต์ (Bytes) ซึ่งอักขระภาษาไทยส่วนใหญ่บนสถาปัตยกรรม UTF-8 จะใช้พื้นที่ถึง 3 ไบต์ต่อหนึ่งตัวอักษร
หากเราใช้ฟังก์ชันตัดสตริงแบบธรรมดา โค้ดจะเข้าไปหั่นกลางไบต์ ส่งผลให้อักขระไทยตัวนั้นเสียหาย แสดงผลเพี้ยน หรือกลายเป็นเครื่องหมายคำถามกล่องสี่เหลี่ยม () การแปลงเป็น []rune จะบังคับให้ Go มองตำแหน่งตัวอักษรเป็นรายตัว (Code Point) ช่วยให้ตัดคำภาษาไทยได้อย่างถูกต้องสมบูรณ์ 100% ครับ
สรุป: Chunking ดี มีชัยไปกว่าครึ่ง 🎯
การออกแบบระบบ RAG ให้มีความแม่นยำและฉลาด ตอบคำถามได้ตรงประเด็น ไม่ได้ขึ้นอยู่กับความเทพของโมเดล LLM เพียงอย่างเดียว แต่เริ่มต้นตั้งแต่ "คุณภาพของข้อมูล" ที่เราป้อนเข้าไปครับ
เทคนิคการทำ Chunking Strategies ร่วมกับการตั้งค่า Overlap ที่เหมาะสม จึงเป็นขั้นตอนสำคัญ (Data Pipeline) ที่เราจะมองข้ามไม่ได้เด็ดขาด เพราะถ้าเราหั่นข้อมูลไม่ดี ต่อให้ AI จะฉลาดแค่ไหนก็อาจจะตีความบริบทผิดเพี้ยนไปได้ และการเลือกใช้ภาษา Go ในการสร้าง Chunking Engine ก็ช่วยการันตีได้ว่า ระบบของคุณจะสามารถรองรับการสเกลและการประมวลผลไฟล์เอกสารมหาศาลได้อย่างรวดเร็วและประหยัดทรัพยากรที่สุดครับ
ในตอนต่อไป (EP.156) 🚀: เมื่อเราหั่นเอกสารคลังความรู้ออกเป็น Chunk ที่สมบูรณ์และนำไปอัปโหลดเก็บใน Qdrant เรียบร้อยแล้ว เฟสถัดไปคือการดึงข้อมูลออกมาใช้งานจริง!
ในตอนหน้า เราจะมาเจาะลึกระบบ "Semantic Search: การค้นหาข้อมูลตามความหมาย" เพื่อดูวิธีควานหาชิ้นส่วนข้อความที่ตรงใจผู้ใช้มากที่สุด โดยไม่ต้องพึ่งพาการ Match คีย์เวิร์ดแบบเดิมๆ แล้วพบกันในตอนต่อไปครับ!
ฝากกดติดตามพวกเราได้ที่ Superdev Academy ในทุกช่องทางนะครับ!
🔵 Facebook: Superdev Academy Thailand (อัปเดตข่าวสารและบทความใหม่)
🎬 YouTube: Superdev Academy Channel (ติวเข้มแบบวิดีโอ)
📸 Instagram: @superdevacademy (เกร็ดความรู้สั้นๆ และเบื้องหลังการทำงาน)
🎬 TikTok: @superdevacademy (Tips & Tricks ฉบับย่อยง่าย)
🌐 Website: superdevacademy.com (คลังบทความและคอร์สเรียนฉบับเต็ม)