การทำดัชนี: การสร้างและจัดเก็บส่วนข้อความ
สร้างเวกเตอร์ฝังตัวของแต่ละส่วนด้วย API เวกเตอร์ฝังตัวของ OpenAI และเพิ่มหรืออัปเดตเวกเตอร์พร้อมข้อมูลกำกับลงในแหล่งจัดเก็บเวกเตอร์ เพื่อสร้างดัชนีเอกสารที่ค้นหาได้
การทำดัชนี: การสร้างและจัดเก็บส่วนข้อความ เป็นบทเรียน AI Engineering Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน AI Engineering Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส AI Engineering Academy มีบทเรียนทั้งหมด 4 บทเรียน
ขั้นตอนการสร้างดัชนี: ภาพรวม
หลังจากโหลดและแบ่งเอกสารเป็นชิ้นข้อความแล้ว คุณจะเข้าสู่ ขั้นตอนการสร้างดัชนี ซึ่งคือการแปลงชิ้นข้อความเป็นเวกเตอร์ฝังและจัดเก็บไว้ในฐานข้อมูลเวกเตอร์ที่ค้นหาได้ นี่เป็นขั้นตอนออฟไลน์สุดท้ายก่อนที่จะเริ่มตอบคำถาม คุณภาพของเวกเตอร์ฝัง รวมถึงประสิทธิภาพของกลยุทธ์การจัดเก็บและสร้างดัชนี จะเป็นตัวกำหนดโดยตรงว่าระบบ RAG ของคุณตอบคำถามได้รวดเร็วและแม่นยำเพียงใด
การสร้างเวกเตอร์ฝังผ่าน OpenAI API
แนวทางที่ใช้กันมากที่สุดคือเรียก API สำหรับเวกเตอร์ฝังของ OpenAI พร้อมข้อความของชิ้นข้อความ โมเดล text-embedding-3-small สร้างเวกเตอร์ขนาด 1536 มิติ และมีค่าใช้จ่าย 0.02 ดอลลาร์ต่อหนึ่งล้านโทเค็น ซึ่งถูกมากสำหรับงานส่วนใหญ่ ส่งข้อความหลายรายการในการเรียก API ครั้งเดียว (สูงสุด 2048 อินพุต) เพื่อเพิ่มอัตราการประมวลผลให้สูงสุด ผลลัพธ์จะมีเวกเตอร์ฝังหนึ่งรายการต่อข้อความอินพุต โดยเรียงตามลำดับเดียวกัน
from openai import OpenAI
client = OpenAI()
def embed_batch(texts, model='text-embedding-3-small'):
response = client.embeddings.create(
model=model,
input=texts # up to 2048 texts per call
)
return [item.embedding for item in response.data]
# Embed one batch of 100 chunk texts
texts = [chunk['text'] for chunk in chunks[:100]]
vectors = embed_batch(texts)
print(f'Embedding dimension: {len(vectors[0])}')
print(f'Embedded {len(vectors)} chunks')การประมวลผลเป็นชุดเพื่อประสิทธิภาพ
เมื่อสร้างดัชนีชิ้นข้อความหลายพันชิ้น ประสิทธิภาพเป็นสิ่งสำคัญ ให้ประมวลผลชิ้นข้อความเป็น ชุดละ 100-500 ชิ้น เพื่อสร้างสมดุลระหว่างอัตราการประมวลผลกับการใช้หน่วยความจำ ติดตามตำแหน่งการประมวลผลไว้ เพื่อให้สามารถทำงานต่อหลังเกิดข้อผิดพลาดได้โดยไม่ต้องสร้างเวกเตอร์ฝังซ้ำสำหรับชิ้นข้อความที่ประมวลผลไปแล้ว บันทึกความคืบหน้าเป็นระยะ สำหรับชิ้นข้อความ 100,000 ชิ้น โดยแบ่งชุดละ 500 ชิ้น จะมีการเรียก API 200 ครั้ง ซึ่งโดยทั่วไปเสร็จสิ้นภายในไม่กี่นาที
def embed_all_chunks(chunks, batch_size=200):
embedded = []
total = len(chunks)
for i in range(0, total, batch_size):
batch = chunks[i:i+batch_size]
texts = [c['text'] for c in batch]
vectors = embed_batch(texts)
for chunk, vector in zip(batch, vectors):
embedded.append({
**chunk,
'embedding': vector
})
if (i // batch_size) % 10 == 0:
print(f'Progress: {min(i+batch_size, total)}/{total}')
return embeddedการจัดการขีดจำกัดอัตราระหว่างสร้างดัชนี
API สำหรับเวกเตอร์ฝังของ OpenAI มีขีดจำกัดอัตราที่วัดเป็นโทเค็นต่อนาที (TPM) งานสร้างดัชนีขนาดใหญ่อาจชนขีดจำกัดเหล่านี้และได้รับ RateLimitError ให้ใช้ การถอยกลับแบบทวีคูณพร้อมความแปรผันแบบสุ่ม เมื่อเกิดข้อผิดพลาดจากขีดจำกัดอัตรา ให้รอเป็นช่วงเวลาสุ่มสั้น ๆ ก่อนลองใหม่ และเพิ่มเวลารอเป็นสองเท่าในการล้มเหลวครั้งถัดไป วิธีนี้ช่วยกระจายการลองใหม่และป้องกันไม่ให้ผู้ปฏิบัติงานแบบขนานทั้งหมดส่งคำขอถี่เกินไปยัง API ในเวลาเดียวกัน
import time
import random
from openai import RateLimitError
def embed_batch_with_retry(texts, max_retries=5):
for attempt in range(max_retries):
try:
return embed_batch(texts)
except RateLimitError:
if attempt == max_retries - 1:
raise
wait = (2 ** attempt) + random.uniform(0, 1)
print(f'Rate limited. Waiting {wait:.1f}s...')
time.sleep(wait)
return []การเพิ่มเวกเตอร์เข้าสู่ Pinecone
หลังจากสร้างเวกเตอร์ฝังตัวแล้ว ให้อัปโหลดหรือปรับปรุงเวกเตอร์เหล่านั้นในคลังเวกเตอร์ การ อัปโหลดหรือปรับปรุง หมายถึงการแทรกเวกเตอร์ใหม่ หรือปรับปรุงเวกเตอร์เดิมหากมี ID เดียวกันอยู่แล้ว ซึ่งออกแบบมาให้ทำซ้ำได้โดยไม่ก่อให้เกิดผลลัพธ์ซ้ำซ้อน ใน Pinecone ระเบียนแต่ละรายการที่อัปโหลดหรือปรับปรุงจะประกอบด้วย ID ของเวกเตอร์ ค่าการฝังตัว และพจนานุกรมข้อมูลเมตาของฟิลด์ที่คุณต้องการใช้กรองหรือแสดงภายหลัง เพื่อประสิทธิภาพสูงสุด ควรอัปโหลดหรือปรับปรุงครั้งละไม่เกิน 100 ระเบียนต่อการเรียกหนึ่งครั้ง
import pinecone
pc = pinecone.Pinecone(api_key='YOUR_KEY')
index = pc.Index('rag-index')
def upsert_to_pinecone(embedded_chunks, batch_size=100):
for i in range(0, len(embedded_chunks), batch_size):
batch = embedded_chunks[i:i+batch_size]
vectors = [
(
chunk['id'],
chunk['embedding'],
{
'text': chunk['text'],
'source': chunk['metadata']['source'],
'page': chunk['metadata'].get('page', 0)
}
)
for chunk in batch
]
index.upsert(vectors=vectors)
print(f'Upserted {min(i+batch_size, len(embedded_chunks))}/{len(embedded_chunks)}')การจัดเก็บใน pgvector
เมื่อใช้ pgvector คุณจะแทรกเวกเตอร์ฝังตัวลงในตาราง PostgreSQL โดยตรงด้วย SQL มาตรฐาน ชนิดข้อมูล vector รองรับรายการตัวเลขทศนิยมของ Python ที่แปลงเป็นสตริง หลังจากแทรกแถวทั้งหมดแล้ว ให้สร้างดัชนี HNSW เพื่อให้การค้นหาเพื่อนบ้านใกล้เคียงโดยประมาณทำได้รวดเร็ว การสร้างดัชนีจากตารางที่มีหลายล้านแถวอาจใช้เวลาหลายนาที ดังนั้นควรสร้างดัชนีหลังการแทรกข้อมูลจำนวนมาก แทนที่จะสร้างก่อน
import psycopg2
from psycopg2.extras import execute_values
def upsert_to_pgvector(conn, embedded_chunks):
with conn.cursor() as cur:
records = [
(
chunk['id'],
chunk['text'],
chunk['metadata']['source'],
chunk['metadata'].get('page', 0),
chunk['embedding'] # list of floats
)
for chunk in embedded_chunks
]
execute_values(cur, '''
INSERT INTO document_chunks (id, text, source, page, embedding)
VALUES %s
ON CONFLICT (id) DO UPDATE
SET text = EXCLUDED.text, embedding = EXCLUDED.embedding
''', records)
conn.commit()การสร้างดัชนี HNSW
HNSW (โลกขนาดเล็กที่นำทางได้แบบลำดับชั้น) คือชนิดของดัชนีที่ช่วยให้ค้นหาเพื่อนบ้านใกล้เคียงโดยประมาณได้อย่างรวดเร็ว ต่างจากการค้นหาแบบไล่ตรวจทุกค่า ซึ่งเปรียบเทียบเวกเตอร์คำค้นกับเวกเตอร์ที่จัดเก็บไว้ทุกตัว HNSW จะสร้างโครงสร้างกราฟหลายชั้นเพื่อจำกัดพื้นที่ค้นหา พารามิเตอร์ m ควบคุมจำนวนการเชื่อมต่อของแต่ละโหนด (ค่าสูงขึ้น = ความครอบคลุมการค้นหาดีขึ้น แต่ใช้หน่วยความจำมากขึ้น) ส่วน ef_construction ควบคุมคุณภาพของดัชนีระหว่างการสร้าง
-- Build HNSW index after bulk insertion
CREATE INDEX CONCURRENTLY ON document_chunks
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- Set ef_search at query time to trade recall vs speed
SET hnsw.ef_search = 100;
-- Verify index was created
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'document_chunks';การออกแบบโครงสร้างข้อมูลเมตา
ข้อมูลเมตาที่จัดเก็บไว้คู่กับเวกเตอร์แต่ละตัวช่วยให้ การดึงข้อมูลแบบมีตัวกรอง ทำได้อย่างมีประสิทธิภาพ ควรออกแบบโครงสร้างข้อมูลเมตาก่อนสร้างดัชนี เพราะการเพิ่มฟิลด์ใหม่ภายหลังจำเป็นต้องสร้างดัชนีใหม่ รวมฟิลด์ที่จะใช้กรอง (แผนก ประเภทเอกสาร ช่วงวันที่) ฟิลด์ที่จะแสดงในการอ้างอิงแหล่งที่มา (ชื่อเรื่อง หน้า ผู้เขียน) และฟิลด์ที่มีประโยชน์สำหรับการแก้ไขข้อบกพร่อง (ดัชนีส่วนย่อย จำนวนส่วนย่อยทั้งหมด เวลาที่สร้างดัชนี) ควรเก็บค่าข้อมูลเมตาให้เรียบง่าย ได้แก่ สตริง ตัวเลข และค่าบูลีน ซึ่งสร้างดัชนีและกรองได้อย่างมีประสิทธิภาพ ส่วนออบเจ็กต์ซ้อนกันไม่เหมาะกับงานนี้
# Well-designed metadata schema
METADATA_SCHEMA = {
# For filtering at retrieval time
'department': 'HR', # string
'doc_type': 'policy', # string
'year': 2025, # integer
'is_active': True, # boolean
# For display in citations
'title': 'Employee Handbook 2025',
'author': 'HR Team',
'page': 12,
'source': 's3://docs/handbook_2025.pdf',
# For debugging and updates
'chunk_index': 3,
'total_chunks': 24,
'indexed_at': '2025-09-01T10:00:00Z'
}การบันทึกจุดตรวจสำหรับงานสร้างดัชนีที่ใช้เวลานาน
การสร้างดัชนีจากคลังเอกสารขนาดใหญ่อาจใช้เวลาหลายชั่วโมง หากโปรแกรมหยุดทำงานระหว่างทาง ความคืบหน้าทั้งหมดจะสูญเปล่า ให้สร้าง ไฟล์จุดตรวจ เพื่อบันทึกว่าส่วนย่อยใดสร้างดัชนีสำเร็จแล้ว เมื่อเริ่มทำงานใหม่ ให้ข้ามส่วนย่อยที่สร้างดัชนีแล้วและทำต่อจากจุดที่หยุดไว้ วิธีนี้ทำให้งานสร้างดัชนีทำซ้ำได้โดยไม่ก่อให้เกิดผลลัพธ์ซ้ำซ้อน และสามารถกลับมาทำต่อได้อย่างปลอดภัย จัดเก็บจุดตรวจเป็นชุด ID ของส่วนย่อยที่ประมวลผลแล้วในไฟล์ JSON หรือตารางฐานข้อมูล
import json
from pathlib import Path
CHECKPOINT_FILE = '/tmp/index_checkpoint.json'
def load_checkpoint():
if Path(CHECKPOINT_FILE).exists():
return set(json.loads(Path(CHECKPOINT_FILE).read_text()))
return set()
def save_checkpoint(indexed_ids):
Path(CHECKPOINT_FILE).write_text(json.dumps(list(indexed_ids)))
def index_with_checkpoint(chunks, index):
done = load_checkpoint()
remaining = [c for c in chunks if c['id'] not in done]
print(f'Resuming: {len(done)} done, {len(remaining)} remaining')
for chunk in remaining:
upsert_to_pinecone([chunk], index)
done.add(chunk['id'])
save_checkpoint(done)การตรวจสอบความครบถ้วนของดัชนี
หลังจากสร้างดัชนีแล้ว ให้ตรวจสอบว่าส่วนย่อยทั้งหมดถูกเพิ่มลงในคลังเวกเตอร์ เปรียบเทียบจำนวนส่วนย่อยที่ตัวแบ่งของคุณสร้างขึ้นกับจำนวนเวกเตอร์ที่ดัชนีรายงาน ลองค้นหาด้วยข้อความจากเอกสารที่ทราบแน่ชัด และยืนยันว่าผลลัพธ์ที่คาดหวังปรากฏอยู่ใน 5 อันดับแรก เรียกใช้คำค้นที่ทราบผลลัพธ์จากชุดทดสอบมาตรฐานของคุณสองสามรายการ และตรวจสอบว่าค่าความแม่นยำอยู่ในระดับที่คาดหวัง อย่าคิดเอาเองว่าดัชนีครบถ้วนโดยไม่ตรวจสอบ
def verify_index(index, chunks, sample_size=10):
index_stats = index.describe_index_stats()
total_vectors = index_stats.total_vector_count
expected = len(chunks)
print(f'Index vectors: {total_vectors}, Expected: {expected}')
if total_vectors != expected:
print('WARNING: mismatch — some chunks may not have been indexed')
# Spot-check retrieval
import random
sample = random.sample(chunks, sample_size)
for chunk in sample:
vec = embed_batch([chunk['text']])[0]
results = index.query(vector=vec, top_k=1, include_metadata=True)
top_id = results.matches[0].id if results.matches else None
if top_id != chunk['id']:
print(f'WARNING: expected {chunk["id"]}, got {top_id}')ทางเลือกสำหรับการสร้างเวกเตอร์ฝังตัวภายในเครื่อง
สำหรับข้อมูลที่มีความอ่อนไหวด้านความเป็นส่วนตัวและไม่สามารถส่งออกนอกโครงสร้างพื้นฐานของคุณได้ ให้ใช้ โมเดลสร้างเวกเตอร์ฝังตัวที่โฮสต์ภายในเครื่อง ไลบรารี sentence-transformers มีโมเดลคุณภาพสูง เช่น all-MiniLM-L6-v2 (384 มิติ ขนาด 22MB ทำงานเร็วมาก) และ bge-large-en-v1.5 (1024 มิติ คุณภาพดีกว่า) ใช้ CPU สำหรับปริมาณงานระดับปานกลาง หรือใช้ GPU สำหรับงานสร้างดัชนีขนาดใหญ่ โมเดลภายในเครื่องช่วยตัดค่าใช้จ่ายของ API และการส่งข้อมูลออกนอกระบบ แต่คุณต้องจัดการไฟล์โมเดลและทรัพยากรการประมวลผลเอง
from sentence_transformers import SentenceTransformer
# Load once at startup
model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')
def embed_locally(texts, batch_size=64):
# encode() handles batching internally
embeddings = model.encode(
texts,
batch_size=batch_size,
show_progress_bar=True,
convert_to_numpy=True
)
return embeddings.tolist() # convert numpy array to Python list
vectors = embed_locally([c['text'] for c in chunks])ตรวจสอบความเข้าใจ
ทดสอบความเข้าใจแนวคิดวิศวกรรม AI จากบทเรียนนี้
สรุปบทเรียน
ในบทเรียนนี้ คุณได้เรียนรู้เกี่ยวกับ การสร้างเวกเตอร์ฝังตัวเป็นชุดด้วย OpenAI API และการจัดการข้อจำกัดอัตราการเรียกใช้ด้วยการหน่วงเวลาแบบทวีคูณ การอัปโหลดหรือปรับปรุงเวกเตอร์ใน Pinecone และ pgvector พร้อมข้อมูลเมตา การสร้างดัชนี HNSWสำหรับการค้นหาเพื่อนบ้านใกล้เคียงโดยประมาณที่รวดเร็ว และ แนวปฏิบัติที่ดีสำหรับระบบจริง ซึ่งรวมถึงการทำงานต่อโดยใช้จุดตรวจ การออกแบบโครงสร้างข้อมูลเมตา และการตรวจสอบความครบถ้วนของดัชนี บทถัดไป เราจะสร้างขั้นตอนการค้นหาที่ดึงส่วนย่อยและสร้างคำตอบที่มีข้อมูลรองรับ
เรียนรู้ Python ด้วย AI tutor — ฟรี
เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป
- คอร์ส
- 30
- บทเรียน
- 120
คำถามที่พบบ่อย
บทเรียน “การทำดัชนี: การสร้างและจัดเก็บส่วนข้อความ” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “การทำดัชนี: การสร้างและจัดเก็บส่วนข้อความ” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส AI Engineering Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส AI Engineering Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “การทำดัชนี: การสร้างและจัดเก็บส่วนข้อความ”
สร้างเวกเตอร์ฝังตัวของแต่ละส่วนด้วย API เวกเตอร์ฝังตัวของ OpenAI และเพิ่มหรืออัปเดตเวกเตอร์พร้อมข้อมูลกำกับลงในแหล่งจัดเก็บเวกเตอร์ เพื่อสร้างดัชนีเอกสารที่ค้นหาได้ คุณปฏิบัติ AI Engineering Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน AI Engineering Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน AI Engineering Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน
บทเรียน “การทำดัชนี: การสร้างและจัดเก็บส่วนข้อความ” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน AI Engineering Academy นี้ได้ไหม
ได้ บทเรียน AI Engineering Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- การโหลดเอกสารและการดึงข้อความ
- กลยุทธ์การแบ่งส่วน: ขนาดคงที่ เทียบกับประโยค เทียบกับแบบเรียกซ้ำ
- การทำดัชนี: การสร้างและจัดเก็บส่วนข้อความ
- ค้นคำค้น คืนข้อมูล และสร้างคำตอบ