📄 memoryManager.ts • 3488 bytes
/**
* CmdCode 向量记忆系统 - 统一对外接口
*/
import { t } from '../i18n'
import {
getDb,
initializeDatabase,
closeDb,
DB_PATH
} from './database'
import {
createSession,
getSession,
listSessions,
deleteSession,
addMessage,
getSessionMessages,
searchFTS,
type SessionInfo,
type MessageInfo
} from './sessionStore'
import {
searchVectors,
storeMessageEmbedding,
hasVector,
getVectorCount,
deleteMessageVector
} from './vectorSearch'
import { rrfFusion, simpleFusion, type SearchResult } from './rrf'
import { startBackfill, stopBackfill, triggerBackfill, getBackfillStatus } from './backfill'
import { loadConfig as loadEmbeddingConfig } from './embedding'
// 导出类型
export type { SessionInfo, MessageInfo, SearchResult }
// 导出函数
export {
initializeDatabase,
closeDb,
DB_PATH,
createSession,
getSession,
listSessions,
deleteSession,
addMessage,
getSessionMessages,
searchFTS,
searchVectors,
storeMessageEmbedding,
hasVector,
getVectorCount,
deleteMessageVector,
rrfFusion,
simpleFusion,
startBackfill,
stopBackfill,
triggerBackfill,
getBackfillStatus
}
// 内存占用
let memoryUsage = 0
/** 统计摘要 */
export function getSummary(): {
sessions: number
messages: number
vectors: number
cacheSize: number
backfillStatus: { running: boolean; interval: number | null }
} {
const db = getDb()
const sessions = (db.prepare('SELECT COUNT(*) as cnt FROM sessions').get() as { cnt: number }).cnt
const messages = (db.prepare('SELECT COUNT(*) as cnt FROM messages').get() as { cnt: number }).cnt
return {
sessions,
messages,
vectors: getVectorCount(),
cacheSize: memoryUsage,
backfillStatus: getBackfillStatus()
}
}
/** 搜索记忆(双路融合) */
export async function searchMemory(query: string, sessionId?: string, limit = 20): Promise<SearchResult[]> {
// 1. FTS5 关键词搜索
const ftsResults = searchFTS(query, sessionId, limit)
// 2. 向量语义搜索
const vecResults = await searchVectors(query, sessionId, limit)
// 3. RRF 融合
const map = new Map<string, SearchResult[]>()
map.set('fts', ftsResults.map((r, i) => ({
...r,
score: 1 / (60 + i + 1),
source: 'fts' as const
})))
map.set('vec', vecResults.map((r, i) => ({
...r,
score: 1 / (60 + i + 1),
source: 'vec' as const
})))
return rrfFusion(map, 60).slice(0, limit)
}
/** 保存新消息并自动向量化 */
export async function saveMessage(sessionId: string, role: string, content: string): Promise<MessageInfo> {
const message = addMessage(sessionId, role, content)
// 异步存储向量(不阻塞)
triggerBackfill().catch(console.error)
return message
}
/** 初始化记忆系统 */
export function initMemorySystem(): void {
console.log(' 🧠 ' + t('memory.init'))
// 注:getDb() 首次调用时已自动执行 initializeDatabase(),无需重复调用
// 检查 Embedding API 密钥是否已配置
const embedConfig = loadEmbeddingConfig()
if (!embedConfig.key) {
console.log(' ⚠️ 未配置 Embedding API 密钥,向量搜索功能暂时不可用。')
console.log(' 请使用 /keypool add embedding 或 /set mem 命令添加密钥。')
}
startBackfill(5 * 60 * 1000)
console.log(' ✅ ' + t('memory.ready'))
console.log(` ${t('memory.db_path')}: ${DB_PATH}`)
console.log(` ${t('memory.vector_count')}: ${getVectorCount()}`)
}