บันทึกทั้งหมด

ความ⁠ปลอดภัยของ AI · AI สนทนาด้วยเสียง · วิศวกรรมซอฟต์แวร์

AI รับสายของคลินิก ห้ามพูดอะไรบ้าง

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

อ่านบทความนี้เป็นภาษา⁠อังกฤษ
โทรศัพท์ของคลินิกวางบนโต๊ะข้างสมุดที่ปิดอยู่ ในแสงยามเย็น

พนักงานต้อนรับคลินิกรับโทรศัพท์ เช็กตารางหมอ ลงนัด และตามเจ้า⁠หน้า⁠ที่เมื่อมีเรื่องที่ต้องให้ทีมรักษาดูแล หน้าที่นี้รวมถึงการรู้ด้วยว่าคำถามไหนไม่ควรตอบเอง ถ้าคนไข้เล่าอาการ พนักงานต้อนรับไม่ควรตอบว่า “อาการแบบนี้น่าจะติดเชื้อนะ⁠คะ ลองทานยา…” แม้จะอยากช่วยก็ตาม

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

ระหว่างทำ เราพบว่าการเขียนข้อห้ามไว้ใน prompt อย่างเดียวไม่พอ บทความนี้จึงเล่าถึงข้อห้ามสามข้อ และวิธีที่เราเพิ่มการตรวจเข้าไปในระบบ

สามเรื่องที่ห้ามตอบ

  1. ห้ามวินิจฉัย รวมถึงการคาดเดาว่า “อาจจะเป็น X” เพราะคนไข้ยังอาจเข้าใจว่าเป็นความเห็นทางการแพทย์ แม้จะใช้คำที่ฟังไม่ฟันธงก็ตาม
  2. ห้ามแนะนำเรื่องยา ทั้งปริมาณที่ควรทาน ความถี่ หรือวิธีจัดการเมื่อลืมทานยา
  3. ห้ามรับ⁠ประกันผลการรักษา เช่น “ทำแล้วหายแน่นอน” หรือ “เห็นผลชัวร์”

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

ทำไม prompt อย่างเดียวไม่พอ

เวอร์ชันแรกของเราก็ใส่กฎเหล่านี้ไว้ใน system prompt และส่วน⁠ใหญ่โมเดลทำตามได้ ปัญหาคือยังมีบางครั้งที่หลุด ซึ่งเป็นเรื่องที่ปล่อยผ่านไม่ได้สำหรับงานนี้

ตอนแก้ เราเริ่มจากเพิ่มคำ⁠สั่งและย้ำกฎเดิม แต่ยิ่ง prompt ยาว การแก้พฤติกรรมหนึ่งก็ยิ่งกระทบอีกพฤติกรรมหนึ่ง กฎที่ย้ำไปหลายรอบยังหลุดได้เป็นครั้งคราว เราจึงเริ่มย้ายการตรวจบางส่วนออกมาไว้ในโค้ด เพื่อให้ระบุได้ชัดว่าจะตรวจอะไร ที่จุดไหน และเขียนเทสต์ครอบคลุมได้

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

โค้ดตรวจอะไรบ้าง

ทุกคำ⁠ตอบผ่านชุดตรวจหลังโมเดลร่างเสร็จ และก่อนระบบส่งข้อความหรือเสียงออกไป

  1. ข้อความจากคนไข้ ตรวจหาอาการเฉียบพลันที่ระบบกำหนดไว้ หากพบให้เข้าสู่ขั้นตอนส่ง⁠ต่อ โดยไม่ต้องรอให้โมเดลเป็นผู้ตัดสินเพียงตัวเดียว
  2. โมเดลร่างคำ⁠ตอบ เรียบเรียงภาษา⁠ไทยและเลือกน้ำเสียงให้เหมาะกับบท⁠สนทนา
  3. ตรวจความ⁠ปลอดภัย ตรวจหาการวินิจฉัย คำแนะนำเรื่องยา และการรับ⁠ประกันผล ก่อนตัดเนื้อหาหรือระงับคำ⁠ตอบที่ไม่ควรส่ง
  4. คำ⁠ตอบที่ส่ง⁠ออก ข้อความหรือเสียงหลังผ่านการตรวจแล้ว
ส่วนที่โมเดลทำคือร่างคำ⁠ตอบ ส่วนการตรวจขาเข้าและขา⁠ออกอยู่ในโค้ดที่เราเขียนเทสต์แยกได้

ตัวตรวจขา⁠ออกจะหาการวินิจฉัย คำแนะนำทางการแพทย์ และการอ้างผลการรักษาในคำ⁠ตอบดิบ มันทำงานแยกจากโมเดลและจาก logic การส่ง⁠ต่อ เพื่อให้เราตรวจสอบความผิดพลาดของแต่ละส่วนได้ชัดขึ้น

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

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

อีกส่วนที่ต้องจัดการคือข้อความที่คนอ่านเข้าใจได้ แต่ตัวตรวจจับรูป⁠แบบอาจมองข้าม เช่น อักขระที่มองไม่เห็นแบบ zero-width ตัวเลขที่หน้าตาคล้ายกัน หรือคำที่ผสมอักขระจากหลายภาษาไว้ด้วยกัน เราต้องจัดการรูป⁠แบบเหล่านี้ก่อนตรวจเนื้อหาด้วย

ส่งเรื่องให้เจ้า⁠หน้า⁠ที่รับต่อ

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

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

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

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

ข้อมูลเดียวกัน เขียนได้หลายแบบ

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

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

081-234-5678
081 234 5678
๐๘๑-๒๓๔-๕๖๗๘
ศูนย์ แปด หนึ่ง สอง สาม สี่ ห้า หก เจ็ด แปด

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

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

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

ตัวอย่างย่อสำหรับเบอร์โทรคือแปลงรูป⁠แบบที่ต่างกันให้เป็นค่ามาตรฐานก่อน แล้วเทียบกับข้อมูลที่บันทึกไว้ หากไม่ตรงกัน ให้ใช้เบอร์จากข้อมูลต้นทางแทนข้อความที่โมเดลร่างมา

// Collapse every surface form to one canonical value, then compare it
// with the record. The record wins; the model's version never ships.
const canonical = (s: string) =>
  thaiWordsToDigits(thaiNumeralsToAscii(stripInvisible(s)))
    .replace(/\D/g, '');

if (canonical(readBack) !== canonical(record.phone)) {
  reply = withPhone(reply, record.phone);
}

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

ตรวจคำ⁠ตอบหลังผ่านตัวกรองด้วย

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

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

การประเมินคุณภาพก็ต้องระวังในลักษณะเดียวกัน การใช้ AI ให้คะแนนตัวอย่างที่ผู้⁠ช่วยมีส่วนเขียนเองเสี่ยงจะได้ผลที่เข้าข้างผู้⁠ช่วยมากเกินไป เราจึงให้การตรวจแบบ deterministic ซึ่งตรวจตามกฎและให้ผลเดิมเมื่อข้อมูลเหมือนเดิม เป็นด่านแรก ส่วนคะแนนจาก AI ที่ทำหน้าที่กรรมการจะนำมาใช้ก็ต่อเมื่อเทียบกับบท⁠สนทนาที่คนให้คะแนนไว้แล้ว

ขอบเขตงานต้องชัดในข้อ⁠ตกลงด้วย

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

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

อยากเห็นระบบนี้ทำงานกับคลินิกของคุณไหม

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