การพัฒนาความสามารถหลักของ RAG และเอเจนต์
สร้างกระบวนการนำเข้าเอกสาร การทำดัชนีคลังเวกเตอร์ การค้นคืนพร้อมจัดอันดับใหม่ และการผสานรวมเครื่องมือของเอเจนต์ตามรูปแบบที่เรียนรู้มาตลอดหลักสูตร
การพัฒนาความสามารถหลักของ RAG และเอเจนต์ เป็นบทเรียน AI Engineering Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน AI Engineering Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส AI Engineering Academy มีบทเรียนทั้งหมด 4 บทเรียน
ลำดับการพัฒนาและสิ่งที่ต้องพึ่งพา
สร้างระบบจากล่างขึ้นบน: เริ่มจากส่วนประกอบที่ไม่มีสิ่งภายนอกต้องพึ่งพา แล้วจึงเพิ่มชั้นของส่วนประกอบที่พึ่งพาส่วนประกอบเหล่านั้น สำหรับระบบ RAG + เอเจนต์ ลำดับคือ: (1) โครงสร้างสคีมาคลังเวกเตอร์ (2) กระบวนการนำเข้าข้อมูล (3) ตัวค้นคืน (4) ห่วงโซ่ตอบคำถามพื้นฐาน (5) จุดปลายทางแบบสตรีม (6) เอเจนต์พร้อมเครื่องมือ (7) ชั้นแคช (8) การติดตั้งเครื่องมือติดตามร่องรอย ทดสอบแต่ละส่วนประกอบแยกกันก่อนผสานเข้ากับกระบวนการ
# Build order:
IMPL_ORDER = [
'pgvector_schema', # prerequisite for everything
'document_ingestion', # populate the vector store
'hybrid_retriever', # test retrieval in isolation
'qa_chain_basic', # integrate LLM with retrieval
'streaming_endpoint', # expose via API
'function_calling', # add agent tool calls
'semantic_cache', # reduce repeat API calls
'langsmith_tracing', # add after core works
'injection_filter', # harden before load testing
]การตั้งค่าคลังเวกเตอร์
สร้างตารางพีจีเวกเตอร์ด้วยสคีมาที่ถูกต้องก่อนนำเข้าเอกสาร ใส่คอลัมน์สำหรับเวกเตอร์ ข้อความของชิ้นส่วน ฟิลด์ข้อมูลกำกับทั้งหมด และการประทับเวลา updated_at สำหรับการทำดัชนีใหม่เฉพาะส่วน สร้างดัชนีเวกเตอร์ (HNSW หรือ IVFFlat) บนคอลัมน์เวกเตอร์ฝังทันที เพราะการเพิ่มดัชนีหลังมีแถวหลายล้านแถวนั้นช้ากว่าการเพิ่มตั้งแต่ต้นบนตารางว่างมาก
-- PostgreSQL schema with pgvector
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE document_chunks (
id BIGSERIAL PRIMARY KEY,
doc_id TEXT NOT NULL,
chunk_text TEXT NOT NULL,
embedding VECTOR(1536) NOT NULL,
source_file TEXT,
page_number INT,
section TEXT,
doc_type TEXT,
tenant_id TEXT NOT NULL, -- for data isolation
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_chunks_hnsw ON document_chunks
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
CREATE INDEX idx_chunks_tenant ON document_chunks(tenant_id);การสร้างกระบวนการนำเข้าข้อมูล
พัฒนาการนำเข้าข้อมูลเป็นฟังก์ชันแบบอะซิงก์เดียวที่รับเส้นทางไฟล์หรือ URL และคืนค่าจำนวนชิ้นส่วนที่ทำดัชนี ใช้ตัวโหลดเอกสารของ LangChain สำหรับประเภทไฟล์ต่าง ๆ และตัวแบ่งข้อความตามอักขระแบบเรียกซ้ำสำหรับการแบ่งส่วน แบ่งการเรียกสร้างเวกเตอร์ฝังเป็นชุดเพื่อให้อยู่ภายในขีดจำกัดอินพุต 2,048 โทเค็น และลดการเรียก API จากหลักพันครั้งเหลือเพียงหลักสิบครั้ง
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from openai import AsyncOpenAI
async def ingest_document(file_path: str, doc_id: str, tenant_id: str) -> int:
loader = PyPDFLoader(file_path)
pages = loader.load()
splitter = RecursiveCharacterTextSplitter(chunk_size=800, chunk_overlap=100)
chunks = splitter.split_documents(pages)
# Batch embed
texts = [c.page_content for c in chunks]
client = AsyncOpenAI()
embeddings_response = await client.embeddings.create(
model='text-embedding-3-small',
input=texts
)
embeddings = [e.embedding for e in embeddings_response.data]
await insert_chunks_to_pgvector(chunks, embeddings, doc_id, tenant_id)
return len(chunks)การสร้างตัวค้นคืนแบบผสม
ผสานการค้นหาเวกเตอร์แบบหนาแน่นเข้ากับการค้นหาคำสำคัญ BM25 และรวมผลลัพธ์ด้วยการหลอมรวมอันดับแบบผกผัน พัฒนาตัวค้นคืนเป็นคลาสที่มี retrieve(query, tenant_id, top_k) เพียงเมธอดเดียว ภายในนั้นให้เรียกใช้การค้นหาทั้งสองแบบพร้อมกันด้วย asyncio.gather รวมรายการที่จัดอันดับด้วย RRF ลบรายการซ้ำตาม ID ของชิ้นส่วน และคืนผลลัพธ์อันดับต้น ๆ ตาม top_k พร้อมข้อมูลกำกับของแหล่งที่มา
import asyncio
from rank_bm25 import BM25Okapi
class HybridRetriever:
def __init__(self, pool, k_rrf: int = 60):
self.pool = pool
self.k_rrf = k_rrf
async def retrieve(self, query: str, tenant_id: str, top_k: int = 10) -> list:
dense_results, sparse_results = await asyncio.gather(
self._dense_search(query, tenant_id, top_k * 3),
self._bm25_search(query, tenant_id, top_k * 3)
)
merged = self._rrf_merge(dense_results, sparse_results)
return merged[:top_k]
def _rrf_score(self, rank: int) -> float:
return 1.0 / (self.k_rrf + rank + 1)การพัฒนาห่วงโซ่ตอบคำถามหลัก
สร้างห่วงโซ่ตอบคำถามหลักด้วย LangChain LCEL ห่วงโซ่นี้รับคำถามและชิ้นส่วนที่ค้นคืนมา จัดรูปแบบพรอมต์เสริมพร้อมคำแนะนำให้ใช้เฉพาะบริบทที่ให้มา และสตรีมคำตอบ เพิ่มคำแนะนำอย่างชัดเจนให้โมเดลอ้างอิงแหล่งที่มาตามชื่อเอกสารและหมายเลขหน้า และตอบว่า 'ฉันไม่ทราบ' เมื่อคำตอบไม่มีอยู่ในบริบทที่ค้นคืนมา
from langchain.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain.schema.output_parser import StrOutputParser
RAG_PROMPT = ChatPromptTemplate.from_messages([
('system', 'You are a precise assistant. Answer ONLY using the provided context. '
'Cite sources as [DocName, p.N]. If the answer is not in the context, say "I don\'t have information about that."'),
('user', 'Context:\n{context}\n\nQuestion: {question}')
])
llm = ChatOpenAI(model='gpt-4o', temperature=0, streaming=True)
qa_chain = RAG_PROMPT | llm | StrOutputParser()
async def answer_question(question: str, chunks: list) -> str:
context = '\n\n'.join(f'[{c["source"]}]\n{c["text"]}' for c in chunks)
return await qa_chain.ainvoke({'context': context, 'question': question})การเพิ่มการเรียกใช้ฟังก์ชันให้เอเจนต์
ขยายระบบตอบคำถามพื้นฐานด้วยการเรียกใช้ฟังก์ชัน เพื่อจัดการคำค้นที่ต้องใช้ข้อมูลแบบเรียลไทม์หรือการคำนวณ กำหนดเครื่องมือสำหรับ: ค้นหาข้อมูลปัจจุบันบนเว็บ เรียกใช้คำค้น SQL กับฐานข้อมูลที่ปลอดภัยและอ่านได้อย่างเดียว และค้นหาระเบียนเฉพาะตาม ID เอเจนต์จะตัดสินใจว่าจะเรียกใช้เครื่องมือใดจากคำถาม ดำเนินการกับเครื่องมือเหล่านั้น และนำผลลัพธ์มารวมไว้ในคำตอบสุดท้าย
from openai import AsyncOpenAI
import json
TOOLS = [
{
'type': 'function',
'function': {
'name': 'search_knowledge_base',
'description': 'Search the internal document knowledge base for relevant information',
'parameters': {
'type': 'object',
'properties': {
'query': {'type': 'string', 'description': 'Search query'},
'top_k': {'type': 'integer', 'default': 5}
},
'required': ['query']
}
}
}
]
async def agent_with_tools(question: str, tenant_id: str) -> str:
client = AsyncOpenAI()
messages = [{'role': 'user', 'content': question}]
while True:
resp = await client.chat.completions.create(
model='gpt-4o', messages=messages, tools=TOOLS)
if resp.choices[0].finish_reason != 'tool_calls':
return resp.choices[0].message.content
tool_call = resp.choices[0].message.tool_calls[0]
args = json.loads(tool_call.function.arguments)
result = await dispatch_tool(tool_call.function.name, args, tenant_id)
messages.append({'role': 'tool', 'tool_call_id': tool_call.id, 'content': result})การสตรีมคำตอบของเอเจนต์
ห่อหุ้มเอเจนต์ด้วย StreamingResponse ของ FastAPI โดยใช้เหตุการณ์ที่ส่งจากเซิร์ฟเวอร์ เพื่อให้ส่วนหน้าแสดงโทเค็นทันทีที่มาถึง สำหรับการตอบกลับของเอเจนต์ที่มีการเรียกใช้เครื่องมือ ให้สตรีมตัวบ่งชี้ความคืบหน้าขณะที่เครื่องมือกำลังทำงาน เช่น 'กำลังค้นหาฐานความรู้...' จากนั้นจึงสตรีมคำตอบสุดท้ายทีละโทเค็น วิธีนี้ป้องกันไม่ให้หน้าดูเหมือนค้างระหว่างที่เครื่องมือใช้เวลาในการทำงาน
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import asyncio
app = FastAPI()
async def event_stream(question: str, tenant_id: str):
yield 'data: {"type": "start"}\n\n'
chunks = await retriever.retrieve(question, tenant_id)
yield 'data: {"type": "retrieving", "count": ' + str(len(chunks)) + '}\n\n'
async for token in qa_chain.astream({'context': format_context(chunks), 'question': question}):
yield f'data: {{"type": "token", "content": {repr(token)}}}\n\n'
yield 'data: {"type": "done"}\n\n'
@app.post('/query/stream')
async def stream_query(question: str, tenant_id: str):
return StreamingResponse(event_stream(question, tenant_id), media_type='text/event-stream')เชื่อมต่อ Semantic Cache
เพิ่ม Semantic Cache เป็นขั้นตอนก่อนการค้นคืนในกระบวนการประมวลผลคำค้น ก่อนเรียกใช้ตัวค้นคืนและ LLM ให้นำคำถามของผู้ใช้ไปสร้างเวกเตอร์ฝังตัวและตรวจสอบแคช หากพบรายการในแคชที่มีค่าความคล้ายคลึงสูงกว่าเกณฑ์ ให้ส่งคืนคำตอบที่แคชไว้ทันทีพร้อมแฟล็ก cached: true หากไม่พบรายการ ให้ดำเนินการผ่านกระบวนการทั้งหมด และจัดเก็บคำตอบที่ได้ลงในแคชเพื่อใช้กับคำค้นที่คล้ายกันในอนาคต
async def query_pipeline(question: str, tenant_id: str) -> dict:
# 1. Check semantic cache
cache_hit = await semantic_cache.lookup(question, tenant_id, threshold=0.92)
if cache_hit:
return {'answer': cache_hit.answer, 'cached': True, 'sources': cache_hit.sources}
# 2. Retrieve
chunks = await retriever.retrieve(question, tenant_id, top_k=5)
chunks = await reranker.rerank(question, chunks, top_n=3)
# 3. Generate
answer = await answer_question(question, chunks)
sources = [c['source'] for c in chunks]
# 4. Cache result
await semantic_cache.store(question, tenant_id, answer, sources)
return {'answer': answer, 'cached': False, 'sources': sources}เพิ่มการติดตามด้วย LangSmith
เพิ่มการตรวจวัดกระบวนการด้วย LangSmith โดยกำหนดตัวแปรสภาพแวดล้อมสองรายการ การเรียกใช้สายงานของ LangChain ทุกครั้งจะถูกติดตามโดยอัตโนมัติ พร้อมจำนวนโทเค็น เวลาแฝง ข้อมูลนำเข้า ข้อมูลส่งออก และข้อผิดพลาดต่าง ๆ สำหรับโค้ดที่กำหนดเองและไม่ใช่ LangChain เช่น การค้นคืนและการจัดอันดับใหม่ ให้ห่อหุ้มการเรียกใช้ด้วยตัวตกแต่ง @traceable เพื่อรวมไว้ในการติดตาม วิธีนี้ทำให้มองเห็นกระบวนการทุกขั้นตอนได้อย่างครบถ้วนตั้งแต่ต้นจนจบ
import os
from langsmith import traceable
os.environ['LANGCHAIN_TRACING_V2'] = 'true'
os.environ['LANGCHAIN_API_KEY'] = os.environ['LANGSMITH_API_KEY']
os.environ['LANGCHAIN_PROJECT'] = 'document-qa-production'
# Wrap non-LangChain steps with @traceable
@traceable(name='hybrid_retrieval')
async def traced_retrieval(question: str, tenant_id: str, top_k: int) -> list:
return await retriever.retrieve(question, tenant_id, top_k)
@traceable(name='cohere_reranking')
async def traced_reranking(question: str, chunks: list) -> list:
return await reranker.rerank(question, chunks)
# LangChain LCEL chains are automatically traced — no extra code neededเรียกใช้การทดสอบการเชื่อมต่อระบบ
เขียนการทดสอบการเชื่อมต่อระบบที่ตรวจสอบกระบวนการประมวลผลคำค้นทั้งหมด ตั้งแต่รับคำถามจนถึงคำตอบสุดท้าย ใช้คลังเวกเตอร์สำหรับการทดสอบขนาดเล็กที่มีเอกสารเป็นที่รู้จัก เพื่อให้เขียนการตรวจสอบแบบกำหนดผลลัพธ์แน่นอนได้ว่า ควรค้นคืนส่วนใดและคำตอบควรมีเนื้อหาอะไร เรียกใช้การทดสอบการเชื่อมต่อระบบกับสภาพแวดล้อมเตรียมใช้งานที่จำลองโครงสร้างพื้นฐานจริง แต่ใช้คลังเวกเตอร์และคีย์ LLM แยกต่างหาก
import pytest
@pytest.mark.asyncio
async def test_full_pipeline_returns_grounded_answer():
# Setup: ingest known document
await ingest_document('tests/fixtures/policy.pdf', 'policy_v1', 'test_tenant')
# Query with a question that has a known answer in the document
result = await query_pipeline(
question='What is the cancellation policy?',
tenant_id='test_tenant'
)
assert result['answer'] is not None
assert len(result['answer']) > 50
assert '24 hours' in result['answer'] # known fact in document
assert 'policy.pdf' in str(result['sources'])
assert result['cached'] is False # fresh queryวัดคุณภาพการค้นคืนพื้นฐาน
ก่อนปรับปรุงสิ่งใด ให้กำหนด ค่าพื้นฐานของการค้นคืน เสียก่อน ใช้ชุดข้อมูลสำหรับประเมินเพื่อวัดอัตราการพบผลลัพธ์ที่ต้องการ (เอกสารที่ถูกต้องปรากฏในผลลัพธ์ 5 อันดับแรกหรือไม่) และ MRR (เอกสารนั้นอยู่ในอันดับสูงเพียงใด) เรียกใช้การวัดพื้นฐานนี้หลังตั้งค่าคลังเวกเตอร์เริ่มต้นแล้ว และก่อนเพิ่มการจัดอันดับใหม่หรือการค้นหาแบบผสม ค่าพื้นฐานจะบอกให้ทราบว่าการปรับปรุงแต่ละอย่างให้ผลดีขึ้นจริงเพียงใด ทำให้มีหลักฐานประกอบการตัดสินใจว่าเทคนิคใดควรเก็บไว้
async def measure_retrieval_baseline(test_cases: list) -> dict:
hits = 0
reciprocal_ranks = []
for case in test_cases:
results = await retriever.retrieve(case['question'], case['tenant_id'], top_k=5)
result_docs = [r['doc_id'] for r in results]
if case['relevant_doc'] in result_docs:
hits += 1
rank = result_docs.index(case['relevant_doc']) + 1
reciprocal_ranks.append(1.0 / rank)
else:
reciprocal_ranks.append(0.0)
return {
'hit_rate_at_5': hits / len(test_cases),
'mrr': sum(reciprocal_ranks) / len(reciprocal_ranks)
}ตรวจสอบความเข้าใจ
ทดสอบความเข้าใจเกี่ยวกับการสร้างความสามารถหลักของ RAG และเอเจนต์
สรุปบทเรียน
ในบทเรียนนี้ คุณได้เรียนรู้ว่า ลำดับการนำไปใช้จากพื้นฐานขึ้นไป ช่วยให้ทดสอบแต่ละองค์ประกอบแยกกันก่อนการผสานรวมได้ การค้นคืนแบบผสมด้วยการค้นหาแบบหนาแน่นและ BM25 พร้อมกัน ซึ่งรวมผลด้วย RRF ให้คุณภาพการค้นคืนดีที่สุด และ การติดตามด้วย LangSmith พร้อมตัวตกแต่ง @traceable ช่วยให้มองเห็นกระบวนการทั้งหมดได้อย่างครบถ้วน ต่อไปเราจะเสริมความแข็งแกร่งให้ระบบด้วยรูปแบบด้านความปลอดภัย การแคช และความน่าเชื่อถือ
เรียนรู้ Python ด้วย AI tutor — ฟรี
เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป
- คอร์ส
- 30
- บทเรียน
- 120
คำถามที่พบบ่อย
บทเรียน “การพัฒนาความสามารถหลักของ RAG และเอเจนต์” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “การพัฒนาความสามารถหลักของ RAG และเอเจนต์” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส AI Engineering Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส AI Engineering Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “การพัฒนาความสามารถหลักของ RAG และเอเจนต์”
สร้างกระบวนการนำเข้าเอกสาร การทำดัชนีคลังเวกเตอร์ การค้นคืนพร้อมจัดอันดับใหม่ และการผสานรวมเครื่องมือของเอเจนต์ตามรูปแบบที่เรียนรู้มาตลอดหลักสูตร คุณปฏิบัติ AI Engineering Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน AI Engineering Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน AI Engineering Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน
บทเรียน “การพัฒนาความสามารถหลักของ RAG และเอเจนต์” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน AI Engineering Academy นี้ได้ไหม
ได้ บทเรียน AI Engineering Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- การออกแบบสถาปัตยกรรมสำหรับใช้งานจริง
- การพัฒนาความสามารถหลักของ RAG และเอเจนต์
- การเสริมความแข็งแกร่ง: ความปลอดภัย การแคช และความน่าเชื่อถือ
- การประเมินผล การนำไปใช้งาน และการทบทวนหลังดำเนินการ