retrieval fusion

This commit is contained in:
Storme-bit
2026-04-27 05:21:43 -07:00
parent 9c6c5c9a42
commit 49982a85de
4 changed files with 29 additions and 10 deletions

View File

@@ -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

View File

@@ -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) => {

View File

@@ -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,
};

View File

@@ -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,
};