Combinazione della ricerca web con RAG
Retrieval ibrido: vector store locale e ricerca web in tempo reale per risposte aggiornate
Combinazione della ricerca web con RAG è una lezione AI Agents gratuita su CoddyKit. Questa è la lezione 4 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento AI Agents, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso AI Agents include 4 lezioni in totale.
La sfida del recupero ibrido
La maggior parte degli agenti del mondo reale necessita di due tipi di conoscenze: conoscenze statiche di dominio (documentazione aziendale, manuali dei prodotti, policy) e informazioni aggiornate (notizie, prezzi attuali, eventi recenti).
Un vector store locale gestisce il primo tipo, mentre la ricerca sul Web gestisce il secondo. Combinarli consente di ottenere il meglio di entrambi.
Architettura: RAG locale + ricerca sul Web
Il sistema ibrido indirizza ogni domanda verso la fonte di recupero corretta:
- Vector store (RAG) — documenti indicizzati, conoscenze stabili, dati privati
- Ricerca sul Web — eventi attuali, release recenti, dati in tempo reale
- Entrambi — quando la domanda richiede il contesto dei documenti E informazioni aggiornate
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?'))
Il classificatore di routing
Il classificatore decide quale fonte di recupero utilizzare. Può implementarlo come una chiamata LLM, un insieme di regole basate su parole chiave oppure un piccolo classificatore addestrato. Un router basato su LLM è la soluzione più flessibile.
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 routeConfigurazione di un vector store locale
Per la knowledge base statica, utilizzi ChromaDB, un database vettoriale leggero che viene eseguito all'interno del processo. Indicizzi i documenti una volta sola e li interroghi durante l'esecuzione.
Installi con 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]))Recupero tramite ricerca sul Web
Il percorso di ricerca sul Web utilizza Tavily per recuperare informazioni aggiornate. Formatti i risultati in modo coerente, così potranno essere combinati con i risultati del RAG locale nella stessa struttura del prompt.
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', [])
]Unione dei risultati locali e Web
Quando utilizza entrambe le fonti, unisca i risultati e assegni a ciascuno un'etichetta che ne indichi l'origine. In questo modo l'LLM può attribuire loro il giusto peso: i documenti locali per i fatti specifici dell'azienda e il Web per i dati aggiornati.
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))
Rilevamento delle domande che richiedono informazioni aggiornate
Oltre al classificatore LLM, utilizzi euristiche basate su parole chiave per rilevare le domande che richiedono informazioni aggiornate. È più veloce ed evita una chiamata LLM aggiuntiva nei casi evidenti.
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}"')
Gestione dei conflitti tra le fonti
Si verifica un conflitto quando i documenti locali affermano una cosa e un risultato Web ne afferma un'altra. Ad esempio, il documento interno sui prezzi indica $50/mese, mentre un risultato Web indica che il prezzo è cambiato a $80/mese.
Istruisca l'LLM a segnalare i conflitti e a preferire le fonti Web per i fatti sensibili al fattore tempo.
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
))Rilevamento dell'obsolescenza dei documenti locali
I documenti locali diventano obsoleti nel tempo. Aggiunga un controllo di obsolescenza: se un documento locale è più vecchio di una determinata soglia, completi le informazioni con una ricerca sul Web anche se il router ha classificato la domanda come «statica».
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)Assegnazione del punteggio di affidabilità
Associ un punteggio di affidabilità a ogni elemento di contesto recuperato. Le fonti altamente affidabili (recenti, provenienti da un dominio autorevole e con elevata similarità tra embedding) ricevono un peso maggiore nella risposta 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)Flusso completo del recupero ibrido
Considerando tutti gli elementi: routing rapido → recupero intelligente da una o da entrambe le fonti → integrazione delle informazioni obsolete → unione → assegnazione dei punteggi → formattazione → generazione della risposta.
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/webVerifica delle conoscenze
Quando dovrebbe l'agente di recupero ibrido preferire i risultati della ricerca sul Web ai risultati dei documenti locali?
Riepilogo: combinazione della ricerca sul Web con il RAG
Il recupero ibrido combina un vector store locale (per conoscenze statiche, private o specifiche del dominio) con la ricerca sul Web (per informazioni attuali e pubbliche). Un classificatore di routing indirizza ogni domanda verso la fonte corretta, oppure verso entrambe quando necessario.
Tecniche fondamentali: routing euristico rapido con segnali basati su parole chiave, rilevamento dell'obsolescenza dei documenti locali, istruzioni per la risoluzione dei conflitti nel prompt e assegnazione di punteggi di affidabilità per ponderare il contesto recuperato.
Domande Frequenti
La lezione «Combinazione della ricerca web con RAG» è gratuita?
Sì — il testo completo di «Combinazione della ricerca web con RAG» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso AI Agents, passa a CoddyKit PRO. Il corso AI Agents include 4 lezioni in totale.
Cosa imparerò in «Combinazione della ricerca web con RAG»?
Retrieval ibrido: vector store locale e ricerca web in tempo reale per risposte aggiornate Eserciti AI Agents con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare AI Agents?
Non è richiesta alcuna esperienza precedente. AI Agents su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 4 di 4.
Quanto tempo richiede la lezione «Combinazione della ricerca web con RAG»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione AI Agents?
Sì. Ogni lezione AI Agents include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.
Tutte le lezioni di questo corso
- Tavily e SerpAPI per la ricerca degli agenti
- Classificazione e filtraggio dei risultati di ricerca
- Pattern del ciclo di ricerca approfondita
- Combinazione della ricerca web con RAG