ในโลก AI Agent ยุคใหม่ การสร้าง workflow ที่มี human oversight ไม่ใช่ทางเลือกอีกต่อไป แต่เป็นความจำเป็น โดยเฉพาะเมื่อต้องจัดการกับ sensitive operations เช่น การ approve การทำธุรกรรม การตรวจสอบเนื้อหา หรือการอนุมัติการเปลี่ยนแปลงระบบ บทความนี้จะพาคุณสร้าง production-grade approval workflow ด้วย LangGraph, DeepSeek V4 และ MCP protocol พร้อม benchmark จริงจาก production environment
ทำไมต้อง Human-in-the-Loop?
แม้ AI จะเก่งขึ้นมาก แต่ในบาง scenario การมี human review ยังคงจำเป็นอย่างยิ่ง
- Compliance & Audit: ธุรกิจที่อยู่ภายใต้กฎหมาย เช่น การเงิน สุขภาพ ต้องมี audit trail
- Accuracy Guarantee: ลด hallucination ที่อาจเกิดความเสียหาย
- Cost Control: ป้องกันการเรียก API ที่ไม่จำเป็น ลดค่าใช้จ่ายได้ถึง 60-70%
- Business Logic: กฎทางธุรกิจที่ซับซ้อนเกินกว่า AI จะตัดสินใจได้
สถาปัตยกรรม LangGraph Approval Workflow
LangGraph มีข้อได้เปรียบเหนือ framework อื่นในเรื่อง checkpointing และ interruption ทำให้สร้าง state machine สำหรับ approval ได้อย่างเป็นธรรมชาติ
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.memory import MemorySaver
from langgraph.types import interrupt, Command
from pydantic import BaseModel, Field
from enum import Enum
from datetime import datetime
from typing import Optional
import asyncio
class ApprovalStatus(str, Enum):
PENDING = "pending"
APPROVED = "approved"
REJECTED = "rejected"
TIMEOUT = "timeout"
class ApprovalRequest(BaseModel):
request_id: str
action: str
params: dict
risk_level: str = Field(default="low")
estimated_cost: float = 0.0
created_at: datetime = Field(default_factory=datetime.utcnow)
requested_by: str = "agent"
class ApprovalState(BaseModel):
request: Optional[ApprovalRequest] = None
status: ApprovalStatus = ApprovalStatus.PENDING
approver_comments: Optional[str] = None
approved_at: Optional[datetime] = None
retry_count: int = 0
mcp_tool_results: list[dict] = []
class HumanApprovalGraph:
def __init__(self, llm_client, mcp_tools):
self.llm = llm_client
self.mcp_tools = mcp_tools
self.checkpointer = MemorySaver()
def build_graph(self):
builder = StateGraph(ApprovalState)
# Nodes
builder.add_node("analyze_request", self.analyze_request)
builder.add_node("gather_mcp_context", self.gather_mcp_context)
builder.add_node("request_approval", self.request_approval)
builder.add_node("execute_action", self.execute_action)
builder.add_node("handle_rejection", self.handle_rejection)
builder.add_node("handle_timeout", self.handle_timeout)
# Edges
builder.set_entry_point("analyze_request")
builder.add_edge("analyze_request", "gather_mcp_context")
builder.add_edge("gather_mcp_context", "request_approval")
# Conditional edges for approval
builder.add_conditional_edges(
"request_approval",
self.route_approval,
{
"approved": "execute_action",
"rejected": "handle_rejection",
"timeout": "handle_timeout"
}
)
builder.add_edge("execute_action", END)
builder.add_edge("handle_rejection", END)
builder.add_edge("handle_timeout", END)
return builder.compile(checkpointer=self.checkpointer)
async def analyze_request(self, state: ApprovalState) -> ApprovalState:
"""วิเคราะห์ความเสี่ยงของ request"""
request = state.request
risk_prompt = f"""
Analyze this request for risk assessment:
Action: {request.action}
Parameters: {request.params}
Return JSON with:
- risk_level: low/medium/high/critical
- reasoning: brief explanation
- estimated_cost_usd: approximate cost
"""
# ใช้ DeepSeek V4 ผ่าน HolySheep API
response = await self.llm.chat.completions.create(
model="deepseek-v4",
messages=[{"role": "user", "content": risk_prompt}],
response_format={"type": "json_object"}
)
analysis = json.loads(response.choices[0].message.content)
state.request.risk_level = analysis.get("risk_level", "medium")
state.request.estimated_cost = analysis.get("estimated_cost_usd", 0.0)
return state
async def gather_mcp_context(self, state: ApprovalState) -> ApprovalState:
"""รวบรวมข้อมูลจาก MCP tools สำหรับประกอบการตัดสินใจ"""
request = state.request
# กำหนด tools ที่จะใช้ตามประเภท action
tool_mapping = {
"send_email": ["email_client", "contact_db"],
"update_db": ["database", "audit_log"],
"api_call": ["api_gateway", "rate_limiter"],
}
relevant_tools = tool_mapping.get(request.action, ["generic"])
mcp_results = []
for tool_name in relevant_tools:
if tool_name in self.mcp_tools:
result = await self.mcp_tools[tool_name].call(
action=request.action,
params=request.params
)
mcp_results.append({
"tool": tool_name,
"result": result,
"timestamp": datetime.utcnow().isoformat()
})
state.mcp_tool_results = mcp_results
return state
def request_approval(self, state: ApprovalState) -> ApprovalState:
"""หยุดรอ human approval - LangGraph interrupt"""
interrupt({
"type": "human_approval",
"request_id": state.request.request_id,
"action": state.request.action,
"risk_level": state.request.risk_level,
"mcp_context": state.mcp_tool_results,
"timeout_seconds": 3600
})
return state
async def execute_action(self, state: ApprovalState) -> ApprovalState:
"""ดำเนินการหลังได้รับอนุมัติ"""
request = state.request
# Execute via appropriate MCP tool
action_prompt = f"""
Based on approval for:
Action: {request.action}
Parameters: {request.params}
Approved by: {state.approver_comments}
Execute the action and return result.
"""
# ส่งไปยัง executor agent
result = await self.llm.chat.completions.create(
model="deepseek-v4",
messages=[{"role": "user", "content": action_prompt}]
)
return state
def route_approval(self, state: ApprovalState) -> str:
"""กำหนดเส้นทางตามผลการอนุมัติ"""
if state.status == ApprovalStatus.APPROVED:
return "approved"
elif state.status == ApprovalStatus.REJECTED:
return "rejected"
else:
return "timeout"
def handle_rejection(self, state: ApprovalState) -> ApprovalState:
"""บันทึก rejection และแจ้งเตือน"""
# Log to audit system, send notifications, etc.
return state
def handle_timeout(self, state: ApprovalState) -> ApprovalState:
"""จัดการเมื่อ timeout"""
state.status = ApprovalStatus.TIMEOUT
return state
MCP Tool Integration สำหรับ Context Gathering
MCP (Model Context Protocol) ช่วยให้ AI เข้าถึง external tools ได้อย่างปลอดภัย ใน approval workflow เราใช้ MCP สำหรับดึงข้อมูลที่จำเป็นก่อนขอ approval
import json
from typing import Any
from dataclasses import dataclass
from datetime import datetime, timedelta
import hashlib
HolySheep API Integration
from openai import AsyncOpenAI
HOLYSHEEP_BASE_URL = "https://api.holysheep.ai/v1"
HOLYSHEEP_API_KEY = "YOUR_HOLYSHEEP_API_KEY" # แทนที่ด้วย API key จริง
@dataclass
class MCPConfig:
name: str
endpoint: str
auth_token: str
rate_limit: int = 100 # requests per minute
timeout: int = 30
class MCPToolResult:
def __init__(self, success: bool, data: Any = None, error: str = None):
self.success = success
self.data = data
self.error = error
self.timestamp = datetime.utcnow()
self.request_id = self._generate_request_id()
def _generate_request_id(self) -> str:
timestamp = datetime.utcnow().isoformat()
return hashlib.sha256(timestamp.encode()).hexdigest()[:16]
def to_dict(self) -> dict:
return {
"success": self.success,
"data": self.data,
"error": self.error,
"timestamp": self.timestamp.isoformat(),
"request_id": self.request_id
}
class MCPConnectionPool:
"""Connection pool สำหรับ MCP tools - optimize performance"""
def __init__(self, max_connections: int = 10):
self.max_connections = max_connections
self.pools: dict[str, asyncio.Queue] = {}
self._semaphores: dict[str, asyncio.Semaphore] = {}
def register_tool(self, tool_name: str):
if tool_name not in self.pools:
self.pools[tool_name] = asyncio.Queue(maxsize=self.max_connections)
self._semaphores[tool_name] = asyncio.Semaphore(self.max_connections)
async def acquire(self, tool_name: str) -> asyncio.Semaphore:
return self._semaphores.get(tool_name)
async def execute_with_pooling(
self,
tool_name: str,
func: callable,
*args,
**kwargs
) -> MCPToolResult:
semaphore = await self.acquire(tool_name)
async with semaphore:
try:
result = await asyncio.wait_for(
func(*args, **kwargs),
timeout=30
)
return MCPToolResult(success=True, data=result)
except asyncio.TimeoutError:
return MCPToolResult(
success=False,
error=f"Tool {tool_name} timeout after 30s"
)
except Exception as e:
return MCPToolResult(success=False, error=str(e))
class ProductionMCPClient:
"""MCP Client production-ready พร้อม circuit breaker และ retry"""
def __init__(self):
self.holysheep_client = AsyncOpenAI(
base_url=HOLYSHEEP_BASE_URL,
api_key=HOLYSHEEP_API_KEY,
timeout=60.0,
max_retries=3
)
self.connection_pool = MCPConnectionPool(max_connections=20)
self._circuit_breakers: dict[str, dict] = {}
# Register common tools
self._setup_tools()
def _setup_tools(self):
"""ตั้งค่า MCP tools"""
tools = [
MCPConfig("contact_db", "http://internal:8081/db", "internal_token"),
MCPConfig("audit_log", "http://internal:8082/audit", "internal_token"),
MCPConfig("email_client", "http://internal:8083/email", "internal_token"),
MCPConfig("api_gateway", "http://internal:8084/gateway", "internal_token"),
]
for tool in tools:
self.connection_pool.register_tool(tool.name)
self._circuit_breakers[tool.name] = {
"failure_count": 0,
"last_failure": None,
"state": "closed" # closed, open, half_open
}
async def call_deepseek_v4(
self,
prompt: str,
system_prompt: str = "",
temperature: float = 0.3,
max_tokens: int = 2000
) -> str:
"""เรียก DeepSeek V4 ผ่าน HolySheep API"""
messages = []
if system_prompt:
messages.append({"role": "system", "content": system_prompt})
messages.append({"role": "user", "content": prompt})
try:
response = await self.holysheep_client.chat.completions.create(
model="deepseek-v4",
messages=messages,
temperature=temperature,
max_tokens=max_tokens
)
return response.choices[0].message.content
except Exception as e:
print(f"DeepSeek V4 call failed: {e}")
raise
async def call_tool(
self,
tool_name: str,
action: str,
params: dict,
use_cache: bool = True
) -> MCPToolResult:
"""เรียก MCP tool พร้อม circuit breaker"""
# Check circuit breaker
cb = self._circuit_breakers.get(tool_name, {})
if cb.get("state") == "open":
if datetime.utcnow() - cb.get("last_failure", datetime.min) < timedelta(minutes=1):
return MCPToolResult(
success=False,
error=f"Circuit breaker open for {tool_name}"
)
# Execute tool
async def _execute():
# Mock execution - แทนที่ด้วย actual HTTP call
await asyncio.sleep(0.1) # Simulate network latency
return {"action": action, "params": params, "tool": tool_name}
result = await self.connection_pool.execute_with_pooling(
tool_name, _execute
)
# Update circuit breaker
if not result.success:
cb["failure_count"] = cb.get("failure_count", 0) + 1
cb["last_failure"] = datetime.utcnow()
if cb["failure_count"] >= 5:
cb["state"] = "open"
else:
cb["failure_count"] = 0
cb["state"] = "closed"
return result
async def gather_context_for_approval(
self,
action: str,
params: dict,
risk_level: str
) -> dict:
"""รวบรวม context ทั้งหมดสำหรับ approval"""
# Determine which tools to call based on action type
tool_mapping = {
"send_email": ["contact_db", "email_client"],
"update_record": ["contact_db", "audit_log"],
"external_api_call": ["api_gateway", "audit_log"],
"delete_data": ["contact_db", "audit_log"],
}
required_tools = tool_mapping.get(action, ["contact_db"])
# เรียก tools พร้อมกัน
tasks = [
self.call_tool(tool, action, params)
for tool in required_tools
]
results = await asyncio.gather(*tasks, return_exceptions=True)
# Aggregate results
context = {
"action": action,
"risk_level": risk_level,
"timestamp": datetime.utcnow().isoformat(),
"tool_results": []
}
for result in results:
if isinstance(result, MCPToolResult):
context["tool_results"].append(result.to_dict())
elif isinstance(result, Exception):
context["tool_results"].append({
"success": False,
"error": str(result)
})
# Generate summary using DeepSeek V4
summary_prompt = f"""
Summarize this approval context in Thai:
Action: {action}
Risk Level: {risk_level}
Tool Results: {json.dumps(context['tool_results'], ensure_ascii=False, indent=2)}
Provide:
1. Summary of what will happen
2. Key data points the approver should know
3. Potential concerns
"""
context["ai_summary"] = await self.call_deepseek_v4(
summary_prompt,
system_prompt="คุณเป็นผู้ช่วยสรุปข้อมูลสำหรับการอนุมัติ ตอบเป็นภาษาไทย",
temperature=0.3,
max_tokens=500
)
return context
Benchmark utility
async def run_benchmark():
"""วัดประสิทธิภาพของ workflow"""
client = ProductionMCPClient()
test_cases = [
("send_email", {"to": "[email protected]", "subject": "Test"}),
("update_record", {"id": 12345, "field": "status", "value": "approved"}),
("external_api_call", {"endpoint": "/api/users", "method": "POST"}),
]
results = []
for action, params in test_cases:
# Test MCP context gathering
start = time.time()
context = await client.gather_context_for_approval(
action, params, risk_level="medium"
)
mcp_time = time.time() - start
# Test DeepSeek V4 call
start = time.time()
response = await client.call_deepseek_v4(
f"Analyze this: {json.dumps(params)}",
temperature=0.3
)
llm_time = time.time() - start
results.append({
"action": action,
"mcp_latency_ms": round(mcp_time * 1000, 2),
"llm_latency_ms": round(llm_time * 1000, 2),
"total_ms": round((mcp_time + llm_time) * 1000, 2)
})
return results
Concurrency Control และ Resource Management
ใน production environment การจัดการ concurrency ที่ดีเป็นสิ่งจำเป็น โดยเฉพาะเมื่อมีหลาย approval requests พร้อมกัน
import asyncio
from typing import Optional
from collections import defaultdict
import threading
class ConcurrencyController:
"""ควบคุม concurrent approval requests"""
def __init__(self, max_concurrent: int = 50):
self.max_concurrent = max_concurrent
self._semaphore = asyncio.Semaphore(max_concurrent)
self._active_requests: dict[str, asyncio.Task] = {}
self._request_limits: dict[str, int] = defaultdict(lambda: 5)
self._lock = asyncio.Lock()
async def acquire(self, request_id: str, category: str = "default") -> bool:
"""ขอ permission สำหรับ request"""
async with self._lock:
# ตรวจสอบ category limit
if self._active_requests.get(category, 0) >= self._request_limits[category]:
return False
# ตรวจสอบ global limit
if len(self._active_requests) >= self.max_concurrent:
return False
# รอ semaphore
await self._semaphore.acquire()
async with self._lock:
self._active_requests[request_id] = asyncio.current_task()
return True
async def release(self, request_id: str):
"""ปล่อย resource"""
self._semaphore.release()
async with self._lock:
if request_id in self._active_requests:
del self._active_requests[request_id]
def set_category_limit(self, category: str, limit: int):
"""กำหนด limit เฉพาะ category"""
self._request_limits[category] = limit
class ApprovalQueue:
"""Priority queue สำหรับ approval requests"""
def __init__(self):
self._queue: asyncio.PriorityQueue = asyncio.PriorityQueue()
self._processing: set[str] = set()
self._lock = asyncio.Lock()
async def enqueue(self, request: ApprovalRequest, priority: int = 5):
"""เพิ่ม request เข้าคิว"""
await self._queue.put((priority, request.request_id, request))
async def dequeue(self, timeout: float = 1.0) -> Optional[ApprovalRequest]:
"""นำ request ออกจากคิว"""
try:
_, request_id, request = await asyncio.wait_for(
self._queue.get(),
timeout=timeout
)
async with self._lock:
self._processing.add(request_id)
return request
except asyncio.TimeoutError:
return None
async def complete(self, request_id: str):
"""ทำเครื่องหมายว่า complete"""
async with self._lock:
self._processing.discard(request_id)
async def requeue(self, request: ApprovalRequest, priority: int = 10):
"""ใส่กลับเข้าคิวเมื่อต้องการ retry"""
await self.enqueue(request, priority)
class RateLimitedExecutor:
"""Executor พร้อม rate limiting"""
def __init__(self, calls_per_minute: int = 60):
self.rate_limit = calls_per_minute
self._calls: list[datetime] = []
self._lock = asyncio.Lock()
async def execute(self, func: callable, *args, **kwargs):
"""Execute พร้อม rate limit"""
async with self._lock:
now = datetime.utcnow()
# ลบ calls ที่เก่ากว่า 1 นาที
self._calls = [
t for t in self._calls
if now - t < timedelta(minutes=1)
]
# ตรวจสอบ rate limit
if len(self._calls) >= self.rate_limit:
oldest = self._calls[0]
wait_time = 60 - (now - oldest).total_seconds()
if wait_time > 0:
await asyncio.sleep(wait_time)
self._calls = self._calls[1:]
self._calls.append(now)
return await func(*args, **kwargs)
Production Orchestrator
class ApprovalOrchestrator:
"""รวมทุก component เข้าด้วยกัน"""
def __init__(self):
self.llm_client = ProductionMCPClient()
self.graph = HumanApprovalGraph(
self.llm_client,
self.llm_client.connection_pool.pools
).build_graph()
self.concurrency = ConcurrencyController(max_concurrent=50)
self.queue = ApprovalQueue()
self.rate_limiter = RateLimitedExecutor(calls_per_minute=100)
async def process_approval_request(
self,
action: str,
params: dict,
priority: int = 5
):
"""ประมวลผล approval request"""
request = ApprovalRequest(
request_id=generate_request_id(),
action=action,
params=params,
risk_level="medium"
)
# เพิ่มเข้าคิว
await self.queue.enqueue(request, priority)
# รอในคิว
while True:
queued_request = await self.queue.dequeue(timeout=5.0)
if queued_request and queued_request.request_id == request.request_id:
break
# ตรวจสอบ concurrency
can_process = await self.concurrency.acquire(
request.request_id,
category=action
)
if not can_process:
# ใส่กลับเข้าคิวด้วย priority ต่ำลง
await self.queue.requeue(request, priority=priority + 5)
return {"status": "queued", "position": "unknown"}
try:
# ประมวลผลผ่าน LangGraph
config = {"configurable": {"thread_id": request.request_id}}
initial_state = ApprovalState(request=request)
async for event in self.graph.astream(
initial_state,
config=config
):
if "interrupt" in str(event):
# รอ human approval
approval_result = await self.wait_for_approval(
request.request_id
)
# Resume graph
self.graph.update_state(
config=config,
values={"status": approval_result.status}
)
await self.queue.complete(request.request_id)
return {
"status": "completed",
"result": event
}
finally:
await self.concurrency.release(request.request_id)
async def wait_for_approval(self, request_id: str) -> ApprovalResult:
"""รอการอนุมัติจาก human"""
# นี่ควรเชื่อมต่อกับ frontend หรือ notification system
# สำหรับ demo ใช้ simple implementation
pass
def generate_request_id() -> str:
import uuid
return str(uuid.uuid4())[:16]
Cost Optimization Strategy
การใช้ DeepSeek V4 ผ่าน HolySheep AI ช่วยประหยัดได้มาก เมื่อเทียบกับ OpenAI หรือ Anthropic
เปรียบเทียบค่าใช้จ่าย (2026/MTok)
| Model | Price ($/MTok) | DeepSeek V4 Savings |
|---|---|---|
| GPT-4.1 | $8.00 | 94.75% |
| Claude Sonnet 4.5 | $15.00 | 97.2% |
| Gemini 2.5 Flash | $2.50 | 83.2% |
| DeepSeek V4 | $0.42 | — |
HolySheep มีอัตราแลกเปลี่ยน ¥1=$1 ทำให้ประหยัดได้ถึง 85%+ เมื่อเทียบกับการใช้งานผ่าน API อื่นๆ โดยมี latency เฉลี่ยน้อยกว่า 50ms และมีเครดิตฟรีเมื่อลงทะเบียน
# Cost tracking utility
class CostTracker:
"""ติดตามค่าใช้จ่ายแบบ real-time"""
# HolySheep Pricing (DeepSeek V4)
PRICING = {
"deepseek-v4": {
"input": 0.42, # $/MTok
"output": 0.42,
},
"deepseek-chat": {
"input": 0.42,
"output": 0.42,
}
}
def __init__(self):
self.total_input_tokens = 0
self.total_output_tokens = 0
self.requests_by_action: dict[str, int] = defaultdict(int)
self._lock = asyncio.Lock()
async def track_request(
self,
model: str,
input_tokens: int,
output_tokens: int,
action: str
):
async with self._lock:
self.total_input_tokens += input_tokens
self.total_output_tokens += output_tokens
self.requests_by_action[action] += 1
def calculate_cost(self) -> dict:
input_cost = (self.total_input_tokens / 1_000_000) * self.PRICING["deepseek-v4"]["input"]
output_cost = (self.total_output_tokens / 1_000_000) * self.PRICING["deepseek-v4"]["output"]
total_cost = input_cost + output_cost
return {
"input_tokens_millions": round(self.total_input_tokens / 1_000_000, 4),
"output_tokens_millions": round(self.total_output_tokens / 1_000_000, 4),
"input_cost_usd": round(input_cost, 4),
"output_cost_usd": round(output_cost, 4),
"total_cost_usd": round(total_cost, 4),
"requests_by_action": dict(self.requests_by_action),
# เปรียบเทียบกับ OpenAI
"openai_equivalent_cost": round(total_cost * (8.0 / 0.42), 2),
"savings_percentage": round((1 - 0.42/8.0) * 100, 2)
}
def estimate_batch_cost(
self,
batch_size: int,
avg_input_tokens: int = 1000,
avg_output_tokens: int = 500
) -> dict:
"""ประมาณค่าใช้จ่ายสำหรับ batch"""
total_input = batch_size * avg_input_tokens
total_output = batch_size * avg_output_tokens
return {
"batch_size": batch_size,
"estimated_input_cost_usd": round(
(total_input / 1_000_000) * 0.42, 4
),
"estimated_output_cost_usd": round(
(total_output / 1_000_000) * 0.42, 4
),
"estimated_total_usd": round(
((total_input + total_output) / 1_000_000) * 0.42, 4
)
}
Batch processing with cost optimization
async def process_batch_with_cost_control(
orchestrator: ApprovalOrchestrator,
requests: list[tuple[str, dict]],
max_concurrent: int = 10,
max_cost_per_batch: float = 10.0
):
"""ประมวลผล batch พร้อม cost control"""
tracker = CostTracker()
semaphore = asyncio.Semaphore(max_concurrent)
async def process_one(action: str, params: dict):
async with semaphore:
# ตรวจสอบ cost limit
current_cost = tracker.calculate_cost()["total_cost_usd"]
if current_cost >= max_cost_per_batch:
return {"status": "skipped", "reason": "cost_limit_reached"}
try:
result = await orchestrator.process