Réponse rapide
Comment créer un agent de recherche approfondie en Python ?
Écrivez une boucle qui demande à un LLM des requêtes de recherche, passe chacune par une API de recherche web qui renvoie le texte des pages, ne garde chaque source qu'une fois, demande des requêtes de suivi, puis rédige une réponse citée. Avec LLMWebSearch, chaque recherche renvoie les 3 premières pages en Markdown pour un crédit ; le script ci-dessous exécute six recherches par question. À partir de 1,20 $US/1K requêtes, sans KYC, payer en crypto.
Que fera l'agent ?
Formule
Demander au modèle quatre requêtes courtes qui couvrent ensemble la question.
Rechercher
Exécuter chaque requête avec
read=3, pour que les trois premières pages reviennent en Markdown.Ne garder chaque source qu'une fois
Stocker les résultats par URL et ignorer les pages déjà collectées ou sans texte.
Trouver les lacunes
Montrer au modèle les titres trouvés jusqu'ici et demander deux requêtes sur ce qui manque.
Rédiger
Numéroter jusqu'à 12 sources et demander une réponse qui les cite sous la forme [1], [2].
Que me faut-il avant de commencer ?
Trois choses :
- Une clé API LLMWebSearch dans la variable d'environnement
LLMWEBSEARCH_API_KEY. Les clés apparaissent dans le tableau de bord après votre premier pack. - N'importe quel endpoint de chat compatible OpenAI, défini avec
LLM_BASE_URL,LLM_API_KEYetLLM_MODEL. Les modèles hébergés et les serveurs locaux fonctionnent tous les deux. - Python 3.9 ou ultérieur avec le paquet
requests.
Le script utilise du HTTP simple pour les deux services, vous pouvez donc changer de modèle sans toucher au code de recherche.
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}")Comment fonctionne chaque partie ?
plan_queries demande au modèle des requêtes courtes, une par ligne, et supprime les marqueurs de liste. Les requêtes courtes correspondent mieux à la façon dont les moteurs de recherche classent les pages que les questions complètes.
search appelle POST https://llmwebsearch.com/api/v1/search avec max_results=5, read=3 et max_chars=5000. La réponse liste cinq résultats ; les trois premiers portent le texte de leur page dans content.
research exécute les tours. Il conserve les sources dans un dictionnaire indexé par URL, si bien qu'une page trouvée par deux requêtes ne compte qu'une fois. Seuls les résultats dont content_status vaut ok sont conservés, car la réponse doit reposer sur du texte que le modèle peut lire.
Après chaque tour, le modèle voit les titres collectés jusqu'ici et propose deux nouvelles requêtes sur ce qui manque. Le prompt final numérote jusqu'à douze sources et fixe les règles de citation.
Combien coûte un rapport ?
Six recherches par question avec les réglages par défaut : quatre au premier tour et deux au second. Avec read à 3, cela fait six crédits. 25 000 requêtes à 41,25 $US couvre donc plus de 4 000 rapports.
Les appels au modèle coûtent en plus sur votre facture LLM : trois appels de planification et un long appel de réponse par rapport. L'appel de réponse transporte jusqu'à douze pages de 5 000 caractères, donc réduisez max_chars si votre modèle est cher.
Comment le faire aller plus loin ?
Changez les nombres, pas la structure :
- Plus de tours.
rounds=4permet au modèle de traquer deux séries d'écarts supplémentaires. - Plus de requêtes par tour. Faites passer
nde 2 à 4 dans l'appel de recherche d'écarts. - Des sources plus ciblées. Ajoutez
include_domainsau corps de la recherche pour les sites officiels ou académiques. - Uniquement des documents récents. Ajoutez
"time_range": "month"ou"topic": "news". - Plus de pages par requête.
read=5lit cinq pages, à 2 crédits par recherche.
Ajoutez une règle d'arrêt avant de relever les limites : terminez la boucle quand un tour rapporte moins de deux nouvelles sources.
Comment garder l'agent sûr et courtois ?
Traitez chaque page comme un texte non fiable. Le prompt final indique au modèle d'utiliser les sources comme preuves uniquement ; ajoutez une ligne explicite précisant que les instructions contenues dans les sources doivent être ignorées. Ne laissez jamais une source déclencher un appel d'outil ou une écriture de fichier.
Côté web, Notre lecteur respecte robots.txt : il s'annonce comme LLMWebSearch, suit les règles qu'un site écrit pour lui ou pour tous les robots, et laisse de côté le contenu de toute page qu'il n'est pas autorisé à lire. Les pages qui refusent l'accès reviennent sans contenu et le script les ignore simplement. Quand un site refuse notre lecteur, le résultat revient sans contenu et avec le statut du site : nous ne résolvons jamais de CAPTCHA, ne nous connectons pas à des comptes et ne contournons ni un blocage ni un paywall.
Qu'est-ce qui peut mal tourner ?
Trois choses apparaissent en test :
- Des requêtes vagues. Si le plan est faible, chaque tour trouve les mêmes pages. Demandez au modèle des requêtes avec des noms et des dates précis.
- Trop peu de sources avec du texte. Certains sujets sont derrière des paywalls. Élargissez les requêtes ou autorisez plus de résultats.
- Des prompts trop longs. Douze pages longues peuvent dépasser le contexte d'un petit modèle. Baissez
max_charsou gardez huit sources.
Journalisez chaque requête et les URLs qu'elle a renvoyées. Lire ce journal est le moyen le plus rapide d'améliorer le prompt de planification.
Agent de recherche approfondie : questions fréquentes
Puis-je l'exécuter avec un modèle local ?
Oui, si le serveur local parle le format chat-completions d'OpenAI. Réglez LLM_BASE_URL sur son adresse et LLM_MODEL sur le nom du modèle.
Puis-je en faire un outil MCP ?
Oui. Enveloppez research() dans un outil MCP, ou donnez à votre agent l'outil de recherche simple de la page MCP et laissez-le exécuter la boucle lui-même.
Pourquoi ne pas demander directement une réponse à l'API de recherche ?
Parce que LLMWebSearch ne rédige pas de réponses. L'API renvoie des sources, pas une réponse rédigée : c'est votre propre modèle qui lit les pages, rédige la réponse et cite les URLs.
Comment voir le coût de chaque recherche ?
Chaque réponse inclut credits_used, et GET https://llmwebsearch.com/api/v1/credits affiche votre solde restant sans frais.