[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"academy-blogs-th-1-1-all-golang-the-series-ep156-semantic-search-vector-qdrant-all--*":3,"academy-blog-translations-or1jhl03h1oifdj":80},{"data":4,"page":66,"perPage":66,"totalItems":66,"totalPages":66},[5],{"alt":6,"collectionId":7,"collectionName":8,"content":9,"cover_image":10,"cover_image_path":11,"created":12,"created_by":13,"expand":14,"id":74,"keywords":75,"locale":46,"published_at":76,"scheduled_at":62,"school_blog":70,"short_description":77,"status":68,"title":78,"updated":79,"updated_by":13,"slug":71,"views":73},"ไดอะแกรมระบบ Semantic Search Pipeline ด้วย Go, OpenAI Embedding และ Qdrant DB","sclblg987654321","school_blog_translations","\u003Cp>ยินดีต้อนรับเข้าสู่ \u003Cstrong>EP.156\u003C\u002Fstrong> ครับ! ในตอนที่แล้วเราได้เรียนรู้วิธีการหั่นข้อความยาวๆ ออกเป็นชิ้นๆ (\u003Cem>Chunking\u003C\u002Fem>) อย่างมีกลยุทธ์เพื่อคงบริบทเอาไว้ และในตอนนี้ก็ถึงเวลาที่จิ๊กซอว์ทุกชิ้นที่เราสะสมมาตั้งแต่ต้นซีซัน จะโคจรมาเจอกันเพื่อสร้างฟีเจอร์ที่เรียกว่า \u003Cstrong>Semantic Search\u003C\u002Fstrong> หรือระบบค้นหาข้อมูลอัจฉริยะตามความหมายเชิงลึกของภาษาคนครับ!\u003C\u002Fp>\u003Cp>ลองจินตนาการว่าหากผู้ใช้พิมพ์ค้นหาคำว่า \u003Cem>\"วิธีแก้ไขปัญหาต่อเน็ตไม่ได้\"\u003C\u002Fem> ระบบแบบเก่าอย่าง \u003Cstrong>Keyword Search\u003C\u002Fstrong> จะไม่มีทางเจอเอกสารคู่มือที่เขียนว่า \u003Cem>\"คู่มือการเซ็ตอัปเราเตอร์และการแก้ปัญหาสัญญาณ Wi-Fi หลุด\"\u003C\u002Fem> เลย เพราะไม่มีคีย์เวิร์ดคำว่า \"เน็ต\" หรือ \"อินเทอร์เน็ต\" อยู่ในนั้นเลยสักคำ แต่สำหรับ \u003Cstrong>Semantic Search\u003C\u002Fstrong> มันสามารถรับรู้ได้ทันทีว่าทั้งสองประโยคนี้กำลังพูดถึงเรื่องเดียวกัน... ผ่านพิกัดเวกเตอร์!\u003C\u002Fp>\u003Cp>วันนี้เราจะมาเขียน Go เพื่อสร้างระบบค้นหานี้กันครับ\u003C\u002Fp>\u003Ch2>ลำดับขั้นตอนการทำงานของ Semantic Search Pipeline\u003C\u002Fh2>\u003Cp>เมื่อมีคำถามยิงเข้ามาในระบบ Backend ของเรา กระบวนการทำงานหลังบ้านจะถูกแบ่งออกเป็น 3 ขั้นตอนหลักๆ ดังนี้ครับ:\u003C\u002Fp>\u003Col>\u003Cli>\u003Cp>\u003Cstrong>Query Embedding:\u003C\u002Fstrong> นำคำถามดิบของ User (เช่น \u003Cem>\"เปลี่ยนรหัสผ่านยังไง\"\u003C\u002Fem>) ส่งไปที่ Embedding API เพื่อแปลงให้เป็น Vector (\u003Ccode>[]float32\u003C\u002Fcode>) ขนาด 1,536 มิติ\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>Vector Similarity Search:\u003C\u002Fstrong> นำ Vector คำถามนั้น ยิงไปค้นหาใน Qdrant Vector Database โดยระบุให้ค้นหาแบบ \u003Cem>Nearest Neighbor\u003C\u002Fem> เพื่อหาข้อความ (Chunks) ที่อยู่ใกล้กับพิกัดความหมายของคำถามที่สุด\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>Payload Extraction:\u003C\u002Fstrong> แกะข้อความดิบและ Metadata ที่เก็บอยู่ใน Payload ของ Qdrant กลับมาแสดงผลให้ผู้ใช้ หรือเตรียมส่งต่อให้ LLM นำไปสรุปคำตอบในสเต็ปถัดไป\u003C\u002Fp>\u003C\u002Fli>\u003C\u002Fol>\u003Ch2>เขียน Go ทำระบบ Semantic Search ร่วมกับ Qdrant\u003C\u002Fh2>\u003Cp>มาดูตัวอย่างโค้ด Go ที่จำลองการรับคำถามจากผู้ใช้ นำไปแปลงเป็น Vector แล้วยิงไปค้นหาใน Qdrant Collection ที่เราสร้างไว้ใน EP.154 กันครับ\u003C\u002Fp>\u003Cp>Go\u003C\u002Fp>\u003Cpre>\u003Ccode>package main\r\n\r\nimport (\r\n\t\"context\"\r\n\t\"fmt\"\r\n\t\"log\"\r\n\r\n\t\"github.com\u002Fqdrant\u002Fgo-client\u002Fqdrant\"\r\n\t\"github.com\u002Fsashabaranov\u002Fgo-openai\"\r\n)\r\n\r\nfunc main() {\r\n\tctx := context.Background()\r\n\r\n\t\u002F\u002F 1. Setup Client ของ OpenAI และ Qdrant (ในโปรดักชันจริงควรดึงผ่าน Env นะครับ)\r\n\topenaiClient := openai.NewClient(\"YOUR_OPENAI_API_KEY\")\r\n\tqdrantClient, err := qdrant.NewClient(&amp;qdrant.Config{\r\n\t\tHost: \"localhost\",\r\n\t\tPort: 6334, \u002F\u002F ใช้พอร์ต gRPC เพื่อความเร็ว\r\n\t})\r\n\tif err != nil {\r\n\t\tlog.Fatalf(\"เชื่อมต่อ Qdrant ไม่สำเร็จ: %v\", err)\r\n\t}\r\n\tdefer qdrantClient.Close()\r\n\r\n\t\u002F\u002F สมมุติตัวอย่างคำถามจาก User\r\n\tuserQuery := \"อยากเปลี่ยนพาสเวิร์ดระบบต้องทำตรงไหน\"\r\n\r\n\t\u002F\u002F 2. แปลงคำถามให้เป็น Vector (Query Embedding)\r\n\tembReq := openai.EmbeddingRequest{\r\n\t\tInput: []string{userQuery},\r\n\t\tModel: openai.SmallEmbedding3Small, \u002F\u002F โมเดลยอดนิยม 1,536 มิติ\r\n\t}\r\n\tembResp, err := openaiClient.CreateEmbeddings(ctx, embReq)\r\n\tif err != nil {\r\n\t\tlog.Fatalf(\"ทำ Embedding คำถามไม่สำเร็จ: %v\", err)\r\n\t}\r\n\tqueryVector := embResp.Data[0].Embedding\r\n\r\n\t\u002F\u002F 3. ยิงค้นหาแบบ Semantic Search ใน Qdrant\r\n\tsearchLimit := uint64(3) \u002F\u002F ดึงผลลัพธ์ที่ใกล้เคียงที่สุด 3 อันดับแรก\r\n\tsearchResp, err := qdrantClient.Query(ctx, &amp;qdrant.QueryPoints{\r\n\t\tCollectionName: \"ai_knowledge_base\",\r\n\t\tQuery:          qdrant.NewQuery(queryVector...), \u002F\u002F กระจายอาร์เรย์ float32\r\n\t\tLimit:          &amp;searchLimit,\r\n\t})\r\n\tif err != nil {\r\n\t\tlog.Fatalf(\"ค้นหาข้อมูลใน Qdrant ล้มเหลว: %v\", err)\r\n\t}\r\n\r\n\t\u002F\u002F 4. ดึงข้อมูลใน Payload ออกมาโชว์\r\n\tfmt.Printf(\"🔍 ผลการค้นหาสำหรับคำถาม: '%s'\\n\\n\", userQuery)\r\n\tfor i, point := range searchResp {\r\n\t\tpayloadMap := point.Payload\r\n\t\t\r\n\t\t\u002F\u002F ดึงข้อความต้นฉบับในคีย์ \"content\" ออกมาเช็คอย่างปลอดภัย\r\n\t\tcontentValue, exists := payloadMap[\"content\"]\r\n\t\tif !exists {\r\n\t\t\tcontinue\r\n\t\t}\r\n\t\t\r\n\t\tcontent := contentValue.GetStringValue()\r\n\t\tscore := point.Score \u002F\u002F ค่า Cosine Similarity ยิ่งเข้าใกล้ 1 ยิ่งหมายความตรงกัน\r\n\r\n\t\tfmt.Printf(\"[%d] Score ความคล้ายคลึง: %.4f\\n\", i+1, score)\r\n\t\tfmt.Printf(\"   เนื้อหาเอกสาร: %s\\n\\n\", content)\r\n\t}\r\n}\r\n\u003C\u002Fcode>\u003C\u002Fpre>\u003Ch2>ทำไมระบบค้นหาแบบ Semantic Search บน Go ถึงเร็วและทรงพลัง?\u003C\u002Fh2>\u003Cul>\u003Cli>\u003Cp>\u003Cstrong>Low Latency Serialization:\u003C\u002Fstrong> ตัว Go Client ของ Qdrant คุยกันผ่านโปรโตคอล \u003Cstrong>gRPC\u003C\u002Fstrong> ซึ่งจะบีบอัดพิกัดเวกเตอร์จำนวนมากให้อยู่ในรูปแบบ Binary ตั้งแต่ต้นทาง ทำให้ไม่มีโหลด (Overhead) เรื่องการแปลงตัวเลขทศนิยมยาว ๆ ให้เป็น JSON Text ระบบเลยดึงคำตอบกลับมาได้ไวระดับมิลลิวินาทีครับ\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>Ready for RAG:\u003C\u002Fstrong> ข้อความดิบที่เราดึงออกมาตามอันดับ Score ในโค้ดด้านบนนี่แหละครับ มันคือ \u003Cstrong>\"หน้าคู่มืออ้างอิง\"\u003C\u002Fstrong> ที่เราจะเอาไปมัดรวมกับคำถามของ User แล้วส่งต่อให้ AI อ่านเพื่อสรุปเป็นคำตอบที่ถูกต้องในสเต็ปการทำ RAG ตอนต่อไปนั่นเอง\u003C\u002Fp>\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>🎯 ท้าให้ลอง (Daily Mission)\u003C\u002Fh2>\u003Cp>ลองนำโค้ดโครงสร้าง Semantic Search ชุดนี้ ไปเชื่อมต่อเข้ากับ \u003Cstrong>Gin Web Server\u003C\u002Fstrong> ที่เราเคยลุยกันใน Workshop EP.150 ดูครับ โดยเปลี่ยนจาก Endpoint ที่เคยสุ่มพ่นข้อความดิบ ให้กลายเป็นระบบค้นหาเอกสารอัจฉริยะแทน\u003C\u002Fp>\u003Cp>\u003Cstrong>💡 การบ้านชวนคิด:\u003C\u002Fstrong> ลองส่งคำถามแบบ \"ใช้คำตรงกับคลังเอกสารเป๊ะ ๆ\" เปรียบเทียบกับคำถามที่ \"ใช้คำพ้องความหมาย (Synonym)\" แล้วสังเกตค่า \u003Cstrong>Score\u003C\u002Fstrong> ที่ส่งกลับมาจาก Qdrant ดูครับว่ามีความต่างกันขนาดไหน? และถ้าต้องทำระบบบน Production จริง เราควรตั้งค่าขั้นต่ำของ Score (Threshold) ไว้ที่เท่าไหร่ดี เพื่อตัดข้อมูลขยะที่ไม่เกี่ยวข้องทิ้งไป? ลองไปลองเล่นกันดูนะ!\u003C\u002Fp>\u003Ch2>💬 FAQ (คำถามที่พบบ่อยประจำตอน)\u003C\u002Fh2>\u003Ch3>ค่า Score เท่าไหร่ ถึงจะถือว่ามีความหมาย \"ใกล้เคียงพอ\" ที่จะนำไปใช้งานจริงได้?\u003C\u002Fh3>\u003Cp>สำหรับโมเดล \u003Ccode>text-embedding-3-small\u003C\u002Fcode> ของ OpenAI ที่วัดระยะห่างแบบ Cosine Similarity ค่า Score ที่ปลอดภัยสำหรับภาษาไทยมักจะอยู่ที่ \u003Cstrong>0.45 - 0.60 ขึ้นไป\u003C\u002Fstrong> ครับ (ขึ้นอยู่กับความยาวของ Chunk ด้วย) แนะนำให้ดัก Threshold เบื้องต้นไว้ที่ \u003Ccode>0.50\u003C\u002Fcode> ก่อน หากต่ำกว่านี้ก็ปัดตกเป็นข้อมูลที่ไม่เกี่ยวข้องได้เลย\u003C\u002Fp>\u003Ch3>ในหน้างานจริง เราค้นหาโดยใช้ทั้ง Keyword แบบเก่า ร่วมกับ Semantic Search พร้อมกันได้ไหม?\u003C\u002Fh3>\u003Cp>\u003Cstrong>ทำได้ครับ\u003C\u002Fstrong> และนี่คือท่ามาตรฐานระดับ Enterprise ที่เรียกว่า \u003Cstrong>Hybrid Search\u003C\u002Fstrong> โดยระบบจะใช้การจับคำศัพท์แบบคลาสสิก (เช่น BM25) ควบคู่ไปกับ Vector Search จากนั้นค่อยเอาผลลัพธ์ทั้งสองฝั่งมาจัดอันดับใหม่ด้วยอัลกอริทึม RRF (Reciprocal Rank Fusion) ซึ่งข่าวดีคือ Qdrant มีฟีเจอร์นี้เตรียมไว้ให้เราเรียกใช้ได้ทันทีครับ\u003C\u002Fp>\u003Cdiv data-type=\"horizontalRule\">\u003Chr>\u003C\u002Fdiv>\u003Ch2>📝 สรุป\u003C\u002Fh2>\u003Cp>ระบบค้นหาอัจฉริยะในตอนนี้เปิดทางให้เราเข้าใกล้ระบบ AI ที่ตอบคำถามได้ตรงใจผู้ใช้เข้าไปอีกขั้น ผ่านการทำความเข้าใจบริบทด้วยพิกัดเวกเตอร์แทนการดักคีย์เวิร์ดแบบเดิม ๆ\u003C\u002Fp>\u003Cp>\u003Cstrong>ในตอนต่อไป (EP.157):\u003C\u002Fstrong> ตอนนี้ระบบค้นหาก็พร้อม คลังเวกเตอร์ก็ทำงานได้ฉลาดแล้ว แต่ในโลกความเป็นจริง เราคงไม่มานั่งพิมพ์ข้อความใส่ Payload เองทีละประโยค เอกสารส่วนใหญ่ในองค์กรส่วยใหญ่มาเป็นไฟล์ PDF หรือ Word หนาเป็นร้อย ๆ หน้า ตอนหน้าเราจะมาสร้างระบบ \u003Cstrong>\"Document Ingestion Pipeline: ดึงข้อมูลจาก PDF\u002FWord เข้าสู่ Database แบบอัตโนมัติ\"\u003C\u002Fstrong> ห้ามพลาดครับ!\u003C\u002Fp>\u003Cp>\u003Cstrong>ฝากกดติดตามพวกเราได้ที่ Superdev Academy\u003C\u002Fstrong> ในทุกช่องทางนะครับ!\u003C\u002Fp>\u003Cul>\u003Cli>\u003Cp>\u003Cstrong>🔵 Facebook: \u003C\u002Fstrong>\u003Ca target=\"_blank\" rel=\"noopener\" class=\"ng-star-inserted\" href=\"https:\u002F\u002Fwww.facebook.com\u002Fsuperdev.academy.th\">\u003Cstrong>Superdev Academy Thailand\u003C\u002Fstrong>\u003C\u002Fa>\u003Cstrong> \u003C\u002Fstrong>(อัปเดตข่าวสารและบทความใหม่)\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>🎬 YouTube: \u003C\u002Fstrong>\u003Ca target=\"_blank\" rel=\"noopener\" class=\"ng-star-inserted\" href=\"https:\u002F\u002Fwww.youtube.com\u002F@SuperdevAcademy\">\u003Cstrong>Superdev Academy Channel\u003C\u002Fstrong>\u003C\u002Fa>\u003Cstrong> \u003C\u002Fstrong>(ติวเข้มแบบวิดีโอ)\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>📸 Instagram: \u003C\u002Fstrong>\u003Ca target=\"_blank\" rel=\"noopener\" class=\"ng-star-inserted\" href=\"https:\u002F\u002Fwww.instagram.com\u002Fsuperdevacademy\u002F\">\u003Cstrong>@superdevacademy\u003C\u002Fstrong>\u003C\u002Fa>\u003Cstrong> \u003C\u002Fstrong>(เกร็ดความรู้สั้นๆ และเบื้องหลังการทำงาน)\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>🎬 TikTok: \u003C\u002Fstrong>\u003Ca target=\"_blank\" rel=\"noopener\" class=\"ng-star-inserted\" href=\"https:\u002F\u002Fwww.tiktok.com\u002F@superdevacademy?lang=th-TH\">\u003Cstrong>@superdevacademy\u003C\u002Fstrong>\u003C\u002Fa>\u003Cstrong> \u003C\u002Fstrong>(Tips &amp; Tricks ฉบับย่อยง่าย)\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>🌐 Website: \u003C\u002Fstrong>\u003Ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"http:\u002F\u002Fsuperdevacademy.com\">\u003Cstrong>superdevacademy.com\u003C\u002Fstrong>\u003C\u002Fa>\u003Cstrong> \u003C\u002Fstrong>(คลังบทความและคอร์สเรียนฉบับเต็ม)\u003C\u002Fp>\u003C\u002Fli>\u003C\u002Ful>\u003Cp>\u003C\u002Fp>","31w5qtjjff0n_f2zeujbdkd.png","https:\u002F\u002Ftwsme-r2.tumwebsme.com\u002Fsclblg987654321\u002F4k07xxnwbkc6n5w\u002F31w5qtjjff0n_f2zeujbdkd.png","2026-06-30 03:39:56.294Z","76qprkevbgfdps8",{"keywords":15,"locale":40,"school_blog":50},[16,22,27,31,35],{"collectionId":17,"collectionName":18,"created":19,"created_by":13,"id":20,"name":21,"updated":19,"updated_by":13},"sclkey987654321","school_keywords","2026-06-30 03:38:48.005Z","1rc8skkqdrc2jp3","สอนเขียน Go",{"collectionId":17,"collectionName":18,"created":23,"created_by":13,"id":24,"name":25,"updated":26,"updated_by":13},"2026-03-04 08:20:14.253Z","ah6lvy4x8qe08l5","Golang","2026-06-07 06:45:08.193Z",{"collectionId":17,"collectionName":18,"created":28,"created_by":13,"id":29,"name":30,"updated":28,"updated_by":13},"2026-06-30 03:39:00.665Z","8h7mqqojid7akf4","ระบบค้นหาอัจฉริยะ",{"collectionId":17,"collectionName":18,"created":32,"created_by":13,"id":33,"name":34,"updated":32,"updated_by":13},"2026-06-11 16:14:22.575Z","gluay8aj98wheus","RAG",{"collectionId":17,"collectionName":18,"created":36,"created_by":13,"id":37,"name":38,"updated":39,"updated_by":13},"2026-05-11 04:12:24.718Z","zo53ndb3rj4jxci","Vector Database","2026-06-07 06:49:11.382Z",{"code":41,"collectionId":42,"collectionName":43,"created":44,"flag":45,"id":46,"is_default":47,"label":48,"updated":49},"th","pbc_1989393366","locales","2026-01-22 10:59:55.832Z","twemoji:flag-thailand","s8wri3bt4vgg2ji",true,"Thai","2026-04-10 15:42:46.614Z",{"category":51,"collectionId":52,"collectionName":53,"created":54,"expand":55,"id":70,"slug":71,"updated":72,"views":73},"wqxt7ag2gn7xcmk","pbc_2105096300","school_blogs","2026-06-30 03:39:56.012Z",{"category":56},{"blogIds":57,"collectionId":58,"collectionName":59,"created":60,"created_by":13,"id":51,"image":61,"image_alt":62,"image_path":63,"label":64,"name":65,"priority":66,"publish_at":67,"scheduled_at":62,"status":68,"updated":69,"updated_by":13},[],"sclcatblg987654321","school_category_blogs","2026-03-04 08:33:53.210Z","59ty92ns80w_15oc1implw.png","","https:\u002F\u002Ftwsme-r2.tumwebsme.com\u002Fsclcatblg987654321\u002Fwqxt7ag2gn7xcmk\u002F59ty92ns80w_15oc1implw.png",{"en":65,"th":65},"Golang The Series",1,"2026-03-16 04:39:38.440Z","published","2026-06-07 06:45:03.856Z","or1jhl03h1oifdj","golang-the-series-ep156-semantic-search-vector-qdrant","2026-06-30 06:27:02.106Z",112,"4k07xxnwbkc6n5w",[20,24,29,33,37],"2026-06-30 04:01:32.789Z","บอกลาการค้นหาแบบเดิม ๆ เรียนรู้วิธีสร้างระบบ Semantic Search ค้นหาข้อมูลตามความหมายและบริบทของภาษาคนด้วย Go ร่วมกับ OpenAI และ Qdrant Vector DB ทำงานผ่าน gRPC ความเร็วสูง","Golang The Series EP.156: Semantic Search ระบบค้นหาอัจฉริยะตามความหมาย","2026-06-30 04:01:32.790Z",{"th":71,"en":71}]