chatoai-backend / src /store.js
Keyurjotaniya007's picture
backend at repo root for docker space
0a36812
Raw
History Blame Contribute Delete
2.92 kB
// ----------------------------------------------------------------------------
// In-memory document store: recursive chunking + cosine retrieval.
//
// For a single-instance deployment this is fine and fast. If you scale to
// multiple instances or want persistence, swap this Map for Redis / a vector DB
// (Qdrant, pgvector). The interface (put/get/retrieve) stays the same.
// ----------------------------------------------------------------------------
const docs = new Map(); // docId -> { chunks: string[], vectors: number[][], at: number }
// --- recursive character splitter (LangChain-style) ------------------------
export function recursiveSplit(
text,
chunkSize = 1000,
chunkOverlap = 150,
separators = ['\n\n', '\n', '. ', ' ', ''],
) {
const splitRec = (input, seps) => {
if (input.length <= chunkSize) return input.trim() ? [input] : [];
const [sep, ...rest] = seps.length ? seps : [''];
const parts = sep === '' ? input.split('') : input.split(sep);
const chunks = [];
let current = '';
for (const part of parts) {
const piece = sep === '' ? part : part + sep;
if ((current + piece).length > chunkSize) {
if (current) chunks.push(current);
if (piece.length > chunkSize && rest.length) {
chunks.push(...splitRec(piece, rest));
current = '';
} else {
current = piece;
}
} else {
current += piece;
}
}
if (current) chunks.push(current);
return chunks;
};
const raw = splitRec(text, separators).filter((c) => c.trim().length > 0);
if (chunkOverlap <= 0) return raw;
const out = [];
for (let i = 0; i < raw.length; i++) {
const prevTail = i > 0 ? raw[i - 1].slice(-chunkOverlap) : '';
out.push((prevTail + raw[i]).trim());
}
return out;
}
export function cosine(a, b) {
let dot = 0;
let na = 0;
let nb = 0;
for (let i = 0; i < a.length; i++) {
dot += a[i] * b[i];
na += a[i] * a[i];
nb += b[i] * b[i];
}
return dot / (Math.sqrt(na) * Math.sqrt(nb) + 1e-8);
}
export function hasDoc(docId) {
return docs.has(docId);
}
export function putDoc(docId, chunks, vectors) {
docs.set(docId, { chunks, vectors, at: Date.now() });
}
/** Retrieve top-k chunks across one or more docs for a query vector. */
export function retrieve(docIds, queryVec, k = 4) {
const scored = [];
for (const id of docIds) {
const doc = docs.get(id);
if (!doc) continue;
for (let i = 0; i < doc.chunks.length; i++) {
scored.push({ text: doc.chunks[i], score: cosine(queryVec, doc.vectors[i]) });
}
}
return scored.sort((a, b) => b.score - a.score).slice(0, k).map((s) => s.text);
}
// Optional: evict docs older than N ms to cap memory (call periodically).
export function evictOlderThan(ms) {
const cutoff = Date.now() - ms;
for (const [id, doc] of docs) {
if (doc.at < cutoff) docs.delete(id);
}
}