Combiner recherche Web et RAG
Récupération hybride : magasin vectoriel local et recherche Web en direct pour des réponses à jour.
Combiner recherche Web et RAG est une leçon AI Agents gratuite sur CoddyKit. Ceci est la leçon 4 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage AI Agents, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours AI Agents comprend 4 leçons au total.
Le défi de la récupération hybride
La plupart des agents utilisés dans le monde réel ont besoin de deux types de connaissances : des connaissances statiques propres au domaine (documentation de l'entreprise, manuels des produits, politiques) et des informations actuelles (actualités, prix en temps réel, événements récents).
Un magasin vectoriel local gère les premières, tandis que la recherche web gère les secondes. Leur combinaison réunit les avantages des deux approches.
Architecture : RAG local + recherche web
Le système hybride dirige chaque question vers la bonne source de récupération :
- Magasin vectoriel (RAG) — documents indexés, connaissances stables, données privées
- Recherche web — événements actuels, versions récentes, données en temps réel
- Les deux — lorsque la question nécessite le contexte des documents AND des informations actuelles
class HybridRetrievalAgent:
def __init__(self, vector_store, search_client):
self.vector_store = vector_store # e.g., ChromaDB or FAISS
self.search_client = search_client # e.g., TavilyClient
def answer(self, question):
route = self.classify_question(question)
if route == 'static':
context = self.vector_store.query(question, n_results=5)
elif route == 'current':
context = self.web_search(question)
else: # 'both'
local = self.vector_store.query(question, n_results=3)
web = self.web_search(question)
context = local + web
return self.generate_answer(question, context)
if __name__ == '__main__':
class DemoAgent(HybridRetrievalAgent):
def classify_question(self, question):
return 'static' if 'company' in question.lower() else 'current'
def web_search(self, question):
return [('Web result about ' + question, {})]
def generate_answer(self, question, context):
return f'Answer using {len(context)} context item(s).'
class FakeVectorStore:
def query(self, question, n_results=5):
return [('Local doc snippet', {})]
agent = DemoAgent(FakeVectorStore(), search_client=None)
print(agent.answer('What is our company policy on refunds?'))
Le classifieur de routage
Le classifieur détermine la source de récupération à utiliser. Vous pouvez l'implémenter sous la forme d'un appel au LLM, d'un ensemble de règles fondées sur des mots-clés ou d'un petit classifieur entraîné. Un routeur fondé sur un LLM est le plus flexible.
ROUTING_PROMPT = '''Classify this question into one of three categories:
- "static": answered from company documents, product docs, or stable technical knowledge
- "current": requires up-to-date information (news, prices, recent events, latest releases)
- "both": needs both company context and current information
Question: {question}
Respond with exactly one word: static, current, or both.'''
def classify_question(question):
response = llm_call(ROUTING_PROMPT.format(question=question))
route = response.strip().lower()
if route not in ('static', 'current', 'both'):
return 'both' # safe default
return routeConfiguration d'un magasin vectoriel local
Pour la base de connaissances statiques, utilisez ChromaDB, une base de données vectorielle légère qui s'exécute dans le même processus. Indexez vos documents une fois, puis interrogez-les au moment de l'exécution.
Installez-le avec pip install chromadb openai.
import chromadb
from chromadb.utils import embedding_functions
import os
client = chromadb.PersistentClient(path='./vector_db')
ef = embedding_functions.OpenAIEmbeddingFunction(
api_key=os.getenv('OPENAI_API_KEY'),
model_name='text-embedding-3-small'
)
collection = client.get_or_create_collection(
name='company_docs',
embedding_function=ef
)
def index_document(doc_id, text, metadata=None):
collection.add(
ids=[doc_id],
documents=[text],
metadatas=[metadata or {}]
)
def local_retrieve(question, n_results=5):
results = collection.query(
query_texts=[question],
n_results=n_results
)
return list(zip(results['documents'][0], results['metadatas'][0]))Récupération par recherche web
Le parcours de recherche web utilise Tavily pour récupérer des informations actuelles. Mettez les résultats en forme de manière cohérente afin de pouvoir les combiner avec les résultats du RAG local dans la même structure de consigne.
from tavily import TavilyClient
import os
tavily = TavilyClient(api_key=os.getenv('TAVILY_API_KEY'))
def web_retrieve(question, n_results=3):
results = tavily.search(
query=question,
max_results=n_results,
search_depth='basic'
)
# Normalize to same format as local results
return [
(
r['content'][:600], # text
{'source': r['url'], 'title': r['title'], 'type': 'web'} # metadata
)
for r in results.get('results', [])
]Fusion des résultats locaux et web
Lorsque les deux sources sont utilisées, fusionnez les résultats et indiquez l'origine de chacun. Le LLM peut ainsi leur attribuer le poids approprié : les documents locaux pour les faits propres à l'entreprise et le web pour les données actuelles.
def merge_results(local_results, web_results):
merged = []
for text, meta in local_results:
merged.append({
'content': text,
'source': meta.get('source', 'internal document'),
'type': 'local',
'title': meta.get('title', 'Company Document')
})
for text, meta in web_results:
merged.append({
'content': text,
'source': meta.get('source', 'web'),
'type': 'web',
'title': meta.get('title', 'Web Result')
})
return merged
def format_merged_for_prompt(merged_results):
parts = []
for i, r in enumerate(merged_results, 1):
tag = '[INTERNAL]' if r['type'] == 'local' else '[WEB]'
parts.append(f'[{i}] {tag} {r["title"]}\n{r["content"]}')
return '\n\n'.join(parts)
if __name__ == '__main__':
local = [('Refunds are processed within 5 business days.', {'source': 'handbook', 'title': 'Refund Policy'})]
web = [('Company X reported Q2 earnings today.', {'source': 'reuters.com', 'title': 'Q2 Earnings'})]
merged = merge_results(local, web)
print(format_merged_for_prompt(merged))
Détection des questions nécessitant des informations à jour
En complément du classifieur fondé sur un LLM, utilisez des heuristiques fondées sur des mots-clés pour détecter les questions qui nécessitent des informations actuelles. Cette approche est plus rapide et évite un appel supplémentaire au LLM dans les cas évidents.
CURRENT_EVENTS_SIGNALS = [
'latest', 'current', 'today', 'now', 'recent',
'this week', 'this month', 'this year',
'just released', 'new version', 'updated',
'price', 'stock', 'news', 'announcement',
'2024', '2025'
]
STATIC_SIGNALS = [
'how does', 'what is', 'explain', 'tutorial',
'documentation', 'our product', 'company policy',
'internal', 'handbook'
]
def fast_route(question):
lower = question.lower()
current_score = sum(1 for s in CURRENT_EVENTS_SIGNALS if s in lower)
static_score = sum(1 for s in STATIC_SIGNALS if s in lower)
if current_score > static_score:
return 'current'
elif static_score > current_score:
return 'static'
else:
return 'both'
if __name__ == '__main__':
for q in ['What is our company handbook policy on PTO?', 'What is the latest stock price today?']:
print(f'{fast_route(q)!r} <- "{q}"')
Gestion des conflits entre les sources
Un conflit survient lorsque les documents locaux affirment une chose et qu'un résultat web en affirme une autre. Par exemple, votre document interne sur les tarifs indique 50 $ par mois, tandis qu'un résultat web indique que le prix est passé à 80 $ par mois.
Demandez au LLM de signaler les conflits et de privilégier les sources web pour les faits sensibles à l'actualité.
HYBRID_ANSWER_PROMPT = '''You are answering a question using two types of sources:
- [INTERNAL] sources: company documents (may be outdated)
- [WEB] sources: current web information
For factual claims about current state (prices, versions, availability):
PREFER [WEB] sources over [INTERNAL] ones.
For company-specific processes, policies, and architecture:
PREFER [INTERNAL] sources.
If sources conflict, note the discrepancy in your answer.
Sources:
{sources}
Question: {question}
Answer:'''
def generate_hybrid_answer(question, merged_results):
sources_text = format_merged_for_prompt(merged_results)
return llm_call(HYBRID_ANSWER_PROMPT.format(
sources=sources_text,
question=question
))Détection de l'obsolescence des documents locaux
Les documents locaux deviennent obsolètes avec le temps. Ajoutez une vérification d'obsolescence : si un document local est plus ancien qu'un certain seuil, complétez-le par une recherche web, même si le routeur a classé la question comme « statique ».
from datetime import datetime, timedelta
STALENESS_THRESHOLD_DAYS = 90
def check_staleness(metadata):
indexed_at = metadata.get('indexed_at')
if not indexed_at:
return False # unknown age — assume fresh
indexed_date = datetime.fromisoformat(indexed_at)
age = datetime.now() - indexed_date
return age > timedelta(days=STALENESS_THRESHOLD_DAYS)
def smart_retrieve(question, route):
local_results = []
web_results = []
if route in ('static', 'both'):
local_results = local_retrieve(question, n_results=4)
# Check if any local results are stale
stale = any(check_staleness(meta) for _, meta in local_results)
if stale:
print('Stale local docs — adding web search')
web_results = web_retrieve(question, n_results=2)
if route in ('current', 'both'):
web_results = web_retrieve(question, n_results=3)
return merge_results(local_results, web_results)Évaluation de la confiance
Associez un indice de confiance à chaque élément de contexte récupéré. Les sources présentant un niveau de confiance élevé (récentes, provenant d'un domaine faisant autorité et ayant une forte similarité avec la représentation vectorielle) doivent peser davantage dans la réponse finale.
def score_result(result, query_embedding):
score = 0.5 # base score
# Recency bonus for web results
if result.get('type') == 'web':
pub_date = result.get('published_date', '')
if '2024' in pub_date or '2025' in pub_date:
score += 0.2
# Embedding similarity to query
if result.get('content'):
result_emb = embed(result['content'][:500])
sim = cosine_similarity(query_embedding, result_emb)
score += sim * 0.3
# Domain authority
from urllib.parse import urlparse
domain = urlparse(result.get('source', '')).netloc
if any(auth in domain for auth in ['docs.', 'developer.', 'official.']):
score += 0.1
return min(score, 1.0)Flux complet de récupération hybride
Pour tout réunir : routage rapide → récupération intelligente depuis l'une des sources ou les deux → complément en cas d'obsolescence → fusion → évaluation → mise en forme → génération de la réponse.
def hybrid_answer(question):
# 1. Route (fast heuristic first, LLM fallback for ambiguous)
route = fast_route(question)
if route == 'both':
route = classify_question(question) # LLM for ambiguous cases
print(f'Route: {route}')
# 2. Retrieve
merged = smart_retrieve(question, route)
if not merged:
return 'I could not find relevant information to answer your question.'
# 3. Generate
answer = generate_hybrid_answer(question, merged)
return answer
# Usage
print(hybrid_answer('What is our refund policy?')) # -> static/local
print(hybrid_answer('What is GPT-4 pricing today?')) # -> current/webVérification des connaissances
Quand l'agent de récupération hybride doit-il privilégier les résultats de recherche web aux résultats des documents locaux ?
Récapitulatif : combiner la recherche web et le RAG
La récupération hybride combine un magasin vectoriel local (pour les connaissances statiques, privées ou propres au domaine) avec la recherche web (pour les informations publiques et actuelles). Un classifieur de routage dirige chaque question vers la bonne source, ou vers les deux lorsque cela est nécessaire.
Techniques principales : routage heuristique rapide fondé sur des signaux liés aux mots-clés, détection de l'obsolescence des documents locaux, consignes de résolution des conflits dans la consigne et évaluation de la confiance pour pondérer le contexte récupéré.
Questions Fréquemment Posées
La leçon « Combiner recherche Web et RAG » est-elle gratuite ?
Oui — le texte complet de « Combiner recherche Web et RAG » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours AI Agents, passe à CoddyKit PRO. Le cours AI Agents comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « Combiner recherche Web et RAG » ?
Récupération hybride : magasin vectoriel local et recherche Web en direct pour des réponses à jour. Tu pratiques AI Agents avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer AI Agents ?
Aucune expérience préalable n'est requise. AI Agents sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 4 sur 4.
Combien de temps prend la leçon « Combiner recherche Web et RAG » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon AI Agents ?
Oui. Chaque leçon AI Agents inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Tavily et SerpAPI pour la recherche des agents
- Classer et filtrer les résultats de recherche
- Schéma de boucle de recherche approfondie
- Combiner recherche Web et RAG