Skip to content

Commit c9d80da

Browse files
committed
feat(ffi): Update Python FFI for metadata support
- Added metadata parameter to insert methods - Updated FFI wrapper to handle metadata field - Updated Cargo.toml dependencies
1 parent a147d0f commit c9d80da

File tree

2 files changed

+82
-8
lines changed

2 files changed

+82
-8
lines changed

ffi/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ name = "valori_ffi"
88
crate-type = ["cdylib"]
99

1010
[dependencies]
11-
valori-kernel = { path = "../crates/kernel" }
11+
valori-kernel = { path = ".." }
1212
valori-node = { path = "../node" }
1313
pyo3 = { version = "0.23", features = ["extension-module"] }
1414
serde_json = "1.0"

ffi/src/lib.rs

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)