Agent kamu bisa manggil API, baca file, dan refactor satu modul tanpa keringetan. Tapi tanya soal rilis bulan lalu, dia jawab dari ingatan, dengan signature fungsi yang salah dan percaya diri penuh.
Tool search menyelesaikan itu. Kebanyakan orang langsung ambil API search berbayar: daftar, salin key, bayar per query. Jalan sih, sampai satu loop multi-agent nembak puluhan pencarian per task, atau key-nya kena rate limit pas demo.
SearXNG opsi yang di-host sendiri. Dia metasearch: query kamu dikirim ke Google, Bing, DuckDuckGo, Brave, dan sederet engine kecil, lalu hasilnya digabung, dibuang duplikatnya, dan diranking dalam bentuk JSON. Tanpa key, tanpa tagihan per query, dan query-nya nggak keluar dari infrastruktur yang kamu pegang.
Tapi bukan berarti dia API search plus-plus. SearXNG nggak crawl halaman, nggak bikin ringkasan, dan nggak merapikan snippet seperti Tavily. Yang kamu dapat: judul, URL, snippet, dan metadata engine. Ngubah itu jadi jawaban tetap kerjaan model.
Yang perlu disiapkan
- Docker Engine plus plugin compose (cek pakai
docker compose version) - Python 3.10+ buat kode client
httpxdanopenaiterpasang- Sepuluh menit
Langkah 1: jalankan compose resmi
SearXNG punya template compose dengan dua service: core buat aplikasinya dan valkey, yang baru kepakai kalau limiter dinyalakan.
mkdir -p ./searxng/core-config && cd ./searxng
curl -fsSL -O https://raw.githubusercontent.com/searxng/searxng/master/container/docker-compose.yml
curl -fsSL -O https://raw.githubusercontent.com/searxng/searxng/master/container/.env.example
cp .env.example .env
Edit .env biar port-nya cuma bind ke loopback:
SEARXNG_HOST=127.0.0.1
SEARXNG_PORT=8080
Nyalakan sekali. Di boot pertama container menyalin template settings ke core-config/ dan bikin secret_key acak buat kamu:
docker compose up -d
docker compose logs --tail 10 core
Buka core-config/settings.yml. Isinya diawali use_default_settings: true, artinya file itu cuma nyimpen override dan sisanya ikut image. Tambahkan format JSON:
search:
formats:
- html
- json
Restart aplikasinya biar file baru kebaca:
docker compose restart core
Output JSON mati secara default di SearXNG. Lewatin langkah ini dan tiap request API balas 403, bukan hasil kosong. Ini yang bikin orang sibuk nyari bug di client-nya.
Langkah 2: cek endpoint JSON
curl -s "http://127.0.0.1:8080/search?q=docker+multi-stage+build&format=json" | jq '.results[0] | {title, url, engine}'
Di instance baru hasilnya kira-kira begini:
{
"title": "Multi-stage builds | Docker Docs",
"url": "https://docs.docker.com/build/building/multi-stage/",
"engine": "brave"
}
Response-nya bawa results, answers, infoboxes, suggestions, corrections, dan unresponsive_engines. Di image sekarang nggak ada field total hasil, jadi len(data["results"]) itu hitunganmu. Tiap result punya url, title, content (snippet), engine, engines (semua engine yang balikin URL itu), score, category, dan kadang publishedDate.
403 di sini cuma berarti satu hal: format-nya belum aktif. Cek search.formats dulu sebelum curiga ke tempat lain.
Langkah 3: parameter yang beneran ngaruh
q satu-satunya parameter wajib. Empat lainnya yang paling ngaruh buat agent:
categories: dipisah koma, misalnyageneral,news,it,science. Buat pertanyaan developer,itpaling berguna: docs, Q&A, halaman package.language:en,id, dan kode locale lain. Set eksplisit, kalau nggak, default instance yang nentuin.time_range:day,month, atauyear. Engine yang nggak dukung bakal mengabaikannya tanpa error.pageno: nomor halaman kalau butuh hasil lebih banyak.
format=json wajib ada di tiap request. Format yang belum diaktifkan balas 403, dan itu perilaku resmi, bukan bug.
SearXNG menggabung URL duplikat antar engine lalu ranking hasil gabungannya. Array engines jadi sinyal relevansi murah: halaman docs yang muncul dari tiga engine biasanya lebih layak dipilih daripada satu post forum dari satu engine.
Langkah 4: client yang bisa dipanggil agent
Bagian pentingnya: timeout pendek, error dibalikin sebagai data bukan exception, dedupe per URL, dan snippet dibatasi biar satu halaman panjang nggak makan context window.
import os
import httpx
SEARXNG_URL = os.environ.get("SEARXNG_URL", "http://127.0.0.1:8080")
def web_search(query: str, categories: str = "general", language: str = "en",
time_range: str | None = None, limit: int = 8) -> list[dict]:
params = {
"q": query,
"format": "json",
"categories": categories,
"language": language,
"safesearch": 0,
}
if time_range:
params["time_range"] = time_range
try:
resp = httpx.get(f"{SEARXNG_URL}/search", params=params, timeout=10.0)
except httpx.RequestError as exc:
return [{"error": f"search backend unreachable: {exc}"}]
if resp.status_code == 403:
return [{"error": "JSON format disabled: add json to search.formats"}]
if resp.status_code == 429:
return [{"error": "rate limited by the SearXNG limiter"}]
resp.raise_for_status()
seen, out = set(), []
for item in resp.json().get("results", []):
url = item.get("url")
if not url or url in seen:
continue
seen.add(url)
out.append({
"title": item.get("title", "").strip(),
"url": url,
"snippet": (item.get("content") or "").strip()[:400],
"engine": ",".join(item.get("engines") or [item.get("engine", "")]),
"published": item.get("publishedDate"),
})
if len(out) >= limit:
break
return out
Jalanin dulu tanpa LLM buat mastiin jalurnya bener:
SEARXNG_URL=http://127.0.0.1:8080 python -c "from search_client import web_search; print(web_search('kubernetes pod disruption budget', limit=3))"
Balikin error sebagai dict kelihatan malas, tapi itu yang bikin agent tetap jalan. Model yang terima {"error": "JSON format disabled"} bisa retry dengan argumen lain atau ngasih tahu user. Stack trace cuma bikin run-nya berhenti.
Langkah 5: pasang sebagai tool
Client OpenAI-compatible apa pun bisa dipakai, termasuk model lokal di belakang Ollama atau vLLM. Schema tool sengaja dibuat kecil, karena tiap field tambahan jadi satu cara lagi buat model salah manggil.
import json
import os
from openai import OpenAI
from search_client import web_search
client = OpenAI()
tools = [{
"type": "function",
"function": {
"name": "web_search",
"description": "Search the web. Returns titles, URLs and snippets.",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Search query"},
"categories": {"type": "string", "description": "general, news, it, science"},
"time_range": {"type": "string", "enum": ["day", "month", "year"]},
},
"required": ["query"],
},
},
}]
model = os.environ.get("MODEL", "gpt-4.1-mini")
messages = [{"role": "user", "content": "What changed in Kubernetes 1.34 about sidecar containers?"}]
resp = client.chat.completions.create(model=model, messages=messages, tools=tools)
msg = resp.choices[0].message
if msg.tool_calls:
messages.append(msg)
for call in msg.tool_calls:
args = json.loads(call.function.arguments)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(web_search(**args)),
})
resp = client.chat.completions.create(model=model, messages=messages, tools=tools)
print(resp.choices[0].message.content)
Begitu search jalan, tool berikutnya yang paling masuk akal adalah fetch: ambil URL teratas, tarik halamannya, buang HTML-nya, dan biarkan model mengutip dari sumber asli, bukan dari snippet.
Langkah 6: jaga tetap hidup
SearXNG nggak punya autentikasi. Simpan di loopback atau jaringan privat. Kalau harus diakses dari luar, taruh reverse proxy dengan auth di depannya.
Kalau instance-nya publik, nyalakan limiter. Limiter butuh Valkey, dan compose tadi sudah menjalankannya:
server:
limiter: true
valkey:
url: valkey://searxng-valkey:6379/0
Di belakang reverse proxy, limiter baca IP client dari X-Forwarded-For dan X-Real-IP. Kalau proxy nggak set header itu, semua request kelihatan dari satu alamat dan agent kamu sendiri yang kena throttle.
Engine juga bisa rusak. Google ngasih CAPTCHA, Bing rate limit, DuckDuckGo diem. SearXNG men-suspend engine setelah gagal, default sehari untuk access denied dan CAPTCHA, sejam untuk terlalu banyak request. Statusnya kelihatan per request di unresponsive_engines, dan bisa diatur di search.suspended_times.
Kalau satu engine terus gagal, matikan saja. Engine lebih sedikit berarti lebih jarang kena ban dan response lebih cepat:
engines:
- name: google
disabled: true
Update pakai dua perintah standar, setelah cek dulu perubahan di template compose-nya:
docker compose pull
docker compose up -d
Kapan pakai ini, kapan bayar API search
API hosted seperti Tavily, Brave, Exa, atau Serper ngasih uptime yang dikelola, snippet lebih rapi, dan endpoint tambahan seperti ekstraksi halaman atau generasi jawaban. Buat prototipe atau beban beberapa ribu query per bulan, langganannya lebih murah daripada satu sore ngoprek engine.
Scrape endpoint HTML DuckDuckGo sendiri itu opsi tanpa infrastruktur, dan rusak diam-diam begitu markup-nya berubah. Satu engine, tanpa dedupe, tanpa fallback waktu dia menganggap kamu bot.
SearXNG menang kalau volume search tinggi atau susah ditebak, kalau query nggak boleh keluar jaringan, dan kalau kamu mau beberapa engine di satu endpoint. Tukarnya jelas: uptime jadi tanggung jawabmu, dan engine bakal rewel.
Error yang kemungkinan besar kamu temui
403diformat=json:jsonbelum ada disearch.formats. Restartcoresetelah edit.- HTTP 200 tapi nol hasil: query terlalu sempit,
time_rangeterlalu ketat, atau filter bahasa menyingkirkan semuanya. Longgarkan salah satu. unresponsive_enginesterus nambah: engine memblokir IP instance. Cekdocker compose logs core | grep -i captcha, kurangi jumlah kategori, dan tunggu masa suspend selesai.- Agent di container lain nggak bisa akses
127.0.0.1:8080: taruh keduanya di satu network compose dan pakaihttp://core:8080. - Edit settings nggak ngaruh: file harus ada di direktori
core-config/yang di-mount, dan service perlu restart. Error YAML muncul di log dan bikin container berhenti. - Limiter memblokir agent sendiri: limiter bekerja per IP client. Matikan di jaringan privat, atau atur
trusted_proxiesdilimiter.toml.
Penutup
Dua perintah cukup buat sebagian besar setup: docker compose up -d dan satu curl dengan format=json. Sisanya tuning di sekitar dua itu.
Peningkatan berikutnya yang paling terasa adalah tool fetch, karena snippet cukup buat milih URL tapi nggak cukup buat mengutipnya dengan akurat.
Referensi
- Instalasi container SearXNG: https://docs.searxng.org/admin/installation-docker.html
- Parameter dan format Search API: https://docs.searxng.org/dev/search_api.html
- Referensi setting
search:: https://docs.searxng.org/admin/settings/settings_search.html - Limiter dan bot detection: https://docs.searxng.org/admin/searx.limiter.html