การสร้าง Agent ที่ซับซ้อนด้วย LangGraph นั้น การจัดการลูป (Loop) และเงื่อนไข (Conditional Branching) เป็นหัวใจหลักที่ต้องเข้าใจอย่างลึกซึ้ง ในบทความนี้ผมจะพาทุกท่านไปสำรวจรูปแบบการออกแบบที่ใช้งานจริงใน Production พร้อมโค้ดตัวอย่างที่รันได้ทันที โดยใช้ HolySheep AI เป็น API Provider หลัก
ตารางเปรียบเทียบบริการ AI Relay
| เกณฑ์ | HolySheep AI | Official OpenAI | Official Anthropic | บริการ Relay อื่นๆ |
|---|---|---|---|---|
| อัตราแลกเปลี่ยน | ¥1 = $1 (ประหยัด 85%+) | ราคาปกติ USD | ราคาปกติ USD | มีค่าธรรมเนียมต่างๆ |
| วิธีชำระเงิน | WeChat / Alipay | บัตรเครดิตระหว่างประเทศ | บัตรเครดิตระหว่างประเทศ | หลากหลาย |
| ความหน่วง (Latency) | < 50ms | 100-300ms | 150-400ms | 80-200ms |
| เครดิตฟรี | ✅ มีเมื่อลงทะเบียน | ❌ ไม่มี | ❌ ไม่มี | มีบ้างไม่มีบ้าง |
| GPT-4.1 | $8/MTok | $8/MTok | - | $8.5-10/MTok |
| Claude Sonnet 4.5 | $15/MTok | - | $15/MTok | $16-18/MTok |
| Gemini 2.5 Flash | $2.50/MTok | - | - | $3-4/MTok |
| DeepSeek V3.2 | $0.42/MTok | - | - | $0.50-0.80/MTok |
พื้นฐาน LangGraph State Graph
ก่อนจะเข้าสู่เรื่องลูปและเงื่อนไข มาทำความเข้าใจโครงสร้างพื้นฐานของ LangGraph กันก่อน LangGraph ใช้ StateGraph เป็นหัวใจหลัก โดยแต่ละ Node จะรับ State ปัจจุบันและคืนค่า State ใหม่กลับมา
การตั้งค่า Environment และการเชื่อมต่อ
import os
from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI
from typing import TypedDict, Annotated
import operator
ตั้งค่า API Key และ Base URL สำหรับ HolySheep AI
os.environ["OPENAI_API_KEY"] = "YOUR_HOLYSHEEP_API_KEY"
os.environ["OPENAI_API_BASE"] = "https://api.holysheep.ai/v1"
สร้าง LLM instance โดยใช้ HolySheep AI
llm = ChatOpenAI(
model="gpt-4.1",
temperature=0.7,
api_key=os.environ["OPENAI_API_KEY"],
base_url=os.environ["OPENAI_API_BASE"]
)
กำหนด State Schema สำหรับ Graph
class AgentState(TypedDict):
messages: list
step: int
result: str
should_continue: bool
รูปแบบลูป (Loop Patterns) ใน LangGraph
1. ลูปแบบมีจำนวนรอบจำกัด (Fixed Iteration Loop)
รูปแบบแรกที่พบบ่อยคือการทำงานซ้ำๆ เป็นจำนวนรอบที่กำหนดไว้ เหมาะสำหรับงานที่ต้องการปรับปรุงผลลัพธ์ทีละขั้นตอน เช่น การ Refine ข้อความ หรือการวิเคราะห์ข้อมูลหลายมุมมอง
from langgraph.graph import StateGraph, END, START
def create_refinement_graph(max_iterations: int = 3):
"""สร้าง Graph สำหรับการปรับปรุงผลลัพธ์ซ้ำๆ"""
# กำหนด State Schema
class RefinementState(TypedDict):
original_text: str
refined_text: str
iteration: int
quality_score: float
# Node สำหรับปรับปรุงข้อความ
def refine_node(state: RefinementState) -> RefinementState:
iteration = state["iteration"]
prompt = f"""ปรับปรุงข้อความต่อไปนี้ให้ดีขึ้น (รอบที่ {iteration}/{max_iterations}):
ข้อความเดิม: {state.get('refined_text', state['original_text'])}
โปรดปรับปรุง:
1. ความชัดเจนของเนื้อหา
2. ความกระชับของประโยค
3. ความถูกต้องทางไวยากรณ์"""
response = llm.invoke(prompt)
return {
"refined_text": response.content,
"iteration": iteration + 1
}
# Node สำหรับประเมินคุณภาพ
def evaluate_node(state: RefinementState) -> RefinementState:
prompt = f"""ให้คะแนนคุณภาพข้อความนี้ 0-10:
{state['refined_text']}
ตอบกลับเฉพาะตัวเลขคะแนนเท่านั้น"""
response = llm.invoke(prompt)
try:
score = float(response.content.strip()[0])
except:
score = 5.0
return {"quality_score": score}
# ฟังก์ชันตัดสินใจว่าจะทำต่อหรือหยุด
def should_continue(state: RefinementState) -> str:
if state["iteration"] >= max_iterations:
return "end"
return "refine"
# สร้าง Graph
graph = StateGraph(RefinementState)
graph.add_node("refine", refine_node)
graph.add_node("evaluate", evaluate_node)
graph.add_edge(START, "refine")
graph.add_edge("refine", "evaluate")
graph.add_conditional_edges(
"evaluate",
should_continue,
{
"refine": "refine",
"end": END
}
)
return graph.compile()
ใช้งาน
graph = create_refinement_graph(max_iterations=3)
result = graph.invoke({
"original_text": "วันนี้อากาศดีมากเลยนะ",
"refined_text": "",
"iteration": 0,
"quality_score": 0.0
})
print(f"ผลลัพธ์สุดท้าย: {result['refined_text']}")
print(f"จำนวนรอบ: {result['iteration']}")
2. ลูปแบบมีเงื่อนไขการหยุด (Conditional Stop Loop)
รูปแบบนี้จะทำงานไปเรื่อยๆ จนกว่าจะเข้าเงื่อนไขที่กำหนด เหมาะสำหรับงานที่ต้องการความยืดหยุ่นในการตัดสินใจหยุด เช่น การค้นหาจนกว่าจะเจอคำตอบที่ดีพอ
from enum import Enum
class LoopMode(Enum):
CONTINUE = "continue"
END = "end"
def create_conditional_search_graph():
"""สร้าง Graph สำหรับการค้นหาที่หยุดเมื่อเจอคำตอบที่ดีพอ"""
class SearchState(TypedDict):
query: str
search_results: list
current_result: str
attempts: int
confidence: float
found_answer: bool
# Node สำหรับค้นหาข้อมูล
def search_node(state: SearchState) -> SearchState:
attempts = state["attempts"] + 1
prompt = f"""ค้นหาข้อมูลเกี่ยวกับ: {state['query']}
รอบที่: {attempts}
ผลลัพธ์ก่อนหน้า: {state.get('current_result', 'ยังไม่มี')}
หากพบคำตอบที่น่าเชื่อถือ ให้ตอบกลับด้วยคำตอบนั้น
หากยังไม่แน่ใจ ให้บอกว่าต้องการข้อมูลเพิ่มเติม"""
response = llm.invoke(prompt)
return {
"current_result": response.content,
"attempts": attempts
}
# Node สำหรับประเมินความมั่นใจ
def evaluate_confidence(state: SearchState) -> SearchState:
prompt = f"""ประเมินความมั่นใจของคำตอบนี้ 0.0 - 1.0:
คำถาม: {state['query']}
คำตอบ: {state['current_result']}
ตอบกลับเฉพาะตัวเลขทศนิยม 1 ตำแหน่ง"""
response = llm.invoke(prompt)
try:
confidence = float(response.content.strip()[:4])
except:
confidence = 0.5
found = confidence >= 0.85 or state["attempts"] >= 5
return {
"confidence": confidence,
"found_answer": found
}
# ฟังก์ชันตัดสินใจ - รองรับทั้ง str และ LoopMode enum
def should_loop(state: SearchState) -> str:
if state["found_answer"]:
return "end"
return "continue"
# สร้าง Graph
graph = StateGraph(SearchState)
graph.add_node("search", search_node)
graph.add_node("evaluate", evaluate_confidence)
graph.add_edge(START, "search")
graph.add_edge("search", "evaluate")
graph.add_conditional_edges(
"evaluate",
should_loop,
{
"continue": "search",
"end": END
}
)
return graph.compile()
ใช้งาน
graph = create_conditional_search_graph()
result = graph.invoke({
"query": "วิธีทำกาแฟในบ้านแบบง่ายๆ",
"search_results": [],
"current_result": "",
"attempts": 0,
"confidence": 0.0,
"found_answer": False
})
print(f"ความมั่นใจ: {result['confidence']}")
print(f"จำนวนครั้งที่ค้นหา: {result['attempts']}")
print(f"คำตอบ: {result['current_result']}")
รูปแบบเงื่อนไขแตกแขนง (Conditional Branching)
3. เงื่อนไขแตกแขนงหลายทาง (Multi-branch Conditional)
ในบางกรณี เราต้องการแตกแขนงออกเป็นหลายเส้นทางตามเงื่อนไขที่ซับซ้อน เช่น การจัดการประเภทต่างๆ ของผู้ใช้หรือประเภทของงานที่แตกต่างกัน
from typing import Literal
def create_multi_branch_support_graph():
"""สร้าง Graph สำหรับระบบ Support ที่แตกแขนงตามประเภทปัญหา"""
class SupportState(TypedDict):
user_message: str
issue_type: str
priority: str
response: str
escalation_needed: bool
# Node จำแนกประเภทปัญหา
def classify_issue(state: SupportState) -> SupportState:
prompt = f"""จำแนกปัญหาต่อไปนี้เป็นหมวดหมู่:
ข้อความ: {state['user_message']}
หมวดหมู่ที่เป็นไปได้:
- billing: ปัญหาเกี่ยวกับการเงิน/การชำระเงิน
- technical: ปัญหาทางเทคนิค
- general: คำถามทั่วไป
- complaint: ข้อร้องเรียน
ตอบกลับเฉพาะหมวดหมู่เท่านั้น"""
response = llm.invoke(prompt)
issue_type = response.content.strip().lower()
# กำหนด Priority ตามเนื้อหา
priority = "medium"
if "ด่วน" in state["user_message"] or "เร่งด่วน" in state["user_message"]:
priority = "high"
elif "ฉุกเฉิน" in state["user_message"]:
priority = "critical"
return {
"issue_type": issue_type,
"priority": priority
}
# Node สำหรับจัดการปัญหาการเงิน
def handle_billing(state: SupportState) -> SupportState:
prompt = f"""คุณคือฝ่ายบริการลูกค้าเกี่ยวกับการเงิน
ข้อความลูกค้า: {state['user_message']}
Priority: {state['priority']}
ตอบกลับอย่างเป็นมิตรและให้ข้อมูลที่เป็นประโยชน์"""
response = llm.invoke(prompt)
return {
"response": response.content,
"escalation_needed": state["priority"] in ["high", "critical"]
}
# Node สำหรับจัดการปัญหาทางเทคนิค
def handle_technical(state: SupportState) -> SupportState:
prompt = f"""คุณคือฝ่ายสนับสนุนทางเทคนิค
ข้อความลูกค้า: {state['user_message']}
Priority: {state['priority']}
ให้คำตอบทางเทคนิคที่ละเอียดและเข้าใจง่าย"""
response = llm.invoke(prompt)
return {
"response": response.content,
"escalation_needed": state["priority"] == "critical"
}
# Node สำหรับจัดการคำถามทั่วไป
def handle_general(state: SupportState) -> SupportState:
prompt = f"""ตอบคำถามต่อไปนี้อย่างเป็นมิตร:
{state['user_message']}"""
response = llm.invoke(prompt)
return {
"response": response.content,
"escalation_needed": False
}
# Node สำหรับจัดการข้อร้องเรียน
def handle_complaint(state: SupportState) -> SupportState:
prompt = f"""คุณคือหัวหน้าฝ่ายบริการลูกค้า
ลูกค้ามีข้อร้องเรียน: {state['user_message']}
Priority: {state['priority']}
ตอบกลับด้วยความเข้าใจ แสดงความเสียใจ และเสนอแนวทางแก้ไข"""
response = llm.invoke(prompt)
return {
"response": response.content,
"escalation_needed": True # ข้อร้องเรียนต้อง Escalate เสมอ
}
# Node สำหรับ Escalation
def escalate_node(state: SupportState) -> SupportState:
escalation_msg = f"\n\n[ESCALATED to Human Agent - Priority: {state['priority']}]"
return {"response": state["response"] + escalation_msg}
# ฟังก์ชันตัดสินใจเส้นทาง
def route_issue(state: SupportState) -> Literal["billing", "technical", "general", "complaint"]:
return state["issue_type"]
# ฟังก์ชันตัดสินใจการ Escalate
def should_escalate(state: SupportState) -> str:
return "escalate" if state["escalation_needed"] else "end"
# สร้าง Graph
graph = StateGraph(SupportState)
graph.add_node("classify", classify_issue)
graph.add_node("billing", handle_billing)
graph.add_node("technical", handle_technical)
graph.add_node("general", handle_general)
graph.add_node("complaint", handle_complaint)
graph.add_node("escalate", escalate_node)
graph.add_edge(START, "classify")
graph.add_conditional_edges(
"classify",
route_issue,
{
"billing": "billing",
"technical": "technical",
"general": "general",
"complaint": "complaint"
}
)
graph.add_conditional_edges(
"billing",
should_escalate,
{"escalate": "escalate", "end": END}
)
graph.add_conditional_edges(
"technical",
should_escalate,
{"escalate": "escalate", "end": END}
)
graph.add_edge("general", END)
graph.add_edge("complaint", "escalate")
graph.add_edge("escalate", END)
return graph.compile()
ใช้งาน
graph = create_multi_branch_support_graph()
result = graph.invoke({
"user_message": "ฉันต้องการขอคืนเงินค่าบริการเดือนที่แล้ว เพราะระบบไม่ทำงานเลย",
"issue_type": "",
"priority": "",
"response": "",
"escalation_needed": False
})
print(f"ประเภทปัญหา: {result['issue_type']}")
print(f"Priority: {result['priority']}")
print(f"ต้อง Escalate: {result['escalation_needed']}")
print(f"คำตอบ: {result['response']}")
รูปแบบผสม: ลูปซ้อนเงื่อนไข (Nested Loop with Conditionals)
4. Research Agent แบบครอบคลุม
ตัวอย่างนี้เป็นรูปแบบที่ใช้งานจริงใน Production มากที่สุด เป็นการผสมผสานระหว่างลูปและเงื่อนไขเข้าด้วยกัน สร้าง Research Agent ที่ทำงานซ้ำจนกว่าจะได้ข้อมูลครบถ้วน
from datetime import datetime
def create_research_agent():
"""สร้าง Research Agent ที่มีลูปและเงื่อนไขซับซ้อน"""
class ResearchState(TypedDict):
topic: str
current_phase: str
findings: list
gaps: list
verification_count: int
final_report: str
is_complete: bool
confidence_score: float
# Phase 1: รวบรวมข้อมูลเบื้องต้น
def collect_initial_data(state: ResearchState) -> ResearchState:
prompt = f"""วิจัยเรื่อง: {state['topic']}
รวบรวมข้อมูลเบื้องต้น:
1. คำนิยามและความเข้าใจพื้นฐาน
2. ประเด็นสำคัญ 3-5 ข้อ
3. ผู้เชี่ยวชาญหรือแหล่งข้อมูลที่น่าเชื่อถือ
ใช้ภาษาไทยในการตอบกลับ"""
response = llm.invoke(prompt)
return {
"current_phase": "collection",
"findings": [response.content],
"gaps": []
}
# Phase 2: ค้นหาช่องว่างของข้อมูล
def identify_gaps(state: ResearchState) -> ResearchState:
prompt = f"""จากข้อมูลที่รวบรวมได้:
{state['findings'][-1]}
ระบุช่องว่างของข้อมูลที่ต้องการค้นหาเพิ่มเติม:
1. ระบุ 2-3 ประเด็นที่ยังไม่ชัดเจน
2. ระบุข้อมูลที่ขัดแย้งกัน (ถ้ามี)
3. ระบุแง่มุมที่ควรศึกษาเพิ่มเติม
ใช้ภาษาไทยในการตอบกลับ"""
response = llm.invoke(prompt)
return {
"current_phase": "gap_analysis",
"gaps": response.content.split("\n")
}
# Phase 3: เจาะลึกข้อมูลที่ยังขาด
def deep_dive(state: ResearchState) -> ResearchState:
prompt = f"""เจาะลึกเรื่อง: {state['topic']}
ช่องว่างที่ต้องเติม:
{state['gaps']}
ค้นหาข้อมูลเพิ่มเติมเพื่อเติมเต็มช่องว่างเหล่านี้
ใช้ภาษาไทยในการตอบกลับ"""
response = llm.invoke(prompt)
new_findings = state["findings"] + [response.content]
return {
"current_phase": "deep_dive",
"findings": new_findings,
"verification_count": state["verification_count"] + 1
}
# Phase 4: ตรวจสอบความน่าเชื่อถือ
def verify_findings(state: ResearchState) -> ResearchState:
prompt = f"""ตรวจสอบความน่าเชื่อถือของข้อมูล:
{state['findings'][-1]}
ให้คะแนนความมั่นใจ 0.0 - 1.0 และระบุเหตุผล:
- คะแนนสูง: ข้อมูลมาจากแหล่งที่น่าเชื่อถือ สอดคล้องกัน
- คะแนนต่ำ: ข้อมูลขัดแย้ง ไม่มีแหล่งอ้างอิง
ตอบกลับในรูปแบบ: SCORE: [คะแนน] REASON: [เหตุผล]"""
response = llm.invoke(prompt)
try:
score_text = response.content.split("SCORE:")[1].split("REASON:")[0].strip()
confidence = float(score_text[:4])
except:
confidence = 0.5
return {
"current_phase": "verification",
"confidence_score": confidence
}
# Phase 5: สร้างรายงานสุดท้าย
def generate_report(state: ResearchState) -> ResearchState:
all_findings = "\n\n---\n\n".join(state["findings"])
prompt = f"""สร้างรายงานวิจัยเรื่อง: {state['topic']}
จากข้อมูลที่รวบรวมได้:
{all_findings}
จำนวนแหล่งข้อมูล: {len(state['findings'])}
คะแนนความมั่นใจ: {state['confidence_score']}
สร้างรายงานที่:
1. มีโครงสร้างชัดเจน
2. อ้างอิงข้อมูลจากแหล่งต่างๆ
3. ระบุข้อจำกัดของการวิจัย
4. ใช้ภาษาไทยที่เป็นทางการ
รายงานควรมีความยาว 500-1000 คำ"""
response = llm.invoke(prompt)
return {
"current_phase": "complete",
"final_report": response.content,
"is_complete": True
}
# ตัดสินใจว่าจะทำลูปต่อหรือไม่
def decision_loop(state: ResearchState) -> str:
# เงื่อนไขการหยุด
max_verifications = 3
min_confidence = 0.75
if state["verification_count"] >= max_verifications:
return "generate_report"
if state["confidence_score"] >= min_confidence and state["verification_count"] >= 1:
return "generate_report"
return "deep_dive"
# สร้า