67 lines
1.9 KiB
JavaScript
67 lines
1.9 KiB
JavaScript
const VOYAGE_API_URL = "https://api.voyageai.com/v1";
|
|
const EMBED_MODEL = "voyage-3";
|
|
const RERANK_MODEL = "rerank-2";
|
|
const EMBED_BATCH_SIZE = 32;
|
|
|
|
function getApiKey() {
|
|
const key = process.env.VOYAGE_API_KEY;
|
|
if (!key) throw new Error("VOYAGE_API_KEY is not set");
|
|
return key;
|
|
}
|
|
|
|
async function voyagePost(path, body) {
|
|
const res = await fetch(`${VOYAGE_API_URL}${path}`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${getApiKey()}`,
|
|
},
|
|
body: JSON.stringify(body),
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const text = await res.text();
|
|
throw new Error(`Voyage ${path} failed (${res.status}): ${text}`);
|
|
}
|
|
|
|
return res.json();
|
|
}
|
|
|
|
// Embed text - voyage-3. inputType must be "document" (ingest) or "query" (search).
|
|
|
|
export async function embedTexts(texts, inputType = "document") {
|
|
if (texts.length === 0) return [];
|
|
|
|
const embeddings = [];
|
|
for (let i = 0; i < texts.length; i += EMBED_BATCH_SIZE) {
|
|
const batch = texts.slice(i, i + EMBED_BATCH_SIZE);
|
|
const json = await voyagePost("/embeddings", {
|
|
model: EMBED_MODEL,
|
|
input: batch,
|
|
input_type: inputType,
|
|
});
|
|
const ordered = (json.data || [])
|
|
.slice()
|
|
.sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
|
|
embeddings.push(...ordered.map((d) => d.embedding));
|
|
}
|
|
return embeddings;
|
|
}
|
|
|
|
export async function rerank(query, documents) {
|
|
if (!documents || documents.length === 0) return [];
|
|
|
|
const texts = documents.map((d) => (typeof d === "string" ? d : d.text));
|
|
const json = await voyagePost("/rerank", {
|
|
model: RERANK_MODEL,
|
|
query,
|
|
documents: texts,
|
|
});
|
|
|
|
return (json.data || []).map((item) => ({
|
|
index: item.index,
|
|
document: documents[item.index],
|
|
relevanceScore: item.relevance_score,
|
|
}));
|
|
}
|