@adonis-agora/agent
Version:
Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.
83 lines • 3.26 kB
JavaScript
import { errorMessageOf, HttpModelError } from './openai-embeddings.js';
/**
* A cross-encoder {@link Reranker} behind an HTTP `/rerank` endpoint — the Cohere-style contract
* most providers and self-hosted servers share. Compose it with `RerankingRetriever`:
*
* ```ts
* const reranker = new HttpReranker({ url: 'https://api.cohere.com/v2/rerank', model: 'rerank-v3.5', apiKey });
* const retriever = new RerankingRetriever(hybrid, reranker, { fetchTopK: 30 });
* ```
*
* Reads `results: [{ index, relevance_score }]` (Cohere, Jina), `data: [{ index, relevance_score }]`
* (Voyage) and a bare `[{ index, score }]` (TEI). Each passage keeps its fields and takes the new
* score; the list is re-sorted and cut to `topK`. Indices the server invents are ignored.
*/
export class HttpReranker {
options;
format;
doFetch;
constructor(options) {
this.options = options;
this.format = options.format ?? 'auto';
this.doFetch = options.fetch ?? fetch;
}
async rerank(query, passages, options = {}) {
if (passages.length === 0) {
return [];
}
const topK = options.topK ?? passages.length;
const documents = passages.map((passage) => passage.text);
const response = await this.doFetch(this.options.url, {
method: 'POST',
headers: {
'content-type': 'application/json',
...(this.options.apiKey ? { authorization: `Bearer ${this.options.apiKey}` } : {}),
...this.options.headers,
},
body: JSON.stringify({
...(this.options.model !== undefined ? { model: this.options.model } : {}),
query,
...(this.format !== 'tei' ? { documents, top_n: topK } : {}),
...(this.format !== 'cohere' ? { texts: documents } : {}),
}),
signal: AbortSignal.timeout(this.options.timeoutMs ?? 30_000),
});
const body = await response.text();
if (!response.ok) {
throw new HttpModelError(response.status, `Rerank request failed (${response.status}): ${errorMessageOf(body)}`);
}
const items = itemsOf(body);
if (items === undefined) {
throw new HttpModelError(502, 'Rerank response has no results');
}
const seen = new Set();
const reranked = [];
for (const item of items) {
const index = item.index;
const passage = typeof index === 'number' ? passages[index] : undefined;
if (passage === undefined || seen.has(index)) {
continue;
}
seen.add(index);
reranked.push({ ...passage, score: Number(item.relevance_score ?? item.score ?? 0) });
}
reranked.sort((a, b) => b.score - a.score);
return reranked.slice(0, topK);
}
}
function itemsOf(body) {
let json;
try {
json = JSON.parse(body);
}
catch {
return undefined;
}
if (Array.isArray(json)) {
return json;
}
const wrapped = json;
const items = wrapped?.results ?? wrapped?.data;
return Array.isArray(items) ? items : undefined;
}
//# sourceMappingURL=http-reranker.js.map