ในการพัฒนา AI Agent หรือแชทบอทที่ซับซ้อน การจัดการสถานะการสนทนา (Dialogue State Management) เป็นหัวใจสำคัญที่กำหนดความสำเร็จของระบบ ในบทความนี้ผมจะเปรียบเทียบ 3 วิธีการหลักที่นิยมใช้กัน ได้แก่ FSM (Finite State Machine), Graph-based และ LLM Router พร้อมวิเคราะห์จากประสบการณ์ตรงในการใช้งานจริง
ทำความรู้จัก 3 วิธีการจัดการสถานะการสนทนา
1. Finite State Machine (FSM)
FSM เป็นวิธีดั้งเดิมที่ใช้หลักการ state และ transition กำหนดสถานะชัดเจน มีการเปลี่ยนแปลงตามเงื่อนไขที่กำหนดไว้ล่วงหน้า เหมาะกับระบบที่มี flow ตายตัว
2. Graph-based Architecture
ใช้โครงสร้างกราฟในการเชื่อมต่อสถานะต่างๆ แต่ละโหนดสามารถมีหลายทางออกและรองรับการ branch ได้ดีกว่า FSM
3. LLM Router
ใช้ Large Language Model ในการตัดสินใจว่าควรเปลี่ยนไปสถานะใด มีความยืดหยุ่นสูงและปรับตัวตามบริบทของการสนทนาได้
การเปรียบเทียบประสิทธิภาพตามเกณฑ์ที่ชัดเจน
| เกณฑ์การประเมิน | FSM | Graph-based | LLM Router |
|---|---|---|---|
| ความหน่วง (Latency) | ~5-15ms | ~10-30ms | ~150-500ms |
| อัตราสำเร็จ (Success Rate) | 95%+ (flow ตายตัว) | 90%+ | 75-85% |
| ความสะดวกในการพัฒนา | ง่ายมาก | ปานกลาง | ยาก (ต้อง prompt engineering) |
| ความครอบคลุมของโมเดล | ไม่ต้องใช้ LLM | บางส่วน | ต้องใช้ LLM เต็มรูปแบบ |
| ค่าใช้จ่าย (ต่อ 1M tokens) | $0 | $0-2.50 | $2.50-15 |
| ความยืดหยุ่น (Flexibility) | ต่ำ | ปานกลาง | สูงมาก |
| Debug และ Testing | ง่ายมาก | ปานกลาง | ยาก |
ตัวอย่างโค้ดแต่ละวิธีการ
FSM Implementation
from enum import Enum
from typing import Callable, Dict, Optional
class DialogueState(Enum):
GREETING = "greeting"
MENU_SELECTION = "menu_selection"
ORDER_CONFIRM = "order_confirm"
PAYMENT = "payment"
COMPLETED = "completed"
FAILED = "failed"
class FSMDialogueManager:
def __init__(self):
self.current_state = DialogueState.GREETING
self.user_data = {}
self.transitions: Dict[DialogueState, Dict[str, DialogueState]] = {
DialogueState.GREETING: {
"hi": DialogueState.MENU_SELECTION,
"hello": DialogueState.MENU_SELECTION
},
DialogueState.MENU_SELECTION: {
"select_item": DialogueState.ORDER_CONFIRM,
"cancel": DialogueState.GREETING
},
DialogueState.ORDER_CONFIRM: {
"confirm": DialogueState.PAYMENT,
"modify": DialogueState.MENU_SELECTION,
"cancel": DialogueState.GREETING
},
DialogueState.PAYMENT: {
"success": DialogueState.COMPLETED,
"failed": DialogueState.FAILED
}
}
def process_input(self, user_input: str) -> DialogueState:
intent = self.classify_intent(user_input)
if self.current_state in self.transitions:
next_state = self.transitions[self.current_state].get(
intent,
self.current_state
)
self.current_state = next_state
return self.current_state
def classify_intent(self, text: str) -> str:
text_lower = text.lower()
if any(word in text_lower for word in ["hi", "hello", "สวัสดี"]):
return "hi"
elif "confirm" in text_lower or "ยืนยัน" in text_lower:
return "confirm"
return "unknown"
def reset(self):
self.current_state = DialogueState.GREETING
self.user_data = {}
ทดสอบ FSM
fsm = FSMDialogueManager()
print(f"Initial state: {fsm.current_state.value}")
fsm.process_input("hello")
print(f"After 'hello': {fsm.current_state.value}")
fsm.process_input("select item")
print(f"After 'select item': {fsm.current_state.value}")
LLM Router Implementation พร้อม HolySheep API
import requests
import json
from typing import List, Dict, Optional
class LLMStateRouter:
def __init__(self, api_key: str, base_url: str = "https://api.holysheep.ai/v1"):
self.api_key = api_key
self.base_url = base_url
self.available_states = [
"greeting", "menu_selection", "order_confirm",
"payment", "completed", "failed", "transfer_to_agent"
]
def determine_next_state(self, conversation_history: List[Dict]) -> Dict:
"""
ใช้ LLM ตัดสินใจว่าควรไป state ใดต่อ
คืนค่า dict ที่มี next_state, confidence, reasoning
"""
prompt = self._build_routing_prompt(conversation_history)
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {
"model": "gpt-4.1",
"messages": [
{"role": "system", "content": "You are a dialogue state router."},
{"role": "user", "content": prompt}
],
"temperature": 0.3,
"max_tokens": 200
}
response = requests.post(
f"{self.base_url}/chat/completions",
headers=headers,
json=payload,
timeout=30
)
if response.status_code != 200:
raise Exception(f"API Error: {response.status_code} - {response.text}")
result = response.json()
return self._parse_llm_response(result["choices"][0]["message"]["content"])
def _build_routing_prompt(self, history: List[Dict]) -> str:
context = "\n".join([
f"{msg['role']}: {msg['content']}"
for msg in history[-10:]
])
return f"""Based on this conversation, determine the next dialogue state.
Available states: {', '.join(self.available_states)}
Conversation:
{context}
Respond in JSON format:
{{"next_state": "state_name", "confidence": 0.0-1.0, "reasoning": "brief explanation"}}
"""
def _parse_llm_response(self, response_text: str) -> Dict:
try:
return json.loads(response_text)
except json.JSONDecodeError:
return {
"next_state": "greeting",
"confidence": 0.5,
"reasoning": "Failed to parse, defaulting to greeting"
}
ตัวอย่างการใช้งาน
router = LLMStateRouter(api_key="YOUR_HOLYSHEEP_API_KEY")
conversation = [
{"role": "user", "content": "สวัสดีครับ อยากสั่งซื้อสินค้า"},
{"role": "assistant", "content": "สวัสดีครับ ยินดีให้บริการ กรุณาเลือกรายการสินค้า"},
{"role": "user", "content": "ขอสั่งโทรศัพท์ iPhone 16 Pro"}
]
result = router.determine_next_state(conversation)
print(f"Next state: {result['next_state']}")
print(f"Confidence: {result['confidence']}")
print(f"Reasoning: {result['reasoning']}")
Graph-based Architecture
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Callable
import networkx as nx
@dataclass
class DialogueNode:
state_id: str
prompt: str
handlers: List[Callable] = field(default_factory=list)
validators: List[Callable] = field(default_factory=list)
@dataclass
class DialogueEdge:
from_state: str
to_state: str
condition: Callable[[str], bool]
priority: int = 0
class GraphDialogueManager:
def __init__(self):
self.graph = nx.DiGraph()
self.current_state: Optional[str] = None
self.context: Dict = {}
self.nodes: Dict[str, DialogueNode] = {}
def add_node(self, state_id: str, prompt: str) -> "GraphDialogueManager":
node = DialogueNode(state_id=state_id, prompt=prompt)
self.nodes[state_id] = node
self.graph.add_node(state_id)
if self.current_state is None:
self.current_state = state_id
return self
def add_edge(
self,
from_state: str,
to_state: str,
condition: Callable[[str], bool],
priority: int = 0
) -> "GraphDialogueManager":
edge = DialogueEdge(
from_state=from_state,
to_state=to_state,
condition=condition,
priority=priority
)
self.graph.add_edge(from_state, to_state)
self._edges[(from_state, to_state)] = edge
return self
def process_input(self, user_input: str) -> str:
edges_from_current = [
(u, v, data)
for u, v, data in self.graph.out_edges(self.current_state, data=True)
]
edges_from_current.sort(key=lambda x: x[2].get("priority", 0), reverse=True)
for _, to_state, _ in edges_from_current:
edge_key = (self.current_state, to_state)
if edge_key in self._edges:
edge = self._edges[edge_key]
if edge.condition(user_input):
self.current_state = to_state
return to_state
return self.current_state
def get_reachable_states(self) -> List[str]:
if self.current_state:
return list(nx.descendants(self.graph, self.current_state))
return []
สร้างกราฟสำหรับระบบสั่งซื้อ
dialogue_graph = GraphDialogueManager()
dialogue_graph.add_node("start", "สวัสดีครับ ยินดีให้บริการ")
dialogue_graph.add_node("browse", "กรุณาเลือกสินค้า")
dialogue_graph.add_node("cart", "สินค้าในตะกร้า")
dialogue_graph.add_node("payment", "ชำระเงิน")
dialogue_graph.add_node("complete", "คำสั่งซื้อเสร็จสมบูรณ์")
dialogue_graph._edges = {}
dialogue_graph.add_edge("start", "browse",
lambda x: "เลือก" in x or "ดู" in x, priority=10)
dialogue_graph.add_edge("browse", "cart",
lambda x: "เพิ่ม" in x or "สั่ง" in x, priority=10)
dialogue_graph.add_edge("cart", "payment",
lambda x: "ชำระ" in x or "จ่าย" in x, priority=10)
dialogue_graph.add_edge("payment", "complete",
lambda x: "เสร็จ" in x or "สำเร็จ" in x, priority=10)
print(f"Current state: {dialogue_graph.current_state}")
dialogue_graph.process_input("ต้องการเลือกสินค้า")
print(f"After input: {dialogue_graph.current_state}")
ราคาและ ROI
| วิธีการ | ค่าใช้จ่ายต่อเดือน (approx) | ROI เมื่อเทียบกับ FSM | ความคุ้มค่า |
|---|---|---|---|
| FSM | $0 (ไม่ต้องใช้ LLM) | Baseline | เหมาะกับงานที่ flow ตายตัว |
| Graph-based | $5-50 (ขึ้นกับโมเดล) | ประหยัดเวลา development 30-50% | เหมาะกับงานปานกลาง |
| LLM Router | $50-500 (ขึ้นกับ traffic) | ปรับตัวได้ดี แต่ค่าใช้จ่ายสูง | เหมาะกับงานที่ต้องการความยืดหยุ่นสูง |
เหมาะกับใคร / ไม่เหมาะกับใคร
✅ FSM เหมาะกับ:
- ระบบที่มี flow ชัดเจนและตายตัว เช่น ระบบตอบคำถาม FAQ
- ทีมที่มีทรัพยากรจำกัดและต้องการ deployment เร็ว
- โปรเจกต์ที่ต้องการ latency ต่ำมาก (เกม, IoT, Real-time)
- กรณีที่ต้องการ debug และ test ได้ง่าย
❌ FSM ไม่เหมาะกับ:
- ระบบที่ต้องรองรับการสนทนาแบบ Natural Language ที่หลากหลาย
- โมเดลธุรกิจที่มี rules และ exceptions มากมาย
✅ Graph-based เหมาะกับ:
- ระบบที่มีหลายเส้นทางการสนทนาที่ซับซ้อน
- ทีมที่ต้องการ visualization ของ dialogue flow
- แชทบอทที่ต้องมีการ branch และ merge สถานะ
✅ LLM Router เหมาะกับ:
- ระบบที่ต้องเข้าใจ intent ที่ซับซ้อนและหลากหลาย
- แอปพลิเคชันที่ต้องการ context-aware responses
- โมเดลธุรกิจที่ต้องปรับตัวตามพฤติกรรมผู้ใช้
ทำไมต้องเลือก HolySheep
จากการทดสอบทั้ง 3 วิธีการ พบว่า LLM Router ให้ความยืดหยุ่นสูงสุด แต่มีค่าใช้จ่ายสูงตามไปด้วย สมัครที่นี่ เพื่อรับ API ที่คุ้มค่าที่สุดสำหรับงานนี้
- อัตราแลกเปลี่ยนพิเศษ: ¥1 = $1 ประหยัดมากกว่า 85% เมื่อเทียบกับ OpenAI หรือ Anthropic โดยตรง
- ความหน่วงต่ำ: Latency ต่ำกว่า 50ms เหมาะสำหรับ real-time applications
- รองรับหลายโมเดล: GPT-4.1, Claude Sonnet 4.5, Gemini 2.5 Flash, DeepSeek V3.2
- ชำระเงินง่าย: รองรับ WeChat และ Alipay
- เครดิตฟรี: รับเครดิตฟรีเมื่อลงทะเบียน ทดลองใช้งานก่อนตัดสินใจ
| โมเดล | ราคา (ต่อ 1M tokens) | เหมาะกับงาน |
|---|---|---|
| DeepSeek V3.2 | $0.42 | Routing พื้นฐาน, งานที่ต้องประหยัด |
| Gemini 2.5 Flash | $2.50 | Fast routing, real-time |
| GPT-4.1 | $8.00 | Complex routing, high accuracy |
| Claude Sonnet 4.5 | $15.00 | Best reasoning, enterprise |
ข้อผิดพลาดที่พบบ่อยและวิธีแก้ไข
ข้อผิดพลาดที่ 1: LLM Router ตอบสถานะที่ไม่มีอยู่ในระบบ
# ❌ วิธีที่ผิด - ไม่ validate output
def determine_next_state(self, user_input: str) -> str:
response = self.call_llm(user_input)
return response["next_state"] # อาจไม่ตรงกับ states ที่มี
✅ วิธีที่ถูกต้อง - validate output
def determine_next_state(self, user_input: str) -> str:
response = self.call_llm(user_input)
next_state = response.get("next_state", "greeting")
# Map to valid state if LLM returns unknown
state_mapping = {
"greeting": "greeting",
"hello": "greeting",
"menu": "menu_selection",
"select": "menu_selection",
"order": "order_confirm",
"pay": "payment"
}
return state_mapping.get(next_state.lower(), "greeting")
ข้อผิดพลาดที่ 2: Context ล้น (Context Overflow) ในการสนทนายาว
# ❌ วิธีที่ผิด - ส่ง history ทั้งหมด
def call_llm(self, history: List[Dict]) -> str:
return self.client.chat.completions.create(
model="gpt-4.1",
messages=history # อาจมี thousands of tokens
)
✅ วิธีที่ถูกต้อง - summarize และ truncate
from holysheep import HolySheepClient
class OptimizedStateRouter:
def __init__(self, api_key: str):
self.client = HolySheepClient(api_key)
self.max_history_tokens = 4000
def call_llm(self, history: List[Dict], current_input: str) -> str:
# Summarize old messages
summarized = self._summarize_history(history[:-5])
# Keep only recent messages
recent = history[-5:]
messages = summarized + recent
messages.append({"role": "user", "content": current_input})
# Trim if exceeds limit
messages = self._trim_to_token_limit(messages, self.max_history_tokens)
return self.client.chat.completions.create(
model="gpt-4.1",
messages=messages
)
def _summarize_history(self, old_messages: List[Dict]) -> List[Dict]:
if len(old_messages) <= 2:
return old_messages
summary_prompt = f"""Summarize this conversation briefly (under 100 words):
{old_messages}"""
# Use cheap model for summarization
summary = self.client.chat.completions.create(
model="deepseek-v3.2",
messages=[{"role": "user", "content": summary_prompt}]
)
return [{"role": "system", "content": f"Summary: {summary}"}]
def _trim_to_token_limit(self, messages: List[Dict], limit: int) -> List[Dict]:
# Rough estimate: 1 token ≈ 4 characters
estimated_tokens = sum(len(m["content"]) for m in messages) // 4
while estimated_tokens > limit and len(messages) > 2:
messages.pop(0)
estimated_tokens = sum(len(m["content"]) for m in messages) // 4
return messages
ข้อผิดพลาดที่ 3: Rate Limit เมื่อ Scale
import time
from collections import defaultdict
from threading import Lock
❌ วิธีที่ผิด - ไม่จัดการ rate limit
class BadStateRouter:
def call_llm(self, user_id: str, message: str):
return requests.post(
"https://api.holysheep.ai/v1/chat/completions",
headers={"Authorization": f"Bearer {self.api_key}"},
json={"model": "gpt-4.1", "messages": [{"role": "user", "content": message}]}
)
✅ วิธีที่ถูกต้อง - implement rate limiting
class RateLimitedStateRouter:
def __init__(self, api_key: str, requests_per_minute: int = 60):
self.api_key = api_key
self.rpm = requests_per_minute
self.request_times = defaultdict(list)
self.lock = Lock()
def call_llm(self, user_id: str, message: str) -> dict:
with self.lock:
current_time = time.time()
# Remove requests older than 1 minute
self.request_times[user_id] = [
t for t in self.request_times[user_id]
if current_time - t < 60
]
# Wait if at limit
if len(self.request_times[user_id]) >= self.rpm:
sleep_time = 60 - (current_time - self.request_times[user_id][0])
if sleep_time > 0:
time.sleep(sleep_time)
self.request_times[user_id].pop(0)
self.request_times[user_id].append(current_time)
# Make request
response = requests.post(
"https://api.holysheep.ai/v1/chat/completions",
headers={
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
},
json={
"model": "gpt-4.1",
"messages": [{"role": "user", "content": message}],
"max_tokens": 200
},
timeout=30
)
if response.status_code == 429:
time.sleep(5)
return self.call_llm(user_id, message) # Retry
response.raise_for_status()
return response.json()
def batch_process(self, user_id: str, messages: List[str]) -> List[dict]:
results = []
for msg in messages:
result = self.call_llm(user_id, msg)
results.append(result)
time.sleep(0.1) # Small delay between requests
return results
ข้อผิดพลาดที่ 4: ไม่จัดการ Fallback สถานะ
# ❌ วิธีที่ผิด - ไม่มี fallback
def process(self, user_input: str) -> str:
return self.llm_router.determine_state(user_input)
✅ วิธีที่ถูกต้อง - มี fallback และ escalation
class RobustStateRouter:
def __init__(self, api_key: str):
self.llm_router = LLMStateRouter(api_key)
self.fsm_fallback = FSMDialogueManager()
self.confidence_threshold = 0.7
def process(self, conversation_history: List[Dict], user_input: str) -> str:
try:
result = self.llm_router.determine_next_state(conversation_history)
# High confidence - use LLM decision
if result["confidence"] >= self.confidence_threshold:
return result["next_state"]
# Medium confidence - combine with FSM
fsm_state = self.fsm_fallback.process_input(user_input)
return self._blend_decisions(result, fsm_state)
except Exception as e:
# Low confidence or error - escalate to human
print(f"Routing error: {e}, escalating to human agent")
return "transfer_to_agent"
def _blend_decisions(self, llm_result: Dict, fsm_state: str) -> str:
# Prefer FSM for simple commands
simple_keywords = ["help", "cancel", "reset", "stop"]
if any(kw in fsm_state for kw in simple_keywords):
return fsm_state
# Use LLM for complex intent
if llm_result["confidence"] > 0.5:
return llm_result["next_state"]
# Default to safer option (FSM)
return fsm_state
สรุปและคำแนะนำ
จากการทดสอบเชิงลึกทั้ง 3 วิธีการ พบว่าการเลือกวิธีที่เหมาะสมขึ้นอยู่กับ:
- ความซับซ้อนของ flow - ถ้าตายตัวใช้ FSM ถ้ายืดหยุ่นใช้ LLM Router
- งบประมาณ -