Risposta rapida
Come chiamo un'API di ricerca web per agenti AI da Node.js?
Usa il fetch integrato di Node 18+ per inviare la tua query in POST a https://llmwebsearch.com/api/v1/search con una chiave Bearer. LLMWebSearch risponde con risultati classificati e le prime 3 pagine in Markdown, pronti da numerare e passare a un modello per una risposta con citazioni. Una ricerca costa 1 credito, a partire da 1,20 USD/1K ricerche a ottobre 2026. Nessun pacchetto npm necessario. Senza KYC; paghi in criptovalute.
Cosa ti serve
| Node.js | 18 o superiore, per il fetch integrato; salva gli script come .mjs per il await di primo livello |
|---|---|
| Pacchetti | Nessuno |
| Chiave | LLMWEBSEARCH_API_KEY nel tuo ambiente |
| Per il ciclo di risposta | Un endpoint di chat compatibile con OpenAI: LLM_BASE_URL, LLM_API_KEY, LLM_MODEL |
| Fatturazione | Un credito paga una ricerca: fino a 20 risultati classificati, con le pagine dei primi 3 lette e restituite come Markdown. Leggere da 4 a 10 pagine costa 2 crediti. Una chiamata che termina in errore non costa nulla |
Come scrivo una funzione di ricerca riutilizzabile?
Avvolgi la chiamata una volta e riutilizzala ovunque. La funzione qui sotto invia il body, lancia un errore leggibile per qualsiasi errore HTTP e restituisce il JSON analizzato. Gli errori portano il code dell'API stessa, come rate_limited o insufficient_credits, così chi chiama può decidere cosa fare.
const API = 'https://llmwebsearch.com/api/v1/search';
export async function search(body) {
const res = await fetch(API, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LLMWEBSEARCH_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
const data = await res.json();
if (!res.ok) {
const err = new Error(`${res.status} ${data.error?.code}: ${data.error?.message}`);
err.status = res.status;
throw err;
}
return data;
}
const data = await search({ q: 'how to size a postgres connection pool', read: 3 });
for (const r of data.results) {
console.log(r.position, r.content_status, r.url, r.word_count ?? '');
}
Come posso eseguire più ricerche senza incorrere nel rate limit?
Eseguile tramite un piccolo pool: un numero fisso di worker che prendono ciascuno la query successiva da una lista condivisa. Quattro worker sono un buon punto di partenza. Ogni account ha un limite di chiamate al minuto, mostrato nella dashboard; chiedi al supporto e può essere alzato.
Il pool qui sotto non richiede alcuna libreria. Ogni worker mantiene i propri risultati e una ricerca fallita viene registrata con il suo errore invece di interrompere l'esecuzione. Questo conta per gli agenti di ricerca, dove una sotto-domanda sbagliata non dovrebbe compromettere l'intero task.
const API = 'https://llmwebsearch.com/api/v1/search';
const questions = [
'what is connection pooling in postgres',
'recommended pool size per cpu core',
'pgbouncer transaction mode limitations',
'connection pool exhaustion symptoms',
'pool size for serverless functions',
];
async function search(q) {
const res = await fetch(API, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.LLMWEBSEARCH_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ q, read: 2, max_chars: 4000 }),
});
const data = await res.json();
if (!res.ok) return { q, error: data.error?.code ?? String(res.status) };
return { q, urls: data.results.filter((r) => r.content_status === 'ok').map((r) => r.url) };
}
async function runPool(items, size, task) {
const out = [];
let next = 0;
const worker = async () => {
while (next < items.length) {
const i = next++;
out[i] = await task(items[i]);
}
};
await Promise.all(Array.from({ length: size }, worker));
return out;
}
for (const row of await runPool(questions, 4, search)) console.log(row);
Come trasformo i risultati in una risposta con citazioni?
Numera le fonti, metti l'URL accanto a ciascuna, riducile a un budget e chiedi al modello di citare per numero. L'API restituisce fonti, non una risposta scritta: il tuo modello legge le pagine, scrive la risposta e cita gli URL. Lo script qui sotto comunica con qualsiasi endpoint chat/completions compatibile con OpenAI, scelto tramite tre variabili d'ambiente, quindi funziona sia con modelli hostati che locali.
Il budget di circa 24.000 caratteri, all'incirca 6.000 token, è diviso equamente tra le fonti. Un risultato la cui pagina non è stata letta viene passato come snippet, contrassegnato come tale.
const SEARCH = 'https://llmwebsearch.com/api/v1/search';
const BUDGET = 24000; // characters, about 6,000 tokens
async function sources(question) {
const res = await fetch(SEARCH, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.LLMWEBSEARCH_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ q: question, max_results: 5, read: 3 }),
});
const { results } = await res.json();
const share = Math.floor(BUDGET / Math.max(results.length, 1));
return results
.map((r, i) => {
const body = r.content_status === 'ok' ? r.content.slice(0, share) : `(snippet only) ${r.snippet}`;
return `[${i + 1}] ${r.title} - ${r.url}\n${body}`;
})
.join('\n\n');
}
async function answer(question) {
const prompt = 'Answer using only the sources below. Cite them as [1], [2] and so on. '
+ 'Say so if they do not answer the question.\n\n' + (await sources(question)) + '\n\nQuestion: ' + question;
const res = await fetch(`${process.env.LLM_BASE_URL.replace(/\/$/, '')}/chat/completions`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.LLM_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ model: process.env.LLM_MODEL, messages: [{ role: 'user', content: prompt }] }),
});
const data = await res.json();
return data.choices[0].message.content;
}
console.log(await answer('How should I size a Postgres connection pool?'));
Devo usare GET o POST da Node?
POST, con un body JSON, nel codice dell'applicazione. Gli array come include_domains e le domande lunghe viaggiano più puliti in un body che in una query string. GET serve per test rapidi e per strumenti che non possono inviare un body; costruisci la sua query string con URLSearchParams e unisci le liste con virgole.
Entrambe le forme costano lo stesso e restituiscono lo stesso JSON. Il riferimento della ricerca elenca ogni parametro per entrambe.
Dove dovrebbe stare la chiave?
In una variabile d'ambiente letta a runtime, mai nel file sorgente. Su un server, usa il secret store della tua piattaforma. In un browser, non chiamare affatto l'API: la chiave sarebbe visibile a ogni visitatore. Chiama il tuo backend, che poi chiama LLMWebSearch.
Crea una chiave API per agente, app o teammate e revoca qualsiasi di esse dalla dashboard senza toccare le altre. Una chiave per servizio rende la cronologia della dashboard più facile da leggere e una chiave compromessa facile da revocare.
Node.js: domande frequenti
Esiste un pacchetto npm?
No. fetch è incluso in Node 18 e versioni successive, e l'API è semplice JSON su HTTPS. Le funzioni di questa pagina sono l'intera integrazione.
Funziona su Deno, Bun o runtime edge?
Il codice usa solo fetch, JSON e process.env. I runtime con fetch funzionano dopo aver sostituito process.env con il loro modo di leggere i segreti.
Come ritento le chiamate fallite?
Ritenta solo 429, 502 e 504, con una pausa che raddoppia ogni volta. Non ritentare mai 400, 401 o 402. La pagina degli errori spiega perché e mostra un helper.