← ห้องสมุดของ Jizo

หนังสือเล่มที่ 6 · stack ของฝูง

หู ปาก ตา

ฝูงนี้ฟัง พูด และมองด้วยอะไร และอะไรที่วางไว้แล้วแต่ยังไม่ได้ต่อ

คำนำ ทำไมต้องเขียน stack ลงกระดาษ

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

ปัญหาของ stack แบบนี้คือ เวลามีคนถามว่า "เราใช้อะไรอยู่" คำตอบที่ได้มักจะเป็นชื่อของทางหลัก ส่วนทางสำรองจะถูกลืม ทั้งที่วันที่มันทำงานคือวันที่ทุกอย่างกำลังพัง แล้วเราจะไล่ไม่ถูกว่าเสียงที่ออกมาแปลกๆ มาจากชั้นไหน

เล่มนี้เลยเขียนสิ่งที่ฝูงใช้จริงลงกระดาษ ทีละชั้น พร้อมบอกว่าอะไรตัดสินว่าจะใช้ตัวไหน และที่สำคัญกว่านั้นคือแยกให้ชัดว่า อะไรต่อแล้ว กับ อะไรวางไว้แต่ยังไม่ได้ต่อ เพราะสองอย่างนี้อยู่ในเอกสารเดียวกันได้ แต่ต้องไม่อยู่ในประโยคเดียวกัน

