Kısa yanıt
Python'da nasıl derin araştırma ajanı oluştururum?
Bir LLM'e arama sorguları soran, her birini sayfa metni döndüren bir web arama API'si üzerinden çalıştıran, her kaynağı bir kez tutan, takip sorguları isteyen ve ardından atıflı bir yanıt yazan bir döngü yazın. LLMWebSearch ile her arama, ilk 3 sayfayı bir kredi karşılığında Markdown olarak döndürür; aşağıdaki betik soru başına altı arama çalıştırır. $1,20/1K arama itibarıyla KYC olmadan, kripto ile ödeme.
Ajan ne yapacak?
Plan
Modelden, soruyu birlikte kapsayan dört kısa sorgu isteyin.
Ara
Her sorguyu
read=3ile çalıştırın; böylece ilk üç sayfa Markdown olarak geri dönsün.Kaynakları bir kez tutun
Sonuçları URL'ye göre saklayın; zaten toplanmış veya metni olmayan sayfaları atlayın.
Boşlukları bulun
Şimdiye kadar bulunan başlıkları modele gösterin ve eksik olanlar hakkında iki sorgu isteyin.
Yazın
En fazla 12 kaynağı numaralandırın ve bunları [1], [2] biçiminde atıflayan bir yanıt isteyin.
Başlamadan önce neye ihtiyacım var?
Üç şey:
- Bir LLMWebSearch API anahtarı,
LLMWEBSEARCH_API_KEYortam değişkeninde. Anahtarlar, ilk paketinizden sonra panoda görünür. - Herhangi bir OpenAI uyumlu sohbet endpoint'i,
LLM_BASE_URL,LLM_API_KEYveLLM_MODELile ayarlanır. Barındırılan modeller ve yerel sunucular da çalışır. - Python 3.9 veya üstü ve
requestspaketi.
Betik her iki servis için de düz HTTP kullanır; yani arama kodunu değiştirmeden modeli değiştirebilirsiniz.
import os
import requests
SEARCH = "https://llmwebsearch.com/api/v1/search"
SEARCH_HEADERS = {"Authorization": "Bearer " + os.environ["LLMWEBSEARCH_API_KEY"]}
# Any OpenAI-compatible chat endpoint works here: set your own base URL, key and model name.
LLM_URL = os.environ.get("LLM_BASE_URL", "https://llm.example.com/v1") + "/chat/completions"
LLM_HEADERS = {"Authorization": "Bearer " + os.environ.get("LLM_API_KEY", "")}
MODEL = os.environ.get("LLM_MODEL", "your-model-name")
def ask_llm(prompt):
body = {"model": MODEL, "messages": [{"role": "user", "content": prompt}]}
resp = requests.post(LLM_URL, headers=LLM_HEADERS, json=body, timeout=120)
resp.raise_for_status()
return resp.json()["choices"][0]["message"]["content"]
def plan_queries(question, n=4):
text = ask_llm(f"Write {n} short web search queries, one per line, to answer: {question}")
queries = [line.strip("-*0123456789. ").strip() for line in text.splitlines()]
return [q for q in queries if q][:n]
def search(query):
body = {"q": query, "max_results": 5, "read": 3, "max_chars": 5000}
resp = requests.post(SEARCH, headers=SEARCH_HEADERS, json=body, timeout=60)
resp.raise_for_status()
return resp.json()["results"]
def research(question, rounds=2):
sources = {} # url -> result, so the same page is never cited twice
queries = plan_queries(question)
for _ in range(rounds):
for query in queries:
for r in search(query):
if r.get("content_status") == "ok" and r["url"] not in sources:
sources[r["url"]] = r
notes = "\n".join(f"- {r['title']}" for r in sources.values())
queries = plan_queries(f"{question}\nAlready found:\n{notes}\nWhat is still missing?", n=2)
numbered = list(sources.values())[:12]
context = "\n\n".join(
f"[{i}] {r['title']} ({r['url']})\n{r['content']}" for i, r in enumerate(numbered, 1)
)
prompt = (
"Answer the question using only the sources below. Cite them as [1], [2]. "
"Say so when the sources disagree or do not cover a point.\n\n"
f"Question: {question}\n\nSources:\n{context}"
)
return ask_llm(prompt), [r["url"] for r in numbered]
if __name__ == "__main__":
answer, urls = research("What changed in the EU rules for general-purpose AI models in 2025?")
print(answer)
for i, url in enumerate(urls, 1):
print(f"[{i}] {url}")Her bir parça nasıl çalışır?
plan_queries modelden her satırda bir tane olacak şekilde kısa sorgular ister ve liste işaretlerini kaldırır. Kısa sorgular, arama motorlarının sayfaları sıralama biçimine tam sorulardan daha iyi uyar.
search, POST https://llmwebsearch.com/api/v1/search'ü max_results=5, read=3 ve max_chars=5000 ile çağırır. Yanıt beş sonuç listeler; ilk üçü sayfa metnini content içinde taşır.
research turları yürütür. Kaynakları URL ile anahtarlanmış bir sözlükte tutar; böylece iki sorguyla bulunan bir sayfa bir kez sayılır. Yalnızca content_status değeri ok olan sonuçlar tutulur, çünkü yanıt modelin okuyabildiği metne dayanmalıdır.
Her turdan sonra model, şimdiye kadar toplanan başlıkları görür ve eksik olanlar için iki yeni sorgu önerir. Son prompt en fazla on iki kaynağı numaralandırır ve atıf kurallarını belirler.
Bir raporun maliyeti ne kadar?
Varsayılan ayarlarla soru başına altı arama: ilk turda dört, ikinci turda iki. read değeri 3 iken bu altı kredidir. Dolayısıyla $41,25 değerindeki 25.000 arama, 4.000'den fazla raporu kapsar.
Model çağrıları LLM faturanıza ek maliyet getirir: rapor başına üç planlama çağrısı ve bir uzun yanıt çağrısı. Yanıt çağrısı 5.000 karakterlik en fazla on iki sayfa taşır; modeliniz pahalıysa max_chars değerini düşürün.
Daha derine inmesini nasıl sağlarım?
Yapıyı değil, sayıları değiştirin:
- Daha fazla tur.
rounds=4, modelin iki tur daha boşlukları kovalamasını sağlar. - Tur başına daha fazla sorgu. Boşluk bulma çağrısındaki
ndeğerini 2'den 4'e çıkarın. - Daha dar kaynaklar. Resmi veya akademik siteler için arama gövdesine
include_domainsekleyin. - Yalnızca güncel materyal.
"time_range": "month"veya"topic": "news"ekleyin. - Sorgu başına daha fazla sayfa.
read=5beş sayfa okur, arama başına 2 kredi.
Ajanı güvende ve kibar nasıl tutarım?
Her sayfayı güvenilmeyen metin olarak ele alın. Son prompt, modele kaynakları yalnızca kanıt olarak kullanmasını söyler; kaynakların içindeki talimatların yok sayılması gerektiğine dair açık bir satır ekleyin. Bir kaynağın asla bir araç çağrısını veya dosya yazmayı tetiklemesine izin vermeyin.
Web tarafında, Okuyucumuz robots.txt'ye uyar: kendini LLMWebSearch olarak tanıtır, bir sitenin onun için ya da tüm robotlar için yazdığı kurallara uyar ve okumasına izin verilmeyen her sayfanın içeriğini dışarıda bırakır. Erişimi reddeden sayfalar içerik olmadan döner ve script bunları atlar. Bir site okuyucumuzu geri çevirdiğinde sonuç, içerik olmadan ve sitenin durumuyla döner: CAPTCHA'ları asla çözmeyiz, hesaplara giriş yapmayız veya bir engeli ya da ödeme duvarını aşmaya çalışmayız.
Neler ters gidebilir?
Testte üç şey ortaya çıkıyor:
- Belirsiz sorgular. Plan zayıfsa her tur aynı sayfaları bulur. Modelden belirli adlar ve tarihler içeren sorgular isteyin.
- Metinli çok az kaynak. Bazı konular ödeme duvarlarının arkasında. Sorguları genişletin veya daha fazla sonuca izin verin.
- Aşırı uzun prompt'lar. On iki uzun sayfa küçük bir modelin bağlamını aşabilir.
max_charsdeğerini düşürün veya sekiz kaynak tutun.
Her sorguyu ve döndürdüğü URL'leri loglayın. Bu logu okumak, plan prompt'unu geliştirmenin en hızlı yoludur.
Derin araştırma ajanı: sık sorulan sorular
Bunu yerel bir modelle çalıştırabilir miyim?
Evet, yerel sunucu OpenAI chat-completions formatını konuşuyorsa. LLM_BASE_URL değerini adresine, LLM_MODEL değerini model adına ayarlayın.
Bunu bir MCP aracına dönüştürebilir miyim?
Evet. research() fonksiyonunu bir MCP aracına sarın veya ajanınıza MCP sayfasındaki düz arama aracını verin ve döngüyü kendisinin çalıştırmasına izin verin.
Neden arama API'sinden doğrudan bir cevap istemiyorum?
Çünkü LLMWebSearch cevap yazmaz. API yazılı bir yanıt değil kaynaklar döndürür: sayfaları kendi modeliniz okur, yanıtı yazar ve URL'leri alıntılar.
Her aramanın maliyetini nasıl görürüm?
Her yanıt credits_used içerir ve GET https://llmwebsearch.com/api/v1/credits kalan bakiyenizi ücretsiz gösterir.