บทนำ: ทำไม NPC แบบเดิมต้องตาย
ในปี 2024 ผมเคยเล่นเกม RPG ที่มี NPC พูดซ้ำ 5 ประโยคเหมือนเครื่องจักรเสียง นั่นคือจุดที่ผมตัดสินใจว่า "พอแล้ว" และเริ่มสร้างระบบ NPC ที่ขับเคลื่อนด้วย LLM ขึ้นมาเอง
NPC แบบดั้งเดิมใช้ระบบ Decision Tree หรือ State Machine ที่มี script แน่นอน เมื่อผู้เล่นถามคำถามนอกเหนือจากที่กำหนด NPC จะตอบแบบวนลูปหรือตอบว่า "ฉันไม่เข้าใจ" ซึ่งทำลาย immersion อย่างสิ้นเชิง
ในปี 2026 นี้ เทคโนโลยี LLM ราคาถูกลง 85% จากปี 2024 ทำให้ indie developer อย่างผมสามารถสร้าง NPC ที่มี "จิตวิญญาณ" ได้แล้ว โดยใช้ API จาก
HolySheep AI ที่มี latency ต่ำกว่า 50ms และราคาเริ่มต้นที่ $0.42/MTok
---
สถาปัตยกรรมระบบ: RAG + Memory + Tool Use
ระบบ NPC ที่ดีต้องมี 3 ส่วนหลักที่ทำงานร่วมกัน:
- RAG (Retrieval Augmented Generation) — ดึงข้อมูลจาก knowledge base ของเกม
- Memory System — จดจำบทสนทนาก่อนหน้าและ relationship
- Tool Use — ให้ NPC สามารถโต้ตอบกับระบบเกมได้
import requests
from datetime import datetime
from typing import List, Dict
class GameNPC:
"""NPC ที่ขับเคลื่อนด้วย LLM - สร้างโดย HolySheep AI"""
def __init__(self, npc_id: str, personality: str, world_kb: List[Dict]):
self.npc_id = npc_id
self.personality = personality
self.world_kb = world_kb # Knowledge base ของโลกเกม
self.conversation_history = []
self.relationship_score = 0 # -100 ถึง 100
# ตั้งค่า HolySheep API
self.api_key = "YOUR_HOLYSHEEP_API_KEY"
self.base_url = "https://api.holysheep.ai/v1"
def retrieve_context(self, query: str) -> str:
"""RAG: ดึง context ที่เกี่ยวข้องจาก knowledge base"""
# Simplified retrieval - ใช้ keyword matching
relevant_docs = []
query_words = set(query.lower().split())
for doc in self.world_kb:
doc_words = set(doc['content'].lower().split())
overlap = query_words.intersection(doc_words)
if len(overlap) >= 2:
relevant_docs.append((len(overlap), doc))
relevant_docs.sort(reverse=True)
context = "\n".join([doc[1]['content'] for doc in relevant_docs[:3]])
return context if context else "ไม่มีข้อมูลในหัวข้อนี้"
def build_prompt(self, player_input: str) -> str:
"""สร้าง prompt ที่รวมทุกองค์ประกอบ"""
context = self.retrieve_context(player_input)
# ประวัติการสนทนา 5 รอบล่าสุด
recent_history = self.conversation_history[-5:]
history_text = ""
for h in recent_history:
history_text += f"ผู้เล่น: {h['player']}\n{npc_name}: {h['npc']}\n"
system_prompt = f"""คุณคือ {self.npc_id} ในเกมแฟนตาซี
บุคลิก: {self.personality}
ความสัมพันธ์กับผู้เล่น: {self.relationship_score}/100
ข้อมูลโลกเกม: {context}
กฎ:
1. ตอบเป็นภาษาไทยธรรมชาติ ไม่เกิน 3 ประโยค
2. แสดงอารมณ์ตามบุคลิกและ relationship
3. ถ้าความสัมพันธ์ต่ำ ให้ตอบหยาบกระด้างหรือหลีกเลี่ยง
4. สามารถพูดถึงข้อมูลใน knowledge base ได้
"""
return system_prompt, history_text
def chat(self, player_input: str) -> str:
"""ส่งข้อความไป LLM และรับคำตอบ"""
system_prompt, history_text = self.build_prompt(player_input)
payload = {
"model": "deepseek-chat",
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": f"{history_text}\nผู้เล่น: {player_input}"}
],
"temperature": 0.8, # สูงขึ้นเพื่อความ creative
"max_tokens": 150
}
response = requests.post(
f"{self.base_url}/chat/completions",
headers={
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
},
json=payload,
timeout=5
)
result = response.json()
npc_reply = result['choices'][0]['message']['content']
# อัพเดท conversation history
self.conversation_history.append({
"player": player_input,
"npc": npc_reply,
"timestamp": datetime.now().isoformat()
})
# ปรับ relationship ตาม sentiment
self._adjust_relationship(player_input, npc_reply)
return npc_reply
def _adjust_relationship(self, player_input: str, npc_reply: str):
"""ปรับ relationship score ตามเนื้อหาบทสนทนา"""
positive_words = ["ขอบคุณ", "เก่ง", "ดี", "ช่วย", "เพื่อน"]
negative_words = ["ห่วย", "ไม่เอา", "รำคาญ", "เกลียด"]
for word in positive_words:
if word in player_input:
self.relationship_score = min(100, self.relationship_score + 5)
for word in negative_words:
if word in player_input:
self.relationship_score = max(-100, self.relationship_score - 10)
---
ระบบ Quest อัจฉริยะ: NPC จำทุกอย่างและมอบหมายภารกิจ
นี่คือส่วนที่ทำให้เกมของคุณแตกต่างจากเกมอื่น NPC จะจำว่าผู้เล่นเคยทำอะไรให้บ้าง และมอบหมาย quest ที่สอดคล้องกับประวัติ
class QuestSystem:
"""ระบบ Quest ที่ขับเคลื่อนด้วย LLM"""
def __init__(self):
self.active_quests = []
self.completed_quests = []
self.npc_quest_templates = self._load_quest_templates()
def _load_quest_templates(self) -> List[Dict]:
"""โหลดเทมเพลต quest จาก database"""
return [
{
"id": "herb_collector",
"title": "รวบรวมสมุนไพร",
"npc": "หมอผี",
"prerequisites": ["meet_shaman"],
"context": "ผู้เล่นเคยช่วยหมอผีเก็บยา",
"difficulty": "easy"
},
{
"id": "dragon_slayer",
"title": "ปราบมังกร",
"prerequisites": ["herb_collector", "level_10"],
"context": "ผู้เล่นมีประสบการณ์เยอะพอ",
"difficulty": "hard"
}
]
def generate_quest_for_player(self, player_profile: Dict,
available_npcs: List[GameNPC]) -> Dict:
"""ใช้ LLM สร้าง quest ที่เหมาะสมกับผู้เล่น"""
# หา NPC ที่ relationship สูงที่สุด
best_npc = max(available_npcs, key=lambda n: n.relationship_score)
prompt = f"""คุณคือ AI ที่สร้าง quest สำหรับเกม RPG
ข้อมูลผู้เล่น:
- เลเวล: {player_profile.get('level', 1)}
- เพศ: {player_profile.get('gender', 'ไม่ระบุ')}
- ภารกิจที่ทำสำเร็จ: {player_profile.get('completed_quests', [])}
- ความสัมพันธ์กับ {best_npc.npc_id}: {best_npc.relationship_score}/100
สร้าง quest ที่:
1. เหมาะกับเลเวลผู้เล่น
2. สอดคล้องกับประวัติการทำ quest
3. NPC ที่มอบหมายควรมี relationship สูงพอ
ตอบเป็น JSON format:
{{"title": "ชื่อภารกิจ", "description": "รายละเอียด", "reward": "รางวัล", "npc": "ชื่อ NPC"}}
"""
payload = {
"model": "deepseek-chat",
"messages": [{"role": "user", "content": prompt}],
"response_format": {"type": "json_object"},
"temperature": 0.7
}
response = requests.post(
"https://api.holysheep.ai/v1/chat/completions",
headers={
"Authorization": "Bearer YOUR_HOLYSHEEP_API_KEY",
"Content-Type": "application/json"
},
json=payload
)
return response.json()['choices'][0]['message']['content']
---
Performance Optimization: ทำให้ NPC ตอบเร็ว & ราคาถูก
จากประสบการณ์ของผม มี 3 เทคนิคที่ช่วยลด cost และ latency:
- Caching ด้วย Semantic Cache — ถ้าคำถามคล้ายกัน ใช้คำตอบเดิม
- Streaming Response — แสดงคำตอบทีละคำ ลด perceived latency
- Model Routing — คำถามง่ายใช้ DeepSeek V3.2 ($0.42/MTok), คำถามซับซ้อนใช้ GPT-4.1
import hashlib
import json
from collections import OrderedDict
class SemanticCache:
"""Cache ที่ใช้ semantic similarity แทน exact match"""
def __init__(self, max_size: int = 1000):
self.cache = OrderedDict()
self.max_size = max_size
self.hit_count = 0
self.miss_count = 0
def _normalize(self, text: str) -> str:
"""ทำความสะอาดข้อความก่อน hash"""
return text.lower().strip()
def _get_key(self, text: str) -> str:
"""สร้าง key จากข้อความ"""
normalized = self._normalize(text)
return hashlib.sha256(normalized.encode()).hexdigest()[:16]
def get(self, query: str) -> str:
"""ค้นหาคำตอบที่ cache"""
key = self._get_key(query)
if key in self.cache:
self.hit_count += 1
self.cache.move_to_end(key) # LRU update
return self.cache[key]['response']
self.miss_count += 1
return None
def set(self, query: str, response: str):
"""บันทึกคำตอบลง cache"""
key = self._get_key(query)
if len(self.cache) >= self.max_size:
self.cache.popitem(last=False) # Remove oldest
self.cache[key] = {
'query': query,
'response': response,
'timestamp': datetime.now().isoformat()
}
def stats(self) -> Dict:
"""ดูสถิติ cache hit rate"""
total = self.hit_count + self.miss_count
hit_rate = (self.hit_count / total * 100) if total > 0 else 0
return {
"hit_rate": f"{hit_rate:.1f}%",
"hits": self.hit_count,
"misses": self.miss_count,
"cache_size": len(self.cache)
}
การใช้งาน
cache = SemanticCache()
def smart_chat(npc: GameNPC, player_input: str) -> str:
"""chat ที่ใช้ cache เพื่อประหยัด cost"""
# ลองดึงจาก cache ก่อน
cached = cache.get(player_input)
if cached:
print(f"[Cache HIT] {player_input[:30]}...")
return cached
# ถ้าไม่มี เรียก LLM
response = npc.chat(player_input)
# บันทึกลง cache
cache.set(player_input, response)
print(f"[Cache MISS] → LLM call")
return response
---
Context Window Management: จัดการ Memory ระยะยาว
LLM มี context window จำกัด ผมใช้เทคนิค "Summarization + Key Facts" เพื่อให้ NPC จำสิ่งสำคัญได้นาน
class ConversationMemory:
"""จัดการ memory ระยะยาวของ NPC"""
def __init__(self, max_history: int = 20, summary_threshold: int = 10):
self.messages = []
self.max_history = max_history
self.summary_threshold = summary_threshold
self.key_facts = [] # ข้อเท็จจริงสำคัญที่ต้องจำ
def add_message(self, role: str, content: str):
"""เพิ่มข้อความใหม่"""
self.messages.append({
"role": role,
"content": content,
"timestamp": datetime.now().isoformat()
})
# ถ้าถึง threshold ให้ summarize
if len(self.messages) >= self.summary_threshold:
self._summarize_old_messages()
def _summarize_old_messages(self):
"""สรุปข้อความเก่าและเก็บเฉพาะ key facts"""
old_messages = self.messages[:-5] # เก็บ 5 ข้อความล่าสุด
summary_prompt = f"""สรุปบทสนทนาต่อไปนี้เป็นภาษาไทย โดย:
1. ข้อเท็จจริงสำคัญที่ต้องจำ (NPC ต้องรู้)
2. ความรู้สึก/ทัศนคติของผู้เล่น
3. เหตุการณ์สำคัญที่เกิดขึ้น
บทสนทนา:
{' '.join([m['content'] for m in old_messages])}
ตอบเป็น JSON: {{"key_facts": [], "player_sentiment": "", "important_events": []}}
"""
response = requests.post(
"https://api.holysheep.ai/v1/chat/completions",
headers={"Authorization": "Bearer YOUR_HOLYSHEEP_API_KEY"},
json={
"model": "deepseek-chat",
"messages": [{"role": "user", "content": summary_prompt}],
"response_format": {"type": "json_object"}
}
)
summary = json.loads(response.json()['choices'][0]['message']['content'])
self.key_facts = summary.get('key_facts', [])
# ลบข้อความเก่าออก เก็บเฉพาะ summary
self.messages = self.messages[-5:]
self.messages.append({
"role": "system",
"content": f"[Memory] สิ่งที่จำได้: {', '.join(self.key_facts)}",
"timestamp": datetime.now().isoformat()
})
def get_context_for_llm(self) -> str:
"""สร้าง context string สำหรับส่งให้ LLM"""
return "\n".join([
f"{m['role']}: {m['content']}"
for m in self.messages[-self.max_history:]
])
---
ข้อผิดพลาดที่พบบ่อยและวิธีแก้ไข
1. "The model generated text before min_tokens was reached" — Response หยุดกลางคัน
สาเหตุ: max_tokens ต่ำเกินไป หรือ streaming ถูก interrupt
❌ ผิด: max_tokens ต่ำเกินไป
payload = {"max_tokens": 50} # น้อยเกินไปสำหรับคำตอบยาว
✅ ถูก: ใช้ค่าที่เหมาะสม + error handling
def chat_with_retry(npc: GameNPC, player_input: str, max_retries: int = 3) -> str:
for attempt in range(max_retries):
try:
payload = {
"model": "deepseek-chat",
"messages": [{"role": "user", "content": player_input}],
"max_tokens": 500, # เผื่อไว้เยอะพอ
"stop": None
}
response = requests.post(
f"{npc.base_url}/chat/completions",
headers={"Authorization": f"Bearer {npc.api_key}"},
json=payload,
timeout=10
)
result = response.json()
if 'choices' in result and len(result['choices']) > 0:
return result['choices'][0]['message']['content']
except requests.exceptions.Timeout:
print(f"Attempt {attempt + 1}: Timeout, retrying...")
continue
except KeyError as e:
print(f"Response structure error: {e}")
continue
return "ขอโทษครับ ระบบมีปัญหา กรุณาลองใหม่"
2. "Invalid API key" — Authentication Error
สาเหตุ: API key ไม่ถูกต้อง หรือ format Authorization header ผิด
❌ ผิด: Authorization format ผิด
headers = {"Authorization": "YOUR_HOLYSHEEP_API_KEY"} # ลืม Bearer
✅ ถูก: ใช้ Bearer token + validate key format
def validate_and_call_api(api_key: str, base_url: str, payload: dict) -> dict:
if not api_key or api_key == "YOUR_HOLYSHEEP_API_KEY":
raise ValueError("กรุณาใส่ API key ที่ถูกต้องจาก HolySheep AI")
if not api_key.startswith("sk-"):
raise ValueError("API key ต้องขึ้นต้นด้วย 'sk-'")
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
response = requests.post(
f"{base_url}/chat/completions",
headers=headers,
json=payload,
timeout=10
)
if response.status_code == 401:
raise PermissionError("API key ไม่ถูกต้อง กรุณาตรวจสอบที่ https://www.holysheep.ai/register")
return response.json()
การใช้งาน
try:
result = validate_and_call_api(
api_key="YOUR_HOLYSHEEP_API_KEY",
base_url="https://api.holysheep.ai/v1",
payload={"model": "deepseek-chat", "messages": []}
)
except ValueError as e:
print(f"Configuration error: {e}")
except PermissionError as e:
print(f"Auth error: {e}")
3. Token Limit Exceeded — Context Window เต็ม
สาเหตุ: conversation history สะสมจนเกิน context limit
❌ ผิด: ไม่จำกัดจำนวน messages
all_messages = conversation_history # สะสมไม่รู้จบ
✅ ถูก: จำกัด + truncate เมื่อใกล้ limit
def build_safe_messages(system_prompt: str, history: List[Dict],
max_tokens: int = 3000) -> List[Dict]:
"""สร้าง messages ที่ไม่เกิน token limit"""
# Token estimation: 1 token ≈ 4 characters สำหรับภาษาไทย
max_chars = max_tokens * 4
messages = [{"role": "system", "content": system_prompt}]
# เริ่มจากข้อความล่าสุด
for msg in reversed(history):
msg_text = f"ผู้เล่น: {msg['player']}\nNPC: {msg['npc']}"
# คำนวณขนาดรวม
total_chars = sum(len(m['content']) for m in messages) + len(msg_text)
if total_chars > max_chars:
break
messages.append({"role": "user", "content": msg_text})
# กลับลำดับ (เก่าสุดไปใหม่สุด)
return list(reversed(messages))
การใช้งาน
safe_messages = build_safe_messages(
system_prompt="คุณคือ NPC ในเกม",
history=conversation_history,
max_tokens=2500 # เผื่อให้ system prompt + response
)
4. Rate Limit — เรียก API บ่อยเกินไป
สาเหตุ: ผู้เล่นพิมพ์เร็วเกิน หรือ multi-NPC system ทำให้เรียก API พร้อมกันเยอะ
import time
from threading import Lock
class RateLimiter:
"""จำกัดจำนวน API calls ต่อวินาที"""
def __init__(self, max_calls: int = 10, period: float = 1.0):
self.max_calls = max_calls
self.period = period
self.calls = []
self.lock = Lock()
def acquire(self) -> bool:
"""รอจนกว่าจะเรียกได้"""
with self.lock:
now = time.time()
# ลบ calls เก่ากว่า period
self.calls = [t for t in self.calls if now - t < self.period]
if len(self.calls) >= self.max_calls:
# คำนวณเวลารอ
sleep_time = self.period - (now - self.calls[0])
time.sleep(max(0, sleep_time))
return self.acquire() # retry
self.calls.append(now)
return True
การใช้งาน
rate_limiter = RateLimiter(max_calls=10, period=1.0) # 10 calls/วินาที
def throttled_chat(npc: GameNPC, player_input: str) -> str:
rate_limiter.acquire()
return npc.chat(player_input)
---
สรุป: เริ่มต้นสร้าง NPC ที่มีชีวิตในปี 2026
จากประสบการณ์ของผมในการพัฒนา NPC ระบบนี้มาครึ่งปี สิ่งที่ทำให้สำเร็จคือ:
- เริ่มจาก RAG แบบง่ายที่สุดก่อน — อย่าพยายามทำทุกอย่างพร้อมกัน
- ใช้ DeepSeek V3.2 เป็นหลัก ($0.42/MTok) สำหรับ NPC ทั่วไป และ GPT-4.1 สำหรับ cutscene สำคัญ
- Implement cache ตั้งแต่วันแรก — ประหยัด cost ได้ 40-60%
- ทดสอบกับผู้เล่นจริงอย่างน้อย 20 คน ก่อนจะ deploy
ต้นทุนต่อ NPC ต่อเดือน (ถ้าใช้ HolySheep AI):
- 1,000 บทสนทนา/วัน × 30 วัน = 30,000 บทสนทนา
- เฉลี่ย 100 tokens/บทสนทนา = 3M tokens
- DeepSeek V3.2: $0.42/MTok = $1.26/เดือน ต่อ NPC
ใช่ครับ คุณอ่านไม่ผิด — ต้นทุนต่อ NPC ต่อเดือนอยู่ที่ประมาณ $1.26 เท่านั้น ถูกกว่าค่าเช่า server เกมเล็กๆ แม้แต่ราคาของ API ใหม่ล่าสุดอย่าง Gemini 2.5 Flash ที่ $2.50/MTok ก็ยังถูกมากเมื่อเทียบกับ $15/MTok ของ Claude Sonnet 4.5
---
👉
สมัคร HolySheep AI — รับเค
แหล่งข้อมูลที่เกี่ยวข้อง
บทความที่เกี่ยวข้อง