0Pricing
AI Agents · Lección

Combinación de búsqueda web con RAG

Recuperación híbrida: almacén vectorial local y búsqueda web en tiempo real para obtener respuestas actualizadas.

Combinación de búsqueda web con RAG es una lección gratuita de AI Agents en CoddyKit. Esta es la lección 4 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de AI Agents, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de AI Agents incluye 4 lecciones en total.

El desafío de la recuperación híbrida

La mayoría de los agentes del mundo real necesitan dos tipos de conocimiento: conocimiento estático del dominio (documentación de la empresa, manuales de productos y políticas) e información actual (noticias, precios en tiempo real y acontecimientos recientes).

Un almacén vectorial local gestiona el primero; la búsqueda web, el segundo. Combinar ambos ofrece lo mejor de cada opción.

Arquitectura: RAG local + búsqueda web

El sistema híbrido dirige cada pregunta a la fuente de recuperación adecuada:

  • Almacén vectorial (RAG) — documentos indexados, conocimiento estable y datos privados
  • Búsqueda web — acontecimientos actuales, lanzamientos recientes y datos en tiempo real
  • Ambos — cuando la pregunta necesita contexto de los documentos Y también información actual
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?'))

El clasificador de enrutamiento

El clasificador decide qué fuente de recuperación utilizar. Puede implementarlo como una llamada a un LLM, un conjunto de reglas basadas en palabras clave o un clasificador pequeño entrenado. Un enrutador basado en un LLM es la opción más 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 route

Configuración de un almacén vectorial local

Para la base de conocimiento estática, utilice ChromaDB, una base de datos vectorial ligera que se ejecuta dentro del proceso. Indexe los documentos una vez y consúltelos en tiempo de ejecución.

Instálelo 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]))

Recuperación mediante búsqueda web

La ruta de búsqueda web utiliza Tavily para obtener información actual. Dé un formato coherente a los resultados para poder combinarlos con los resultados de RAG local en la misma estructura de 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', [])
    ]

Combinación de resultados locales y web

Cuando utilice ambas fuentes, combine los resultados y etiquete cada uno con su procedencia. Esto permite que el LLM les asigne el peso adecuado: los documentos locales aportan datos específicos de la empresa y la web aporta información actual.

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))

Detección de preguntas que requieren información actual

Además del clasificador basado en un LLM, utilice heurísticas de palabras clave para detectar preguntas que requieren información actual. Esto es más rápido y evita una llamada adicional al LLM en los casos evidentes.

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}"')

Gestión de conflictos entre fuentes

Se produce un conflicto cuando los documentos locales afirman una cosa y un resultado web afirma otra. Por ejemplo: el documento interno de precios indica 50 $ al mes, pero un resultado web indica que el precio ha cambiado a 80 $ al mes.

Indique al LLM que señale los conflictos y dé preferencia a las fuentes web para los datos sensibles al paso del tiempo.

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
    ))

Detección de obsolescencia en documentos locales

Los documentos locales quedan obsoletos con el tiempo. Añada una comprobación de obsolescencia: si un documento local es anterior a un umbral determinado, compleméntelo con una búsqueda web aunque el enrutador haya clasificado la pregunta como «estática».

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)

Puntuación de confianza

Asigne una puntuación de confianza a cada fragmento de contexto recuperado. Las fuentes con mayor confianza (recientes, procedentes de dominios autorizados y con una alta similitud entre embeddings) deben tener más peso en la respuesta final.

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)

Flujo completo de recuperación híbrida

En conjunto, el flujo es: enrutamiento rápido → recuperación inteligente de una o ambas fuentes → complemento por obsolescencia → combinación → puntuación → formato → generación de la respuesta.

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

Comprobación de conocimientos

¿Cuándo debe el agente de recuperación híbrida dar preferencia a los resultados de búsqueda web frente a los resultados de documentos locales?

Repaso: combinación de búsqueda web y RAG

La recuperación híbrida combina un almacén vectorial local (para conocimiento estático, privado o específico del dominio) con la búsqueda web (para información pública y actual). Un clasificador de enrutamiento dirige cada pregunta a la fuente adecuada, o a ambas cuando es necesario.

Técnicas clave: enrutamiento heurístico rápido mediante señales de palabras clave, detección de obsolescencia en documentos locales, instrucciones de resolución de conflictos en el prompt y puntuación de confianza para asignar peso al contexto recuperado.

Preguntas frecuentes

¿La lección «Combinación de búsqueda web con RAG» es gratis?

Sí — el texto completo de «Combinación de búsqueda web con RAG» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de AI Agents, actualiza a CoddyKit PRO. El curso de AI Agents incluye 4 lecciones en total.

¿Qué aprenderé en «Combinación de búsqueda web con RAG»?

Recuperación híbrida: almacén vectorial local y búsqueda web en tiempo real para obtener respuestas actualizadas. Practicas AI Agents con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar AI Agents?

No se requiere experiencia previa. AI Agents en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 4 de 4.

¿Cuánto tiempo toma la lección «Combinación de búsqueda web con RAG»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de AI Agents?

Sí. Cada lección de AI Agents incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Tavily y SerpAPI para búsquedas de agentes
  2. Clasificación y filtrado de resultados de búsqueda
  3. Patrón de ciclo de investigación profunda
  4. Combinación de búsqueda web con RAG
← Volver a AI Agents