UNPKG

@adonis-agora/agent

Version:

Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.

131 lines • 5.24 kB
import { matchesFilter } from './filter.js'; import { applyMetadataPatch, assertRemovalFilter, documentIdOf, effectivePatchKeys, filterDeniesAll, } from './vector-store.js'; /** * An in-process {@link VectorStore} — cosine similarity over a Map, no infra. The reference adapter for * tests and small/embedded corpora; a pgvector/Lucid-backed store for production scale is deferred. */ export class MemoryVectorStore { records = new Map(); async upsert(records) { for (const record of records) { this.records.set(record.id, record); } } async remove(documentId) { for (const id of this.records.keys()) { if (documentIdOf(id) === documentId) { this.records.delete(id); } } } /** * {@link VectorStore.updateMetadata} — rewrite every chunk's metadata for one document, touching * neither `text` nor `embedding`. Both are carried across by the spread, so the stored `embedding` * stays the **same array instance** it was upserted with: nothing here can perturb a vector even in * principle. Merge semantics come from the shared {@link applyMetadataPatch}, so this adapter cannot * drift from the other two. */ async updateMetadata(documentId, patch) { if (effectivePatchKeys(patch).length === 0) { return 0; } let written = 0; for (const [id, record] of this.records) { if (documentIdOf(id) !== documentId) { continue; } this.records.set(id, { ...record, metadata: applyMetadataPatch(record.metadata, patch) }); written += 1; } return written; } async listDocuments(filter) { const documents = new Map(); for (const record of this.records.values()) { if (filter !== undefined && !matchesFilter(record.metadata, filter)) { continue; } const id = documentIdOf(record.id); if (!documents.has(id)) { documents.set(id, { id, ...(record.metadata !== undefined ? { metadata: record.metadata } : {}), }); } } return [...documents.values()]; } /** * {@link VectorStore.listDocumentIds} — the ids `listDocuments` would report, without building the * per-document metadata entries. Filtering goes through the same {@link matchesFilter} `search` uses. */ async listDocumentIds(filter) { const ids = new Set(); for (const record of this.records.values()) { if (filter !== undefined && !matchesFilter(record.metadata, filter)) { continue; } ids.add(documentIdOf(record.id)); } return [...ids]; } /** * {@link VectorStore.removeWhere} — delete every chunk the same filter would let `search` reach. * Parity is by construction: the predicate below is the identical {@link matchesFilter} call, on the * identical argument, as the one in {@link MemoryVectorStore.search}. */ async removeWhere(filter) { assertRemovalFilter(filter); if (filterDeniesAll(filter)) { return 0; } let removed = 0; for (const [id, record] of [...this.records]) { if (!matchesFilter(record.metadata, filter)) { continue; } this.records.delete(id); removed += 1; } return removed; } async search(embedding, options) { const scored = []; for (const record of this.records.values()) { if (options.filter !== undefined && !matchesFilter(record.metadata, options.filter)) { continue; } const score = cosineSimilarity(embedding, record.embedding); // Relevance floor: drop below-threshold passages before the top-K cut, so `minScore` never // leaves the returned K padded with weakly-related sources. if (options.minScore !== undefined && score < options.minScore) { continue; } scored.push({ id: record.id, text: record.text, score, ...(record.source !== undefined ? { source: record.source } : {}), ...(record.metadata !== undefined ? { metadata: record.metadata } : {}), }); } return scored.sort((a, b) => b.score - a.score).slice(0, options.topK); } } /** Cosine similarity of two vectors; `0` when either has zero magnitude. Pure. */ export function cosineSimilarity(a, b) { let dot = 0; let normA = 0; let normB = 0; const length = Math.min(a.length, b.length); for (let index = 0; index < length; index += 1) { const valueA = a[index] ?? 0; const valueB = b[index] ?? 0; dot += valueA * valueB; normA += valueA * valueA; normB += valueB * valueB; } const denominator = Math.sqrt(normA) * Math.sqrt(normB); return denominator === 0 ? 0 : dot / denominator; } //# sourceMappingURL=memory-vector-store.js.map