Mengurutkan dan Menyaring Hasil Pencarian
Nilai relevansi, deduplikasi, dan pemilihan hasil terbaik untuk konteks.
Mengurutkan dan Menyaring Hasil Pencarian adalah pelajaran AI Agents 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 Agents, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus AI Agents mencakup 4 pelajaran total.
Mengapa Pemeringkatan dan Penyaringan Penting
API pencarian mengembalikan 5–10 hasil, tetapi tidak semuanya sama-sama relevan, dapat diandalkan, atau berguna untuk tugas agen. Memasukkan hasil mentah langsung ke LLM memboroskan token konteks dan dapat menimbulkan gangguan atau informasi yang salah.
Pemeringkatan dan penyaringan meningkatkan rasio sinyal terhadap derau sebelum hasil mencapai LLM.
Penilaian Relevansi dengan BM25
BM25 (Best Match 25) adalah algoritme klasik untuk memeringkat teks yang menilai dokumen berdasarkan kesamaan kata kunci dengan kueri. Algoritme ini bekerja baik untuk pencocokan leksikal — ketika kueri dan dokumen menggunakan kata yang sama.
Instal dengan pip install rank-bm25.
from rank_bm25 import BM25Okapi
def rank_with_bm25(query, results):
# Tokenize: lowercase and split into words
tokenized_results = [
r['content'].lower().split()
for r in results
]
bm25 = BM25Okapi(tokenized_results)
query_tokens = query.lower().split()
scores = bm25.get_scores(query_tokens)
# Sort results by score descending
ranked = sorted(
zip(scores, results),
key=lambda x: x[0],
reverse=True
)
return [(score, result) for score, result in ranked]Penilaian Relevansi dengan Vektor Semantik
BM25 hanya mencocokkan kata yang sama persis. Kemiripan representasi vektor menangkap makna semantik — sehingga 'pengembangan web dengan Python' dan 'membangun situs web dengan Django' memiliki kemiripan tinggi meskipun menggunakan kata yang berbeda.
Gunakan kemiripan kosinus antara representasi vektor kueri dan representasi vektor hasil.
import numpy as np
import openai
import os
client = openai.OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
def embed(text):
resp = client.embeddings.create(
model='text-embedding-3-small',
input=text[:8000]
)
return np.array(resp.data[0].embedding)
def cosine_similarity(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
def rank_by_embedding(query, results):
q_emb = embed(query)
scored = []
for r in results:
r_emb = embed(r['content'][:1000])
score = cosine_similarity(q_emb, r_emb)
scored.append((score, r))
return sorted(scored, key=lambda x: x[0], reverse=True)Pemeringkatan Hibrida: BM25 + Vektor Semantik
Penilaian BM25 dan representasi vektor menangkap aspek relevansi yang berbeda. Pemeringkatan hibrida menggabungkan kedua skor dengan rata-rata berbobot, sehingga memperoleh hasil terbaik dari pencocokan leksikal dan semantik.
def hybrid_rank(query, results, bm25_weight=0.4, embed_weight=0.6):
# Get BM25 scores (normalized 0-1)
bm25_scored = rank_with_bm25(query, results)
max_bm25 = max(s for s, _ in bm25_scored) or 1
bm25_norm = {r['url']: s / max_bm25 for s, r in bm25_scored}
# Get embedding scores
embed_scored = rank_by_embedding(query, results)
embed_norm = {r['url']: s for s, r in embed_scored}
# Combine
combined = []
for r in results:
url = r['url']
score = (bm25_weight * bm25_norm.get(url, 0) +
embed_weight * embed_norm.get(url, 0))
combined.append((score, r))
return sorted(combined, key=lambda x: x[0], reverse=True)Deduplikasi berdasarkan URL
Hasil pencarian sering kali mengandung duplikat yang hampir sama: artikel yang sama dari beberapa sumber sindikasi, atau halaman yang sama dengan parameter URL berbeda. Deduplikasi menghapus hasil-hasil ini sebelum meneruskannya ke LLM.
from urllib.parse import urlparse, urlunparse
def normalize_url(url):
parsed = urlparse(url)
# Remove query params and fragment (tracking params, etc.)
clean = parsed._replace(query='', fragment='')
return urlunparse(clean).rstrip('/')
def deduplicate_results(results):
seen_urls = set()
unique = []
for r in results:
url = normalize_url(r.get('url', ''))
if url not in seen_urls:
seen_urls.add(url)
unique.append(r)
return unique
# Also deduplicate by content similarity (near-duplicate detection)
def deduplicate_by_content(results, min_unique_ratio=0.7):
unique = [results[0]] if results else []
for candidate in results[1:]:
cand_words = set(candidate['content'].lower().split())
is_duplicate = False
for kept in unique:
kept_words = set(kept['content'].lower().split())
overlap = len(cand_words & kept_words) / max(len(cand_words), 1)
if overlap > (1 - min_unique_ratio):
is_duplicate = True
break
if not is_duplicate:
unique.append(candidate)
return unique
if __name__ == '__main__':
demo_results = [
{'url': 'https://example.com/a?utm_source=x'},
{'url': 'https://example.com/a'},
{'url': 'https://example.com/b'},
]
unique = deduplicate_results(demo_results)
print(f'{len(demo_results)} results -> {len(unique)} unique')
for r in unique:
print(' -', r['url'])
Penilaian Kualitas Domain
Hasil dari docs.python.org lebih tepercaya daripada hasil dari blog acak. Tetapkan pengali kualitas untuk tingkatan domain dan sertakan nilainya dalam pemeringkatan akhir.
DOMAIN_QUALITY = {
# Tier 1 — authoritative (1.3x boost)
'docs.python.org': 1.3,
'developer.mozilla.org': 1.3,
'arxiv.org': 1.3,
'github.com': 1.2,
'stackoverflow.com': 1.2,
# Tier 2 — good (1.0x, no change)
# Tier 3 — low quality (penalty)
'pinterest.com': 0.3,
'quora.com': 0.5,
'wikihow.com': 0.6
}
def get_domain_multiplier(url):
from urllib.parse import urlparse
domain = urlparse(url).netloc.lower().replace('www.', '')
return DOMAIN_QUALITY.get(domain, 1.0) # default: no change
def apply_domain_boost(scored_results):
boosted = []
for score, r in scored_results:
multiplier = get_domain_multiplier(r.get('url', ''))
boosted.append((score * multiplier, r))
return sorted(boosted, key=lambda x: x[0], reverse=True)
if __name__ == '__main__':
scored = [(1.0, {'url': 'https://pinterest.com/x'}), (1.0, {'url': 'https://docs.python.org/x'})]
for score, r in apply_domain_boost(scored):
print(f"{r['url']}: boosted score {score:.2f}")
Memfilter Hasil Berkualitas Rendah
Beberapa hasil secara struktural berkualitas rendah terlepas dari domainnya: terlalu singkat untuk berguna, sebagian besar berisi teks navigasi, atau berasal dari halaman yang dibatasi login. Saring hasil-hasil ini sebelum melakukan pemeringkatan.
MIN_CONTENT_LENGTH = 200 # characters
LOW_QUALITY_SIGNALS = [
'sign in to view',
'please log in',
'subscribe to read',
'404 not found',
'access denied',
'this content is for members only'
]
def is_quality_result(result):
content = result.get('content', '')
# Too short
if len(content) < MIN_CONTENT_LENGTH:
return False
# Paywall / access barrier detected
content_lower = content.lower()
for signal in LOW_QUALITY_SIGNALS:
if signal in content_lower:
return False
return True
def filter_results(results):
return [r for r in results if is_quality_result(r)]
if __name__ == '__main__':
demo_results = [
{'content': 'Please log in to view this article which has plenty of extra padding text here.'},
{'content': 'A' * 250},
]
kept = filter_results(demo_results)
print(f'{len(demo_results)} results -> {len(kept)} passed quality filter')
Pemotongan Hasil untuk Anggaran Konteks
Bahkan setelah pemfilteran, Anda mungkin memiliki 5 hasil berkualitas tinggi dengan masing-masing 600 karakter — totalnya 3.000 karakter. Tentukan berapa banyak hasil yang sesuai dengan anggaran konteks LLM Anda, lalu potong hasil tersebut sesuai kebutuhan.
MAX_CONTEXT_CHARS = 4000
MAX_SNIPPET_CHARS = 600
def truncate_for_context(ranked_results, budget=MAX_CONTEXT_CHARS):
selected = []
used_chars = 0
for score, result in ranked_results:
content = result.get('content', '')[:MAX_SNIPPET_CHARS]
entry = f"Source: {result['title']}\nURL: {result['url']}\nContent: {content}"
entry_len = len(entry)
if used_chars + entry_len > budget:
break
selected.append(result)
used_chars += entry_len
return selected
ranked_results = [
(0.9, {'title': 'Doc A', 'url': 'http://a', 'content': 'x' * 800}),
(0.7, {'title': 'Doc B', 'url': 'http://b', 'content': 'y' * 800}),
]
budget = 1000
selected = truncate_for_context(ranked_results, budget=budget)
print(f'Selected {len(selected)} results within {budget}-char budget')Memformat Hasil untuk Perintah LLM
Setelah melakukan pemeringkatan, deduplikasi, dan pemotongan, format hasil sebagai daftar bernomor dalam perintah LLM. Sumber bernomor memudahkan model mengutipnya dalam jawabannya.
def format_results_for_prompt(results):
lines = ['Here are relevant search results:\n']
for i, r in enumerate(results, 1):
lines.append(f'[{i}] {r["title"]}')
lines.append(f' URL: {r["url"]}')
lines.append(f' {r.get("content", "")[:400]}')
lines.append('')
lines.append('Use these sources to answer the question. Cite sources as [1], [2], etc.')
return '\n'.join(lines)
# Usage in agent prompt
formatted = format_results_for_prompt(selected_results)
response = llm_call(
system='You are a research assistant.',
user=f'{formatted}\n\nQuestion: {user_question}'
)Menyimpan Hasil Pencarian dalam Cache
Kueri yang sama mungkin diulang dalam berbagai sesi atau perulangan agen. Simpan hasil pencarian dalam cache dengan TTL singkat (misalnya, 1 jam) untuk mengurangi biaya API dan mempercepat respons pada kueri yang diulang.
import hashlib
import time
search_cache = {} # In production: use Redis or disk cache
CACHE_TTL = 3600 # 1 hour
def cached_search(query, **kwargs):
cache_key = hashlib.md5(query.encode()).hexdigest()
entry = search_cache.get(cache_key)
if entry and (time.time() - entry['ts']) < CACHE_TTL:
print('Search cache hit')
return entry['results']
results = client.search(query=query, **kwargs)
search_cache[cache_key] = {
'results': results,
'ts': time.time()
}
return resultsAlur Pemrosesan Lengkap untuk Pemfilteran dan Pemeringkatan
Rangkaikan semua langkah ke dalam satu fungsi alur pemrosesan: ambil → saring hasil berkualitas rendah → hapus duplikat → beri peringkat → terapkan peningkatan domain → potong untuk konteks → format untuk perintah.
def search_and_rank(query, max_context_chars=4000):
# 1. Fetch
raw = cached_search(query, max_results=8)
results = raw.get('results', [])
# 2. Filter quality
results = filter_results(results)
# 3. Deduplicate
results = deduplicate_results(results)
# 4. Rank (BM25 fast path — save embedding costs)
scored = rank_with_bm25(query, results)
# 5. Domain boost
scored = apply_domain_boost(scored)
# 6. Truncate to context budget
selected = truncate_for_context(scored, budget=max_context_chars)
# 7. Format
return format_results_for_prompt(selected)Pemeriksaan Pengetahuan
Apa keunggulan utama pemeringkatan hibrida (BM25 + representasi vektor) dibandingkan hanya menggunakan BM25?
Rangkuman: Pemeringkatan dan Pemfilteran Hasil Pencarian
Hasil pencarian mentah perlu diproses sebelum diteruskan ke LLM. Alurnya: saring hasil berkualitas rendah (terlalu singkat, berbayar) → hapus duplikat berdasarkan URL dan isi → beri peringkat berdasarkan BM25 dan/atau kemiripan representasi vektor → terapkan peningkatan kualitas domain → potong sesuai anggaran konteks → format sebagai sumber bernomor.
Simpan hasil pencarian dalam cache untuk mengurangi biaya API pada kueri yang diulang. Sitasi bernomor dalam perintah memungkinkan LLM mengaitkan klaim dengan sumber tertentu.
Pertanyaan yang Sering Diajukan
Apakah pelajaran “Mengurutkan dan Menyaring Hasil Pencarian” gratis?
Ya — teks lengkap “Mengurutkan dan Menyaring Hasil Pencarian” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus AI Agents, upgrade ke CoddyKit PRO. Kursus AI Agents mencakup 4 pelajaran total.
Apa yang akan aku pelajari di “Mengurutkan dan Menyaring Hasil Pencarian”?
Nilai relevansi, deduplikasi, dan pemilihan hasil terbaik untuk konteks. Kamu berlatih AI Agents 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 Agents?
Tidak diperlukan pengalaman sebelumnya. AI Agents 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 “Mengurutkan dan Menyaring Hasil Pencarian” 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 Agents ini?
Ya. Setiap pelajaran AI Agents 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
- Tavily dan SerpAPI untuk Pencarian Agen
- Mengurutkan dan Menyaring Hasil Pencarian
- Pola Putaran Riset Mendalam
- Menggabungkan Pencarian Web dengan RAG