YaCy est un moteur de recherche décentralisé et pair-à-pair qui explore et indexe le Web sans dépendre d'un serveur central. Combiné avec llama.cpp pour l'inférence LLM locale, vous obtenez un pipeline de recherche IA entièrement hors ligne sans frais d'API. Le compromis est la qualité de l'index — YaCy indexe ce que ses pairs partagent, ce qui est bien plus petit que Google ou Bing. Ce tutoriel configure YaCy, le relie à llama.cpp via le pont yacy_expert, et ajoute un repli de recherche Scavio pour les requêtes où la couverture de YaCy est faible. Coût : 0 $ pour les requêtes locales, 0,005 $ par repli Scavio.
Prérequis
- Docker installé
- Au moins 8 Go de RAM pour llama.cpp
- Un fichier de modèle GGUF (par exemple, Mistral 7B Q4)
- Python 3.9+ installé
- Une clé API Scavio pour les recherches de repli
Parcours
Étape 1: Lancer YaCy dans Docker
Exécutez YaCy en tant que conteneur Docker. L'interface d'administration tourne sur le port 8090 et l'API de recherche sur le port 8090/yacysearch.json.
# Pull and run YaCy
docker run -d --name yacy \
-p 8090:8090 \
-v yacy_data:/opt/yacy_search_server/DATA \
yacy/yacy_search_server:latest
# Wait for startup
echo 'Waiting for YaCy to initialize...'
sleep 15
# Test the search API
curl -s 'http://localhost:8090/yacysearch.json?query=python+programming&maximumRecords=3' | python3 -m json.tool | head -20
# Seed the index with some crawl targets
curl -s 'http://localhost:8090/Crawler_p.html?crawlingURL=https://docs.python.org&crawlingDepth=2&range=wide'Étape 2: Configurer le serveur llama.cpp
Exécutez llama.cpp en tant que serveur API compatible OpenAI. Il gère l'inférence LLM pour résumer les résultats de recherche.
# Download and build llama.cpp (or use pre-built binary)
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp && make -j$(nproc)
# Start the server with your GGUF model
./llama-server \
--model ~/models/mistral-7b-instruct-v0.3.Q4_K_M.gguf \
--host 0.0.0.0 \
--port 8080 \
--ctx-size 4096 \
--n-gpu-layers 35
# Test it
curl -s http://localhost:8080/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model": "local", "messages": [{"role": "user", "content": "Hello"}], "max_tokens": 50}'Étape 3: Construire le pont yacy_expert en Python
Créez un script Python qui interroge YaCy, formate les résultats en contexte, et les envoie à llama.cpp pour une réponse fondée.
import requests, os
YACY_URL = 'http://localhost:8090/yacysearch.json'
LLAMA_URL = 'http://localhost:8080/v1/chat/completions'
SCAVIO_KEY = os.environ.get('SCAVIO_API_KEY', '')
def yacy_search(query: str, count: int = 5) -> list:
resp = requests.get(YACY_URL, params={
'query': query, 'maximumRecords': count, 'resource': 'global'
}, timeout=10)
channels = resp.json().get('channels', [{}])
items = channels[0].get('items', []) if channels else []
return [{'title': r.get('title', ''), 'snippet': r.get('description', ''),
'url': r.get('link', '')} for r in items]
def scavio_fallback(query: str, count: int = 5) -> list:
if not SCAVIO_KEY:
return []
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers={'x-api-key': SCAVIO_KEY, 'Content-Type': 'application/json'},
json={'query': query, 'country_code': 'us', 'num_results': count})
return [{'title': r['title'], 'snippet': r.get('snippet', ''),
'url': r['link']} for r in resp.json().get('organic_results', [])[:count]]
def search(query: str) -> list:
results = yacy_search(query)
if len(results) < 2:
print('YaCy coverage thin, falling back to Scavio ($0.005)')
results = scavio_fallback(query) + results
return results
results = search('python asyncio tutorial')
for r in results:
print(f' {r["title"][:60]}')Étape 4: Ajouter la génération de réponses par LLM
Envoyez les résultats de recherche à llama.cpp pour générer une réponse fondée et citée. Le LLM résume uniquement ce que la recherche a trouvé.
def ask(query: str) -> str:
results = search(query)
if not results:
return 'No results found in YaCy or fallback.'
context = '\n\n'.join(
f'[{i+1}] {r["title"]}\n{r["snippet"]}\nSource: {r["url"]}'
for i, r in enumerate(results)
)
resp = requests.post(LLAMA_URL, json={
'model': 'local',
'messages': [
{'role': 'system', 'content': 'Answer using ONLY the search results below. Cite sources as [1], [2], etc.'},
{'role': 'user', 'content': f'Search results:\n{context}\n\nQuestion: {query}'}
],
'max_tokens': 512,
'temperature': 0.3
}, timeout=60)
answer = resp.json()['choices'][0]['message']['content']
return answer
print(ask('How do I use asyncio gather in Python?'))Exemple Python
import requests, os
YACY = 'http://localhost:8090/yacysearch.json'
LLAMA = 'http://localhost:8080/v1/chat/completions'
SCAVIO_KEY = os.environ.get('SCAVIO_API_KEY', '')
def search(query, count=5):
results = requests.get(YACY, params={'query': query, 'maximumRecords': count}).json()
items = results.get('channels', [{}])[0].get('items', [])
if len(items) < 2 and SCAVIO_KEY:
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers={'x-api-key': SCAVIO_KEY, 'Content-Type': 'application/json'},
json={'query': query, 'country_code': 'us', 'num_results': count})
items = [{'title': r['title'], 'description': r.get('snippet', ''), 'link': r['link']}
for r in resp.json().get('organic_results', [])]
return items
def ask(query):
results = search(query)
ctx = '\n'.join(f'[{i+1}] {r.get("title","")}: {r.get("description","")}' for i, r in enumerate(results))
resp = requests.post(LLAMA, json={'model': 'local', 'messages': [
{'role': 'system', 'content': 'Answer from search results only. Cite [1],[2].'},
{'role': 'user', 'content': f'{ctx}\n\nQ: {query}'}], 'max_tokens': 512})
return resp.json()['choices'][0]['message']['content']
print(ask('python asyncio gather example'))Exemple JavaScript
const YACY = 'http://localhost:8090/yacysearch.json';
const LLAMA = 'http://localhost:8080/v1/chat/completions';
const SCAVIO_KEY = process.env.SCAVIO_API_KEY;
async function search(query, count = 5) {
const yacyResp = await fetch(`${YACY}?query=${encodeURIComponent(query)}&maximumRecords=${count}`);
let items = (await yacyResp.json()).channels?.[0]?.items || [];
if (items.length < 2 && SCAVIO_KEY) {
const resp = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST',
headers: { 'x-api-key': SCAVIO_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ query, country_code: 'us', num_results: count })
});
const data = await resp.json();
items = (data.organic_results || []).map(r => ({ title: r.title, description: r.snippet, link: r.link }));
}
return items;
}
async function ask(query) {
const results = await search(query);
const ctx = results.map((r, i) => `[${i+1}] ${r.title}: ${r.description}`).join('\n');
const resp = await fetch(LLAMA, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ model: 'local', messages: [
{ role: 'system', content: 'Answer from search results only.' },
{ role: 'user', content: `${ctx}\n\nQ: ${query}` }], max_tokens: 512 })
});
console.log((await resp.json()).choices[0].message.content);
}
ask('python asyncio gather example');Sortie attendue
YaCy coverage thin, falling back to Scavio ($0.005)
Python asyncio.gather() documentation
Real Python: Async IO in Python
Stack Overflow: How to use asyncio.gather
Based on the search results, asyncio.gather() runs multiple coroutines
concurrently and waits for all to complete [1]. You pass awaitable objects
as arguments: results = await asyncio.gather(coro1(), coro2()) [2]...