| "use strict"; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| Object.defineProperty(exports, "__esModule", { value: true }); |
| exports.FastAgentDB = void 0; |
| exports.createFastAgentDB = createFastAgentDB; |
| exports.getDefaultAgentDB = getDefaultAgentDB; |
| |
| let coreModule = null; |
| function getCoreModule() { |
| if (coreModule) |
| return coreModule; |
| try { |
| coreModule = require('@ruvector/core'); |
| return coreModule; |
| } |
| catch { |
| |
| try { |
| coreModule = require('ruvector'); |
| return coreModule; |
| } |
| catch (e) { |
| throw new Error(`Neither @ruvector/core nor ruvector is available: ${e.message}`); |
| } |
| } |
| } |
| |
| |
| |
| class FastAgentDB { |
| |
| |
| |
| |
| |
| |
| constructor(dimensions = 128, maxEpisodes = 100000) { |
| this.episodes = new Map(); |
| this.trajectories = new Map(); |
| this.vectorDb = null; |
| this.episodeOrder = []; |
| this.dimensions = dimensions; |
| this.maxEpisodes = maxEpisodes; |
| } |
| |
| |
| |
| async initVectorDb() { |
| if (this.vectorDb) |
| return; |
| try { |
| const core = getCoreModule(); |
| this.vectorDb = new core.VectorDB({ |
| dimensions: this.dimensions, |
| distanceMetric: 'Cosine', |
| }); |
| } |
| catch (e) { |
| |
| console.warn(`VectorDB not available, using fallback similarity: ${e.message}`); |
| } |
| } |
| |
| |
| |
| |
| |
| |
| async storeEpisode(episode) { |
| await this.initVectorDb(); |
| const id = episode.id ?? this.generateId(); |
| const fullEpisode = { |
| ...episode, |
| id, |
| timestamp: episode.timestamp ?? Date.now(), |
| }; |
| |
| if (this.episodes.size >= this.maxEpisodes) { |
| const oldestId = this.episodeOrder.shift(); |
| if (oldestId) { |
| this.episodes.delete(oldestId); |
| } |
| } |
| this.episodes.set(id, fullEpisode); |
| this.episodeOrder.push(id); |
| |
| if (this.vectorDb && fullEpisode.state.length === this.dimensions) { |
| try { |
| await this.vectorDb.insert({ |
| id, |
| vector: new Float32Array(fullEpisode.state), |
| }); |
| } |
| catch { |
| |
| } |
| } |
| return id; |
| } |
| |
| |
| |
| async storeEpisodes(episodes) { |
| const ids = []; |
| for (const episode of episodes) { |
| const id = await this.storeEpisode(episode); |
| ids.push(id); |
| } |
| return ids; |
| } |
| |
| |
| |
| async getEpisode(id) { |
| const episode = this.episodes.get(id); |
| if (episode) { |
| |
| const idx = this.episodeOrder.indexOf(id); |
| if (idx > -1) { |
| this.episodeOrder.splice(idx, 1); |
| this.episodeOrder.push(id); |
| } |
| } |
| return episode ?? null; |
| } |
| |
| |
| |
| |
| |
| |
| |
| async searchByState(queryState, k = 10) { |
| await this.initVectorDb(); |
| const query = Array.isArray(queryState) ? queryState : Array.from(queryState); |
| |
| if (this.vectorDb && query.length === this.dimensions) { |
| try { |
| const results = await this.vectorDb.search({ |
| vector: new Float32Array(query), |
| k, |
| }); |
| return results |
| .map((r) => { |
| const episode = this.episodes.get(r.id); |
| if (!episode) |
| return null; |
| return { |
| episode, |
| similarity: 1 - r.score, |
| }; |
| }) |
| .filter((r) => r !== null); |
| } |
| catch { |
| |
| } |
| } |
| |
| return this.fallbackSearch(query, k); |
| } |
| |
| |
| |
| fallbackSearch(query, k) { |
| const results = []; |
| for (const episode of this.episodes.values()) { |
| if (episode.state.length !== query.length) |
| continue; |
| const similarity = this.cosineSimilarity(query, episode.state); |
| results.push({ episode, similarity }); |
| } |
| return results |
| .sort((a, b) => b.similarity - a.similarity) |
| .slice(0, k); |
| } |
| |
| |
| |
| cosineSimilarity(a, b) { |
| let dotProduct = 0; |
| let normA = 0; |
| let normB = 0; |
| for (let i = 0; i < a.length; i++) { |
| dotProduct += a[i] * b[i]; |
| normA += a[i] * a[i]; |
| normB += b[i] * b[i]; |
| } |
| const denom = Math.sqrt(normA) * Math.sqrt(normB); |
| return denom === 0 ? 0 : dotProduct / denom; |
| } |
| |
| |
| |
| async storeTrajectory(episodes, metadata) { |
| const trajectoryId = this.generateId(); |
| const storedEpisodes = []; |
| let totalReward = 0; |
| for (const episode of episodes) { |
| const id = await this.storeEpisode(episode); |
| const stored = await this.getEpisode(id); |
| if (stored) { |
| storedEpisodes.push(stored); |
| totalReward += stored.reward; |
| } |
| } |
| const trajectory = { |
| id: trajectoryId, |
| episodes: storedEpisodes, |
| totalReward, |
| metadata, |
| }; |
| this.trajectories.set(trajectoryId, trajectory); |
| return trajectoryId; |
| } |
| |
| |
| |
| async getTrajectory(id) { |
| return this.trajectories.get(id) ?? null; |
| } |
| |
| |
| |
| async getTopTrajectories(k = 10) { |
| return Array.from(this.trajectories.values()) |
| .sort((a, b) => b.totalReward - a.totalReward) |
| .slice(0, k); |
| } |
| |
| |
| |
| async sampleEpisodes(n) { |
| const allEpisodes = Array.from(this.episodes.values()); |
| const sampled = []; |
| for (let i = 0; i < Math.min(n, allEpisodes.length); i++) { |
| const idx = Math.floor(Math.random() * allEpisodes.length); |
| sampled.push(allEpisodes[idx]); |
| } |
| return sampled; |
| } |
| |
| |
| |
| getStats() { |
| return { |
| episodeCount: this.episodes.size, |
| trajectoryCount: this.trajectories.size, |
| dimensions: this.dimensions, |
| maxEpisodes: this.maxEpisodes, |
| vectorDbAvailable: this.vectorDb !== null, |
| }; |
| } |
| |
| |
| |
| clear() { |
| this.episodes.clear(); |
| this.trajectories.clear(); |
| this.episodeOrder = []; |
| } |
| |
| |
| |
| generateId() { |
| return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; |
| } |
| } |
| exports.FastAgentDB = FastAgentDB; |
| |
| |
| |
| function createFastAgentDB(dimensions = 128, maxEpisodes = 100000) { |
| return new FastAgentDB(dimensions, maxEpisodes); |
| } |
| |
| let defaultInstance = null; |
| |
| |
| |
| function getDefaultAgentDB() { |
| if (!defaultInstance) { |
| defaultInstance = new FastAgentDB(); |
| } |
| return defaultInstance; |
| } |
| exports.default = { |
| FastAgentDB, |
| createFastAgentDB, |
| getDefaultAgentDB, |
| }; |
|
|