AI Engineer › Embeddings และ RAG

ประกอบ prompt จากผล retrieval พร้อมอ้างอิง

ประมาณ 40 นาที

เป้าหมายของบท

  • ประกอบ prompt แบบ RAG ที่ยึดบริบท อ้างอิง doc id และอนุญาตให้ตอบว่าไม่รู้
  • ใช้ threshold ของคะแนน retrieval แยกคำถามนอกขอบเขต และรู้ข้อจำกัดของมัน
  • ตรวจการอ้างอิงเบื้องต้น และเข้าใจแนวคิด MMR ที่เพิ่มความหลากหลายของบริบท

จากผลค้นหา → prompt ที่ตอบได้และตรวจสอบได้

หลังได้ top-k เอกสารแล้ว ขั้นต่อไปคือประกอบ prompt ที่สั่งให้โมเดล ตอบจากบริบทที่ให้เท่านั้น อ้างอิง doc id และยอมบอกว่า “ไม่รู้” — สามข้อนี้คือหัวใจที่ทำให้ RAG น่าเชื่อถือกว่าให้โมเดลตอบจากความจำ

TEMPLATE_RAG = (
    "Answer the question using ONLY the context below. Cite the doc ids in square brackets. "
    "If the answer is not in the context, reply exactly: I don't know.\n\n"
    "Context:\n{context}\n\nQuestion: {question}\nAnswer:"
)

hits = retrieve(question, k=3)                       # [(doc_id, score), ...]
context = "\n".join(f"[{d}] {title}: {text}" for d, title, text in rows)
prompt = TEMPLATE_RAG.format(context=context, question=question)

โค้ดจริงส่ง prompt เข้า API (client.messages.create(..., messages=[{"role": "user", "content": prompt}])) ในบทนี้ไม่ได้เรียกโมเดลจริง — เราใช้ mock_answer ที่ สกัดประโยค จากเอกสารที่ดึงมา (เลือกประโยคที่มีคำสำคัญร่วมกับคำถามมากสุด) เป็นตัวแทน เพื่อให้วัดและทดสอบ pipeline ได้ซ้ำทุกครั้ง มันไม่ฉลาดเท่า LLM แต่ทำให้เห็นว่าคุณภาพขึ้นกับสิ่งที่ retrieval ส่งมา

เมื่อไม่มีคำตอบ: threshold ของคะแนน

ถ้าคำถามไม่เกี่ยวกับฐานความรู้ retrieval ก็ยังคืนอะไรบางอย่างเสมอ — ต้องมีเกณฑ์ตัดว่า “ไม่พบข้อมูล” จากข้อมูลของเรา: คะแนนอันดับ 1 ของคำถามในขอบเขต 30 ข้อมี median 0.327 แต่ต่ำสุด 0.0 (คำถาม “Where is my package right now?” ไม่ตรงคำกับเอกสารใดเลย เพราะเอกสารใช้คำว่า parcel), คำถามนอกขอบเขต 6 ข้อ (เช่น “What is the capital of France?”) ได้ 0.0 ทั้งหมดยกเว้น “Do you sell insurance?” ได้ 0.24 เพราะเอกสาร d20 มีคำว่า “sell”

threshold ตอบคำถามในขอบเขตได้ ตอบคำถามนอกขอบเขต (ควรปฏิเสธ)
0.05 – 0.15 29 / 30 1 / 6
0.20 26 / 30 1 / 6

การตั้ง threshold แลกกันระหว่างปฏิเสธคำถามที่ตอบได้ (recall ลด) กับยอมรับคำถามนอกเรื่อง — และเกณฑ์คะแนนตัวเดียวแยกไม่สะอาด (คำถามนอกเรื่องบังเอิญมีคำร่วมได้) ในงานจริงจึงเพิ่มการให้โมเดลปฏิเสธ (จากคำสั่งใน prompt) และตรวจอ้างอิง

ตรวจว่าอ้างอิงจริง (faithfulness เบื้องต้น)

คำตอบที่อ้าง [d01][d99] ต้องอ้างเฉพาะ doc id ที่ อยู่ในบริบทที่ส่งไป จริง — id ที่ไม่มีอยู่คือสัญญาณว่าโมเดลแต่งขึ้น (hallucinate) ตรวจได้ด้วย regex \[(d\d+)\] แล้วเทียบกับชุดที่ดึงมา

ความหลากหลายของบริบท: MMR

