เดือนก่อนทีมงานหนึ่งที่คุยด้วยมาบ่นให้ฟังว่า bill LLM ของเดือนนั้นพุ่งขึ้นเกือบ 3 เท่าโดยไม่มีใครรู้ตัวจนบิลออก สาเหตุไม่ใช่ traffic เพิ่ม แต่เป็นเพราะทีม dev คนหนึ่งเปลี่ยน model จาก Haiku ไปเป็น Opus ใน service เดียวเพื่อ debug แล้วลืมเปลี่ยนกลับ ปัญหาคือไม่มีใคร monitor ระดับ per-service เลย เพราะแต่ละทีมยิง API ตรงเข้า provider ของตัวเอง ใครใช้ key ใคร ใครยิงเท่าไหร่ ไม่มี central view
นี่คือปัญหาคลาสสิกของทีมที่โตเร็วแล้วเริ่มใช้ LLM หลาย provider พร้อมกัน (Bedrock, OpenAI, Anthropic API ตรง) โดยไม่มี layer กลางคั่นระหว่าง application กับ provider เลย
ทำไม direct integration ถึงพังตอนสเกล
ตอนมี 1-2 service เรียก LLM ตรงมันโอเค แต่พอเกิน 5 service ขึ้นไป ปัญหาที่เจอซ้ำๆ คือ
- Key กระจัดกระจาย — แต่ละ service มี API key ของตัวเอง เก็บใน env var บ้าง Secrets Manager บ้าง ไม่มี pattern เดียวกัน
- ไม่มี cost visibility ระดับทีม/โปรเจกต์ — เห็นแต่บิลรวมท้ายเดือน ไม่รู้ว่าใครใช้เท่าไหร่
- Rate limit ชนกันเอง — service A กับ B ใช้ key เดียวกันของ provider เดียวกัน พอ A ยิงหนักช่วงพีค B โดน throttle ไปด้วยทั้งที่ไม่เกี่ยว
- Provider ล่มแล้วจบเลย — Bedrock throttle หรือ region มีปัญหา ไม่มี fallback ไป provider อื่นอัตโนมัติ ต้องรอ manual fix
- Format ไม่ตรงกัน — สลับจาก Anthropic API ไป Bedrock ต้องแก้ code เพราะ request/response shape ไม่เหมือนกัน
วิธีแก้ที่หลายทีมใช้ตอนนี้คือใส่ LLM gateway คั่นกลาง — จุดเดียวที่ทุก service ยิงผ่าน แล้ว gateway เป็นคนจัดการ routing, budget, retry ให้แทน
LiteLLM Proxy ทำอะไรได้บ้าง
LiteLLM Proxy คือ open-source proxy ที่รองรับ provider เกือบทุกเจ้า (OpenAI, Anthropic, Bedrock, Vertex, Azure OpenAI ฯลฯ) ผ่าน interface แบบเดียวกันคือ OpenAI-compatible API ตัว application ฝั่ง client ไม่ต้องรู้เลยว่าข้างหลังยิงไป provider ไหน แค่เรียก endpoint เดียว แล้ว proxy จัดการ routing เอง
Config พื้นฐานหน้าตาประมาณนี้ (ตัวอย่าง ไม่ใช่ค่าจริงจาก production ใดๆ):
model_list:
- model_name: gpt-tier-fast
litellm_params:
model: bedrock/anthropic.claude-3-haiku-20240307-v1:0
aws_region_name: ap-southeast-1
- model_name: gpt-tier-fast
litellm_params:
model: openai/gpt-4o-mini
api_key: os.environ/OPENAI_API_KEY
router_settings:
routing_strategy: latency-based-routing
fallbacks: [{"gpt-tier-fast": ["gpt-tier-fast-backup"]}]
general_settings:
master_key: os.environ/LITELLM_MASTER_KEY
database_url: os.environ/DATABASE_URL
สังเกตว่า model_name เดียวกัน (gpt-tier-fast) ผูกกับ 2 provider — นี่คือจุดที่ทำให้ failover เกิดขึ้นได้แบบ transparent ถ้า Bedrock throttle proxy จะ route ไป OpenAI ให้เองโดย client ไม่ต้องรู้เลย
Token budgeting ต่อทีม ไม่ใช่ต่อบิลรวม
จุดที่ช่วยเรื่อง cost control จริงๆ คือ virtual key แต่ละทีม/โปรเจกต์ขอ key จาก proxy ได้ พร้อมตั้ง budget แยกกัน
curl -X POST http://litellm-proxy:4000/key/generate \
-H "Authorization: Bearer $LITELLM_MASTER_KEY" \
-d '{
"models": ["gpt-tier-fast"],
"max_budget": 50,
"budget_duration": "30d",
"team_id": "rag-search-team"
}'
ทีมไหนเกิน budget key จะถูก block อัตโนมัติ ไม่ต้องรอ finance มาเคาะไหล่ตอนสิ้นเดือน แล้วก็มี dashboard ในตัวให้ดูว่า spend ต่อ team ต่อ model เป็นยังไง อันนี้คือสิ่งที่แก้เคส "เปลี่ยน model แล้วลืมเปลี่ยนกลับ" ได้ตรงจุด เพราะ budget alert จะเด้งก่อนบิลจะบาน ไม่ใช่รู้ตัวหลังบิลออกแล้ว
Failover ที่ต้องคิดมากกว่า retry เฉยๆ
Retry แบบ naive (ยิงซ้ำเมื่อ error) อันตรายกว่าที่คิด โดยเฉพาะถ้า error เป็น rate limit จริง retry รัวๆ จะยิ่งซ้ำเติมปัญหา สิ่งที่ควรตั้งคือ
- Exponential backoff สำหรับ retry ไป provider เดิม (2-3 ครั้งพอ)
- Fallback ไป model/provider สำรอง ถ้า retry เดิมไม่ผ่าน ไม่ใช่ retry ไม่จำกัดรอบ
- Cooldown period — ถ้า provider หนึ่งพัง ให้ proxy จำไว้สักพัก (เช่น 60 วินาที) ก่อนลองยิงกลับไปใหม่ ไม่ใช่ลองทุก request
- Timeout ที่สมเหตุสมผลต่อ use case — chat แบบ interactive timeout สั้นได้ (10-15s) แต่ถ้าเป็น batch summarization ปล่อยยาวกว่านั้นได้
เคยเจอเคสจริงที่ setup fallback ไว้แล้ว แต่ timeout ตั้งไว้สั้นเกินไป (5 วินาที) ผลคือ request ที่ปกติจะสำเร็จถ้ารอสัก 8 วินาที กลับ fallback ไป provider สำรองทุกครั้งเพราะ timeout ก่อน กลายเป็นจ่ายแพงขึ้นโดยไม่จำเป็นเพราะ provider สำรองแพงกว่า บทเรียนคือ tuning timeout สำคัญพอๆ กับ setup fallback logic เอง
RAG กับ proxy — จุดที่มักมองข้าม
ถ้าใช้ RAG pipeline ต้องแยกให้ชัดระหว่าง embedding call กับ generation call เพราะสอง workload นี้ pattern การใช้ต่างกันมาก embedding มัก burst หนักตอน ingest เอกสารใหม่ ส่วน generation กระจายตาม user traffic ถ้าใช้ virtual key เดียวกันสำหรับทั้งคู่ budget/rate limit จะปนกัน แนะนำแยก key ต่อ workload type ไปเลย จะได้ตั้ง rate limit ที่เหมาะกับแต่ละแบบจริงๆ ไม่ใช่เผื่อไว้กว้างๆ จนคุมไม่ได้
Checklist ก่อนเอาเข้า production
- แยก virtual key ต่อทีม/โปรเจกต์ ไม่ใช้ master key ยิงตรงจาก application
- ตั้ง
max_budget+budget_durationให้ทุก key ตั้งแต่วันแรก ไม่ใช่ค่อยตั้งทีหลังตอนบิลบาน - Map model tier (fast/cheap vs premium) แทนการ hardcode ชื่อ model เฉพาะเจาะจงใน application code
- ตั้ง fallback chain อย่างน้อย 1 ชั้น ต่อ model tier ที่สำคัญ
- Tune timeout แยกตาม use case (interactive vs batch) อย่าใช้ค่า default เดียวกันทั้งระบบ
- แยก key ระหว่าง embedding workload กับ generation workload
- เปิด logging ระดับ request ไปที่ observability stack ที่มีอยู่แล้ว (อย่าผูกกับ dashboard ของ proxy อย่างเดียว เผื่อวันหนึ่งย้ายออกจาก LiteLLM)
- ทดสอบ failover จริงด้วยการปิด provider หลักชั่วคราวใน staging ก่อนเชื่อว่ามันทำงาน อย่าเชื่อแค่ตาม config
ส่วนที่คนมักข้ามคือข้อสุดท้าย — เขียน fallback config ไว้แล้วไม่เคยทดสอบจริงว่ามัน trigger ตอนไหน พอวันที่ provider หลักล่มจริงถึงจะรู้ว่า config ผิดจุดไหน ซึ่งเป็นเวลาที่แย่ที่สุดที่จะเพิ่งมาเจอ bug
Gateway แบบนี้ไม่ได้แก้ทุกปัญหา ถ้าทีมเล็กมากมี service เดียวยิง provider เดียว อาจจะ overkill เกินไปด้วยซ้ำ แต่พอเริ่มมีมากกว่า 1 provider หรือมากกว่า 3-4 service ที่ยิง LLM เป็นเรื่องปกติ การมี layer กลางแบบนี้ช่วยประหยัดเวลา debug และเงินได้เยอะกว่าที่คิดตอนแรก

Top comments (0)