AI Engineering Academy · Pelajaran

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.

Pelajaran 4 dari 413 langkah

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 chunks

Langkah 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_message

Langkah 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 delta

Pencatatan 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 result

Menyimpan 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 vector

Pemeriksaan 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.

Gratis untuk memulai

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

  1. Memuat Dokumen dan Mengekstrak Teks
  2. Strategi Pembagian: Tetap vs Kalimat vs Rekursif
  3. Pengindeksan: Menyematkan dan Menyimpan Bagian
  4. Mengueri, Mengambil, dan Menghasilkan
← Kembali ke AI Engineering Academy