Les systèmes RAG qui ne recherchent que dans un index local ne peuvent pas répondre aux questions en dehors de leur corpus. Ce tutoriel construit un système de récupération hybride : recherche locale de documents pour les questions de domaine connu (rapide, gratuit, privé) avec repli automatique vers l'API de recherche de Scavio pour les questions ouvertes (données web actuelles). Le seuil de confiance détermine quand effectuer le repli, et les étiquettes de source permettent au LLM d'attribuer correctement les réponses.
Prérequis
- Python 3.8+ installé
- Un index de recherche local (Meilisearch, Elasticsearch ou SQLite FTS)
- bibliothèque requests installée
- Une clé API Scavio depuis scavio.dev
Parcours
Étape 1: Configurer la fonction de recherche locale
Définissez une fonction qui recherche dans votre index local et renvoie des résultats avec des scores de confiance.
# Example with Meilisearch (replace with your index):
import meilisearch
local_client = meilisearch.Client('http://localhost:7700')
def local_search(query: str, top_k: int = 3) -> list:
results = local_client.index('docs').search(query, {'limit': top_k})
return [{
'text': hit['content'],
'title': hit.get('title', ''),
'score': hit.get('_rankingScore', 0),
'source': 'local'
} for hit in results['hits']]Étape 2: Configurer la fonction de recherche via API
Définissez une fonction qui recherche via Scavio lorsque les résultats locaux sont insuffisants.
import requests, os
H = {'x-api-key': os.environ['SCAVIO_API_KEY']}
def api_search(query: str, platform: str = 'google') -> list:
resp = requests.post('https://api.scavio.dev/api/v1/search', headers=H,
json={'platform': platform, 'query': query}, timeout=10)
return [{
'text': r.get('snippet', ''),
'title': r.get('title', ''),
'url': r.get('link', ''),
'source': 'web'
} for r in resp.json().get('organic', [])[:3]]Étape 3: Construire le récupérateur hybride
Aiguiller vers local ou API selon le seuil de confiance.
CONFIDENCE_THRESHOLD = 0.7
def hybrid_retrieve(query: str) -> dict:
local_results = local_search(query)
if local_results and local_results[0]['score'] >= CONFIDENCE_THRESHOLD:
return {'source': 'local', 'results': local_results}
web_results = api_search(query)
if web_results:
return {'source': 'web', 'results': web_results}
return {'source': 'none', 'results': local_results or []}Étape 4: Formater le contexte pour le LLM
Construire une chaîne de contexte de prompt avec des étiquettes de source pour l'attribution.
def format_context(retrieval: dict) -> str:
source_label = 'Internal docs' if retrieval['source'] == 'local' else 'Web search'
lines = [f'Source: {source_label}']
for r in retrieval['results']:
if r.get('url'):
lines.append(f"- {r['title']}: {r['text']} (ref: {r['url']})")
else:
lines.append(f"- {r['title']}: {r['text']}")
return '\n'.join(lines)
# Use in your RAG prompt:
# context = format_context(hybrid_retrieve(user_question))
# prompt = f'{context}\n\nQuestion: {user_question}\nAnswer:'Exemple Python
import requests, os
H = {'x-api-key': os.environ['SCAVIO_API_KEY']}
def hybrid_rag(query, local_index, threshold=0.7):
local = local_index.search(query, {'limit': 3})
if local['hits'] and local['hits'][0].get('_rankingScore', 0) >= threshold:
return {'source': 'local', 'context': [h['content'] for h in local['hits']]}
web = requests.post('https://api.scavio.dev/api/v1/search', headers=H,
json={'platform': 'google', 'query': query}, timeout=10).json()
return {'source': 'web', 'context': [r['snippet'] for r in web.get('organic', [])[:3]]}Exemple JavaScript
async function hybridRag(query, localIndex, threshold = 0.7) {
const local = await localIndex.search(query, {limit: 3});
if (local.hits?.length && local.hits[0]._rankingScore >= threshold) {
return {source: 'local', context: local.hits.map(h => h.content)};
}
const web = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST', headers: {'x-api-key': process.env.SCAVIO_API_KEY, 'Content-Type': 'application/json'},
body: JSON.stringify({platform: 'google', query})
}).then(r => r.json());
return {source: 'web', context: (web.organic || []).slice(0, 3).map(r => r.snippet)};
}Sortie attendue
A hybrid RAG retriever that searches local docs first and falls back to web search for out-of-corpus questions.