คนพูด / ส่งรูป หู · ASR groq → whisper srv → cli สมอง · claude-opus-5 routing 4 ทาง · brain 4 tier แต่ละ tier มีสิทธิ์ต่างกัน ปาก · TTS eleven_v3 / edge-tts ตา · grok · ComfyUI มองรูป · วาดรูป · ทำวิดีโอ รอยต่อ · MQTT <bud>/voice/* ทำให้เสียงที่ออกไป วัดได้จากข้างนอก เส้นประ = ยังไม่ได้ต่อ local mesh · Qwen3-0.6B token plan · Qwen / DeepSeek
ห้าชั้นที่ต่อกันอยู่จริง กับกล่องเส้นประซ้ายล่างที่วางไว้แล้วยังไม่ได้ต่อ บทที่ 7 ว่าด้วยกล่องนั้นโดยเฉพาะ

ศัพท์ที่ใช้ในเล่มนี้ นิยามครั้งเดียวตรงนี้ ที่เหลือใช้ตามนี้ตลอดเล่ม

ตัวฝูงเอง

บัด · bud
agent หนึ่งตัวในฝูง มีเอกสารประจำตัวของตัวเองเรียกว่าชาร์เตอร์
ชาร์เตอร์ · charter
ไฟล์ที่บอกว่าบัดตัวนั้นเป็นใคร ใช้เสียงอะไร คิดด้วยสมองตัวไหน
seam · รอยต่อ
จุดที่เราตั้งใจเปิดไว้ให้วัดของข้างในได้จากข้างนอก

ชั้นเสียง

ASR · STT
แปลงเสียงพูดเป็นข้อความ สองคำนี้ใช้แทนกันได้ในเล่มนี้
TTS
แปลงข้อความเป็นเสียงพูด
IVC
การโคลนเสียงจากคลิปตัวอย่าง ได้เสียงที่เหมือนคลิปนั้น ไม่ใช่เสียงที่ออกแบบใหม่
คลิปอ้างอิง
คลิปสั้นที่ใช้เป็นต้นแบบของการโคลน ในฝูงนี้ยาว 9 วินาที

การต่อและการจ่าย

fallback · ทางสำรอง
ทางที่ทำงานเมื่อทางหลักพัง จะเห็นมันก็ตอนที่อย่างอื่นกำลังพังอยู่
token plan
แพ็กเกจโควต้าจ่ายล่วงหน้า ต่างจากการจ่ายรายครั้ง หมดแล้วต้องรอรอบใหม่
wired · ต่อแล้ว
โค้ดมีอยู่ ไม่พอ ต้องมีเส้นทางจริงเรียกมันด้วยถึงจะนับว่าต่อแล้ว

บทที่ 1 หู · ฟังด้วยอะไร

เวลาบัดอยู่ในห้องเสียงแล้วมีคนพูด สิ่งที่มันได้รับคือคลื่นเสียง ไม่ใช่ข้อความ ชั้นที่แปลงให้คือ ASR และฝูงนี้ไม่ได้มีตัวเดียว มันมีสามตัวเรียงกัน

groq            cloud   เร็วที่สุด
whisper-server  local   อุ่นค้างไว้ ตอบราว 0.8 วินาที
whisper-cli     local   เรียกใหม่ทุกครั้ง ช้ากว่า แต่ไม่ต้องมี server

สิ่งที่ออกแบบได้ดีในชั้นนี้คือ มันไม่ได้ให้เราไปเดาว่าตอนนี้ขาไหนใช้ได้ มันประกาศตัวเองตอนบูตเลย

STT chain srv → cli

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

รายละเอียดที่ควรรู้คือ ระบบไม่ได้ตัดสินว่า "ใช้ local ได้ไหม" จาก config มันตัดสินจากการมีอยู่ของไฟล์

STT_LOCAL = existsSync(WHISPER) && existsSync(MODEL)

ต่างกันตรงนี้สำคัญ config บอกได้แค่ว่าคนตั้งค่าตั้งใจอะไร ส่วนการเช็คไฟล์บอกว่าของมีอยู่จริงไหม สองอย่างนี้ไม่ตรงกันได้ง่ายมากเวลาย้ายเครื่อง

ของที่ต้องมีจริงคือสองชิ้น ตัวโปรแกรมกับตัวโมเดล บนเครื่องนี้อยู่ที่

~/whisper.cpp/build/bin/whisper-cli
~/whisper.cpp/build/bin/whisper-server
~/whisper.cpp/models/ggml-small.bin      466 MB

เกร็ดจากตอนเขียนเล่มนี้ ตอนแรกเราไปหา binary ผิดที่ แล้วได้คำตอบว่า "ไม่พบ" ซึ่งถ้าเชื่อตรงนั้นก็จะเขียนลงเล่มว่าเชนนี้ประกาศความสามารถที่ไม่มีจริง ของจริงคือโค้ดใช้ homedir() เป็นฐาน ส่วนเราไปหาในโฟลเดอร์งาน ตัวที่ผิดคือการค้นของเรา ไม่ใช่ของระบบ

เสียงเข้ามา groq · cloud ไม่มีคีย์ จึงหายจากเชน whisper-server อุ่นค้าง ตอบราว 0.8 วิ whisper-cli เรียกใหม่ทุกครั้ง ช้ากว่า ทั้งสองขานี้เปิดได้เพราะ existsSync เจอทั้งตัวโปรแกรมและตัวโมเดล เชนที่ได้จริงบนเครื่องนี้ · STT chain srv → cli
ขาที่ไม่มีของ จะหายจากเชนตั้งแต่ตอนบูตแล้วพิมพ์บอก ไม่ใช่ค้างอยู่ในรายการแล้วพังตอนถูกเรียก

พอร์ตของ whisper-server ก็ไม่ได้ตั้งเป็นเลขตายตัว มันคำนวณจากพอร์ตสั่งงานของบัดนั้น STT_PORT = ipc.port + 100 แปลว่าบัดที่คุยกันที่ 14822 จะมี ASR ของตัวเองที่ 14922 บัดเก้าตัวจึงไม่ชนกันโดยไม่ต้องมีใครไล่จดเลขพอร์ต

หลักการของบทนี้

เชนที่ประกาศตัวเองตอนบูต ดีกว่า config ที่ต้องไปเดา และการเช็คว่าไฟล์มีอยู่จริง ดีกว่าธงที่บอกว่าควรมี

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

ทดสอบตัวเองท้ายบท

  1. ระบบของคุณมี fallback กี่ชั้น และถ้าตอนนี้ชั้นแรกใช้ไม่ได้ คุณจะรู้จากอะไร
  2. ความสามารถที่ระบบคุณโฆษณาไว้ อนุมานจาก config หรือจากการเช็คของจริง

จบบทที่ 1

บทที่ 2 ปาก · พูดด้วยอะไร

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

export function elModelForText(text) {
  return /[฀-๿]/.test(String(text ?? "")) ? "eleven_v3" : "eleven_multilingual_v2";
}

ถ้ามีอักขระในช่วงภาษาไทยแม้แต่ตัวเดียว ให้ใช้ eleven_v3 นอกนั้นใช้ eleven_multilingual_v2 เหตุผลไม่ใช่เรื่องคุณภาพเสียง แต่เป็นเรื่องที่หยาบกว่านั้นมาก คือ ในบัญชีนี้มี v3 ตัวเดียวที่ลิสต์ภาษาไทย ตัวอื่นรับข้อความไทยไปแล้วคืนเสียงที่ฟังดูเหมือนภาษาอะไรสักอย่างกลับมา ซึ่งผ่านการตรวจแบบ "มีไฟล์เสียงออกมาไหม" ได้สบาย

นี่คือกับดักที่เจ็บเป็นพิเศษ เพราะตัวตรวจที่ดูว่าได้ไฟล์เสียงกลับมาหรือเปล่าจะเขียวตลอด ต้องตรวจถึงขั้นถอดเสียงกลับมาเป็นข้อความแล้วเทียบเท่านั้นถึงจะจับได้

ชั้นสำรองคือ edge-tts และมันไม่ได้ต่อไว้จุดเดียว ต่อไว้สามจุด

elevenlabs returned N B  not audio       → edge-tts
elevenlabs HTTP <status>                → edge-tts
elevenlabs failed (<error>)              → edge-tts

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

ข้อความที่จะพูด มีอักขระไทยไหม ดูจากเนื้อ ไม่ใช่จาก config มี → eleven_v3 ตัวเดียวที่ลิสต์ภาษาไทย ไม่มี → multilingual_v2 edge-tts ทางสำรอง จุดที่ตกมาทางสำรองมีสามจุด ไม่ใช่จุดเดียว คืนของที่ไม่ใช่เสียง · คืนสถานะผิดพลาด · ล้มระหว่างเรียก
ตัวเลือกโมเดลอ่านเนื้อข้อความ ไม่ได้อ่านค่าที่ตั้งไว้ และทางสำรองถูกดักไว้ทั้งสามแบบที่บริการภายนอกพังได้

บัดที่ไม่มีเสียงโคลนเป็นของตัวเองจะใช้ edge-tts ล้วน วันที่เขียนมีสามตัว คือ fude kensa mamori ชาร์เตอร์ของบัดที่ใช้ edge-tts จะระบุเสียงไว้สองช่อง ช่องหนึ่งสำหรับภาษาทั่วไป อีกช่องสำหรับภาษาไทยโดยเฉพาะ

tts_voice:      "en-US-AndrewMultilingualNeural"
tts_voice_thai: "th-TH-NiwatNeural"

หลักการของบทนี้

เลือกโมเดลจากเนื้อของงาน ไม่ใช่จากค่าที่ตั้งไว้ล่วงหน้า และตัวตรวจของชั้นเสียงต้องถอดเสียงกลับมาเทียบ ไม่ใช่แค่ดูว่ามีไฟล์

✅ ตรวจว่าโมเดลรองรับภาษาที่จะใช้จริง ก่อนจะเชื่อว่ามันพูดได้
✅ ดัก fallback ให้ครบทุกรูปแบบของความล้มเหลว ไม่ใช่แค่ error ที่นึกออก
❌ ยืนยันว่าเสียงถูกต้องเพราะได้ไฟล์เสียงกลับมา

ทดสอบตัวเองท้ายบท

  1. ถ้าโมเดลของคุณคืนของที่หน้าตาถูกต้องแต่เนื้อในผิด ตัวตรวจปัจจุบันจับได้ไหม
  2. บริการภายนอกที่คุณเรียกอยู่ พังได้กี่แบบ แล้วคุณดักไว้กี่แบบ

จบบทที่ 2

บทที่ 3 เสียงของใคร · เสียงประจำตัวมาจากไหน

บัดแต่ละตัวมีเสียงของตัวเอง และเสียงพวกนั้นไม่ได้มาจากการเลือกจากรายการ มันมาจากคลิปตัวอย่างที่ผ่านการคัดแล้วเอาไปโคลน

ทางเดินคือ ออกแบบเสียงจากคำบรรยาย ฟัง เลือกคลิปที่ชอบ เอาคลิปนั้นผ่านตัวคัด แล้วโคลนเป็นเสียงถาวร ตัวคัดชื่อ refclip_screen.py ตัดสินจากหลายแกน สองแกนที่สำคัญคือ

สัดส่วนที่เป็นเสียงพูด   ต้องเกิน 70%
ความเป็นคาบของสัญญาณ    ต้องเกิน 0.45

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

เรื่องที่ต้องเขียนไว้ให้คนที่มาทำต่อ คือบทเรียนที่ฝูงจ่ายไปแล้ว การออกแบบเสียงจะได้ตัวอย่างมาให้ฟัง แต่ตัวอย่างจะกลายเป็นเสียงถาวรก็ต่อเมื่อบันทึก id ของมันไว้ตอนที่ตัวอย่างยังอยู่ มีรอบหนึ่งที่ id หายไปแล้ว แล้วมีการรันการออกแบบใหม่จากคำบรรยายเดิม ซึ่ง ไม่ได้เสียงเดิมกลับมา มันวาดเสียงใหม่ ผลคือมีบัดหลายตัวพูดด้วยเสียงที่ไม่มีใครเคยฟังอยู่หนึ่งวัน กว่าจะจับได้ก็ด้วยหูของคน

ที่กู้กลับมาได้เพราะคลิปอ้างอิงความยาว 9 วินาทีที่ผ่านการคัดยังอยู่ จึงเอาไปโคลนใหม่ได้ คลิปคือสิ่งเดียวที่เหลือรอด

หลักการของบทนี้

ไฟล์เสียงที่อยู่บนดิสก์ไม่ใช่ตัวเสียง มันคือการบันทึกของเสียงหนึ่งครั้ง ถ้าอยากได้เสียงนั้นอีก ต้องเก็บ id ไว้ ไม่ใช่เก็บไฟล์

✅ บันทึก id ของสิ่งที่สร้างขึ้น ก่อนจะเริ่มประเมินว่าชอบหรือไม่ชอบ
✅ เก็บคลิปอ้างอิงไว้ เพราะมันคือทางกู้ทางเดียวที่เหลือ
❌ คิดว่ารันคำบรรยายเดิมซ้ำแล้วจะได้ของเดิม

ทดสอบตัวเองท้ายบท

  1. ของที่ระบบคุณสร้างขึ้นแบบสุ่ม ถ้าอยากได้ตัวเดิมอีกครั้ง ต้องเก็บอะไรไว้ และตอนนี้เก็บอยู่หรือเปล่า
  2. ถ้ามีคนเปลี่ยนเสียงของบัดไปโดยไม่ตั้งใจ อะไรจะจับได้ก่อน คนหรือเครื่อง

จบบทที่ 3

บทที่ 4 ตา · มองและวาดด้วยอะไร

ชั้นภาพแยกเป็นสองเรื่องที่คนมักปนกัน คือ มองรูปที่คนส่งมา กับ วาดรูปให้คนดู ฝูงนี้ใช้คนละทาง

ฝั่งมอง ใช้ grok เป็นตัวอ่าน เมื่อเจ้าของส่งรูปอ้างอิงมาทาง DM ระบบจะบังคับเข้าเส้นทางที่ grok เห็นรูปนั้นจริง แล้วค่อยให้ตัววาดประกอบต่อ เหตุผลที่เลือกตัวนี้เขียนไว้ในโค้ดตรงๆ ว่ามันเห็นภาพและเปิดไฟล์ได้

ฝั่งวาด มีสามทาง แยกกันด้วยงาน ไม่ใช่ด้วยความชอบ

ทางใช้เมื่อเอนจิน
brushอยากได้เร็วgrok
paintอยากได้คุณภาพComfyUI + Flux บน GPU pod เช่า เปิดทีละบัดด้วยตัวแปรสภาพแวดล้อม
reelอยากได้วิดีโอgrok เป็นค่าตั้งต้น สลับไป fal.ai Seedance ได้ด้วยตัวแปรเดียว

สิ่งที่ชอบในการออกแบบชั้นนี้คือ การสลับเอนจินไม่ต้องแก้โค้ด ตั้งตัวแปรสภาพแวดล้อมของบัดนั้นแล้วจบ และบัดที่ไม่ได้เปิด paint ไว้จะไม่พัง มันตอบกลับอย่างสุภาพว่าทางนี้ยังไม่ได้เปิด

หลักการของบทนี้

แยกทางเลือกของเอนจินออกมาเป็นค่าตั้งของสภาพแวดล้อม แล้วให้ทางที่ยังไม่ได้เปิด ตอบว่า "ยังไม่ได้เปิด" ไม่ใช่ล้ม

✅ ทางที่ปิดอยู่ต้องตอบได้ว่าปิดอยู่
❌ ให้ฟีเจอร์ที่ยังไม่ได้เปิด โยน error ที่หน้าตาเหมือนของพัง

ทดสอบตัวเองท้ายบท

  1. ฟีเจอร์ที่ต้องตั้งค่าเพิ่มถึงจะใช้ได้ ถ้าไม่ตั้ง ผู้ใช้เห็นอะไร
  2. ถ้าจะเปลี่ยนเอนจินของงานหนึ่งพรุ่งนี้ ต้องแก้กี่ไฟล์

จบบทที่ 4

บทที่ 5 สมอง · ใครตัดสินใจ และตัดสินใจด้วยอะไร

สมองที่คิดคือ claude-opus-5 ระบุไว้ในชาร์เตอร์ของบัดแต่ละตัว แต่คำถามที่น่าสนใจกว่าไม่ใช่ว่าใช้โมเดลอะไร มันคือ ข้อความที่เข้ามาถูกตัดสินยังไงว่าจะทำอะไรกับมัน

ส่วนนั้นถูกแยกออกมาเป็นฟังก์ชันบริสุทธิ์ ไม่ปนกับ Discord ไม่ปนกับ I/O รับเข้าเป็นค่าจริงเท็จล้วนๆ แล้วคืนหนึ่งในสี่ทาง

skip     ไม่ทำอะไร ไม่ใช่ของเรา หรือเจ้าของกำลังคุยกับคนอื่น
ack      ตอบสั้นว่ารับรู้ ใช้กับการแท็กเปล่า และตอบแม้ตอนถูกปิดเสียง
worker   เปิดคนงานจริง ไปรันคำสั่ง ไปดูของ แล้วรายงาน
reason   ตอบด้วยสมองสนทนาในห้องนั้น

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

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

ระดับทำอะไรได้
tier 1 · สนทนาไม่มีเครื่องมือเลย
voiceไม่มีเครื่องมือ ปิดการคิดยาว เปิดการตอบเป็นท่อน เพื่อลดเวลารอ
tier 2 · สกิลอ่านไฟล์ในโซนความจำได้ เรียกสกิลได้ ไม่มีคำสั่งเชลล์ ไม่มีการเขียนไฟล์ ไม่มีเน็ต
tier 3 · คนงานรันคำสั่งเชลล์และแตะไฟล์ได้ แต่ล้อมอยู่ในโฟลเดอร์งานของตัวเอง ไม่มีเน็ต ไม่แตกลูก
ข้อความเข้า boolean 15 ตัว เข้าฟังก์ชันบริสุทธิ์ skip · ไม่ทำอะไร ack · รับรู้ reason · ตอบในห้อง worker · ไปทำงานจริง สิทธิ์ผูกกับระดับ ไม่ผูกกับคน tier 1 สนทนา · ไม่มีเครื่องมือเลย voice · ไม่มีเครื่องมือ ปิดคิดยาว ตอบเป็นท่อนเพื่อลดเวลารอ tier 2 สกิล · อ่านโซนความจำ เรียกสกิล ไม่มีเชลล์ ไม่เขียนไฟล์ ไม่มีเน็ต tier 3 คนงาน · เชลล์และไฟล์ได้ แต่ล้อมในโฟลเดอร์ตัวเอง ระดับสูงสุดก็ยังไม่มีเน็ต และแตกลูกต่อไม่ได้
ซ้ายคือสี่ทางที่ข้อความจะจบลง ขวาคือสี่ระดับของสมอง ความสามารถเพิ่มทีละขั้นพร้อมความเสี่ยง และไม่มีระดับไหนได้ทุกอย่าง

อ่านตารางนี้แล้วจะเห็นว่าความสามารถเพิ่มขึ้นทีละขั้นพร้อมกับความเสี่ยง และไม่มีระดับไหนได้ทุกอย่าง แม้แต่ระดับที่รันคำสั่งได้ก็ยังไม่มีเน็ตและแตกลูกต่อไม่ได้

หลักการของบทนี้

การตัดสินใจต้องเป็นฟังก์ชันบริสุทธิ์ที่เทสต์ได้โดยไม่ต้องบูตระบบ และสิทธิ์ต้องผูกกับระดับของงาน ไม่ใช่ผูกกับตัวตนของผู้เรียก

✅ แยกกฎการตัดสินใจออกจาก I/O แล้วป้อนค่าจริงเท็จเข้าไปตรงๆ
✅ ให้ระดับที่ทำได้มากที่สุด ยังมีของที่ทำไม่ได้เหลืออยู่
❌ ฝังกฎว่าใครได้ทำอะไรไว้ในตัวจัดการเหตุการณ์ แล้วพิสูจน์ด้วยการรีสตาร์ตแล้วลอง

ทดสอบตัวเองท้ายบท

  1. กฎว่าระบบคุณจะตอบใครบ้าง ตอนนี้พิสูจน์ด้วยเทสต์ได้ไหม หรือต้องรันจริงถึงจะรู้
  2. ส่วนที่มีสิทธิ์สูงที่สุดในระบบคุณ ยังมีอะไรที่มันทำไม่ได้อยู่บ้าง

จบบทที่ 5

บทที่ 6 รอยต่อ · ทำให้เสียงวัดได้

สามชั้นแรกเป็นความสามารถ ชั้นนี้ไม่ใช่ ชั้นนี้คือรอยต่อที่วางไว้เพื่อให้ของข้างในวัดได้จากข้างนอก

ระหว่างที่บัดกำลังพูด ตัวประกาศจะส่งค่าออกไปทาง MQTT ทุก 40 มิลลิวินาที สามหัวข้อ

<bud>/voice/level      0..1   ความดังเฉลี่ยของช่วงนั้น
<bud>/voice/peak       0..1   ยอดสูงสุดของช่วงนั้น
<bud>/voice/speaking   bool   ตัวตรวจจับเสียงพูด ไม่ใช่เกณฑ์ความดัง

คำว่า <bud> เป็นของใหม่ในวันที่เขียน ก่อนหน้านั้นมันเป็นชื่อบัดตัวเดียวเขียนตายไว้ ซึ่งแปลว่าถ้าบัดตัวที่สองพูดขึ้นมา มันจะเขียนทับหัวข้อของตัวแรก ตอนนี้แต่ละตัวมีหัวข้อของตัวเอง และผู้บริโภคที่อยากฟังทุกคนใช้ +/voice/#

ตัวประกาศอยู่บนเซิร์ฟเวอร์ ส่วนหน้าจอที่ขยับตามเสียงอยู่บนเครื่องอีกเครื่อง สองฝั่งมี broker คนละตัว จึงต้องมีสะพานเชื่อม และสะพานนี้ตั้งใจให้เดินทางเดียว ส่งออกอย่างเดียว และส่งเฉพาะหัวข้อตระกูลเสียง หัวข้อสถานะอื่นของบ้านไม่ข้าม

สิ่งที่ได้จากการมีรอยต่อแบบนี้คือ คำถามว่า "ตอนนี้บัดพูดอยู่จริงไหม" ตอบได้จากข้างนอกโดยไม่ต้องเข้าไปอ่าน log ของ daemon และตอบได้ด้วยเครื่องมือธรรมดา

หลักการของบทนี้

ความสามารถที่วัดจากข้างนอกไม่ได้ จะกลายเป็นความสามารถที่เถียงกันไม่จบ วางรอยต่อไว้ตั้งแต่ตอนสร้าง ไม่ใช่ตอนที่เริ่มสงสัย

✅ ให้แต่ละผู้ผลิตมีเนมสเปซของตัวเอง ตั้งแต่ตัวแรก ไม่ใช่ตอนมีตัวที่สอง
✅ สะพานข้ามเครื่องเดินทางเดียว และจำกัดว่าหัวข้อไหนข้ามได้
❌ ให้ผู้ผลิตหลายตัวเขียนหัวข้อเดียวกัน แล้วหวังว่าจะไม่พูดพร้อมกัน

ทดสอบตัวเองท้ายบท

  1. สถานะภายในของระบบคุณ มีอันไหนที่คนข้างนอกอยากรู้แล้วดูไม่ได้บ้าง
  2. ถ้ามีผู้ผลิตตัวที่สองเพิ่มเข้ามาพรุ่งนี้ เนมสเปซปัจจุบันรับไหวไหม

จบบทที่ 6

บทที่ 7 เลนที่วางไว้แต่ยังไม่ได้ต่อ

บทนี้คือส่วนที่หนังสือ stack ส่วนใหญ่ไม่เขียน คือของที่มีอยู่ในบ้านแล้วแต่ยังไม่ได้ต่อเข้าเส้นทางจริง เขียนไว้เพราะของพวกนี้อ่านผ่านตาแล้วเหมือนใช้ได้ ทั้งที่ยังใช้ไม่ได้

หนึ่ง สมองสำรองในเครื่อง มีไฟล์อยู่ เรียก Ollama ที่พอร์ต 11434 ค่าตั้งต้นเป็น qwen3:8b และมีพี่น้องอีกตัวสำหรับปลายทางแบบ vLLM หัวไฟล์เขียนไว้ตรงๆ ว่า ยังไม่ได้ต่อเข้าตัวจัดการสมองหลัก แปลว่าถ้าสมองหลักล่มตอนนี้ ยังไม่มีอะไรรับช่วง

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

id                unsloth/Qwen3-0.6B-GGUF:Q4_K_M
capabilities      ["text"]
audio_status      none
vision_status     none
multimodal_status none

มันบอกเองว่าไม่มีเสียงและไม่มีภาพ จึงแทนชั้นหูกับชั้นตาไม่ได้ ที่ยกมาเพราะนี่คือตัวอย่างของ API ที่ออกแบบดี คือรายงานขอบเขตของตัวเองมาให้เลย ไม่ต้องให้ใครไปลองแล้วเดา

สาม เลน Qwen แบบ token plan อันนี้คือของที่กำลังพิจารณา มีเครื่องมือบรรทัดคำสั่งติดตั้งและยืนยันตัวตนแล้ว ปลายทางอยู่ที่ภูมิภาคสิงคโปร์ ความสามารถที่มีคำสั่งรองรับคือ

งานคำสั่งโมเดลที่ตั้งใจใช้
ฟังspeech recognizeqwen3-asr-flash
พูดspeech synthesizeqwen3-tts-flash
คิดtext chatqwen3.8-max-preview · deepseek v4 flash 0731
ทุกอย่างในตัวเดียวomniqwen3.8 multimodal · รับภาพเสียงวิดีโอ คืนข้อความและเสียง
ทำดัชนีความหมายไม่มีคำสั่งใน CLItext-embedding-v3

ที่น่าสนใจสำหรับฝูงคือช่องที่สี่ เพราะทุกวันนี้ทางเดินเสียงของเราต่อกันสามชิ้น คือฟังด้วยตัวหนึ่ง คิดด้วยตัวหนึ่ง พูดด้วยอีกตัวหนึ่ง ส่วนตัวเดียวที่ทำครบจะมีจุดต่อให้พังน้อยกว่า

สมองสำรองในเครื่อง ollama qwen3:8b หัวไฟล์เขียนเองว่ายังไม่ wired ตาข่ายในเครื่อง 9337 Qwen3-0.6B ตอบจริง audio · vision · multimodal = none เลน Qwen token plan asr · tts · omni · embedding ยิงครบแล้ว คิดผ่าน ที่เหลือคนละเหตุผล ทั้งสามกล่องอ่านผ่านตาแล้วเหมือนพร้อมใช้ ทั้งที่ยังใช้ไม่ได้ทั้งสามอัน เหตุผลที่ยังใช้ไม่ได้ ต่างกันคนละเรื่อง จึงต้องเขียนแยกทีละอัน ไม่ได้ต่อ · ไม่มีความสามารถ · ยิงแล้วแตกเป็นสี่คำตอบ
สามเลนนี้ไม่ได้ติดปัญหาเดียวกัน อันหนึ่งมีของแต่ไม่มีใครเรียก อันหนึ่งเรียกได้แต่ทำไม่ได้ อีกอันพอยิงจริงแล้วแตกเป็นสี่คำตอบคนละแบบ

ตอนเขียนบทนี้ครั้งแรก ตรงนี้เขียนไว้ว่า ยังไม่ได้พิสูจน์สักตัว เพราะยิงแล้วชนโควต้ารายสัปดาห์ทั้งแผง สิ่งที่ได้กลับมามีสามอย่าง คือปลายทางไม่ยอมบอกรายการโมเดล โควต้าหมด และรายการเสียงของตัวสังเคราะห์มีแต่จีนกับอังกฤษ

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

ชั้นผลที่วัดได้หลักฐาน
คิดใช้ได้จริงqwen3.8-max-preview และ deepseek v4 flash 0731 ตอบกลับมาทั้งคู่
ฟังยังไปไม่ถึงตัวโมเดลตายที่ขั้นขอสิทธิ์อัปโหลดไฟล์ 401 ไม่ใช่ที่โมเดล
พูดปลายทางไม่มีชื่อนี้404
ทุกอย่างในตัวเดียวปลายทางไม่มีชื่อนี้404 · ลองมาแล้วสี่ชื่อ ยังไม่เจอชื่อจริง
ทำดัชนีความหมายปลายทางไม่มีชื่อนี้404 ทั้งสามเส้นทางที่ลอง

ตัวที่คิดได้ ไม่เคยพัง เราเรียกมันผิดเอง รอบแรกมันคืนรหัส 400 เปล่าๆ ตัวจำแนกของเราจึงจัดเข้าช่อง อื่นๆ ซึ่งอ่านแล้วเหมือนโมเดลมีปัญหา พอยิงใหม่โดยไม่สั่งให้เงียบ ข้อความเต็มบอกตรงๆ ว่า

The value of the enable_thinking parameter is restricted to True

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

ที่ต้องจดคู่กันคือ อีกเลนหนึ่งที่ฝูงใช้อยู่ ต้องปิด โหมดคิด ไม่งั้นได้ไฟล์เปล่ากลับมา ธงตัวเดียวกันจึงกลับด้านกันคนละโมเดล อย่าตั้งเป็นค่าเดียวทั้งฝูง

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

และรอบแรกเลนฟังไม่ได้คำตอบเลยด้วยซ้ำ เพราะเครื่องที่ตั้งเวลาไว้ไม่มีตัวสังเคราะห์เสียงกับตัวแปลงไฟล์ ผลจึงออกมาเป็นคำว่า ข้าม ซึ่งหน้าตาเหมือน ไม่มีปัญหา ทั้งที่แปลว่า ไม่เคยยิง พอย้ายมารันบนเครื่องที่มีเสียงไทยในตัว มันถึงเดินไปไกลพอจะไปเจอ 401 ที่ว่า

ส่วนรายการเสียงที่มีแต่จีนกับอังกฤษ ยังเป็นข้อที่ต้องระวังที่สุดเหมือนเดิม เพราะหน้าตาเหมือนกับดักในบทที่ 2 เป๊ะ คือรายการภาษาไม่มีไทย ซึ่งไม่ได้แปลว่าพูดไทยไม่ได้แน่นอน แต่แปลว่า ห้ามเชื่อจนกว่าจะทดสอบแบบพูดไทยแล้วถอดกลับมาเทียบ การเห็นว่ามีไฟล์เสียงออกมาไม่พอ

หลักการของบทนี้

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

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

✅ เขียนสิ่งที่ทดสอบไม่ได้และเหตุผลที่ทดสอบไม่ได้ ลงไปด้วย
✅ API ที่รายงานขอบเขตของตัวเองมาให้ ดีกว่า API ที่ต้องลองแล้วเดา
✅ เห็นคำว่า ข้าม ให้ถามก่อนว่าข้ามเพราะไม่มีปัญหา หรือเพราะไม่เคยยิง
✅ ยิงตัวคุมด้วยกุญแจดอกเดียวกันในนาทีเดียวกัน ก่อนจะสรุปว่ากุญแจเสีย
❌ ลิสต์ชื่อโมเดลที่ยังไม่เคยเรียกไว้ในหน้าเดียวกับของที่ใช้อยู่จริง
❌ สั่งให้เครื่องมือเงียบ แล้วอ่านรหัสสถานะเปล่าๆ เป็นข้อสรุป

ทดสอบตัวเองท้ายบท

  1. ในเอกสารของคุณ ของที่ใช้อยู่จริงกับของที่วางแผนไว้ แยกกันชัดแค่ไหน
  2. ของที่วางไว้แล้วยังไม่ได้ต่อ ถ้าวันหนึ่งต้องพึ่งมันกะทันหัน จะรู้ได้ยังไงว่ามันพร้อม

จบบทที่ 7

ภาคผนวก proposal vs built · ตารางเดียวจบ

ถ้าจำทั้งเล่มไม่ไหว ให้จำตารางนี้ ทุกบรรทัดตรวจกับโค้ดหรือเครื่องที่รันอยู่ในวันที่เขียน

ชั้นของที่ใช้จริงสถานะ
หู · ASRwhisper-server แล้ว whisper-cli · โมเดล ggml-small 466 MBBUILT · เชนพิมพ์ srv → cli ตอนบูต
หู · ขา groqเรียกก่อนสองตัวข้างบนถ้ามีคีย์ปิดอยู่บนเครื่องนี้ · ไม่มีคีย์ จึงไม่ขึ้นในเชน
ปาก · TTSeleven_v3 เมื่อมีอักขระไทย นอกนั้น multilingual_v2BUILT · เลือกจากเนื้อข้อความ
ปาก · สำรองedge-tts ดักไว้สามจุดBUILT · บัด fude kensa mamori ใช้ทางนี้ล้วน
เสียงประจำตัวโคลนจากคลิป 9 วินาทีที่ผ่านตัวคัดสองแกนBUILT · ทะเบียนอยู่ใน CAST.md
ตา · มองgrok ผ่านเส้นทางรูปอ้างอิงทาง DMBUILT
ตา · วาดgrok เร็ว · ComfyUI + Flux คุณภาพ · วิดีโอ grok หรือ fal SeedanceBUILT · เปิดทีละบัดด้วยตัวแปรสภาพแวดล้อม
สมองclaude-opus-5 · ตัดสินใจสี่ทาง · สมองสี่ระดับ สิทธิ์ต่างกันBUILT
รอยต่อเสียงMQTT หนึ่งหัวข้อต่อบัด พร้อมสะพานข้ามเครื่องทางเดียวBUILT · ต่อเสร็จวันเดียวกับที่เขียนเล่มนี้
สมองสำรองในเครื่องOllama qwen3:8b หรือ vLLMมีไฟล์ ยังไม่ได้ต่อ · หัวไฟล์เขียนเองว่ายังไม่ wired
ตาข่ายโมเดลในเครื่องQwen3-0.6B ที่พอร์ต 9337ตอบจริง แต่ text อย่างเดียว · เสียงและภาพเป็น none
เลน Qwen token planasr-flash · tts-flash · 3.8-max-preview · omni · embedding-v3 · deepseek v4 flashยังไม่ได้พิสูจน์สักตัว · โควต้าหมดรอบสัปดาห์ตอนที่ลอง

ปิดเล่ม สามอย่างที่อยากให้ติดตัวไป

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

สอง ตัวที่เลือกโมเดลควรเป็นเนื้องาน ไม่ใช่ค่าที่ตั้งไว้ล่วงหน้า ชั้นปากดูว่ามีอักขระไทยไหมแล้วค่อยเลือก เพราะสิ่งที่ตัดสินไม่ใช่ว่าบัดตัวนี้ชอบเสียงแบบไหน แต่คือประโยคนี้ต้องใช้โมเดลที่รองรับภาษานี้

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

เริ่มจากตรงไหนดี ลองเขียน stack ของตัวเองลงหน้ากระดาษหนึ่งหน้า ชั้นละบรรทัด แล้วเติมคอลัมน์ที่สองว่า "รู้ได้ยังไงว่ามันทำงาน" ถ้ามีบรรทัดไหนที่คอลัมน์สองว่าง นั่นคือชั้นที่คุณกำลังเชื่อโดยไม่มีหลักฐาน

ที่มา References