AI Engineer › พื้นฐาน LLM และ API

Messages, ประวัติสนทนา และการสุ่ม (temperature)

ประมาณ 35 นาที

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

  • สร้างคำขอแบบ messages (system + user/assistant) และตัดประวัติให้พอดีงบ token
  • คำนวณ softmax ที่ปรับด้วย temperature และเข้าใจผลต่อความหลากหลายของคำตอบ
  • อธิบาย top-k และ top-p (nucleus) ด้วยการคำนวณเอง

คำขอ LLM คือ “list ของ messages”

API แชตส่วนใหญ่รับข้อมูลเป็น list ของข้อความที่มี role (user / assistant) และ content พร้อมคำสั่งระดับระบบ (system) แยกต่างหาก — และ ไม่มีความจำ: ทุกครั้งที่เรียกต้องส่งประวัติที่จำเป็นกลับไปเองทั้งหมด

request = {
    "model": "claude-opus-5",
    "max_tokens": 300,
    "system": "You are a helpful support assistant for an online store.",
    "messages": [
        {"role": "user", "content": "How long does standard delivery take?"},
    ],
}
# ใน notebook (ต้องมี API key):
#   import anthropic
#   client = anthropic.Anthropic()
#   reply = client.messages.create(**request)
#   reply.content[0].text, reply.usage.input_tokens, reply.usage.output_tokens

ข้อควรรู้: ข้อความ user กับ assistant สลับกันเสมอ, ประวัติยิ่งยาว input ยิ่งแพง จึงต้อง ตัดประวัติให้พอดีงบ token — วิธีพื้นฐานคือเก็บ system prompt ไว้เสมอ แล้วเก็บข้อความ ใหม่ล่าสุด ย้อนหลังไปเท่าที่งบพอ (ประวัติตัวอย่าง 5 ข้อความมี 10, 20, 30, 10, 15 token ที่งบ 60 token จะเก็บได้ 3 ข้อความหลังสุดรวม 55 token)

LLM สร้างข้อความโดย “สุ่มเลือก token ถัดไป”

โมเดลให้คะแนน (logit) กับทุก token ที่เป็นไปได้ แล้วแปลงเป็นความน่าจะเป็นด้วย softmax ที่หารด้วย temperature ก่อน:

def softmax(values, temperature=1.0):
    z = values / temperature
    z = z - z.max()          # เพื่อความเสถียรของตัวเลข
    e = np.exp(z)
    return e / e.sum()

logits = np.array([2.0, 1.0, 0.5, 0.1])       # the, a, cat, dog
softmax(logits, 1.0)   # [0.5745, 0.2114, 0.1282, 0.0859]
softmax(logits, 0.5)   # [0.8282, 0.1121, 0.0412, 0.0185]  คมขึ้น
softmax(logits, 2.0)   # [0.4056, 0.2460, 0.1916, 0.1569]  แบนลง
  • temperature ต่ำ → ความน่าจะเป็นกระจุกที่ตัวเลือกอันดับหนึ่ง คำตอบนิ่ง/ซ้ำได้ (“the” ถูกเลือก 99.6% ของ 1,000 ครั้งที่ T = 0.2 เทียบ 58.2% ที่ T = 1.0 เมื่อสุ่มด้วย seed 42)
  • temperature สูง → หลากหลายและเสี่ยงหลุดประเด็นมากขึ้น
  • top-k เก็บเฉพาะ k ตัวเลือกแรกแล้วปรับให้ผลรวมเป็น 1 · top-p (nucleus) เก็บชุดเล็กสุดที่ความน่าจะเป็นสะสม ≥ p (ตัวอย่างนี้ p = 0.9 ได้ 3 ตัวเลือก: the, a, cat)

งานที่ต้องการความถูกต้อง (สกัดข้อมูล, ตอบจากเอกสาร) ใช้ temperature ต่ำ; งานสร้างสรรค์ใช้สูงกว่า — แต่ต่ำก็ไม่การันตีว่าผลเหมือนเดิมทุกครั้งเสมอไป (ขึ้นกับผู้ให้บริการ) จึงควรเก็บผลที่ต้องการทำซ้ำได้ไว้ (cache) แทนการหวังให้สุ่มซ้ำได้เหมือนเดิม

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

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

softmax กับ temperature

ดูว่า temperature เปลี่ยนความน่าจะเป็นอย่างไร

  1. ที่ T = 1.0 ตัวเลือกแรก ("the") ได้ 0.5745

    Python

ตัดประวัติให้พอดีงบ token

เก็บข้อความใหม่ล่าสุดย้อนหลังจนเต็มงบ

  1. ประมาณ token ของแต่ละข้อความ: [10, 20, 30, 10, 15]

    Python

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

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

ยังไม่ได้ทำ