ถ้า top-k คล้ายกันเองหมด (ซ้ำซ้อน) จะเสียที่ context ไปเปล่าๆ MMR เลือกทีละอันด้วยคะแนน λ·(คล้ายคำถาม) − (1−λ)·(คล้ายอันที่เลือกแล้วมากสุด) ในข้อมูลของเรา MMR (λ = 0.7, k = 3) ได้ hit@3 0.8 เทียบกับ top-3 ปกติ 0.8333 — ไม่ได้ช่วย เพราะเอกสารสั้นและไม่ซ้ำซ้อนกันอยู่แล้ว (ควรใช้เมื่อมีข้อมูลซ้ำมาก)

ผลของ mock_answer เมื่อได้ top-3: ประโยคที่เลือกมาจากเอกสารเฉลย 19 จาก 30 ข้อ (0.6333) — เท่ากับตอนได้แค่ top-1 ซึ่งบอกว่า คอขวดอยู่ที่การเลือกประโยคและ retrieval อันดับ 1 ไม่ใช่จำนวนเอกสารที่ส่ง

ตัวอย่างแก้โจทย์

กด "ขั้นต่อไป" ทีละขั้น ลองรันและแก้โค้ดในแต่ละขั้นได้

ประกอบ prompt จาก top-3

ดึง 3 เอกสารแล้วประกอบบริบทพร้อม doc id

  1. retrieve คำถามแรก ("How long does standard delivery take?") ได้ d01 อันดับหนึ่ง (0.1787)

    Python

threshold และคำถามนอกขอบเขต

ดูคะแนนอันดับ 1 ของคำถามในขอบเขตเทียบกับนอกขอบเขต

  1. คำถามในขอบเขต 30 ข้อ: คะแนนอันดับ 1 ต่ำสุดและ median

    Python

เติมช่องว่าง

แทน ___ ด้วยโค้ดที่ถูกต้อง แล้วกดตรวจคำตอบ

ยังไม่ได้ทำ

ตัดสินว่าจะตอบหรือปฏิเสธ: ถ้าคะแนนอันดับ 1 ≥ threshold ให้ตอบ (True) เก็บผลไว้ในตัวแปร answered เติม ___ ให้ครบ

แบบฝึกท้ายบท

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

🟢 พื้นฐาน

🟢 พื้นฐานยังไม่ได้ทำ

ใช้ retrieve (ให้ไว้) กับคำถามแรกของ qs (k = 3) เก็บ doc_id ทั้งสามเป็น list ไว้ในตัวแปร top3_q01

🟢 พื้นฐานยังไม่ได้ทำ

ประกอบบริบทของ top-3 ของคำถามแรกในรูป "[doc_id] title: text" หนึ่งบรรทัดต่อเอกสาร (คั่นด้วย "\n") เก็บไว้ใน context และจำนวนบรรทัดไว้ใน n_lines

🟢 พื้นฐานยังไม่ได้ทำ

ใส่บริบทข้อ e2 กับคำถามแรกใน TEMPLATE_RAG (.format(context=..., question=...)) แล้วเก็บ token ที่ประมาณของ prompt (est_tokens) ไว้ในตัวแปร prompt_tokens

🟢 พื้นฐานยังไม่ได้ทำ

คำถามใน qs (30 ข้อ) มีกี่ข้อที่คะแนนอันดับ 1 (sims.max(axis=1)) ต่ำกว่า 0.2 เก็บไว้ในตัวแปร n_below_02

🟡 ประยุกต์

🟡 ประยุกต์ยังไม่ได้ทำ

ที่ threshold 0.15: คำถามใน qs กี่ข้อที่ตอบ (คะแนนอันดับ 1 ≥ 0.15) เก็บไว้ใน n_answered_in และคำถามใน oos (ให้ไว้ ใช้ retrieve(q, 1)) กี่ข้อที่ถูกตอบผิดๆ เก็บไว้ใน n_answered_oos

🟡 ประยุกต์ยังไม่ได้ทำ

ตัดทุกเอกสารเป็นประโยคด้วย sentences (ให้ไว้ ตัดที่ . และ ;) แล้วนับจำนวนประโยครวม (n_sentences) และเฉลี่ยต่อเอกสาร ปัด 2 ตำแหน่ง (avg_sentences)

🟡 ประยุกต์ยังไม่ได้ทำ

