@@ -328,6 +328,104 @@ pub async fn restore_checkpoint(
328328 Ok ( StatusCode :: OK )
329329}
330330
331+ // ============================================================================
332+ // Vector Search (Motomeru)
333+ // ============================================================================
334+
335+ #[ derive( Debug , Deserialize ) ]
336+ pub struct VectorSearchRequest {
337+ pub embedding : Vec < f32 > ,
338+ pub k : usize ,
339+ #[ serde( default = "default_min_similarity" ) ]
340+ pub min_similarity : f32 ,
341+ pub entry_type : Option < String > ,
342+ pub tags : Option < Vec < String > > ,
343+ }
344+
345+ fn default_min_similarity ( ) -> f32 {
346+ 0.0
347+ }
348+
349+ #[ derive( Debug , Serialize ) ]
350+ pub struct VectorIndexStatsDto {
351+ pub point_count : usize ,
352+ pub deleted_count : usize ,
353+ pub dimensions : usize ,
354+ pub memory_bytes : usize ,
355+ }
356+
357+ /// Vector search over memory entries using HNSW index.
358+ pub async fn vector_search (
359+ State ( state) : State < AppState > ,
360+ Json ( req) : Json < VectorSearchRequest > ,
361+ ) -> Result < Json < Vec < MemoryResultDto > > > {
362+ let memory = state. memory . read ( ) . await ;
363+ let results = memory. ltm . vector_search_memories ( & req. embedding , req. k , req. min_similarity ) ;
364+
365+ let mut dtos: Vec < MemoryResultDto > = results
366+ . into_iter ( )
367+ . map ( |( entry, similarity) | MemoryResultDto {
368+ id : entry. id . to_hex ( ) ,
369+ entry_type : entry. entry_type . clone ( ) ,
370+ data : entry. data . clone ( ) ,
371+ tags : entry. tags . iter ( ) . map ( |t| t. 0 . clone ( ) ) . collect ( ) ,
372+ importance : entry. metadata . importance ,
373+ relevance : similarity,
374+ source : "LongTerm" . to_string ( ) ,
375+ created_at : entry. metadata . created_at . 0 . to_string ( ) ,
376+ last_accessed : entry. metadata . last_accessed . 0 . to_string ( ) ,
377+ access_count : entry. metadata . access_count ,
378+ } )
379+ . collect ( ) ;
380+
381+ // Apply optional filters
382+ if let Some ( ref entry_type) = req. entry_type {
383+ dtos. retain ( |d| & d. entry_type == entry_type) ;
384+ }
385+ if let Some ( ref tags) = req. tags {
386+ if !tags. is_empty ( ) {
387+ dtos. retain ( |d| tags. iter ( ) . any ( |t| d. tags . contains ( t) ) ) ;
388+ }
389+ }
390+
391+ Ok ( Json ( dtos) )
392+ }
393+
394+ /// Get HNSW vector index statistics.
395+ pub async fn vector_index_stats (
396+ State ( state) : State < AppState > ,
397+ ) -> Result < Json < VectorIndexStatsDto > > {
398+ let memory = state. memory . read ( ) . await ;
399+ let stats = memory. ltm . hnsw_index ( )
400+ . map ( |idx| idx. stats ( ) )
401+ . unwrap_or ( ineru:: hnsw:: HnswStats {
402+ point_count : 0 ,
403+ deleted_count : 0 ,
404+ dimensions : 0 ,
405+ max_layer : 0 ,
406+ memory_bytes : 0 ,
407+ } ) ;
408+
409+ Ok ( Json ( VectorIndexStatsDto {
410+ point_count : stats. point_count ,
411+ deleted_count : stats. deleted_count ,
412+ dimensions : stats. dimensions ,
413+ memory_bytes : stats. memory_bytes ,
414+ } ) )
415+ }
416+
417+ /// Force rebuild of the HNSW vector index.
418+ pub async fn rebuild_vector_index (
419+ State ( state) : State < AppState > ,
420+ ) -> Result < StatusCode > {
421+ let mut memory = state. memory . write ( ) . await ;
422+ if let Some ( hnsw) = memory. ltm . hnsw_index_mut ( ) {
423+ hnsw. rebuild ( ) ;
424+ tracing:: info!( "HNSW index rebuilt, {} active points" , hnsw. len( ) ) ;
425+ }
426+ Ok ( StatusCode :: OK )
427+ }
428+
331429// ============================================================================
332430// Helpers
333431// ============================================================================
@@ -376,4 +474,8 @@ pub fn memory_router() -> axum::Router<AppState> {
376474 . route ( "/api/v1/memory/checkpoint" , post ( checkpoint) )
377475 . route ( "/api/v1/memory/checkpoints" , get ( list_checkpoints) )
378476 . route ( "/api/v1/memory/restore/{id}" , post ( restore_checkpoint) )
477+ // Motomeru: HNSW vector search endpoints
478+ . route ( "/api/v1/memory/search" , post ( vector_search) )
479+ . route ( "/api/v1/memory/index/stats" , get ( vector_index_stats) )
480+ . route ( "/api/v1/memory/index/rebuild" , post ( rebuild_vector_index) )
379481}
0 commit comments