เติมสูตร softmax ที่ปรับด้วย temperature แล้วเก็บความน่าจะเป็นของ token แรกที่ T = 0.5 (ปัด 4 ตำแหน่ง) ไว้ใน p_first เติม ___ ให้ครบ

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

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

🟢 พื้นฐาน

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

softmax(logits, 1.0) ที่ให้ไว้ — ความน่าจะเป็นของ token แรก ("the") ปัด 4 ตำแหน่ง เก็บไว้ในตัวแปร p_top

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

ที่ temperature 0.5 ความน่าจะเป็นของ token แรกเป็นเท่าไร (ปัด 4 ตำแหน่ง) เก็บไว้ในตัวแปร p_top_low

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

จากประวัติ history ที่ให้ไว้: จำนวนข้อความของ user เก็บไว้ใน n_user และผลรวม token ที่ประมาณของทุกข้อความ (est_tokens ของ content) เก็บไว้ใน history_tokens

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

ตรวจว่า role ของ history สลับ user/assistant กันตลอดหรือไม่ (ข้อความติดกันต้องมี role ต่างกัน) เก็บค่า True/False ไว้ในตัวแปร alternates

🟡 ประยุกต์

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

ตัดประวัติให้พอดีงบ 60 token โดยเก็บข้อความใหม่ล่าสุดย้อนหลัง (ไม่เกินงบ, ใช้ est_tokens ของ content) เก็บจำนวนข้อความที่เก็บไว้ใน kept_count และผลรวม token ของมันใน kept_tokens

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

top-k = 2: เก็บเฉพาะ 2 token ที่ความน่าจะเป็น (T = 1.0) สูงสุด แล้วปรับให้ผลรวมเป็น 1 — ความน่าจะเป็นใหม่ของอันดับ 1 และ 2 (ปัด 4 ตำแหน่ง) เก็บไว้ใน p_top2_first, p_top2_second

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

สุ่ม token 1,000 ครั้งด้วย np.random.RandomState(42).choice(len(tokens), size=1000, p=...) (สร้าง RandomState ใหม่ทุกครั้ง) ที่ T = 1.0 และ T = 0.2 — สัดส่วนที่ได้ token แรก เก็บไว้ใน frac_t1, frac_t02

🔴 ท้าทาย

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

ในช่วง temperature 0.05, 0.10, …, 2.00 (ห่างกัน 0.05 ใช้ round(0.05 * i, 2) เมื่อ i = 1..40) ค่า temperature ที่มากที่สุดที่ทำให้ token แรกยังมีความน่าจะเป็น ≥ 0.9 คือเท่าไร เก็บไว้ใน t_for_90

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

top-p (nucleus) ที่ p = 0.9 ที่ T = 1.0: เรียง token ตามความน่าจะเป็นมากไปน้อย แล้วเลือกชุดเล็กสุดที่ความน่าจะเป็นสะสม ≥ 0.9 — เก็บจำนวนไว้ใน nucleus_size และรายชื่อ token (list ตามลำดับ) ไว้ใน nucleus_tokens

Quiz

1. ทำไมแอปที่คุยกับ LLM API ต้องส่งประวัติสนทนากลับไปทุกครั้ง
2. temperature ต่ำลงมีผลอย่างไรต่อการสุ่ม token
3. ในบทนี้ที่ T = 0.2 "the" ถูกสุ่มได้ 99.6% ของ 1,000 ครั้ง แต่ที่ T = 1.0 ได้ 58.2% ข้อสรุปที่ถูกคือ
4. เมื่อประวัติยาวเกินงบ token วิธีตัดที่เหมาะสมกว่าคือ

สรุป

  • API ไม่มีความจำ: ส่ง system + messages ที่จำเป็นทุกครั้ง และตัดประวัติเก่าสุดให้พอดีงบ token (เก็บ system ไว้เสมอ)
  • softmax(logits / T): T ต่ำ → คมและนิ่ง, T สูง → แบนและหลากหลาย (T=0.2 เลือก "the" 99.6%, T=1.0 เลือก 58.2% ใน 1,000 ครั้ง seed 42)
  • top-k เก็บ k ตัวแรก, top-p เก็บชุดเล็กสุดที่ความน่าจะเป็นสะสม ≥ p; งานที่ต้องแม่นใช้ T ต่ำและ cache ผลที่ต้องทำซ้ำได้

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

LLM API ไม่มีความจำ แล้วแชตบอตจำบทสนทนาได้อย่างไร

ตัวแอปเก็บประวัติเองแล้วส่ง messages ที่เกี่ยวข้องกลับไปทุกคำขอ เมื่อประวัติยาวเกินงบ token ต้องตัดหรือสรุปข้อความเก่า (เก็บ system prompt และข้อความล่าสุด) และอาจดึงข้อมูลที่เกี่ยวข้องมาจากฐานความรู้ภายนอกแทนการพกทุกอย่างไว้ในประวัติ

เมื่อไรควรใช้ temperature ต่ำและเมื่อไรควรสูง

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