ตรวจการอ้างอิง: answer ให้ไว้ อ้าง doc id หลายตัว — ดึง id ทั้งหมดด้วย re.findall(r"[(d\d+)]", answer) เก็บเป็น list ไว้ใน cited และนับ id ที่ไม่อยู่ใน retrieved (ให้ไว้ = ชุดที่ retrieval ส่งมา) ไว้ใน n_invalid

🔴 ท้าทาย

🔴 ท้าทายยังไม่ได้ทำ

ใช้ mock_answer (ให้ไว้) กับทุกคำถามใน qs: (ก) ส่งบริบท top-3 (retrieve) (ข) ส่งเฉพาะ top-1 — สัดส่วนคำถามที่ประโยคที่เลือกมาจากเอกสารเฉลย (gold_doc_id) ของแต่ละแบบ ปัด 4 ตำแหน่ง เก็บไว้ใน from_gold_top3, from_gold_top1

🔴 ท้าทายยังไม่ได้ทำ

เขียน MMR เอง (λ = 0.7, k = 3) บนความคล้ายกับคำถามข้อที่ 8 (index 7) ของ sims และความคล้ายระหว่างเอกสาร dd = cosine_similarity(D): เลือกทีละอันด้วย λ × sims − (1 − λ) × (ค่ามากสุดของ dd กับที่เลือกแล้ว, 0 ถ้ายังไม่มี) เก็บ doc_id ที่เลือกตามลำดับเป็น list ไว้ใน mmr_ids

Quiz

1. สามสิ่งที่ prompt ของ RAG ควรสั่งเพื่อให้น่าเชื่อถือ
2. ในบทนี้คำถามนอกขอบเขต "Do you sell insurance?" ได้คะแนน 0.24 ทำให้ผ่าน threshold 0.15 ข้อสรุปที่ถูกคือ
3. การตรวจว่า id ที่โมเดลอ้างอิง (เช่น [d99]) ต้องอยู่ในชุดที่ retrieval ส่งไป มีไว้ทำไม
4. ผลของ mock_answer ที่เลือกประโยคจากเอกสารเฉลยได้ 0.6333 ทั้งตอนส่ง top-3 และ top-1 บอกอะไร

สรุป

  • Prompt ของ RAG ต้องสั่ง: ตอบจากบริบทเท่านั้น, อ้าง doc id, และตอบ "ไม่รู้" เมื่อไม่มีข้อมูล (mock_answer ในบทนี้เป็นตัวแทน LLM แบบสกัดประโยค ไม่ใช่โมเดลจริง)
  • threshold ของคะแนนอันดับ 1 กรองคำถามนอกขอบเขตได้บางส่วน (ที่ 0.15: ตอบ 29/30 ในขอบเขต, 1/6 นอกขอบเขต) แต่แยกไม่สะอาด ต้องมีชั้นป้องกันอื่น
  • ตรวจว่า id ที่อ้างอยู่ในบริบทจริง; MMR ไม่ช่วยในข้อมูลที่ไม่ซ้ำซ้อน (hit@3 0.8 vs 0.8333) และประโยคที่เลือกมาจากเอกสารเฉลย 0.6333 ทั้ง top-1 และ top-3

คำถามสัมภาษณ์

ออกแบบ prompt ของระบบ RAG เพื่อลดการแต่งคำตอบ (hallucination) อย่างไร

สั่งให้ตอบจากบริบทที่ให้เท่านั้น ให้อ้างอิงแหล่งที่มา (doc id) ให้ปฏิเสธเมื่อบริบทไม่มีคำตอบ แล้วตรวจฝั่งโค้ดว่าการอ้างอิงอยู่ในชุดที่ดึงมาจริง ใช้ threshold คะแนน retrieval กรองคำถามนอกขอบเขต ตั้ง temperature ต่ำ และวัดผลด้วยชุดคำถามที่มีเฉลย

MMR ใช้ทำอะไรและเมื่อไรที่ไม่ช่วย

MMR เลือก chunk ทีละอันโดยถ่วงระหว่างความเกี่ยวข้องกับคำถามและความต่างจากอันที่เลือกแล้ว เพื่อลดข้อมูลซ้ำซ้อนในบริบท มีประโยชน์เมื่อฐานความรู้มีเนื้อหาซ้ำ/คล้ายกันมาก แต่ถ้าเอกสารไม่ซ้ำซ้อนอยู่แล้ว (เช่นในตัวอย่างของเรา) จะไม่ช่วยหรืออาจทำให้ hit@k ลดลงเล็กน้อย