@@ -47,7 +47,8 @@ impl ValoriEngine {
4747
4848 /// Insert a record. Returns the assigned ID.
4949 /// Valori Kernel enforces dense ID packing (first free slot).
50- fn insert ( & self , vector : Vec < f32 > ) -> PyResult < u32 > {
50+ #[ pyo3( signature = ( vector, tag) ) ]
51+ fn insert ( & self , vector : Vec < f32 > , tag : u64 ) -> PyResult < u32 > {
5152 if vector. len ( ) != D {
5253 return Err ( pyo3:: exceptions:: PyValueError :: new_err ( format ! ( "Expected {} dims" , D ) ) ) ;
5354 }
@@ -78,7 +79,7 @@ impl ValoriEngine {
7879
7980 // 3. Commit via Event Log (Preferred) or WAL (Fallback)
8081 if let Some ( ref mut committer) = engine. event_committer {
81- let event = KernelEvent :: InsertRecord { id : rid, vector : fxp_vec } ;
82+ let event = KernelEvent :: InsertRecord { id : rid, vector : fxp_vec, metadata : None , tag } ;
8283 match committer. commit_event ( event. clone ( ) ) {
8384 Ok ( _) => {
8485 // Sync Engine State
@@ -96,16 +97,33 @@ impl ValoriEngine {
9697 }
9798 }
9899
99- fn search ( & self , vector : Vec < f32 > , k : usize ) -> PyResult < Vec < ( u32 , i64 ) > > {
100- if vector. len ( ) != D {
100+ #[ pyo3( signature = ( vector, k, filter_tag=None ) ) ]
101+ fn search ( & self , vector : Vec < f32 > , k : usize , filter_tag : Option < u64 > ) -> PyResult < Vec < ( u32 , i64 ) > > {
102+ if vector. len ( ) != D {
101103 return Err ( pyo3:: exceptions:: PyValueError :: new_err ( format ! ( "Expected {} dims" , D ) ) ) ;
102104 }
103105
104106 let engine = self . inner . lock ( ) . unwrap ( ) ;
105- match engine. search_l2 ( & vector, k) {
106- Ok ( results) => Ok ( results) ,
107- Err ( e) => Err ( pyo3:: exceptions:: PyRuntimeError :: new_err ( format ! ( "{:?}" , e) ) )
107+
108+ // Convert query to FxpVector for kernel search
109+ let mut fxp_vec = FxpVector :: < D > :: new_zeros ( ) ;
110+ for ( i, & v) in vector. iter ( ) . enumerate ( ) {
111+ let fixed = ( v * SCALE ) . round ( ) . clamp ( i32:: MIN as f32 , i32:: MAX as f32 ) as i32 ;
112+ fxp_vec. data [ i] = FxpScalar ( fixed) ;
113+ }
114+
115+ let mut results = vec ! [ valori_kernel:: index:: SearchResult :: default ( ) ; k] ;
116+
117+ // Call Kernel Directly for Filtered Search
118+ let count = engine. state . search_l2 ( & fxp_vec, & mut results, filter_tag) ;
119+
120+ let mut py_results = Vec :: with_capacity ( count) ;
121+ for i in 0 ..count {
122+ let r = results[ i] ;
123+ py_results. push ( ( r. id . 0 as u32 , r. score . 0 as i64 ) ) ;
108124 }
125+
126+ Ok ( py_results)
109127 }
110128
111129 fn save ( & mut self ) -> PyResult < String > {
@@ -115,6 +133,62 @@ impl ValoriEngine {
115133 Err ( e) => Err ( pyo3:: exceptions:: PyRuntimeError :: new_err ( format ! ( "{:?}" , e) ) )
116134 }
117135 }
136+
137+ #[ pyo3( signature = ( kind, record_id=None ) ) ]
138+ fn create_node ( & self , kind : u8 , record_id : Option < u32 > ) -> PyResult < u32 > {
139+ let mut engine = self . inner . lock ( ) . unwrap ( ) ;
140+
141+ let rid = record_id. map ( |r| RecordId ( r) ) ;
142+
143+ use valori_kernel:: types:: enums:: NodeKind ;
144+ let k = NodeKind :: from_u8 ( kind)
145+ . ok_or_else ( || pyo3:: exceptions:: PyValueError :: new_err ( format ! ( "Invalid NodeKind: {}" , kind) ) ) ?;
146+
147+ // Deterministic ID generation (Calculate BEFORE mutable borrow for event log)
148+ // Check NodePool indexing. Assuming 0-based from pool.rs inspection or trial.
149+ let next_id = valori_kernel:: types:: id:: NodeId ( engine. state . node_count ( ) as u32 ) ;
150+
151+ // Use event log if available
152+ if let Some ( ref mut committer) = engine. event_committer {
153+ let event = KernelEvent :: CreateNode { id : next_id, kind : k, record : rid } ;
154+
155+ match committer. commit_event ( event. clone ( ) ) {
156+ Ok ( _) => {
157+ engine. apply_committed_event ( & event) . map_err ( |e| {
158+ pyo3:: exceptions:: PyRuntimeError :: new_err ( format ! ( "Apply failed: {:?}" , e) )
159+ } ) ?;
160+ Ok ( next_id. 0 )
161+ }
162+ Err ( e) => return Err ( pyo3:: exceptions:: PyRuntimeError :: new_err ( format ! ( "Commit failed: {:?}" , e) ) ) ,
163+ }
164+ } else {
165+ // Fallback to direct state mutation
166+ let node_id = engine. state . create_node ( k, rid)
167+ . map_err ( |e| pyo3:: exceptions:: PyRuntimeError :: new_err ( format ! ( "{:?}" , e) ) ) ?;
168+ Ok ( node_id. 0 )
169+ }
170+ }
171+
172+ fn create_edge ( & self , from : u32 , to : u32 , kind : u8 ) -> PyResult < u32 > {
173+ let mut engine = self . inner . lock ( ) . unwrap ( ) ;
174+ use valori_kernel:: types:: id:: NodeId ;
175+ use valori_kernel:: types:: enums:: EdgeKind ;
176+
177+ let k = EdgeKind :: from_u8 ( kind)
178+ . ok_or_else ( || pyo3:: exceptions:: PyValueError :: new_err ( format ! ( "Invalid EdgeKind: {}" , kind) ) ) ?;
179+
180+ // Predict ID for return (though direct create_edge returns it)
181+ // If we want event sourcing support for edges later, we need next_id.
182+ // But for now create_edge calls direct mutation in fallback.
183+ // Wait, current impl calls engine.state.create_edge directly.
184+ // So we don't need to predict ID here unless we implement event sourcing for edges.
185+ // But create_node above DOES event sourcing.
186+
187+ let edge_id = engine. state . create_edge ( NodeId ( from) , NodeId ( to) , k)
188+ . map_err ( |e| pyo3:: exceptions:: PyRuntimeError :: new_err ( format ! ( "{:?}" , e) ) ) ?;
189+
190+ Ok ( edge_id. 0 )
191+ }
118192}
119193
120194#[ pymodule]
0 commit comments