Ollama sequential embedding (#2230)

* ollama: Switch from parallel to sequential chunk embedding

* throw error on empty embeddings

---------

Co-authored-by: John Blomberg <john.jb.blomberg@gmail.com>
This commit is contained in:
Timothy Carambat 2024-09-06 10:06:46 -07:00 committed by GitHub
parent fef01550df
commit 20135835d0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -36,68 +36,49 @@ class OllamaEmbedder {
return result?.[0] || [];
}
/**
* This function takes an array of text chunks and embeds them using the Ollama API.
* chunks are processed sequentially to avoid overwhelming the API with too many requests
* or running out of resources on the endpoint running the ollama instance.
* @param {string[]} textChunks - An array of text chunks to embed.
* @returns {Promise<Array<number[]>>} - A promise that resolves to an array of embeddings.
*/
async embedChunks(textChunks = []) {
if (!(await this.#isAlive()))
throw new Error(
`Ollama service could not be reached. Is Ollama running?`
);
const embeddingRequests = [];
this.log(
`Embedding ${textChunks.length} chunks of text with ${this.model}.`
);
let data = [];
let error = null;
for (const chunk of textChunks) {
embeddingRequests.push(
new Promise((resolve) => {
fetch(this.basePath, {
try {
const res = await fetch(this.basePath, {
method: "POST",
body: JSON.stringify({
model: this.model,
prompt: chunk,
}),
})
.then((res) => res.json())
.then(({ embedding }) => {
resolve({ data: embedding, error: null });
return;
})
.catch((error) => {
resolve({ data: [], error: error.message });
return;
});
})
);
const { embedding } = await res.json();
if (!Array.isArray(embedding) || embedding.length === 0)
throw new Error("Ollama returned an empty embedding for chunk!");
data.push(embedding);
} catch (err) {
this.log(err.message);
error = err.message;
data = [];
break;
}
const { data = [], error = null } = await Promise.all(
embeddingRequests
).then((results) => {
// If any errors were returned from Ollama abort the entire sequence because the embeddings
// will be incomplete.
const errors = results
.filter((res) => !!res.error)
.map((res) => res.error)
.flat();
if (errors.length > 0) {
let uniqueErrors = new Set();
errors.map((error) =>
uniqueErrors.add(`[${error.type}]: ${error.message}`)
);
return {
data: [],
error: Array.from(uniqueErrors).join(", "),
};
}
return {
data: results.map((res) => res?.data || []),
error: null,
};
});
if (!!error) throw new Error(`Ollama Failed to embed: ${error}`);
return data.length > 0 ? data : null;
}