AI Engineering Academy · บทเรียน

การทำดัชนี: การสร้างและจัดเก็บส่วนข้อความ

สร้างเวกเตอร์ฝังตัวของแต่ละส่วนด้วย API เวกเตอร์ฝังตัวของ OpenAI และเพิ่มหรืออัปเดตเวกเตอร์พร้อมข้อมูลกำกับลงในแหล่งจัดเก็บเวกเตอร์ เพื่อสร้างดัชนีเอกสารที่ค้นหาได้

บทเรียน 3 จาก 413 ขั้นตอน

การทำดัชนี: การสร้างและจัดเก็บส่วนข้อความ เป็นบทเรียน 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. การโหลดเอกสารและการดึงข้อความ
  2. กลยุทธ์การแบ่งส่วน: ขนาดคงที่ เทียบกับประโยค เทียบกับแบบเรียกซ้ำ
  3. การทำดัชนี: การสร้างและจัดเก็บส่วนข้อความ
  4. ค้นคำค้น คืนข้อมูล และสร้างคำตอบ
← กลับไปที่ AI Engineering Academy