Menerapkan Fitur Inti RAG dan Agen
Bangun alur kerja penyerapan dokumen, pengindeksan penyimpanan vektor, pengambilan dengan pemeringkatan ulang, serta integrasi alat agen mengikuti pola yang dipelajari sepanjang jalur pembelajaran.
Menerapkan Fitur Inti RAG dan Agen adalah pelajaran AI Engineering Academy gratis di CoddyKit. Ini adalah pelajaran 2 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar AI Engineering Academy, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus AI Engineering Academy mencakup 4 pelajaran total.
Urutan Implementasi dan Dependensi
Bangun sistem Anda dari bawah ke atas: mulai dari komponen yang tidak memiliki dependensi eksternal, lalu tambahkan lapisan komponen yang bergantung padanya. Untuk sistem RAG + agen, urutannya adalah: (1) skema penyimpanan vektor, (2) pipeline pemasukan, (3) pengambil, (4) rantai tanya jawab dasar, (5) endpoint streaming, (6) agen dengan alat, (7) lapisan caching, (8) instrumentasi pelacakan. Uji setiap komponen secara terpisah sebelum mengintegrasikannya ke dalam pipeline.
# 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
]Menyiapkan Penyimpanan Vektor
Buat tabel pgvector dengan skema yang tepat sebelum memasukkan dokumen. Sertakan kolom untuk vektor, teks potongan, semua bidang metadata, dan stempel waktu updated_at untuk pengindeksan ulang selektif. Segera buat indeks vektor (HNSW atau IVFFlat) pada kolom embedding—menambahkan indeks setelah jutaan baris jauh lebih lambat daripada menambahkannya sejak awal pada tabel kosong.
-- 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);Membangun Pipeline Pemasukan
Terapkan pemasukan sebagai fungsi asinkron tunggal yang menerima jalur berkas atau URL dan mengembalikan jumlah potongan yang diindeks. Gunakan pemuat dokumen LangChain untuk berbagai jenis berkas dan pemisah teks karakter rekursif untuk pemenggalan. Kelompokkan pemanggilan embedding agar tetap berada dalam batas masukan 2.048 token dan mengurangi pemanggilan API dari ribuan menjadi puluhan.
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)Membangun Pengambil Hibrida
Gabungkan pencarian vektor padat dengan pencarian kata kunci BM25, lalu gabungkan hasilnya menggunakan penggabungan peringkat timbal balik. Terapkan pengambil sebagai kelas dengan satu metode retrieve(query, tenant_id, top_k). Di dalamnya, jalankan kedua pencarian secara bersamaan dengan asyncio.gather, gabungkan daftar berperingkat dengan RRF, hilangkan duplikasi berdasarkan ID potongan, lalu kembalikan hasil teratas sebanyak top_k beserta metadata sumbernya.
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)Menerapkan Rantai QA Inti
Bangun rantai tanya jawab inti menggunakan LangChain LCEL. Rantai ini menerima pertanyaan dan potongan yang diambil, memformat prompt yang diperkaya dengan instruksi untuk hanya menggunakan konteks yang diberikan, lalu mengalirkan tanggapan. Tambahkan instruksi eksplisit agar model mencantumkan sumber berdasarkan nama dokumen dan nomor halaman, serta mengatakan “Saya tidak tahu” jika jawabannya tidak terdapat dalam konteks yang diambil.
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})Menambahkan Pemanggilan Fungsi ke Agen
Perluas sistem tanya jawab dasar dengan pemanggilan fungsi untuk menangani kueri yang memerlukan data atau komputasi waktu nyata. Tentukan alat untuk: mencari informasi terkini di web, menjalankan kueri SQL terhadap basis data aman yang hanya dapat dibaca, dan mencari catatan tertentu berdasarkan ID. Agen memutuskan alat mana yang akan dipanggil berdasarkan pertanyaan, mengeksekusinya, lalu memasukkan hasilnya ke dalam jawaban akhir.
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})Mengalirkan Tanggapan Agen
Bungkus agen dalam StreamingResponse FastAPI menggunakan peristiwa yang dikirim server agar antarmuka frontend menampilkan token saat token tersebut tiba. Untuk respons agen yang memanggil alat, tampilkan indikator kemajuan selama alat dijalankan ('Mencari basis pengetahuan...'), lalu alirkan jawaban akhir token demi token. Dengan demikian, halaman tidak tampak membeku selama waktu tunggu eksekusi alat.
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')Menghubungkan Cache Semantik
Tambahkan cache semantik sebagai langkah sebelum pengambilan dalam pipeline kueri. Sebelum menghubungi pengambil dan LLM, ubah pertanyaan pengguna menjadi embedding dan periksa cache. Jika terjadi cache hit dengan kemiripan di atas ambang batas, segera kembalikan jawaban yang tersimpan dengan penanda cached: true. Cache miss dilanjutkan melalui seluruh pipeline, lalu jawaban yang dihasilkan disimpan di cache untuk kueri serupa di masa mendatang.
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}Menambahkan Pelacakan LangSmith
Lengkapi pipeline dengan instrumentasi LangSmith dengan menetapkan dua variabel lingkungan. Setiap pemanggilan rantai LangChain secara otomatis dilacak bersama jumlah token, latensi, masukan, keluaran, dan setiap kesalahan. Untuk kode khusus non-LangChain (pengambilan, pemeringkatan ulang), bungkus pemanggilan dalam dekorator @traceable agar tercakup dalam pelacakan. Dengan demikian, Anda memperoleh visibilitas menyeluruh dari ujung ke ujung atas setiap langkah pipeline.
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 neededMenjalankan Pengujian Integrasi
Tulis pengujian integrasi yang menjalankan seluruh pipeline kueri, mulai dari masukan pertanyaan hingga jawaban akhir. Gunakan penyimpanan vektor pengujian berukuran kecil dengan dokumen yang telah diketahui agar Anda dapat menulis pemeriksaan deterministik mengenai potongan mana yang seharusnya diambil dan apa yang seharusnya terdapat dalam jawaban. Jalankan pengujian integrasi pada lingkungan penahapan yang mencerminkan infrastruktur produksi, tetapi menggunakan penyimpanan vektor dan kunci LLM yang terpisah.
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 queryMengukur Kualitas Pengambilan Dasar
Sebelum mengoptimalkan apa pun, tetapkan dasar pengambilan. Gunakan kumpulan data pengujian evaluasi untuk mengukur tingkat keberhasilan (apakah dokumen yang benar muncul dalam 5 hasil teratas?) dan MRR (seberapa tinggi peringkatnya?). Jalankan dasar ini setelah menyiapkan penyimpanan vektor awal, sebelum menambahkan pemeringkatan ulang atau pencarian hibrida. Dasar ini menunjukkan peningkatan yang benar-benar diberikan oleh setiap pengoptimalan, sehingga Anda memiliki bukti untuk menentukan teknik yang layak dipertahankan.
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)
}Pemeriksaan Singkat
Uji pemahaman Anda tentang pembuatan fitur inti RAG dan agen.
Ringkasan Pelajaran
Dalam pelajaran ini Anda mempelajari bahwa urutan implementasi dari bawah ke atas memastikan setiap komponen diuji secara terpisah sebelum integrasi, pengambilan hibrida dengan pencarian padat dan BM25 secara bersamaan yang digabungkan melalui RRF memberikan kualitas pengambilan terbaik, dan pelacakan LangSmith dengan dekorator @traceable memberikan visibilitas menyeluruh atas pipeline. Selanjutnya, kita akan memperkuat sistem dengan pola keamanan, caching, dan keandalan.
Pertanyaan yang Sering Diajukan
Apakah pelajaran “Menerapkan Fitur Inti RAG dan Agen” gratis?
Ya — teks lengkap “Menerapkan Fitur Inti RAG dan Agen” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus AI Engineering Academy, upgrade ke CoddyKit PRO. Kursus AI Engineering Academy mencakup 4 pelajaran total.
Apa yang akan aku pelajari di “Menerapkan Fitur Inti RAG dan Agen”?
Bangun alur kerja penyerapan dokumen, pengindeksan penyimpanan vektor, pengambilan dengan pemeringkatan ulang, serta integrasi alat agen mengikuti pola yang dipelajari sepanjang jalur pembelajaran. Kamu berlatih AI Engineering Academy dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.
Apakah aku perlu pengalaman untuk memulai AI Engineering Academy?
Tidak diperlukan pengalaman sebelumnya. AI Engineering Academy di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 2 dari 4.
Berapa lama pelajaran “Menerapkan Fitur Inti RAG dan Agen” memakan waktu?
Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.
Bisakah aku menulis dan menjalankan kode dalam pelajaran AI Engineering Academy ini?
Ya. Setiap pelajaran AI Engineering Academy menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.
Semua pelajaran dalam kursus ini
- Merancang Arsitektur Produksi
- Menerapkan Fitur Inti RAG dan Agen
- Memperkuat Sistem: Keamanan, Penyimpanan Tembolok, dan Keandalan
- Evaluasi, Penerapan, dan Retrospektif