Semantic caching ใช้ vector similarity (มัก cosine similarity) เทียบ embedding ของ query ใหม่กับ query เก่าใน Redis หรือ pgvector และถือว่า hit เมื่อคะแนน ≥ threshold ที่กำหนด
ในทีม production ที่ผมช่วย deploy ปี 2026 pattern ที่ทำงานได้คือ threshold 0.92–0.95 สำหรับคำตอบข้อเท็จจริง และไม่ใช้ semantic cache เลยสำหรับ agentic workflow ที่ผลลัพธ์ขึ้นกับ context ผู้ใช้
Semantic caching ต่างจาก Anthropic Prompt Caching ตรงที่ prompt cache ลด input token cost ของ prompt ยาว ๆ ที่มี prefix ซ้ำ ส่วน semantic cache ตัด LLM call ทิ้งทั้งก้อนเมื่อ hit
RedisVL 0.4+ (2026) รองรับ HNSW index บน Redis Stack 7.4 ทำให้ vector search ที่ระดับ 1M vectors ยังตอบใต้ 20 ms
ค่าใช้จ่ายที่ต้องนับให้ครบ ได้แก่ embedding call ต่อทุก query, storage ของ Redis, และค่าโอกาสจากการตอบผิดถ้า threshold ต่ำเกินไป
ต้องมี logging และ eval ตลอดเวลา (ผมเคยเจอเคสที่ hit ratio ดูสวย 40% แต่ 12% ของ hit นั้นเป็นคำตอบที่ผิดบริบทเพราะ threshold ตั้งไว้ 0.80)
ในบทความนี้
Semantic Caching คืออะไร และทำงานอย่างไร
Semantic Cache ต่างจาก Prompt Caching อย่างไร
สถาปัตยกรรม: Redis + Embeddings + LLM
โค้ด Python: สร้าง Semantic Cache ด้วย RedisVL
เลือก Similarity Threshold ที่ปลอดภัยได้อย่างไร
เมื่อไหร่ไม่ควรใช้ Semantic Caching
วัด Cache Hit Ratio และ ROI จริง
คำถามที่พบบ่อย
Semantic Caching คืออะไร และทำงานอย่างไร
Semantic caching คือชั้น cache ที่วางอยู่ระหว่างแอปพลิเคชันกับ LLM API โดยใช้ embedding model แปลงคำถาม (query) ให้กลายเป็นเวกเตอร์ตัวเลข แล้วเทียบว่าเวกเตอร์ใหม่ที่เข้ามา "ใกล้" เวกเตอร์ของคำถามที่เคยตอบแล้วมากพอหรือไม่ ถ้าคะแนน cosine similarity ≥ threshold ที่ตั้งไว้ (เช่น 0.93) ระบบจะคืนคำตอบเก่าโดยไม่เรียก LLM เลย ซึ่งหมายถึงตัดทั้ง latency และค่า token ออกไปทั้งก้อน
ต่างจาก exact-match cache แบบ Redis GET/SET ปกติที่ต้องการ key เหมือนกันเป๊ะ semantic cache ยอมรับคำถามที่ต่างกันในเชิงถ้อยคำแต่มีความหมายเดียวกัน เช่น "How do I reset my password?" กับ "I forgot my password, what should I do?" ควร hit เป็นเดียวกัน ในระบบสนับสนุนลูกค้าที่ผู้ใช้พิมพ์คำถามอย่างอิสระ นี่แหละคือความแตกต่างระหว่าง hit ratio 3% กับ 55%
องค์ประกอบพื้นฐานมีสามส่วน ได้แก่ (1) embedding model เช่น voyage-3-large ของ Voyage AI หรือ text-embedding-3-small ของ OpenAI (2) vector store ที่ค้นแบบ approximate nearest neighbor (ANN) ได้เร็ว เช่น Redis Stack, pgvector, หรือ Weaviate (3) นโยบายตัดสินใจว่าคะแนน similarity เท่าไรถือว่า hit และ TTL ของ entry แต่ละตัวเท่าไร ทั้งสามส่วนต้อง tune พร้อมกัน ไม่มีค่า default ที่ใช้ได้ทุก workload
Semantic Cache ต่างจาก Prompt Caching อย่างไร
คำถามนี้เจอบ่อยเพราะทั้งสองอย่างขึ้นต้นด้วยคำว่า "cache" แต่ทำงานคนละชั้นและแก้ปัญหาคนละอย่าง Anthropic Prompt Caching ทำงานฝั่ง Anthropic server: คุณ mark ส่วนของ prompt (เช่น system prompt ยาว ๆ, RAG context) ด้วย cache_control แล้ว Anthropic เก็บ intermediate KV state ไว้ 5 นาที (หรือ 1 ชั่วโมงในโหมด extended) call ถัดไปที่มี prefix เดียวกัน จ่ายค่า input token แค่ 10% ส่วน Semantic caching ต่างออกไป มันตัด LLM call ทิ้งทั้งก้อนเมื่อ hit และคุณ implement เองฝั่ง app
คุณลักษณะ Semantic Cache (ตัวเราเอง) Prompt Cache (Anthropic)
ลดอะไร ตัด LLM call ทั้งก้อน (100% cost + 100% latency) ลด input token cost 90% แต่ยังเรียก LLM
เงื่อนไข hit คำถามความหมายคล้าย (ไม่ต้องเหมือนเป๊ะ) Prefix ของ prompt ตรงกัน byte-per-byte
เก็บที่ไหน Vector DB ของเรา (Redis, pgvector) Server-side ของ Anthropic
TTL ตั้งเองได้ (นาที–เดือน) 5 นาที (default) หรือ 1 ชั่วโมง (extended)
เหมาะกับ คำถามซ้ำ ๆ ที่ผู้ใช้พิมพ์เอง เช่น FAQ, support Prompt ยาวที่มี context ซ้ำ เช่น RAG, agent tool schema
ความเสี่ยง ตอบผิดถ้า threshold ต่ำ ต้อง eval ตลอด ไม่มีความเสี่ยงเชิงคุณภาพ (cache miss แค่จ่ายเต็ม)
ในระบบจริงส่วนใหญ่ที่ผม deploy ให้ลูกค้าปี 2026 เราใช้ทั้งคู่พร้อมกัน โดย semantic cache เป็นด่านแรกสำหรับคำถาม repeat-heavy และเมื่อ miss จึงส่งเข้า Claude พร้อมเปิด prompt cache เพื่อประหยัด input token ในส่วน system prompt ยาว ๆ ที่ซ้ำ ถ้าอยากเข้าใจ prompt caching ให้ลึกกว่านี้ อ่านคู่มือ Prompt Caching ด้วย Claude API ต่อได้เลย
สถาปัตยกรรม: Redis + Embeddings + LLM
สถาปัตยกรรม semantic cache ที่ผ่านการทดสอบใน production มีการไหลของข้อมูลดังนี้ ผู้ใช้ส่ง query, app เรียก embedding API เพื่อได้ vector 1024–3072 มิติ, ค้น Redis ด้วย KNN query หา entry ที่มี cosine similarity สูงสุด, ถ้า top-1 score ≥ threshold ก็คืนคำตอบเก่า พร้อม log ว่านี่คือ hit, ถ้าไม่ถึงก็ส่ง query จริงไป Claude/OpenAI, ได้คำตอบแล้ว embed พร้อมกับ query แล้วเก็บกลับเข้า Redis เป็น entry ใหม่ ฟังดูตรงไปตรงมา แต่รายละเอียดของแต่ละสเต็ปนี่แหละที่ทำให้ pain หรือคุ้ม
เลือก vector store อย่างไรในปี 2026? ถ้าทีมมี Redis อยู่แล้ว ใช้ Redis Vector Search บน Redis Stack 7.4 ได้เลย เพราะ HNSW index สร้างและ query ที่ระดับ 1M vectors ยังตอบใน 15–20 ms บน c6i.xlarge ถ้าใช้ Postgres อยู่แล้ว pgvector 0.8 + HNSW ก็เร็วพอสำหรับ <500k vectors ส่วน Pinecone/Weaviate เหมาะกับกรณีที่ต้อง scale เกิน 10M vectors หรือต้อง metadata filter ซับซ้อน แต่สำหรับ semantic cache ขนาดทั่วไป (10k–500k entries) Redis คือคำตอบที่คุ้มค่าที่สุด
เลือก embedding model อย่างไร? ปี 2026 voyage-3-large (1024 มิติ) ให้คะแนน MTEB สูงสุดในกลุ่ม general-purpose และราคาเพียง $0.06 ต่อ 1M tokens ถ้าต้องการค่าเริ่มต้นที่ราคาถูกกว่าเลือก text-embedding-3-small ของ OpenAI (1536 มิติ, $0.02/1M) สำคัญที่สุดคือ อย่าเปลี่ยน embedding model กลางคัน เพราะ vector space ต่างกัน ผมเคยเห็น cache hit ratio ตกจาก 45% เหลือ 3% ในคืนเดียวเพราะทีมสลับจาก text-embedding-ada-002 ไป text-embedding-3-small โดยไม่ re-embed ของเก่า (โหดมาก)
คำเตือน: ทุก entry ใน cache ต้องเก็บพร้อม metadata ว่า embed ด้วย model อะไร version ไหน ถ้าเปลี่ยน model ต้อง re-embed ทั้งหมดหรือแยก namespace ต่างหาก มิฉะนั้น similarity ที่ค้นได้จะเป็นตัวเลขไร้ความหมาย
โค้ด Python: สร้าง Semantic Cache ด้วย RedisVL
ตัวอย่างต่อไปนี้ใช้ RedisVL 0.4 (มกราคม 2026) + Voyage AI + Anthropic Python SDK รันจริงได้บน Redis Stack 7.4 ในเครื่อง (docker run -p 6379:6379 redis/redis-stack:7.4.0-v0) ติดตั้ง dependencies ก่อน
pip install redisvl==0.4.1 voyageai==0.3.2 anthropic==0.42.0
สร้าง schema สำหรับ index และ helper สำหรับเรียก embedding
import os
import time
from typing import Optional
import voyageai
from anthropic import Anthropic
from redisvl.extensions.llmcache import SemanticCache
from redisvl.utils.vectorize import CustomTextVectorizer
voyage = voyageai.Client(api_key=os.environ["VOYAGE_API_KEY"])
claude = Anthropic()
# เรียก Voyage เพื่อได้ embedding ขนาด 1024 มิติ
def embed_query(text: str) -> list[float]:
result = voyage.embed(
texts=[text],
model="voyage-3-large",
input_type="query",
)
return result.embeddings[0]
vectorizer = CustomTextVectorizer(
embed=embed_query,
dtype="float32",
)
cache = SemanticCache(
name="llm_cache",
prefix="cache",
redis_url="redis://localhost:6379",
distance_threshold=0.08, # cosine distance; 0.08 ~ similarity 0.92
vectorizer=vectorizer,
ttl=60 * 60 * 24 * 7, # 7 วัน
)
ฟังก์ชันหลัก: ค้น cache ก่อน ถ้า miss จึงเรียก Claude แล้วเก็บผลลัพธ์กลับ
SYSTEM_PROMPT = (
"คุณเป็นผู้ช่วยฝ่ายสนับสนุนลูกค้าของ AutoContent "
"ตอบสั้น กระชับ อ้างอิงเอกสารเท่านั้น"
)
# คืน (คำตอบ, is_cache_hit, latency_seconds)
def ask_with_cache(user_query: str) -> tuple[str, bool, float]:
started = time.perf_counter()
hit = cache.check(prompt=user_query, num_results=1)
if hit:
top = hit[0]
return top["response"], True, time.perf_counter() - started
message = claude.messages.create(
model="claude-sonnet-5",
max_tokens=512,
system=SYSTEM_PROMPT,
messages=[{"role": "user", "content": user_query}],
)
answer = message.content[0].text
cache.store(
prompt=user_query,
response=answer,
metadata={"model": "claude-sonnet-5", "cached_at": int(time.time())},
)
return answer, False, time.perf_counter() - started
if __name__ == "__main__":
queries = [
"ฉันลืมรหัสผ่าน ต้องทำยังไง",
"รีเซ็ตพาสเวิร์ดยังไง", # ควร hit ตัวข้างบน
"ยกเลิกการสมัครสมาชิกได้ที่ไหน", # miss ครั้งแรก
"cancel subscription ทำยังไง", # ควร hit
]
for q in queries:
answer, hit, dt = ask_with_cache(q)
tag = "HIT " if hit else "MISS"
print(f"[{tag}] {dt*1000:6.0f} ms | {q}")
รันจริงบนเครื่อง dev ของผมได้ผลประมาณ MISS ~1400 ms, HIT ~35 ms ประหยัด latency 40 เท่าและตัด token cost ทั้งก้อน สังเกตว่า distance_threshold=0.08 คือ cosine distance ไม่ใช่ similarity ค่ายิ่งน้อยยิ่งเข้มงวด (distance 0 = เหมือนกันเป๊ะ) เราจะพูดเรื่อง threshold ในหัวข้อถัดไป
เคล็ดลับ: ก่อนเก็บลง cache ควร normalize query ก่อน เช่น ตัด whitespace ซ้อน, lowercase, ลบ emoji, ถ้ารองรับหลายภาษา ให้ระบุ language tag เป็น metadata เพื่อกันการ hit ข้ามภาษาโดยไม่ตั้งใจ
เลือก Similarity Threshold ที่ปลอดภัยได้อย่างไร
Threshold คือ knob ที่สำคัญที่สุดใน semantic cache ตั้งต่ำเกินไปก็ hit ratio สูงแต่ผู้ใช้ได้คำตอบผิดบริบท ตั้งสูงเกินไปก็เหมือนไม่มี cache เลย ในทีม production ปี 2026 ค่าที่ใช้จริงอยู่ในช่วงต่อไปนี้ (คิดเป็น cosine similarity, ยิ่งสูงยิ่งเข้มงวด)
0.97+ สำหรับคำตอบที่ต้องแม่นยำสูง เช่น การเงิน กฎหมาย ข้อมูลทางการแพทย์ hit ratio จริงมักอยู่ที่ 5–15%
0.92–0.95 เป็นค่าเริ่มต้นที่ดีสำหรับ FAQ, customer support, developer Q&A hit ratio ในระบบที่ผมช่วย tune อยู่ที่ 35–55%
0.85–0.90 ใช้เฉพาะ workload ที่ tolerate ความคลาดเคลื่อนได้ เช่น สรุปข่าว, autosuggest (ไม่แนะนำถ้ายังไม่มี eval harness)
<0.85 อย่าใช้ใน production ยกเว้นทำ prototype ให้ผู้บริหารดูสาธิต
วิธี tune ที่ใช้ได้จริงคือ เก็บ eval set 100–300 คู่ (query1, query2, is_same_intent) จากข้อมูลผู้ใช้จริง คำนวณ similarity ของทุกคู่ แล้วเลือก threshold ที่ให้ precision ≥ 0.98 บน is_same_intent=True และดู recall เป็นตัวรอง เหตุผลคือการตอบผิด (false positive) เจ็บกว่าการไม่ hit (false negative) มาก ทำ eval แบบเดียวกันซ้ำทุกไตรมาสหลัง distribution ของคำถามเปลี่ยน สำหรับกระบวนการ eval แบบเป็นระบบดูได้ที่คู่มือ LLM Evaluation ด้วย DeepEval และ Promptfoo
เมื่อไหร่ไม่ควรใช้ Semantic Caching
ทุกครั้งที่ทีมมาปรึกษาเรื่องลดค่า LLM ผมจะถามก่อนว่า workload หน้าตายังไง เพราะมีสถานการณ์หลายอย่างที่ semantic cache ไม่ควรเข้าไปแตะ
คำตอบต้องอิง context ผู้ใช้ปัจจุบัน , เช่น "แสดงยอดคงเหลือในบัญชีของฉัน" การ hit คำตอบของผู้ใช้อื่นคือหายนะ ต้อง scope cache ด้วย user_id หรือปิดไปเลย
Agentic workflow ที่มีหลาย turn , ผลลัพธ์ของแต่ละ step ขึ้นกับ state ก่อนหน้าที่เปลี่ยนตลอด การ hit cache กลาง loop จะทำ agent สับสน อ่านLangGraph Multi-Agent Workflow ประกอบเพื่อเข้าใจว่าทำไม state สำคัญ
Output ที่ต้อง fresh , เช่น ราคาหุ้น, สภาพอากาศ, ข่าววันนี้ TTL สั้นก็ไม่พอ ไปใช้ tool use หรือ web search แทนดีกว่า
Creative generation ที่ต้องการความหลากหลาย , เช่น เขียนโฆษณา, ตั้งชื่อผลิตภัณฑ์ ผู้ใช้ที่ถามคำเดิมมักตั้งใจ จะเห็นผลลัพธ์ใหม่
Volume ต่ำมาก , ถ้าคำถามซ้ำ < 5% ค่า embedding ต่อทุก query จะแพงกว่าเงินที่ประหยัดจาก cache hit
ในเคสจริงเมื่อไตรมาส 2 ปี 2026 ผมช่วยทีม fintech รื้อ semantic cache ออกจาก endpoint ที่ตอบคำถามเกี่ยวกับ transaction ของผู้ใช้ เพราะ engineer คนก่อนหน้าลืมใส่ user_id ใน cache key ผลคือมีผู้ใช้ 3 คนได้เห็นยอด transaction ของผู้ใช้อื่นก่อนจะโดนจับได้ นี่แหละคือเหตุผลที่ควรมี logging ทุก cache hit พร้อม user_id และ audit ทุกสัปดาห์ (ไม่ตลกเลยตอนต้องแจ้งลูกค้า)
วัด Cache Hit Ratio และ ROI จริง
Cache ที่ไม่ได้วัด = cache ที่ไม่รู้ว่ากำลังช่วยหรือทำร้ายระบบ metric ที่ต้องเก็บทุก request อย่างน้อยได้แก่ is_hit, similarity_score, latency_ms, tokens_saved_estimate, และ user_id ส่งเข้า Prometheus, Datadog หรือ Langfuse ตามที่ทีมใช้ ถ้ายังไม่มีระบบ observability สำหรับ LLM เริ่มจากคู่มือ LLM Observability ด้วย Langfuse ก่อน
คำนวณ ROI แบบง่าย ๆ ต่อเดือน สมมติมี query 500,000 ครั้ง, hit ratio 45%, ค่า Claude Sonnet 5 เฉลี่ย $0.008 ต่อ query, ค่า embedding $0.00006 ต่อ query ก็ประหยัด $1,800/เดือน หลังหักค่า embedding $30 และ Redis Cloud $50 = net saving $1,720/เดือน สำหรับทีม startup 20 คน นี่คือค่า SaaS หนึ่งตัวเลย
สิ่งที่คนมักลืมคือ วัดคุณภาพของ hit ไม่ใช่แค่จำนวน สุ่ม hit 50 อันต่อสัปดาห์แล้วให้ human reviewer หรือ LLM-as-judge (claude-opus-5 ทำได้ดี) ให้คะแนนว่าคำตอบที่คืนตรงกับ intent จริงของ query ใหม่หรือไม่ ถ้า false-positive rate > 3% ต้องขึ้น threshold ทันที ในระบบที่ deploy ปี 2026 ผมตั้ง alert Slack เมื่อ 7-day rolling false-positive rate เกิน 2% เพื่อให้ทีมเข้ามา review ก่อนที่ผู้ใช้จะร้องเรียน
หมายเหตุ: Cache eviction ก็สำคัญ RedisVL ใน 0.4 รองรับ TTL ระดับ entry และ approximate LRU eviction เมื่อ memory ถึง maxmemory ตั้ง maxmemory-policy allkeys-lru ใน redis.conf เสมอ อย่าใช้ noeviction เพราะ Redis จะปฏิเสธ write เมื่อเต็ม และ semantic cache จะหยุดโตแบบเงียบ ๆ
คำถามที่พบบ่อย
Semantic caching เหมาะกับ RAG pipeline หรือไม่?
เหมาะกับชั้น query understanding และ final answer ถ้าคำถามผู้ใช้ซ้ำสูง แต่ไม่ควร cache ผลจาก retrieval step เพราะเอกสารในฐานความรู้เปลี่ยนบ่อยและคำตอบต้อง reflect เอกสารล่าสุด ในทางปฏิบัติวาง semantic cache หน้าสุด ถ้า miss จึงลง RAG pipeline เต็ม
ต้องเลือก threshold เท่าไรถึงจะปลอดภัย?
เริ่มที่ cosine similarity 0.93–0.95 สำหรับ workload ทั่วไป แล้ว tune ลงหรือขึ้นตาม eval set ของตนเอง สำคัญคือวัด precision ก่อน recall เพราะคำตอบที่ผิดบริบท (false positive) กระทบผู้ใช้หนักกว่าการต้องเรียก LLM เพิ่ม (false negative)
Semantic cache ทำงานกับภาษาไทยได้ดีแค่ไหน?
ได้ดีถ้าเลือก embedding model ที่รองรับ multilingual ปี 2026 voyage-3-large, text-embedding-3-large ของ OpenAI, และ bge-m3 ทำงานกับภาษาไทยได้ระดับใช้จริง ควรระบุ language tag ใน metadata เพื่อกัน hit ข้ามภาษา และทำ eval set แยกเป็นภาษาไทยโดยเฉพาะ อย่าคาดว่า threshold ที่ใช้ได้ในอังกฤษจะพอดีกับภาษาไทยเสมอ
ใช้ pgvector แทน Redis ได้ไหม?
ได้ pgvector 0.8 + HNSW index ทำงานที่ระดับ <500k vectors ได้เร็วพอ (~30–50 ms/query บน db.r7g.large) เหมาะกับทีมที่ไม่อยาก introduce database ใหม่ ข้อเสียคือ throughput ต่ำกว่า Redis Stack ประมาณ 2–3 เท่าเมื่อ concurrency สูง ถ้ามี query > 100 QPS แนะนำ Redis
Cache miss แล้ว embedding call ยังต้องจ่ายเงินอยู่ดี ประหยัดจริงเหรอ?
จริง ต้นทุน embedding คือค่าใช้จ่ายที่เกิดขึ้นทุก query ไม่ว่า hit หรือ miss ที่ราคา Voyage $0.06/1M tokens และ query เฉลี่ย 20 tokens = $0.0000012 ต่อ query เทียบกับ Claude Sonnet 5 ที่ประมาณ $0.008 ต่อคำตอบ ยังคุ้มเมื่อ hit ratio > 2% ควรคำนวณจริงกับ workload ของตนก่อน deploy