0Pricing
AI Agents · Lezione

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 route

Configurazione 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/web

Verifica 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

  1. Tavily e SerpAPI per la ricerca degli agenti
  2. Classificazione e filtraggio dei risultati di ricerca
  3. Pattern del ciclo di ricerca approfondita
  4. Combinazione della ricerca web con RAG
← Torna a AI Agents