From 49982a85defbad02083514fa99aac9af8a16c82e Mon Sep 17 00:00:00 2001 From: Storme-bit Date: Mon, 27 Apr 2026 05:21:43 -0700 Subject: [PATCH] retrieval fusion --- packages/memory-service/src/episodic/index.js | 19 ++++++++++++++----- packages/memory-service/src/index.js | 8 +++++--- packages/shared/src/config/constants.js | 9 ++++++++- packages/shared/src/index.js | 3 ++- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/packages/memory-service/src/episodic/index.js b/packages/memory-service/src/episodic/index.js index 5be71ce..c90c347 100644 --- a/packages/memory-service/src/episodic/index.js +++ b/packages/memory-service/src/episodic/index.js @@ -168,17 +168,26 @@ function getRecentEpisodes(sessionId, limit = EPISODIC.DEFAULT_RECENT_LIMIT) { // Searches episodes using FTS5 full-text search, ordered by relevance, with a limit -function searchEpisodes(query, limit = EPISODIC.DEFAULT_SEARCH_LIMIT) { - // FTS5 full-text search across all episodes +function searchEpisodes(query, limit = EPISODIC.DEFAULT_SEARCH_LIMIT, sessionIds = null) { const db = getDB(); - const stmt = db.prepare(` + if (sessionIds && sessionIds.length > 0) { + const ph = sessionIds.map(() => '?').join(','); + return db.prepare(` + SELECT e.* FROM episodes e + JOIN episodes_fts fts ON e.id = fts.rowid + WHERE episodes_fts MATCH ? + AND e.session_id IN (${ph}) + ORDER BY rank + LIMIT ? + `).all(query, ...sessionIds, limit).map(parseRow); + } + return db.prepare(` SELECT e.* FROM episodes e JOIN episodes_fts fts ON e.id = fts.rowid WHERE episodes_fts MATCH ? ORDER BY rank LIMIT ? - `); - return stmt.all(query, limit).map(parseRow); + `).all(query, limit).map(parseRow); } // Deletes an episode by its ID diff --git a/packages/memory-service/src/index.js b/packages/memory-service/src/index.js index f6da6c0..91c670d 100644 --- a/packages/memory-service/src/index.js +++ b/packages/memory-service/src/index.js @@ -131,10 +131,12 @@ app.get('/episodes', (req, res) => { // Search MUST come before /:id — otherwise 'search' gets captured as an id app.get('/episodes/search', (req, res) => { - const { q, limit = EPISODIC.DEFAULT_PAGE_SIZE } = req.query; + const { q, limit = EPISODIC.DEFAULT_PAGE_SIZE, sessionIds } = req.query; if (!q) return res.status(400).json({ error: 'q (query) parameter is required' }); - const results = episodic.searchEpisodes(q, Number(limit)); - res.json(results); + const parsedSessionIds = sessionIds + ? sessionIds.split(',').map(Number).filter(Boolean) + : null; + res.json(episodic.searchEpisodes(q, Number(limit), parsedSessionIds)); }); app.get('/episodes/:id', (req, res) => { diff --git a/packages/shared/src/config/constants.js b/packages/shared/src/config/constants.js index 99c187e..b71112d 100644 --- a/packages/shared/src/config/constants.js +++ b/packages/shared/src/config/constants.js @@ -96,6 +96,12 @@ const ENTITIES = { ], } +const RETRIEVAL = { + RRF_K: 60, // Reciprocal Rank Fusion smoothing constant, softens rank-1 advantage, not exposed in settings + SEMANTIC_WEIGHT: 1.0, // Weight applied to semantic (QDrant) results + KEYWORD_WEIGHT: 0, // Weight applied to keyword (SQLite) results, 0 = disables, set >0 to enable and tune balance between semantic vs keyword matches +} + module.exports = { QDRANT, COLLECTIONS, @@ -108,5 +114,6 @@ module.exports = { SQLITE, ORCHESTRATION, SUMMARIES, - ENTITIES + ENTITIES, + RETRIEVAL, }; \ No newline at end of file diff --git a/packages/shared/src/index.js b/packages/shared/src/index.js index 14f187a..4a321b7 100644 --- a/packages/shared/src/index.js +++ b/packages/shared/src/index.js @@ -1,5 +1,5 @@ const {getEnv} = require('./config/env'); -const {QDRANT, COLLECTIONS, EPISODIC, SERVICES, OLLAMA, PORTS, LLAMACPP, INFERENCE_DEFAULTS, SQLITE, ORCHESTRATION, SUMMARIES, ENTITIES } = require('./config/constants'); +const {QDRANT, COLLECTIONS, EPISODIC, SERVICES, OLLAMA, PORTS, LLAMACPP, INFERENCE_DEFAULTS, SQLITE, ORCHESTRATION, SUMMARIES, ENTITIES, RETRIEVAL } = require('./config/constants'); const {parseRow, formatEpisodeText} = require('./utils') const logger = require('./utils/logger'); @@ -20,4 +20,5 @@ module.exports = { SUMMARIES, ENTITIES, logger, + RETRIEVAL, }; \ No newline at end of file