Créer un agent de recherche IA : tutoriel complet
Construisez un agent de recherche IA avec Python : collecte web, extraction, résumé automatique. Tutoriel complet avec code et architecture pas à pas.
Créer un agent de recherche IA : tutoriel complet
Introduction
Un agent de recherche IA diffère radicalement d'une simple requête Google. Là où un moteur de recherche retourne une liste de liens, un agent IA autonomise le processus complet : il identifie les sources pertinentes, extrait le contenu utile, évalue la qualité de l'information et produit un résumé actionnable.
Concrètement, un tel agent effectue ce parcours : search → fetch → extract → summarize → output. Chaque étape peut échouer, produire du bruit, ou être bloquée. L'agent doit gérer tout cela sans intervention humaine.
Dans ce tutoriel, vous allez construire un agent de recherche IA fonctionnel en Python. Nous utiliserons l'API Brave Search pour la collecte, requests + BeautifulSoup pour l'extraction, et un modèle de langage pour la synthèse. Chaque composant seracodé et testable séparément.
Ce que vous allez apprendre :
- architecturer un agent de recherche en composants réutilisables
- interfacer un moteur de recherche (Brave Search API) et extraire le contenu structuré
- gérer les cas de blocage (user-agent, délais, sites JavaScript)
- synthétiser du contenu long via un LLM avec gestion du context window
- orchestrer le tout avec un code propre et des erreurs explicites
Résumé rapide
| Étape | Outil | Rôle |
|---|---|---|
| Recherche web | Brave Search API | Identifier les URLs pertinentes |
| Extraction contenu | requests + BeautifulSoup | Extraire le texte utile |
| Synthèse LLM | OpenAI / local | Résumer avec score de pertinence |
| Orchestration | Python (asyncio) | Assembler les composants |
Qu'est-ce qu'un agent de recherche IA ?
Un agent de recherche IA est un programme qui combine plusieurs briques :
- Search — interroge un moteur de recherche et récupère des URLs
- Fetch — télécharge le HTML de chaque page
- Extract — parse le HTML pour isoler le contenu texte pertinent
- Summarize — envoie le contenu à un LLM qui produit un résumé
- Output — formate et retourne le résultat final
L'intérêt par rapport à une recherche manuelle ? L'agent peut traiter des dizaines de sources en parallèle, appliquer des critères de pertinence uniformes, et produire un digest structuré en quelques secondes.
C'est différent d'un simple script de scraping parce que l'agent prend des décisions : écarter les sources de faible qualité, relancer une recherche si les résultats sont insuffisants, reformuler la requête si nessus results sont trop vagues.
Pour une introduction aux principes fondamentaux des agents IA, consultez notre guide Créer un agent IA : guide complet.
Architecture de l'agent de recherche
Voici l'architecture que nous allons implémenter :
Query → Brave Search → URL list
↓
Fetch (async)
↓
Extract (BeautifulSoup)
↓
Score + Truncate par LLM
↓
Synthesize
↓
Digest JSON/MD
Chacun des blocs est un composant distinct avec une interface claire. Cette séparation permet de tester chaque pièce individuellement et de remplacer un composant sans toucher aux autres.
Pour les briques de base dont cet agent aura besoin (mémoire, outils, scoring), voir Outils pour agents IA.
Composant 1 : recherche web avec Brave Search API
Installation et configuration
pip install brave-search requests beautifulsoup4 aiohttp
Clé API
Brave Search offre 2 000 requêtes gratuites par mois. Inscrivez-vous sur brave.com/search/api et récupérez votre clé.
import os
import requests
BRAVE_API_KEY = os.getenv("BRAVE_API_KEY")
def search_brave(query: str, count: int = 10) -> list[dict]:
"""Retourne une liste de résultats {title, url, description}."""
url = "https://api.search.brave.com/res/v1/web/search"
headers = {
"Accept": "application/json",
"X-Subscription-Token": BRAVE_API_KEY,
}
params = {"q": query, "count": count}
response = requests.get(url, headers=headers, params=params, timeout=10)
response.raise_for_status()
data = response.json()
results = []
for item in data.get("web", {}).get("results", []):
results.append({
"title": item.get("title", ""),
"url": item.get("url", ""),
"description": item.get("description", ""),
})
return results
Points clés :
timeout=10—,避免 le blocage indéfini si l'API ne répond pascount— nombre de résultats (max 20 par requête)- Vérifiez toujours
response.raise_for_status()— les erreurs HTTP silencieuses sont un piège courant
Alternative : DuckDuckGo (sans clé API)
Si vous ne voulez pas gérer une clé API, duckduckgo-search fonctionne sans authentification :
pip install duckduckgo-search
from duckduckgo_search import DDGS
def search_duckduckgo(query: str, count: int = 10) -> list[dict]:
with DDGS() as ddgs:
results = list(ddgs.text(query, max_results=count))
return [{"title": r["title"], "url": r["href"], "description"]: r["body"]} for r in results]
DuckDuckGo est plus lent et moins fiable pour les requêtes massives. Brave Search est recommandé pour un usage en production.
Composant 2 : extraction du contenu
Extraction basique avec BeautifulSoup
import requests
from bs4 import BeautifulSoup
from urllib.parse import urlparse
HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/122.0.0.0 Safari/537.36"
),
"Accept-Language": "fr-FR,fr;q=0.9,en;q=0.8",
}
def extract_text(url: str, timeout: int = 10) -> str | None:
"""Télécharge une URL et extrait le texte brut du contenu principal."""
try:
response = requests.get(url, headers=HEADERS, timeout=timeout)
response.raise_for_status()
except requests.RequestException as e:
print(f"[extract] Échec {url}: {e}")
return None
soup = BeautifulSoup(response.text, "html.parser")
# Supprimer scripts, styles et nav
for tag in soup(["script", "style", "nav", "header", "footer", "aside"]):
tag.decompose()
# Extraire le corps principal
article = soup.find("article") or soup.find("main") or soup.body
if not article:
return None
# Paragraphes seulement
paragraphs = article.find_all("p")
text = "\n".join(p.get_text(strip=True) for p in paragraphs if len(p.get_text(strip=True)) > 50)
return text if text else None
Gestion des sites problématiques
Sites bloquant le scraping :
import time
import random
def extract_with_retry(url: str, retries: int = 3, delay: float = 1.0) -> str | None:
"""Extrait avec délai aléatoire entre les tentatives."""
for attempt in range(retries):
text = extract_text(url)
if text:
return text
if attempt < retries - 1:
time.sleep(delay + random.uniform(0, 1))
return None
Sites JavaScript-rendered (sans Selenium) :
Certains sites ne fonctionnent qu'en JavaScript. Une alternative légère à Selenium est trafilatura qui arrive à extraire le contenu de nombreux sites même sans rendu JS :
pip install trafilatura
import trafilatura
def extract_trafilatura(url: str) -> str | None:
downloaded = trafilatura.fetch_url(url)
if downloaded:
return trafilatura.extract(downloaded, include_comments=False)
return None
Composant 3 : synthèse LLM avec gestion du context window
Installation
pip install openai
import os
from openai import OpenAI
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
def summarize_text(text: str, query: str, model: str = "gpt-4o-mini") -> str:
"""Résume le texte en lien avec la requête de recherche."""
# Tronquer si trop long (le context window)
max_chars = 12000
if len(text) > max_chars:
text = text[:max_chars]
system_prompt = (
"Tu es un assistant qui synthétise des informations web. "
"Pour chaque source, donne : (1) le point clé, (2) un score de pertinence "
"de 1 à 10 par rapport à la requête, (3) une leçon actionable. "
"Sois concis et factuel."
)
user_prompt = f"Requête : {query}\n\nContenu :\n{text}"
response = client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
],
temperature=0.3,
max_tokens=500,
)
return response.choices[0].message.content
Gestion du context window :
- Le modèle
gpt-4o-minigère ~128k tokens mais facture les prompts longs - La troncature à 12 000 caractères (≈ 3 000 tokens) est un bon compromis coût/résultat
- Pour des documents très longs, divisez en chunks et synthétisez chaque chunk séparément, puis fusionnez
Option locale : Ollama
Si vous préférez éviter les API externes :
# Installation
curl -fsSL https://ollama.com/install.sh | sh
ollama pull llama3.2
import requests
def summarize_local(text: str, query: str) -> str:
payload = {
"model": "llama3.2",
"prompt": f"Requête : {query}\n\nContenu :\n{text[:12000]}\n\nSynthèse :",
"stream": False,
}
response = requests.post("http://localhost:11434/api/generate", json=payload, timeout=120)
return response.json()["response"]
Assembler l'agent complet
Orchestration séquentielle simple
from dataclasses import dataclass
from typing import Optional
@dataclass
class SearchResult:
title: str
url: str
description: str
content: Optional[str] = None
summary: Optional[str] = None
relevance_score: Optional[int] = None
def research_agent(query: str, num_results: int = 5) -> list[SearchResult]:
"""Pipeline complet : recherche → extraction → synthèse."""
# Étape 1 : Recherche
raw_results = search_brave(query, count=num_results)
results = [SearchResult(**r) for r in raw_results]
# Étape 2 : Extraction (séquentiel pour éviter de surcharger)
for result in results:
result.content = extract_with_retry(result.url)
if not result.content:
continue
# Étape 3 : Synthèse
result.summary = summarize_text(result.content, query)
# Extraire le score de pertinence (heuristique simple)
if result.summary:
result.relevance_score = estimate_relevance(result.summary, query)
# Filtrer les résultats sans contenu exploitable
return [r for r in results if r.content and r.summary]
def estimate_relevance(summary: str, query: str) -> int:
"""Score heuristique basé sur la présence des mots-clés de la requête."""
keywords = query.lower().split()
count = sum(1 for kw in keywords if kw in summary.lower())
return min(10, max(1, count * 2))
Version asynchrone (plus rapide)
import asyncio
import aiohttp
async def fetch_content(session: aiohttp.ClientSession, url: str) -> str | None:
"""Fetch asynchrone avec timeout."""
try:
async with session.get(url, headers=HEADERS, timeout=aiohttp.ClientTimeout(total=10)) as resp:
if resp.status == 200:
html = await resp.text()
soup = BeautifulSoup(html, "html.parser")
for tag in soup(["script", "style", "nav", "header", "footer"]):
tag.decompose()
article = soup.find("article") or soup.find("main") or soup.body
if article:
paragraphs = article.find_all("p")
return "\n".join(p.get_text(strip=True) for p in paragraphs if len(p.get_text(strip=True)) > 50)
except Exception:
pass
return None
async def research_agent_async(query: str, num_results: int = 5) -> list[SearchResult]:
raw_results = search_brave(query, count=num_results)
results = [SearchResult(**r) for r in raw_results]
async with aiohttp.ClientSession() as session:
# Extraction parallèle
contents = await asyncio.gather(*[fetch_content(session, r.url) for r in results])
for result, content in zip(results, contents):
result.content = content
if content:
result.summary = summarize_text(content, query)
return [r for r in results if r.content and r.summary]
Exemple concret : agent de veille concurrentielle
Cas d'usage
Vous cherchez à surveiller automatiquement les sorties de nouveaux outils d'IA concurrents. L'agent doit :
- Rechercher les derniers articles sur "nouvel outil IA"
- Extraire le contenu de chaque source
- Produire un digest avec titre, résumé, source et date estimée
from datetime import datetime
def format_digest(results: list[SearchResult], query: str) -> str:
"""Formate les résultats en digest lisible."""
lines = [
f"# Veille IA — {datetime.now().strftime('%Y-%m-%d')}",
f"**Requête :** {query}",
f"**Sources analysées :** {len(results)}\n",
]
for i, r in enumerate(sorted(results, key=lambda x: x.relevance_score or 0, reverse=True), 1):
lines.append(f"## {i}. {r.title}")
lines.append(f"**URL :** {r.url}")
lines.append(f"**Pertinence :** {'⭐' * (r.relevance_score or 0)}")
lines.append(f"\n{r.summary}\n")
lines.append("---")
return "\n".join(lines)
if __name__ == "__main__":
query = "nouvel outil IA 2026"
results = asyncio.run(research_agent_async(query, num_results=8))
digest = format_digest(results, query)
with open("veille_ia_digest.md", "w") as f:
f.write(digest)
print(f"Digest généré — {len(results)} sources traitées")
Sortie exemple :
# Veille IA — 2026-04-20
**Requête :** nouvel outil IA 2026
**Sources analysées :** 6
## 1. Anthropic lance Claude 4 avec capacités de raisement extendues
**URL :** https://example.com/claude-4
**Pertinence :** ⭐⭐⭐⭐⭐
Point clé : Claude 4 introduit un window context de 200k tokens...
Leçon actionable : Intégrer l'API Claude 4 pour les tâches de veille...
---
Ce digest peut être envoyé par email, stocké dans une base, ou posté sur Slack automatiquement.
Pour automatiser ce genre de workflow en production, voir Automatiser sa veille avec des agents IA.
Bonnes pratiques
Limiter les blocages par les sites :
- Toujours définir un
User-Agentréaliste - Respecter
robots.txtquand c'est possible - Ajouter un délai entre les requêtes (1-2 secondes)
- Utiliser
trafilaturaplutôt que BeautifulSoup pour les sites qui bloquent le scraping basique
Gestion du context window LLM :
- Ne jamais envoyer un texte complet à un LLM sans troncature préalable
- Privilégier les modèles avec grand context window pour les documents longs (
gpt-4o,claude-3-sonnet) - Pour les documents très longs (> 50k caractères), divisez en segments, synthétisez chaque segment, puis produisez une synthèse de synthèse
Fiabilité de l'extraction :
- Prévoir systématiquement un fallback quand
extract_textretourneNone(autre méthode, autre source) - Logger les URLs qui échouent pour identifier les patterns de blocage
- Tester sur 10-20 URLs variées avant de lancer un pipeline massif
Architecture :
- Gardez chaque composant testable séparément
- Un composant qui fait une seule chose bien est plus facile à déboguer qu'un pipeline monolithique
- Versionnez vos prompts LLM : un changement de prompt peut changer complètement la qualité des synthèses
Questions fréquentes
Comment gérer les sites qui bloquent le scraping ?
Plusieurs leviers :
- User-Agent réaliste — évitez le User-Agent Python par défaut
- Délai entre requêtes — 1-2 secondes entre chaque page
- trafilatura — meilleure détection du contenu utile que BeautifulSoup pur
- Fallback vers l'API Brave — Brave Search retourne parfois un résumé du contenu sans avoir besoin de crawler
Si un site bloque systématiquement, c'est souvent le signe qu'il n'autorise pas le scraping. Dans ce cas, cherchez une source alternative.
Quel modèle LLM choisir pour la synthèse ?
| Modèle | Context window | Coût | Recommandation |
|---|---|---|---|
| gpt-4o-mini | 128k | € | Bon rapport qualité/prix |
| gpt-4o | 128k | €€€ | Meilleure qualité de synthèse |
| claude-3-haiku | 200k | € | Rapide, bon marché |
| claude-3-sonnet | 200k | €€ | Excellent pour textes longs |
| Llama 3.2 (local) | 128k | Gratuit | Si vous voulez tout en local |
Pour un agent de veille en production, gpt-4o-mini ou claude-3-haiku suffisent largement pour des synthèses de 200-500 mots.
Comment éviter les coûts élevés avec les API LLM ?
- Tronquez systématiquement le texte avant envoi (12 000-15 000 caractères suffisent pour la plupart des synthèses)
- Filtrez les sources avant de les envoyer au LLM — un agent bien calibré peut écarter les sources de faible pertinence avant l'appel LLM
- Utilisez des modèles bon marché (
gpt-4o-mini,claude-3-haiku) pour les tâches de synthèse routine - Mettez en cache les résultats — si vous relancez la même veille plusieurs fois, ne rappelez pas le LLM pour les sources déjà traitées
Comment évaluer la qualité des résultats ?
L'approche la plus simple : un score de pertinence heuristique (mots-clés de la requête dans le résumé × pondération). Plus sophistiqué : fine-tuner un petit modèle de scoring binaire sur des exemples annotés.
En pratique, le meilleur indicateur reste un review humain des 10-20 premiers digests pour ajuster le prompt de synthèse et les critères de pertinence.
Peut-on utiliser cet agent pour de la veille SEO ?
Oui, avec des adaptations :
- Requêtes structurées par cluster de mots-clés (les короткие de vos泡w short-tail et long-tail)
- Stockage des résultats en base pour détecter les nouveautés (comparaison avec la veille précédente)
- Alerte si un concurrent apparaît dans les résultats pour la première fois
L'agent de recherche est la brique de collecte. L'intelligence de la veille (détection de tendance, alerting, stockage) se construit autour.
Articles liés
Un agent de recherche IA s'inscrit dans une démarche plus large de création d'agents autonomes. Pour aller plus loin, voici les ressources complémentaires :
- Créer un agent IA : guide complet — maîtrisez les bases de l'architecture agent avant d'y greffer une capa de recherche
- Outils pour agents IA — mémoire, scoring, outils : les briques qui rendront votre agent plus intelligent
- Automatiser sa veille avec des agents IA — comment passer de l'expérimen à un pipeline de veille en production, avec scheduling et alerting
La recherche web n'est que la première étape. Une fois l'agent capable de collecter de l'information, ouvrez le champ à la prise de décision automatisée : classification, routing, action sur les résultats. C'est là que l'agent de recherche devient un vrai levier opérationnel.
Restez informé sur les agents IA
Nouveaux tutoriels, comparatifs et guides pratiques directement dans votre boîte mail.