Skip to content

NAPI-RS Type Conversion Errors in @ruvector/attention and @ruvector/gnn #35

@ruvnet

Description

@ruvnet

Summary

The NAPI bindings in @ruvector/attention and @ruvector/gnn packages are failing with type conversion errors when called from TypeScript/JavaScript. The error message indicates that Rust is receiving Object types when it expects primitive types like f64.

Error Message:

Failed to convert napi value Object into rust type f64

Affected Functions

@ruvector/gnn Package

1. hierarchicalForward()

  • Error: "Failed to convert napi value Object into rust type f64"
  • Location: Called from RuVectorLearning.enhanceHierarchical() in AgentDB
  • Current binding signature (crates/ruvector-gnn-node/src/lib.rs:364-368):
#[napi]
pub fn hierarchical_forward(
    query: Vec<f64>,
    layer_embeddings: Vec<Vec<Vec<f64>>>,
    gnn_layers_json: Vec<String>,
) -> Result<Vec<f64>>
  • Parameters being passed from JS:
hierarchicalForward(
  Array.from(query),              // Array<number> from Float32Array
  layerEmbeddings.map(layer =>    // Array<Array<Array<number>>>
    layer.map(e => Array.from(e))
  ),
  [layerJson]                     // Array<string> - JSON serialized layer
)

@ruvector/attention Package

2. Attention Mechanisms

All attention mechanisms use Float32Array which works correctly, but there may be edge cases with type coercion:

  • multiHeadAttention() - Uses Float32Array correctly
  • flashAttention() - Uses Float32Array correctly
  • linearAttention() - Uses Float32Array correctly
  • hyperbolicAttention() - Uses Float32Array correctly
  • moeAttention() - Uses Float32Array correctly

Current binding signature (npm/node_modules/@ruvector/attention/src/attention.rs:145-161):

#[napi]
pub fn compute(
    &self,
    query: Float32Array,
    keys: Vec<Float32Array>,
    values: Vec<Float32Array>,
) -> Result<Float32Array>

Root Cause Analysis

The issue appears to be in the NAPI-RS bindings where:

  1. Array Type Mismatch: The hierarchicalForward function expects Vec<f64> but JavaScript's Array.from(Float32Array) produces an Array<number> which NAPI-RS may interpret as Object rather than Vec<f64>.

  2. Nested Array Handling: The deeply nested array structure Array<Array<Array<number>>> for layerEmbeddings may not be properly converted through the NAPI boundary when using Vec<Vec<Vec<f64>>>.

  3. Type Coercion Path: NAPI-RS attempts to convert JS arrays to Rust Vec<f64>, but when the array contains objects or the nesting is complex, it may fail to recognize the proper conversion path.

Expected Behavior

The NAPI bindings should:

  1. Accept standard JavaScript types (Array, number, Float32Array)
  2. Properly convert nested array structures
  3. Handle JSON strings or require pre-parsed objects (with clear documentation)
  4. Provide clear type errors when incorrect types are passed

Current Workaround

AgentDB has implemented graceful fallback that returns the original query when NAPI calls fail:

try {
  const result = this.hierarchicalForward(
    Array.from(query),
    layerEmbeddings.map(layer => layer.map(e => Array.from(e))),
    [layerJson]
  );
  return new Float32Array(result);
} catch (error) {
  console.warn(`[RuVectorLearning] Hierarchical enhancement failed: ${(error as Error).message}`);
  return query;  // Fallback to original query
}

Reproduction Steps

  1. Install @ruvector/gnn package
  2. Create a RuvectorLayer and serialize to JSON:
const layer = new RuvectorLayer(128, 256, 4, 0.1);
const layerJson = layer.toJson();
  1. Call hierarchicalForward() with array parameters:
const query = Array.from(new Float32Array([1.0, 2.0, 3.0, ...]));
const layerEmbeddings = [[[1.0, 2.0], [3.0, 4.0]]];
const result = hierarchicalForward(query, layerEmbeddings, [layerJson]);
  1. Observe NAPI type conversion error

Environment

  • Node.js: v18+
  • TypeScript: 5.7.2
  • Platform: Linux (also affects macOS/Windows)
  • Package Versions:
    • @ruvector/gnn: Latest
    • @ruvector/attention: v0.1.1
    • ruvector: v0.1.24

Suggested Fixes

Option 1: Accept Multiple Input Types via Either

Use NAPI-RS Either to accept both Float32Array and Vec<f64>:

use napi::Either;

#[napi]
pub fn hierarchical_forward(
    query: Either<Float32Array, Vec<f64>>,
    layer_embeddings: Vec<Vec<Vec<f64>>>,
    gnn_layers_json: Vec<String>,
) -> Result<Vec<f64>> {
    let query_vec: Vec<f32> = match query {
        Either::A(arr) => arr.to_vec(),
        Either::B(vec) => vec.iter().map(|&x| x as f32).collect(),
    };
    // ...
}

Option 2: Use Float32Array Consistently

Change the signature to use Float32Array like the attention mechanisms:

#[napi]
pub fn hierarchical_forward(
    query: Float32Array,
    layer_embeddings: Vec<Vec<Float32Array>>,  // Flatten one level
    gnn_layers_json: Vec<String>,
) -> Result<Float32Array>

Option 3: Add TypeScript Wrapper with Validation

Provide a TypeScript wrapper that validates and converts types:

// Export from @ruvector/gnn
export function prepareHierarchicalInput(
  query: Float32Array | number[],
  embeddings: Float32Array[][] | number[][][]
): PreparedInput {
  return {
    query: query instanceof Float32Array ? query : new Float32Array(query),
    embeddings: embeddings.map(layer => 
      layer.map(e => e instanceof Float32Array ? e : new Float32Array(e))
    )
  };
}

Option 4: Improve Error Messages

Add runtime type checking with clear error messages:

#[napi]
pub fn hierarchical_forward(
    query: JsUnknown,
    layer_embeddings: JsUnknown,
    gnn_layers_json: Vec<String>,
) -> Result<Vec<f64>> {
    // Validate query type
    let query_vec = extract_number_array(query)
        .map_err(|_| Error::new(
            Status::InvalidArg,
            "query must be Array<number> or Float32Array"
        ))?;
    // ...
}

Additional Context

  • The errors only occur when optional dependencies are installed
  • When @ruvector/gnn or @ruvector/attention are not installed, AgentDB gracefully falls back to JavaScript implementations which work correctly
  • This confirms the issue is specifically in the NAPI bindings, not the calling code
  • The attention mechanisms in @ruvector/attention use Float32Array directly and work correctly

Impact

  • Severity: Medium - Features degrade gracefully but advanced attention/GNN features are unavailable
  • Workaround: Available (fallback to JS implementations)
  • Affected Users: Anyone using the NAPI bindings in Node.js environments

Related Files

  • crates/ruvector-gnn-node/src/lib.rs:364-396 - hierarchical_forward implementation
  • npm/node_modules/@ruvector/attention/src/attention.rs - Attention mechanism bindings (reference for working Float32Array usage)
  • npm/node_modules/@ruvector/gnn/src/lib.rs - Published GNN bindings

Labels

bug, napi-rs, type-conversion, gnn, attention

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions