Mengueri, Mengambil, dan Menghasilkan
Tulis alur kueri yang menyematkan pertanyaan pengguna, mengambil k bagian teratas, memformat perintah yang diperkaya, memanggil LLM, dan mengembalikan jawaban bersitasi.
Mengueri, Mengambil, dan Menghasilkan adalah pelajaran AI Engineering Academy gratis di CoddyKit. Ini adalah pelajaran 4 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.
Pipeline Kueri: Dari Awal hingga Akhir
Pipeline kueri adalah bagian online dari RAG — kode yang berjalan secara waktu nyata ketika pengguna mengajukan pertanyaan. Pipeline ini menghubungkan semua komponen yang dibangun selama pengindeksan: model embedding, penyimpanan vektor, templat prompt, dan LLM. Pipeline kueri yang diimplementasikan dengan baik selesai dalam waktu kurang dari 500 md untuk sebagian besar beban kerja dan menghasilkan jawaban berlandaskan sumber serta dilengkapi sitasi. Dalam pelajaran ini, kita membangun setiap langkah dari awal.
Langkah 1: Mengubah Kueri Pengguna Menjadi Embedding
Langkah pertama adalah mengubah pertanyaan pengguna dalam bahasa alami menjadi embedding vektor menggunakan model yang sama seperti saat pengindeksan. Embedding ini mengodekan makna semantik pertanyaan dan akan dibandingkan dengan embedding potongan dokumen di penyimpanan vektor. Pastikan langkah ini cepat — gunakan model ringan seperti text-embedding-3-small dan simpan embedding dalam cache untuk kueri identik yang berulang.
from openai import OpenAI
client = OpenAI()
def embed_query(question: str) -> list:
response = client.embeddings.create(
model='text-embedding-3-small',
input=[question]
)
return response.data[0].embedding
user_question = 'What is our remote work policy?'
query_vector = embed_query(user_question)
print(f'Query embedded: {len(query_vector)}-dim vector')Langkah 2: Mengambil K Potongan Teratas
Kirim vektor kueri ke penyimpanan vektor untuk menemukan potongan yang paling mirip secara semantik sebanyak K. Hasil yang dikembalikan diurutkan berdasarkan skor kemiripan kosinus (biasanya 0,0 hingga 1,0; semakin tinggi semakin baik). Nilai K yang ideal menyeimbangkan kekayaan konteks dengan biaya jendela konteks: K=5 merupakan titik awal yang umum. Anda juga dapat menerapkan penyaring metadata di sini untuk membatasi pengambilan pada departemen, tipe dokumen, atau rentang tanggal tertentu.
def retrieve_chunks(query_vector, index, top_k=5, filters=None):
query_params = {
'vector': query_vector,
'top_k': top_k,
'include_metadata': True
}
if filters:
query_params['filter'] = filters
results = index.query(**query_params)
chunks = []
for match in results.matches:
chunks.append({
'score': match.score,
'text': match.metadata['text'],
'source': match.metadata.get('source', ''),
'page': match.metadata.get('page', '')
})
return chunksLangkah 3: Penyaringan Berdasarkan Ambang Skor
Tidak semua potongan yang diambil benar-benar relevan — beberapa mungkin memiliki skor kemiripan rendah, tetapi tetap berada di peringkat K teratas karena kueri berada di luar cakupan indeks. Terapkan ambang skor minimum untuk menyaring hasil dengan keyakinan rendah. Jika semua potongan yang diambil berada di bawah ambang tersebut, kembalikan respons "informasi tidak ditemukan" alih-alih mengirimkan konteks yang tidak relevan ke LLM, karena hal itu akan menghasilkan jawaban yang lebih buruk daripada menolak dengan baik.
MIN_SCORE_THRESHOLD = 0.75
def filter_by_score(chunks, threshold=MIN_SCORE_THRESHOLD):
relevant = [c for c in chunks if c['score'] >= threshold]
if not relevant:
print(f'No chunks above threshold {threshold}. Scores: {[c["score"] for c in chunks]}')
return relevant
retrieved = retrieve_chunks(query_vector, index, top_k=5)
filtered = filter_by_score(retrieved)
if not filtered:
print('Responding: no relevant information found')Langkah 4: Memformat Blok Konteks
Susun potongan yang diambil menjadi blok konteks terstruktur yang akan dibaca LLM. Beri label sumber pada setiap potongan agar model dapat mencantumkannya secara akurat. Tambahkan pemisah antarpotongan agar lebih jelas. Jaga total konteks tetap dalam anggaran token Anda — hitung token dengan tiktoken dan potong atau buang potongan dengan skor lebih rendah jika melebihi batas. Blok konteks disisipkan ke dalam prompt di antara instruksi sistem dan pertanyaan pengguna.
def format_context(chunks):
parts = []
for i, chunk in enumerate(chunks, start=1):
source_label = chunk['source']
if chunk.get('page'):
source_label += f", page {chunk['page']}"
parts.append(
f'[Document {i} | Source: {source_label}]\n{chunk["text"]}'
)
return '\n\n---\n\n'.join(parts)
context = format_context(filtered)
print(f'Context block: {len(context)} characters')Langkah 5: Membangun Prompt yang Diperkaya
Gabungkan blok konteks, instruksi sistem, dan pertanyaan pengguna menjadi prompt akhir. Pesan sistem memberi tahu model untuk hanya menggunakan konteks yang disediakan dan mencantumkan sumber. Pesan pengguna berisi konteks yang telah diformat, lalu pertanyaannya. Pemisahan yang jelas ini mencegah model mencampurkan isi konteks dengan pertanyaan dan memperjelas batas antara data yang diambil dan masukan pengguna.
def build_prompt(question, context):
system_message = (
'You are a helpful assistant. Answer the question using ONLY '
'the information in the provided documents. '
'Cite the document number(s) used, like [Doc 1]. '
'If the documents do not contain the answer, say so.'
)
user_message = (
f'Documents:\n\n{context}\n\n'
f'Question: {question}'
)
return system_message, user_messageLangkah 6: Memanggil LLM dan Mendapatkan Jawaban
Kirim prompt yang telah disusun ke LLM menggunakan API Chat Completions. Gunakan suhu rendah (0,0 hingga 0,3) untuk tanya jawab faktual agar mendapatkan jawaban yang konsisten dan berlandaskan sumber. Suhu yang lebih tinggi menghasilkan respons yang lebih kreatif, tetapi meningkatkan risiko model menambahkan informasi di luar konteks. Uraikan respons dan kembalikan teks jawaban beserta sumber yang diambil agar aplikasi Anda dapat menampilkan sitasi kepada pengguna.
def generate_answer(question, context, sources):
system_msg, user_msg = build_prompt(question, context)
response = client.chat.completions.create(
model='gpt-4o',
temperature=0.1, # low temperature for factual Q&A
messages=[
{'role': 'system', 'content': system_msg},
{'role': 'user', 'content': user_msg}
]
)
answer = response.choices[0].message.content
return {
'answer': answer,
'sources': sources,
'tokens_used': response.usage.total_tokens
}Menggabungkan Semuanya
Pipeline kueri lengkap memanggil langkah-langkah ini secara berurutan. Setiap langkah merupakan fungsi murni yang dapat Anda uji secara independen, dan data mengalir dengan rapi dari satu langkah ke langkah berikutnya. Menambahkan pencatatan di setiap langkah membuat pipeline dapat diamati — Anda dapat melihat dengan tepat potongan mana yang diambil, berapa skornya, bagaimana konteks disusun, dan berapa banyak token yang digunakan. Visibilitas ini penting untuk menelusuri kesalahan dan meningkatkan kualitas pengambilan.
def answer_question(user_question, vector_index):
# Step 1: Embed query
q_vector = embed_query(user_question)
# Step 2: Retrieve
chunks = retrieve_chunks(q_vector, vector_index, top_k=5)
# Step 3: Filter low-confidence matches
chunks = filter_by_score(chunks, threshold=0.70)
if not chunks:
return {'answer': 'I do not have information about that topic.', 'sources': []}
# Step 4 & 5: Format and build prompt
context = format_context(chunks)
sources = [c['source'] for c in chunks]
# Step 6: Generate
return generate_answer(user_question, context, sources)Optimasi Latensi
Pipeline kueri memiliki dua langkah yang terikat I/O: pemanggilan embedding dan pemanggilan LLM. Jalankan keduanya tanpa menunggu yang tidak perlu: pemanggilan embedding cepat (<100 md), sedangkan pemanggilan LLM lambat (500 md–3 dtk). Untuk mengurangi latensi yang dirasakan, alirkan respons LLM agar token muncul saat dihasilkan, bukan setelah seluruh respons selesai. Simpan embedding untuk kueri identik yang berulang di cache agar pemanggilan API yang berulang dapat dihindari.
async def answer_question_streaming(question, index):
q_vector = embed_query(question)
chunks = retrieve_chunks(q_vector, index, top_k=5)
chunks = filter_by_score(chunks)
if not chunks:
yield 'I do not have information about that topic.'
return
context = format_context(chunks)
system_msg, user_msg = build_prompt(question, context)
stream = await client.chat.completions.create(
model='gpt-4o',
stream=True,
messages=[
{'role': 'system', 'content': system_msg},
{'role': 'user', 'content': user_msg}
]
)
async for chunk in stream:
delta = chunk.choices[0].delta.content or ''
yield deltaPencatatan untuk Keterlihatan Sistem
Pipeline RAG produksi memerlukan pencatatan terstruktur agar Anda dapat mendiagnosis kegagalan pengambilan atau jawaban buruk dari LLM. Catat kueri, ID dan skor potongan yang diambil, jumlah token konteks, jawaban, dan latensi untuk setiap permintaan. Simpan catatan ini di basis data atau platform pemantauan. Saat pengguna melaporkan jawaban yang buruk, Anda dapat memutar ulang kueri yang sama persis dan memeriksa potongan mana yang diambil serta alasan potongan tersebut tidak memadai.
import time
import logging
import json
def answer_question_with_logging(question, index):
start = time.time()
q_vector = embed_query(question)
chunks = retrieve_chunks(q_vector, index, top_k=5)
chunks = filter_by_score(chunks)
context = format_context(chunks)
result = generate_answer(question, context, [c['source'] for c in chunks])
latency_ms = (time.time() - start) * 1000
log_entry = {
'question': question,
'num_chunks_retrieved': len(chunks),
'chunk_scores': [c['score'] for c in chunks],
'tokens_used': result.get('tokens_used'),
'latency_ms': round(latency_ms)
}
logging.info(json.dumps(log_entry))
return resultMenyimpan Embedding Kueri di Cache
Jika aplikasi Anda menerima banyak kueri yang berulang atau hampir identik — misalnya bot FAQ yang sering menerima pertanyaan sama — menyimpan embedding kueri di cache merupakan optimasi sederhana dengan dampak besar. Hash string kueri, periksa cache Redis untuk embedding yang sesuai, dan panggil API embedding hanya jika data tidak ditemukan di cache. Rasio keberhasilan cache embedding sebesar 30–60% umum dijumpai dalam bot FAQ dan chatbot dukungan produksi, sehingga biaya API berkurang secara signifikan dan latensi turun 50–100 md untuk setiap kueri yang ditemukan di cache.
import hashlib
import json
import redis
r = redis.Redis(host='localhost', port=6379)
EMBED_CACHE_TTL = 86400 # 24 hours
def embed_query_cached(question):
cache_key = 'embed:' + hashlib.sha256(question.encode()).hexdigest()
cached = r.get(cache_key)
if cached:
return json.loads(cached) # cache hit
# Cache miss: call the API
vector = embed_query(question)
r.setex(cache_key, EMBED_CACHE_TTL, json.dumps(vector))
return vectorPemeriksaan Singkat
Uji pemahaman Anda tentang konsep Rekayasa AI dari pelajaran ini.
Rangkuman Pelajaran
Dalam pelajaran ini, Anda mempelajari: pipeline kueri enam langkah (mengubah kueri menjadi embedding, mengambil potongan, menyaring berdasarkan skor, memformat konteks, membangun prompt, dan menghasilkan jawaban), penyaringan berdasarkan ambang skor untuk menangani kueri di luar cakupan indeks, serta penyempurnaan untuk produksi, termasuk pengaliran respons, pencatatan terstruktur, dan optimasi latensi. Berikutnya, kita akan mempelajari cara mengevaluasi apakah sistem RAG lengkap Anda benar-benar bekerja dengan semestinya.
Belajar Python dengan tutor AI — gratis
Tulis dan jalankan kode asli di browser kamu, dapatkan bantuan instan dari tutor AI 24/7, dan lanjutkan di mana kamu tinggalkan di web atau aplikasi.
- Kursus
- 30
- Pelajaran
- 120
Pertanyaan yang Sering Diajukan
Apakah pelajaran “Mengueri, Mengambil, dan Menghasilkan” gratis?
Ya — teks lengkap “Mengueri, Mengambil, dan Menghasilkan” 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 “Mengueri, Mengambil, dan Menghasilkan”?
Tulis alur kueri yang menyematkan pertanyaan pengguna, mengambil k bagian teratas, memformat perintah yang diperkaya, memanggil LLM, dan mengembalikan jawaban bersitasi. 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 4 dari 4.
Berapa lama pelajaran “Mengueri, Mengambil, dan Menghasilkan” 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
- Memuat Dokumen dan Mengekstrak Teks
- Strategi Pembagian: Tetap vs Kalimat vs Rekursif
- Pengindeksan: Menyematkan dan Menyimpan Bagian
- Mengueri, Mengambil, dan Menghasilkan