@prexo/ai-chat-sdk
Version:
AI Chat Component with Persistent History
154 lines (153 loc) • 5.15 kB
JavaScript
import { DEFAULT_SIMILARITY_THRESHOLD, DEFAULT_TOP_K } from "../../lib/constants.js";
import { nanoid } from "nanoid";
import { BASE_API_ENDPOINT } from "../../lib/utils.js";
function chunkText(text, chunkSize = 500, overlap = 50) {
const chunks = [];
for (let i = 0; i < text.length; i += chunkSize - overlap) {
chunks.push(text.slice(i, i + chunkSize));
}
return chunks;
}
class VectorDB {
index;
constructor(index) {
this.index = index;
}
async reset(options) {
await this.index.reset({ namespace: options?.namespace });
}
async delete({ ids, namespace }) {
await this.index.delete(ids, { namespace });
}
/**
* A method that allows you to query the vector database with plain text.
* It takes care of the text-to-embedding conversion by itself.
* Additionally, it lets consumers pass various options to tweak the output.
*/
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
async retrieve({
question,
similarityThreshold = DEFAULT_SIMILARITY_THRESHOLD,
topK = DEFAULT_TOP_K,
namespace,
contextFilter,
queryMode
}) {
const index = this.index;
const result = await index.query(
{
...typeof question === "string" ? { data: question } : { vector: question },
topK,
includeData: true,
includeMetadata: true,
...typeof contextFilter === "string" && { filter: contextFilter },
queryMode
},
{ namespace }
);
const allValuesUndefined = result.every((embedding) => embedding.data === void 0);
if (allValuesUndefined) {
console.error("There is no answer for this question in the provided context.");
return [
{
data: "There is no answer for this question in the provided context.",
id: "error",
metadata: {}
}
];
}
const facts = result.filter((x) => x.score >= similarityThreshold).map((embedding) => ({
data: embedding.data ?? "",
id: embedding.id.toString(),
metadata: embedding.metadata
}));
return facts;
}
/**
* A method that allows you to add various data types into a vector database.
* It supports plain text, embeddings, PDF, HTML, Text file and CSV. Additionally, it handles text-splitting for CSV, PDF and Text file.
*/
async save(input) {
const { namespace } = input.options ?? {};
if (input.type === "text") {
try {
const returnId = input.id ?? nanoid();
await this.index.upsert(
{
data: input.data,
id: returnId,
metadata: input.options?.metadata
},
{ namespace }
);
return { success: true, ids: [returnId.toString()] };
} catch (error) {
return { success: false, error: JSON.stringify(error, Object.getOwnPropertyNames(error)) };
}
} else if (input.type === "embedding") {
try {
const returnId = input.id ?? nanoid();
await this.index.upsert(
{
vector: input.data,
data: input.text,
id: returnId,
metadata: input.options?.metadata
},
{ namespace }
);
return { success: true, ids: [returnId.toString()] };
} catch (error) {
return { success: false, error: JSON.stringify(error, Object.getOwnPropertyNames(error)) };
}
} else {
try {
let fileSource = void 0;
if ("fileSource" in input && typeof input.fileSource === "string") {
fileSource = input.fileSource;
} else if ("source" in input && typeof input.source === "string") {
fileSource = input.source;
}
if (!fileSource) {
throw new Error("No valid file source provided for extractText");
}
const response = await fetch(`${BASE_API_ENDPOINT}/extractor`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.PREXO_API_KEY}`
},
body: JSON.stringify({ url: fileSource })
});
if (!response.ok) {
throw new Error(`Failed to extract text: ${response.status} ${response.statusText}`);
}
const res = await response.json();
const transformArgs = "config" in input ? input.config : {};
const chunkSize = transformArgs && transformArgs.chunkSize || 500;
const overlap = transformArgs && transformArgs.overlap || 100;
const chunks = chunkText(res.output.txt, chunkSize, overlap);
const ids = [];
for (const chunk of chunks) {
const id = nanoid();
await this.index.upsert(
{
data: chunk,
id,
metadata: input.options?.metadata
},
{ namespace }
);
ids.push(id);
}
return { success: true, ids };
} catch (error) {
console.error(error);
return { success: false, error: JSON.stringify(error, Object.getOwnPropertyNames(error)) };
}
}
}
}
export {
VectorDB
};