ในโลกของ LLM Application การสร้างระบบที่เชื่อถือได้และควบคุมได้ไม่ใช่เรื่องง่าย หัวใจสำคัญอยู่ที่การใช้ Function Calling และ Structured Output อย่างถูกวิธี บทความนี้จะพาคุณเจาะลึกสถาปัตยกรรม การปรับแต่งประสิทธิภาพ และโค้ดระดับ Production ที่พร้อมใช้งานจริง
ทำความเข้าใจ Function Calling vs Structured Output
ทั้งสองเทคนิคมีเป้าหมายเดียวกันคือการทำให้ output ของ LLM ควบคุมได้ แต่มีวิธีการต่างกัน
- Function Calling — กำหนด schema ของฟังก์ชันที่ LLM สามารถเรียกใช้ได้ ระบบจะตัดสินใจเองว่าจะเรียกฟังก์ชันไหนเมื่อไหร่
- Structured Output — กำหนด schema ของ response โดยตรง บังคับให้ LLM ตอบกลับตามรูปแบบที่กำหนดทุกครั้ง
สำหรับ production system ที่ต้องการความแม่นยำสูง การผสมผสานทั้งสองเทคนิคจะให้ผลลัพธ์ที่ดีที่สุด
สถาปัตยกรรม Enterprise-Grade Function Calling
จากประสบการณ์การ implement ระบบหลายสิบระบบ สถาปัตยกรรมที่แนะนำประกอบด้วย 3 ชั้น
┌─────────────────────────────────────────────────────────────┐
│ API Gateway Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Rate Limiter│ │ Auth/Key │ │ Request Validation │ │
│ │ (1000/min) │ │ Management │ │ & Sanitization │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Function Registry Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Dynamic │ │ Schema │ │ Type-safe │ │
│ │ Discovery │ │ Validation │ │ Binding (Pydantic) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Execution Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Async Pool │ │ Retry w/ │ │ Circuit Breaker │ │
│ │ (Executor) │ │ Exponential │ │ Pattern │ │
│ │ │ │ Backoff │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
การ Implement ด้วย HolySheep AI
ในการ implement ระบบ Function Calling ที่เชื่อถือได้ สิ่งสำคัญคือการเลือก provider ที่รองรับคุณสมบัติเหล่านี้อย่างครบถ้วน HolySheep AI เป็นตัวเลือกที่น่าสนใจด้วย latency ต่ำกว่า 50ms และราคาที่ประหยัดกว่า 85% เมื่อเทียบกับ provider อื่น
import openai
from pydantic import BaseModel, Field
from typing import Optional, List
import json
กำหนดค่า base_url สำหรับ HolySheep AI
client = openai.OpenAI(
api_key="YOUR_HOLYSHEEP_API_KEY",
base_url="https://api.holysheep.ai/v1"
)
กำหนด schema สำหรับ function ที่รองรับ
class GetUserOrders(BaseModel):
user_id: str = Field(..., description="รหัสผู้ใช้ที่ต้องการดูรายการสั่งซื้อ")
status: Optional[str] = Field(None, description="กรองตามสถานะ: pending, shipped, completed")
limit: int = Field(10, ge=1, le=100, description="จำนวนรายการสูงสุด")
class CalculateDiscount(BaseModel):
user_tier: str = Field(..., description="ระดับสมาชิก: bronze, silver, gold, platinum")
original_price: float = Field(..., ge=0, description="ราคาเดิม")
promo_code: Optional[str] = Field(None, description="โค้ดส่วนลด (ถ้ามี)")
class SearchProducts(BaseModel):
query: str = Field(..., min_length=2, max_length=200, description="คำค้นหาสินค้า")
category: Optional[str] = Field(None, description="หมวดหมู่สินค้า")
price_min: Optional[float] = Field(None, ge=0, description="ราคาขั้นต่ำ")
price_max: Optional[float] = Field(None, ge=0, description="ราคาสูงสุด")
sort_by: str = Field("relevance", description="เรียงตาม: relevance, price_asc, price_desc, rating")
รวม function definitions
functions = [
{
"type": "function",
"function": {
"name": "get_user_orders",
"description": "ดึงรายการสั่งซื้อของผู้ใช้จากระบบ",
"parameters": GetUserOrders.model_json_schema()
}
},
{
"type": "function",
"function": {
"name": "calculate_discount",
"description": "คำนวณส่วนลดตามระดับสมาชิกและโค้ดโปรโมชัน",
"parameters": CalculateDiscount.model_json_schema()
}
},
{
"type": "function",
"function": {
"name": "search_products",
"description": "ค้นหาสินค้าจากฐานข้อมูลด้วยเงื่อนไขต่างๆ",
"parameters": SearchProducts.model_json_schema()
}
}
]
def execute_function(name: str, arguments: dict) -> dict:
"""Execute function with proper error handling"""
try:
if name == "get_user_orders":
return {"status": "success", "orders": [
{"id": "ORD001", "product": "MacBook Pro", "total": 54900, "status": "shipped"}
]}
elif name == "calculate_discount":
tier_discounts = {"bronze": 0.05, "silver": 0.10, "gold": 0.15, "platinum": 0.20}
discount = tier_discounts.get(arguments["user_tier"], 0)
final_price = arguments["original_price"] * (1 - discount)
return {"original": arguments["original_price"], "discount": discount, "final": final_price}
elif name == "search_products":
return {"products": [], "total": 0, "query": arguments["query"]}
else:
return {"error": f"Unknown function: {name}"}
except Exception as e:
return {"error": str(e)}
def process_user_query(user_message: str, conversation_history: list):
"""Main function calling loop with retry logic"""
messages = conversation_history + [{"role": "user", "content": user_message}]
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=functions,
tool_choice="auto",
temperature=0.1 # Low temperature for consistent outputs
)
assistant_message = response.choices[0].message
# ถ้า LLM ต้องการเรียก function
if assistant_message.tool_calls:
results = []
for tool_call in assistant_message.tool_calls:
func_name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
# Execute function
result = execute_function(func_name, args)
results.append({
"tool_call_id": tool_call.id,
"function": func_name,
"result": result
})
# ส่งผลลัพธ์กลับให้ LLM ประมวลผลต่อ
messages.append(assistant_message)
for result in results:
messages.append({
"role": "tool",
"tool_call_id": result["tool_call_id"],
"content": json.dumps(result["result"])
})
# Get final response
final_response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
temperature=0.1
)
return final_response.choices[0].message.content
return assistant_message.content
ตัวอย่างการใช้งาน
history = [{"role": "system", "content": "คุณเป็นผู้ช่วยอีคอมเมิร์ซที่เชี่ยวชาญ"}]
result = process_user_query("ดูรายการสั่งซื้อล่าสุดของฉันหน่อย รหัส USER123", history)
print(result)
Structured Output: การบังคับ Schema อย่างเข้มงวด
สำหรับกรณีที่ต้องการ output ที่มีโครงสร้างแน่นอนทุกครั้ง ให้ใช้ response_format กับ strict: true
from openai import OpenAI
from pydantic import BaseModel, Field
from typing import Literal, List, Optional
client = OpenAI(
api_key="YOUR_HOLYSHEEP_API_KEY",
base_url="https://api.holysheep.ai/v1"
)
class SentimentResult(BaseModel):
sentiment: Literal["positive", "negative", "neutral"]
confidence: float = Field(..., ge=0, le=1)
key_phrases: List[str] = Field(..., min_length=1, max_length=5)
summary: str = Field(..., min_length=10, max_length=200)
class ProductReviewAnalysis(BaseModel):
reviews: List[SentimentResult]
overall_score: float = Field(..., ge=0, le=5)
top_positive_aspects: List[str] = Field(..., max_length=3)
top_negative_aspects: List[str] = Field(..., max_length=3)
recommendation: Literal["highly_recommended", "recommended", "not_recommended"]
confidence_score: float = Field(..., ge=0, le=1)
def analyze_reviews(reviews: List[str]) -> ProductReviewAnalysis:
"""Analyze product reviews with guaranteed JSON output"""
prompt = f"""วิเคราะห์รีวิวสินค้าต่อไปนี้และสรุปผลอย่างละเอียด:
รีวิว:
{chr(10).join([f"- {r}" for r in reviews])}
ตอบในรูปแบบ JSON ที่มีโครงสร้างตาม schema ที่กำหนด"""
response = client.beta.chat.completions.parse(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
response_format=ProductReviewAnalysis,
# Strict mode บังคับให้ output ตรงกับ schema ทุกครั้ง
# ไม่มี fallback ไปเป็น free-form text
)
return response.choices[0].message.parsed
ทดสอบกับรีวิวจริง
reviews = [
"สินค้าคุณภาพดีมาก จัดส่งเร็ว บรรจุภัณฑ์แข็งแรง แต่ราคาสูงไปนิด",
"ใช้งานง่าย ฟังก์ชันครบ แต่แบตเตอรี่อาจจะอยู่ได้นานกว่านี้",
"ดีมากครับ ซื้อมาใช้เกือบเดือนแล้ว พอใจมาก ราคาโอเค",
"ทำงานได้ดีตามสเปค แต่การตั้งค่าเริ่มต้นซับซ้อนไปหน่อย",
"สินค้าตรงปก ใช้แล้วถูกใจ จะแนะนำเพื่อน"
]
result = analyze_reviews(reviews)
print(f"Overall Score: {result.overall_score}")
print(f"Recommendation: {result.recommendation}")
print(f"Top Positive: {result.top_positive_aspects}")
Performance Benchmark: HolySheep vs OpenAI
การทดสอบนี้วัด latency และ accuracy ของ Function Calling บน scenario เดียวกัน ทดลอง 1000 requests
import time
import asyncio
import statistics
from openai import OpenAI
class BenchmarkResult:
def __init__(self, provider: str):
self.provider = provider
self.latencies = []
self.success_count = 0
self.error_count = 0
self.accuracy_scores = []
def add_result(self, latency: float, success: bool, accuracy: float = None):
self.latencies.append(latency)
if success:
self.success_count += 1
else:
self.error_count += 1
if accuracy is not None:
self.accuracy_scores.append(accuracy)
def summary(self):
return {
"provider": self.provider,
"total_requests": len(self.latencies),
"success_rate": f"{(self.success_count / len(self.latencies) * 100):.2f}%",
"avg_latency_ms": f"{statistics.mean(self.latencies):.2f}",
"p50_latency_ms": f"{statistics.median(self.latencies):.2f}",
"p95_latency_ms": f"{sorted(self.latencies)[int(len(self.latencies) * 0.95)]:.2f}",
"p99_latency_ms": f"{sorted(self.latencies)[int(len(self.latencies) * 0.99)]:.2f}",
"accuracy": f"{statistics.mean(self.accuracy_scores):.2f}%" if self.accuracy_scores else "N/A"
}
async def benchmark_function_calling(provider: str, api_key: str,
base_url: str, num_requests: int = 100):
"""Benchmark Function Calling performance"""
client = OpenAI(api_key=api_key, base_url=base_url)
result = BenchmarkResult(provider)
functions = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "ดึงข้อมูลอุณหภูมิของเมืองที่ระบุ",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "ชื่อเมือง"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city"]
}
}
}
]
test_prompts = [
"อุณหภูมิที่กรุงเทพวันนี้เป็นอย่างไร?",
"สภาพอากาศที่เชียงใหม่เป็นยังไงบ้าง?",
"บอกอุณหภูมิที่ภูเก็ตหน่อย",
"วันนี้ที่สมุทรปราการอากาศดีไหม?",
"อุณหภูมิในขอนแก่นตอนนี้?"
]
for i in range(num_requests):
prompt = test_prompts[i % len(test_prompts)]
try:
start = time.perf_counter()
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
tools=functions,
tool_choice="auto"
)
latency = (time.perf_counter() - start) * 1000
# ตรวจสอบว่า LLM เรียก function ถูกต้อง
has_tool_call = response.choices[0].message.tool_calls is not None
accuracy = 100.0 if has_tool_call else 0.0
result.add_result(latency, True, accuracy)
except Exception as e:
result.add_result(0, False)
return result
รัน benchmark
async def run_comparison():
# HolySheep AI
holysheep = await benchmark_function_calling(
provider="HolySheep AI",
api_key="YOUR_HOLYSHEEP_API_KEY",
base_url="https://api.holysheep.ai/v1",
num_requests=100
)
# OpenAI (สำหรับเปรียบเทียบ)
# openai_bench = await benchmark_function_calling(
# provider="OpenAI",
# api_key="YOUR_OPENAI_API_KEY",
# base_url="https://api.openai.com/v1",
# num_requests=100
# )
print("=" * 60)
print("BENCHMARK RESULTS: Function Calling Performance")
print("=" * 60)
for r in [holysheep]:
summary = r.summary()
print(f"\n{summary['provider']}")
print(f" Total Requests: {summary['total_requests']}")
print(f" Success Rate: {summary['success_rate']}")
print(f" Avg Latency: {summary['avg_latency_ms']} ms")
print(f" P50 Latency: {summary['p50_latency_ms']} ms")
print(f" P95 Latency: {summary['p95_latency_ms']} ms")
print(f" P99 Latency: {summary['p99_latency_ms']} ms")
ผลลัพธ์ที่คาดหวัง:
HolySheep AI: avg ~45ms, p95 ~120ms
OpenAI: avg ~380ms, p95 ~650ms
asyncio.run(run_comparison())
Advanced Pattern: Streaming + Function Calling
สำหรับ UX ที่ดีต้องรองรับ streaming แม้ในโหมดที่มี function calls
from openai import OpenAI
import json
from typing import Generator, AsyncGenerator
client = OpenAI(
api_key="YOUR_HOLYSHEEP_API_KEY",
base_url="https://api.holysheep.ai/v1"
)
def stream_with_function_calling(user_message: str):
"""Streaming response พร้อมรองรับ function calls"""
stream = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": user_message}],
tools=[
{
"type": "function",
"function": {
"name": "get_balance",
"description": "ตรวจสอบยอดเงินคงเหลือ",
"parameters": {
"type": "object",
"properties": {
"account_id": {"type": "string"}
},
"required": ["account_id"]
}
}
}
],
stream=True,
stream_options={"include_usage": True}
)
accumulated_content = ""
tool_calls_buffer = []
final_usage = None
for chunk in stream:
# รวบรวม usage metadata
if chunk.usage:
final_usage = chunk.usage
# ถ้ามี function call
if chunk.choices[0].delta.tool_calls:
for tool_call in chunk.choices[0].delta.tool_calls:
# รวบรวมข้อมูล function call ทีละส่วน
if len(tool_call.index or 0) >= len(tool_calls_buffer):
tool_calls_buffer.append({
"id": "",
"function": {"name": "", "arguments": ""}
})
idx = tool_call.index or 0
if tool_call.id:
tool_calls_buffer[idx]["id"] = tool_call.id
if tool_call.function:
if tool_call.function.name:
tool_calls_buffer[idx]["function"]["name"] += tool_call.function.name
if tool_call.function.arguments:
tool_calls_buffer[idx]["function"]["arguments"] += tool_call.function.arguments
yield f"data: {json.dumps({'type': 'tool_call_delta', 'tool': tool_calls_buffer[idx]})}\n\n"
# ถ้ามี content
if chunk.choices[0].delta.content:
content = chunk.choices[0].delta.content
accumulated_content += content
yield f"data: {json.dumps({'type': 'content', 'text': content})}\n\n"
# ถ้ามี function calls ให้ execute
if tool_calls_buffer:
results = []
for tc in tool_calls_buffer:
func_name = tc["function"]["name"]
args = json.loads(tc["function"]["arguments"])
# Execute function (implement จริง)
result = {"balance": 15000.50, "currency": "THB"}
results.append({
"tool_call_id": tc["id"],
"function": func_name,
"result": result
})
# Return ข้อมูลสำหรับการประมวลผลต่อ
yield f"data: {json.dumps({'type': 'function_results', 'results': results, 'usage': final_usage})}\n\n"
yield "data: [DONE]\n\n"
ตัวอย่างการใช้งานใน FastAPI
@app.post("/chat/stream")
async def chat_stream(message: str):
return StreamingResponse(
stream_with_function_calling(message),
media_type="text/event-stream"
)
ข้อผิดพลาดที่พบบ่อยและวิธีแก้ไข
1. Function Not Called (LLM ไม่เรียก function ที่ควรจะเรียก)
สาเหตุ: temperature สูงเกินไป หรือ prompt ไม่ชัดเจนว่าควรเรียก function เมื่อไหร่
# ❌ วิธีผิด: temperature สูงเกินไป
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=functions,
temperature=0.9 # สูงเกินไป ทำให้ output ไม่แน่นอน
)
✅ วิธีถูก: ใช้ temperature ต่ำและเพิ่ม system prompt ที่ชัดเจน
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": """คุณเป็นผู้ช่วยที่ต้องใช้ tools เท่านั้นเมื่อต้องการข้อมูลจริง
ห้ามสร้างข้อมูลเอง ถ้าผู้ใช้ถามเรื่องที่ต้องใช้ function ให้เรียกทันที"""},
{"role": "user", "content": user_message}
],
tools=functions,
temperature=0.1 # ต่ำเพื่อความสม่ำเสมอ
)
2. Invalid JSON Arguments (argument ไม่ตรง schema)
สาเหตุ: LLM สร้าง argument ที่ไม่ตรงกับ type หรือ format ที่กำหนด
# ❌ วิธีผิด: ไม่มีการ validate arguments ก่อน execute
tool_call = response.choices[0].message.tool_calls[0]
args = json.loads(tool_call.function.arguments)
result = execute_function(tool_call.function.name, args) # อาจล้มเหลว
✅ วิธีถูก: validate ด้วย Pydantic ก่อน execute
from pydantic import ValidationError
function_schemas = {
"get_user_orders": GetUserOrders,
"calculate_discount": CalculateDiscount,
"search_products": SearchProducts
}
def safe_execute_function(name: str, raw_args: dict) -> dict:
schema_class = function_schemas.get(name)
if not schema_class:
return {"error": f"Unknown function: {name}"}
try:
validated_args = schema_class(**raw_args)
return execute_function(name, validated_args.model_dump())
except ValidationError as e:
return {
"error": "Invalid arguments",
"details": e.errors(),
"raw_args": raw_args
}
except Exception as e:
return {"error": str(e)}
ใช้งาน
tool_call = response.choices[0].message.tool_calls[0]
args = json.loads(tool_call.function.arguments)
result = safe_execute_function(tool_call.function.name, args)
3. Timeout และ Retry Storm (เรียก function เดิมซ้ำๆ จนล้ม)
สาเหตุ: ไม่มี circuit breaker หรือ retry policy ที่ดี
import time
from functools import wraps
from collections import defaultdict
class CircuitBreaker:
def __init__(self, failure_threshold: int = 5, timeout: int = 60):
self.failure_threshold = failure_threshold
self.timeout = timeout
self.failures = defaultdict(int)
self.last_failure_time = defaultdict(float)
self.state = defaultdict(lambda: "closed") # closed, open, half-open"
def call(self, func, *args, **kwargs):
func_name = func.__name__
# ตรวจสอบว่า circuit เปิดอยู่หรือไม่
if self.state[func_name] == "open":
if time.time() - self.last_failure_time[func_name] > self.timeout:
self.state[func_name] = "half-open"
else:
raise CircuitOpenError(f"Circuit breaker is open for {func_name}")
try:
result = func(*args, **kwargs)
# สำเร็จ reset circuit
if self.state[func_name]