การสร้างระบบ RAG (Retrieval-Augmented Generation) ที่มีประสิทธิภาพไม่ใช่แค่การดึงข้อมูลมาตอบ แต่ต้องควบคุม ค่าใช้จ่าย ให้อยู่ โดยเฉพาะ ค่า Token ที่ส่งเข้า LLM บทความนี้จะสอนเทคนิคลด context length อย่างเป็นระบบ พร้อมตัวอย่างโค้ดที่ใช้งานได้จริงกับ HolySheep AI
ทำไม Context Length ถึงสำคัญ?
ทุกครั้งที่ส่ง request ไปยัง LLM ค่าใช้จ่ายจะคิดจาก input tokens ทั้งหมด ถ้าเราส่ง context 10,000 tokens แทนที่จะเป็น 2,000 tokens ค่าใช้จ่ายต่อ request จะ แพงขึ้น 5 เท่า
ตารางเปรียบเทียบบริการ AI API
| บริการ | ราคา $1/¥ | ประหยัด vs เวอร์ชันอย่างเป็นทางการ | รองรับ Model ยอดนิยม | ความเร็วเฉลี่ย |
|---|---|---|---|---|
| HolySheep AI | ¥1 = $1 | 85%+ | GPT-4.1, Claude Sonnet 4.5, Gemini 2.5 Flash, DeepSeek V3.2 | <50ms |
| API อย่างเป็นทางการ | $1 = $1 | เริ่มต้น | ราคาเต็ม | 50-200ms |
| บริการ Relay A | $1 = $0.95 | 5% | จำกัด | 100-300ms |
| บริการ Relay B | $1 = $0.90 | 10% | บางส่วน | 80-250ms |
เทคนิคที่ 1: Smart Chunking
แทนที่จะ chunk แบบ fixed size ให้ใช้ semantic chunking ที่รักษาความสัมพันธ์ของเนื้อหา
import tiktoken
from langchain.text_splitter import RecursiveCharacterTextSplitter
class SemanticChunker:
def __init__(self, model_name="cl100k_base"):
self.encoding = tiktoken.get_encoding(model_name)
self.max_tokens = 500 # เลือก context ที่เล็กพอ
def chunk_by_semantics(self, document: str) -> list[str]:
# แบ่งตาม paragraph ก่อน เพื่อรักษาความหมาย
paragraphs = document.split("\n\n")
chunks = []
current_chunk = ""
for para in paragraphs:
tokens = len(self.encoding.encode(para))
if tokens + len(self.encoding.encode(current_chunk)) <= self.max_tokens:
current_chunk += "\n\n" + para
else:
if current_chunk:
chunks.append(current_chunk.strip())
current_chunk = para
if current_chunk:
chunks.append(current_chunk.strip())
return chunks
ตัวอย่างการใช้งาน
chunker = SemanticChunker()
documents = chunker.chunk_by_semantics(long_article)
print(f"จำนวน chunks: {len(documents)}")
print(f"เฉลี่ย tokens ต่อ chunk: {sum(len(chunker.encoding.encode(c)) for c in documents) // len(documents)}")
เทคนิคที่ 2: Query Compression ก่อน Retrieval
บีบอัด query ให้กระชับก่อนดึงข้อมูล จะช่วยให้ retrieval ตรงจุดมากขึ้น
from openai import OpenAI
import os
เชื่อมต่อกับ HolySheep AI
client = OpenAI(
api_key="YOUR_HOLYSHEEP_API_KEY",
base_url="https://api.holysheep.ai/v1"
)
def compress_query(user_query: str) -> str:
"""บีบอัด query ให้กระชับและตรงประเด็น"""
response = client.chat.completions.create(
model="gpt-4.1", # $8/MTok - ใช้ model ราคาถูกสำหรับ compression
messages=[
{
"role": "system",
"content": "你是查询压缩助手。将用户的长查询压缩为3-5个关键词的简短查询,保持核心意图。"
},
{
"role": "user",
"content": f"压缩这个查询: {user_query}"
}
],
max_tokens=50,
temperature=0
)
return response.choices[0].message.content
def rag_pipeline(user_query: str, collection):
# ขั้นตอนที่ 1: บีบอัด query
compressed_query = compress_query(user_query)
# ขั้นตอนที่ 2: retrieval ด้วย query ที่บีบอัดแล้ว
results = collection.query(
query_texts=[compressed_query],
n_results=3 # ดึงแค่ 3 ผลลัพธ์ที่ดีที่สุด
)
# ขั้นตอนที่ 3: สร้าง context ที่กระชับ
context = "\n".join(results['documents'][0])
# ขั้นตอนที่ 4: ตอบด้วย LLM
response = client.chat.completions.create(
model="deepseek-v3.2", # $0.42/MTok - ประหยัดมาก!
messages=[
{"role": "system", "content": "ตอบคำถามโดยใช้ข้อมูลที่ให้มาเท่านั้น"},
{"role": "user", "content": f"คำถาม: {user_query}\n\nข้อมูล: {context}"}
],
max_tokens=500
)
return response.choices[0].message.content
ทดสอบ
answer = rag_pipeline("ฉันต้องการทราบเกี่ยวกับวิธีการลดค่าใช้จ่ายในการใช้งาน AI API โดยเฉพาะในเรื่องของการจัดการ context length ที่ส่งเข้าไปยัง LLM ว่ามีวิธีการอย่างไรบ้าง", my_collection)
print(answer)
เทคนิคที่ 3: Hybrid Search + Reranking
ใช้ keyword search ผสมกับ vector search แล้ว rerank ผลลัพธ์ เพื่อให้ได้ context ที่ตรงที่สุด
from rank_bm25 import BM25Okapi
import numpy as np
class HybridRetriever:
def __init__(self, chunks: list[str], embedding_model):
self.chunks = chunks
self.embedding_model = embedding_model
self.bm25 = BM25Okapi([c.split() for c in chunks])
self.embeddings = embedding_model.encode(chunks)
def retrieve(self, query: str, top_k: int = 5) -> list[tuple[str, float]]:
# 1. Keyword search (BM25)
tokenized_query = query.split()
bm25_scores = self.bm25.get_scores(tokenized_query)
# 2. Vector search
query_embedding = self.embedding_model.encode([query])[0]
cosine_scores = np.dot(self.embeddings, query_embedding) / (
np.linalg.norm(self.embeddings, axis=1) * np.linalg.norm(query_embedding)
)
# 3. รวม scores (weighted)
combined_scores = 0.3 * self._normalize(bm25_scores) + 0.7 * cosine_scores
# 4. เลือก top-k
top_indices = np.argsort(combined_scores)[-top_k:][::-1]
return [(self.chunks[i], combined_scores[i]) for i in top_indices]
def _normalize(self, scores: np.ndarray) -> np.ndarray:
if scores.max() == 0:
return scores
return (scores - scores.min()) / (scores.max() - scores.min())
ใช้กับ HolySheep AI embeddings
from openai import OpenAI
client = OpenAI(
api_key="YOUR_HOLYSHEEP_API_KEY",
base_url="https://api.holysheep.ai/v1"
)
เรียกใช้ embedding API
response = client.embeddings.create(
model="text-embedding-3-small",
input="ตัวอย่างข้อความ"
)
embedding = response.data[0].embedding
เทคนิคที่ 4: Caching และ Deduplication
เก็บผลลัพธ์ที่เคยคำนวณแล้วไว้ใช้ซ้ำ ลดการเรียก API ซ้ำๆ
import hashlib
import json
from functools import lru_cache
class SemanticCache:
def __init__(self, ttl_seconds: int = 3600):
self.cache = {}
self.ttl = ttl_seconds
self.timestamps = {}
def _get_key(self, text: str) -> str:
# สร้าง key จาก semantic meaning ไม่ใช่แค่ text
return hashlib.sha256(text.encode()).hexdigest()[:16]
def get(self, query: str) -> str | None:
key = self._get_key(query)
if key in self.cache:
return self.cache[key]
return None
def set(self, query: str, response: str):
key = self._get_key(query)
self.cache[key] = response
def deduplicate_context(self, contexts: list[str]) -> list[str]:
"""ลบ context ที่ซ้ำกันออก"""
seen = set()
unique = []
for ctx in contexts:
ctx_hash = hashlib.md5(ctx.encode()).hexdigest()
if ctx_hash not in seen:
seen.add(ctx_hash)
unique.append(ctx)
return unique
ใช้งาน
cache = SemanticCache()
query = "วิธีการ optimize RAG"
ตรวจสอบ cache ก่อน
cached_result = cache.get(query)
if cached_result:
print(f"พบใน cache: {cached_result}")
else:
# เรียก API และเก็บใส่ cache
result = call_llm(query)
cache.set(query, result)
print(f"ใหม่: {result}")
เทคนิคที่ 5: Dynamic Context Window
ปรับขนาด context ตามความซับซ้อนของคำถาม
def estimate_context_need(query: str) -> int:
"""ประมาณการว่าต้องการ context มากแค่ไหน"""
question_words = ["เปรียบเทียบ", "วิเคราะห์", "รายงาน", "สรุป", "อธิบาย"]
simple_words = ["ใช่ไหม", "กี่", "อะไร", "ที่ไหน"]
แหล่งข้อมูลที่เกี่ยวข้อง
บทความที่เกี่ยวข้อง