Ejecute SearXNG, Hermes 3 y Qwen 2.5 como una pila de búsqueda de IA local usando Docker Compose, luego compare la calidad y la latencia del resultado con una API de búsqueda en la nube. Las pilas de búsqueda autohospedadas atraen a los equipos centrados en la privacidad, pero requieren un mantenimiento continuo y producen resultados inconsistentes según los motores SearXNG que estén funcionando. Este tutorial configura la pila completa, ejecuta consultas de comparación y le ayuda a decidir dónde funciona el alojamiento propio y dónde una API es la mejor opción.
Requisitos previos
- Docker y Docker Compose instalados
- Más de 16 GB de RAM para inferencia de modelos locales
- Una clave API de Scavio de scavio.dev para comparar
- Python 3.8+ instalado
Guia paso a paso
Paso 1: Escriba el archivo Docker Compose
Definir servicios para SearXNG (metabuscador), Ollama con modelos Hermes/Qwen y un servicio puente.
# docker-compose.yml
# Save this as docker-compose.yml in your project directory
compose_yaml = """
version: '3.8'
services:
searxng:
image: searxng/searxng:latest
ports:
- '8080:8080'
volumes:
- ./searxng:/etc/searxng
restart: unless-stopped
ollama:
image: ollama/ollama:latest
ports:
- '11434:11434'
volumes:
- ollama_data:/root/.ollama
restart: unless-stopped
volumes:
ollama_data:
"""
with open('docker-compose.yml', 'w') as f:
f.write(compose_yaml)
print('docker-compose.yml written')
print('Run: docker compose up -d')
print('Then: docker exec ollama ollama pull hermes3')
print('Then: docker exec ollama ollama pull qwen2.5')Paso 2: Configurar motores SearXNG
Personalice la configuración de SearXNG para habilitar los motores de búsqueda que necesita y deshabilitar los ruidosos.
import os
os.makedirs('searxng', exist_ok=True)
settings_yml = """
use_default_settings: true
server:
secret_key: 'change-this-to-a-random-string'
search:
safe_search: 0
autocomplete: ''
default_lang: 'en'
engines:
- name: google
engine: google
shortcut: g
disabled: false
- name: duckduckgo
engine: duckduckgo
shortcut: ddg
disabled: false
- name: brave
engine: brave
shortcut: br
disabled: false
"""
with open('searxng/settings.yml', 'w') as f:
f.write(settings_yml)
print('SearXNG settings written to searxng/settings.yml')Paso 3: Conecte Hermes a SearXNG
Cree un puente Python que envíe una consulta a SearXNG, pase los resultados a Hermes/Qwen a través de Ollama y devuelva la respuesta fundamentada.
import requests
def searxng_search(query: str) -> list:
resp = requests.get('http://localhost:8080/search',
params={'q': query, 'format': 'json'}, timeout=15)
results = resp.json().get('results', [])
return [{'title': r.get('title', ''), 'url': r.get('url', ''),
'content': r.get('content', '')} for r in results[:5]]
def ollama_generate(model: str, prompt: str) -> str:
resp = requests.post('http://localhost:11434/api/generate',
json={'model': model, 'prompt': prompt, 'stream': False}, timeout=60)
return resp.json().get('response', '')
def local_grounded_search(query: str, model: str = 'hermes3') -> str:
results = searxng_search(query)
context = '\n'.join(f"{r['title']}: {r['content']}" for r in results)
prompt = f'Using this context:\n{context}\n\nAnswer: {query}'
return ollama_generate(model, prompt)
# print(local_grounded_search('best python frameworks 2026'))Paso 4: Consultas de prueba y comparación con API
Ejecute las mismas consultas tanto en la pila local como en Scavio, luego compare la calidad y la latencia de los resultados.
import time
SCAVIO_KEY = os.environ['SCAVIO_API_KEY']
def scavio_search(query: str) -> list:
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers={'x-api-key': SCAVIO_KEY},
json={'platform': 'google', 'query': query}, timeout=10)
return resp.json().get('organic_results', [])[:5]
def compare(query: str) -> dict:
# Local stack
start = time.monotonic()
local_results = searxng_search(query)
local_ms = (time.monotonic() - start) * 1000
# Cloud API
start = time.monotonic()
api_results = scavio_search(query)
api_ms = (time.monotonic() - start) * 1000
print(f'Query: {query}')
print(f' Local: {len(local_results)} results in {local_ms:.0f}ms')
print(f' API: {len(api_results)} results in {api_ms:.0f}ms')
return {'query': query, 'local_count': len(local_results), 'api_count': len(api_results),
'local_ms': round(local_ms), 'api_ms': round(api_ms)}
# compare('best python web framework 2026')Paso 5: Comparar resultados con la línea base de API
Evalúe qué consultas funcionan bien localmente y cuáles necesitan una API en la nube para obtener resultados confiables.
def evaluate_stack(queries: list) -> dict:
results = []
for q in queries:
try:
api = scavio_search(q)
results.append({'query': q, 'api_results': len(api), 'status': 'ok'})
except Exception as e:
results.append({'query': q, 'error': str(e)})
ok = sum(1 for r in results if r.get('status') == 'ok')
print(f'{ok}/{len(queries)} queries returned API results')
print('Local stack: good for privacy, slower, inconsistent engines')
print('Cloud API: consistent results, structured JSON, multi-platform')
return {'total': len(queries), 'ok': ok, 'results': results}
evaluate_stack(['best crm 2026', 'python async tutorial', 'react vs vue performance'])Ejemplo en Python
import requests, os
H = {'x-api-key': os.environ['SCAVIO_API_KEY']}
# Cloud API comparison baseline
def cloud_search(query):
data = requests.post('https://api.scavio.dev/api/v1/search', headers=H,
json={'platform': 'google', 'query': query}).json()
return data.get('organic_results', [])[:5]
# Local SearXNG (when running)
def local_search(query):
try:
data = requests.get('http://localhost:8080/search', params={'q': query, 'format': 'json'}, timeout=10).json()
return data.get('results', [])[:5]
except: return []
print(f'API: {len(cloud_search("best crm 2026"))} results')Ejemplo en JavaScript
const H = {'x-api-key': process.env.SCAVIO_API_KEY, 'Content-Type': 'application/json'};
async function cloudSearch(query) {
const r = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST', headers: H, body: JSON.stringify({platform: 'google', query})
});
return (await r.json()).organic_results || [];
}
async function localSearch(query) {
try {
const r = await fetch(`http://localhost:8080/search?q=${encodeURIComponent(query)}&format=json`);
return (await r.json()).results || [];
} catch { return []; }
}
cloudSearch('best crm 2026').then(r => console.log('API:', r.length, 'results'));Salida esperada
A Docker Compose stack running SearXNG and Ollama with Hermes/Qwen, with comparison benchmarks against Scavio's cloud API for result quality and latency.