Skip to content

Latest commit

 

History

History
713 lines (552 loc) · 19.2 KB

File metadata and controls

713 lines (552 loc) · 19.2 KB

Ahnlich Node.js Client SDK

Ahnlich TestSuite Node Client Tag and Deploy

A Node.js/TypeScript client that interacts with both Ahnlich DB and AI over gRPC.

Table of Contents

Installation

npm install ahnlich-client-node

Package Information

This package provides:

  • gRPC service clients for DB and AI via @connectrpc/connect
  • TypeScript types generated from the Ahnlich .proto definitions
  • Optional auth (TLS + bearer token) and trace ID support

Initialization

DB Client

import { createDbClient } from "ahnlich-client-node";

const client = createDbClient("127.0.0.1:1369");

AI Client

import { createAiClient } from "ahnlich-client-node";

const client = createAiClient("127.0.0.1:1370");

With Authentication

When the server is started with --enable-auth, pass a CA certificate and credentials:

import * as fs from "fs";
import { createDbClient } from "ahnlich-client-node";

const client = createDbClient("127.0.0.1:1369", {
  caCert: fs.readFileSync("ca.crt"),
  auth: { username: "myuser", apiKey: "mykey" },
});

Pass a trace ID to correlate requests across services:

const client = createDbClient("127.0.0.1:1369", {
  traceId: "00-80e1afed08e019fc1110464cfa66635c-7a085853722dc6d2-01",
});

Requests - DB

Ping

import { Ping } from "ahnlich-client-node/grpc/db/query_pb";

const response = await client.ping(new Ping());
console.log(response); // Pong

Info Server

import { InfoServer } from "ahnlich-client-node/grpc/db/query_pb";

const response = await client.infoServer(new InfoServer());
console.log(response.info?.version);

List Connected Clients

import { ListClients } from "ahnlich-client-node/grpc/db/query_pb";

const response = await client.listClients(new ListClients());
console.log(response.clients);

List Stores

import { ListStores } from "ahnlich-client-node/grpc/db/query_pb";

const response = await client.listStores(new ListStores());
console.log(response.stores.map((s) => s.name));

Each StoreInfo object in response.stores includes name, len, sizeInBytes, nonLinearIndices, predicateIndices, and dimension.

Get Store

Returns detailed information about a single store by name.

import { GetStore } from "ahnlich-client-node/grpc/db/query_pb";

const response = await client.getStore(new GetStore({ store: "my_store" }));
console.log(response.name);            // store name
console.log(response.dimension);        // vector dimension
console.log(response.predicateIndices); // indexed predicate keys
console.log(response.nonLinearIndices); // non-linear algorithm indices
console.log(response.len);             // number of entries
console.log(response.sizeInBytes);     // size on disk

Create Store

import { CreateStore } from "ahnlich-client-node/grpc/db/query_pb";

await client.createStore(
  new CreateStore({
    store: "my_store",
    dimension: 4,
    predicates: ["label"],
    errorIfExists: true,
  }),
);

Store dimension is fixed at creation — all inserted vectors must match it.

Set

import { Set } from "ahnlich-client-node/grpc/db/query_pb";
import { DbStoreEntry, StoreKey, StoreValue } from "ahnlich-client-node/grpc/keyval_pb";
import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb";

await client.set(
  new Set({
    store: "my_store",
    inputs: [
      new DbStoreEntry({
        key: new StoreKey({ key: [1.0, 2.0, 3.0, 4.0] }),
        value: new StoreValue({
          value: {
            label: new MetadataValue({ value: { case: "rawString", value: "A" } }),
          },
        }),
      }),
    ],
  }),
);

Get Sim N

Returns the closest N entries to a query vector.

import { GetSimN } from "ahnlich-client-node/grpc/db/query_pb";
import { StoreKey } from "ahnlich-client-node/grpc/keyval_pb";
import { Algorithm } from "ahnlich-client-node/grpc/algorithm/algorithm_pb";

const response = await client.getSimN(
  new GetSimN({
    store: "my_store",
    searchInput: new StoreKey({ key: [1.0, 2.0, 3.0, 4.0] }),
    closestN: 3,
    algorithm: Algorithm.COSINE_SIMILARITY,
  }),
);
console.log(response.entries);

Get Key

import { GetKey } from "ahnlich-client-node/grpc/db/query_pb";
import { StoreKey } from "ahnlich-client-node/grpc/keyval_pb";

const response = await client.getKey(
  new GetKey({
    store: "my_store",
    keys: [new StoreKey({ key: [1.0, 2.0, 3.0, 4.0] })],
  }),
);
console.log(response.entries);

Get By Predicate

import { GetPred } from "ahnlich-client-node/grpc/db/query_pb";
import { PredicateCondition, Predicate, Equals } from "ahnlich-client-node/grpc/predicate_pb";
import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb";

const response = await client.getPred(
  new GetPred({
    store: "my_store",
    condition: new PredicateCondition({
      kind: {
        case: "value",
        value: new Predicate({
          kind: {
            case: "equals",
            value: new Equals({
              key: "label",
              value: new MetadataValue({ value: { case: "rawString", value: "A" } }),
            }),
          },
        }),
      },
    }),
  }),
);

Create Predicate Index

import { CreatePredIndex } from "ahnlich-client-node/grpc/db/query_pb";

await client.createPredIndex(
  new CreatePredIndex({ store: "my_store", predicates: ["label"] }),
);

Drop Predicate Index

import { DropPredIndex } from "ahnlich-client-node/grpc/db/query_pb";

await client.dropPredIndex(
  new DropPredIndex({ store: "my_store", predicates: ["label"], errorIfNotExists: true }),
);

Create Non Linear Algorithm Index

import { CreateNonLinearAlgorithmIndex } from "ahnlich-client-node/grpc/db/query_pb";
import { NonLinearIndex, KDTreeConfig, HNSWConfig } from "ahnlich-client-node/grpc/algorithm/nonlinear_pb";

// Create a KDTree index
await client.createNonLinearAlgorithmIndex(
  new CreateNonLinearAlgorithmIndex({
    store: "my_store",
    nonLinearIndices: [new NonLinearIndex({ index: { case: "kdtree", value: new KDTreeConfig() } })],
  }),
);

// Or create an HNSW index (with optional config)
await client.createNonLinearAlgorithmIndex(
  new CreateNonLinearAlgorithmIndex({
    store: "my_store",
    nonLinearIndices: [new NonLinearIndex({ index: { case: "hnsw", value: new HNSWConfig() } })],
  }),
);

Drop Non Linear Algorithm Index

import { DropNonLinearAlgorithmIndex } from "ahnlich-client-node/grpc/db/query_pb";
import { NonLinearAlgorithm } from "ahnlich-client-node/grpc/algorithm/nonlinear_pb";

await client.dropNonLinearAlgorithmIndex(
  new DropNonLinearAlgorithmIndex({
    store: "my_store",
    nonLinearIndices: [NonLinearAlgorithm.KDTree],
    errorIfNotExists: true,
  }),
);

Delete Key

import { DelKey } from "ahnlich-client-node/grpc/db/query_pb";
import { StoreKey } from "ahnlich-client-node/grpc/keyval_pb";

await client.delKey(
  new DelKey({
    store: "my_store",
    keys: [new StoreKey({ key: [1.0, 2.0, 3.0, 4.0] })],
  }),
);

Delete Predicate

import { DelPred } from "ahnlich-client-node/grpc/db/query_pb";

await client.delPred(
  new DelPred({
    store: "my_store",
    condition: /* same PredicateCondition as Get By Predicate */,
  }),
);

Drop Store

import { DropStore } from "ahnlich-client-node/grpc/db/query_pb";

await client.dropStore(new DropStore({ store: "my_store", errorIfNotExists: true }));

Requests - AI

Ping

import { Ping } from "ahnlich-client-node/grpc/ai/query_pb";

const response = await client.ping(new Ping());

Info Server

import { InfoServer } from "ahnlich-client-node/grpc/ai/query_pb";

const response = await client.infoServer(new InfoServer());

List Stores

import { ListStores } from "ahnlich-client-node/grpc/ai/query_pb";

const response = await client.listStores(new ListStores());
console.log(response.stores.map((s) => s.name));

Get Store

Returns detailed information about a single AI store by name.

import { GetStore } from "ahnlich-client-node/grpc/ai/query_pb";

const response = await client.getStore(new GetStore({ store: "ai_store" }));
console.log(response.name);           // store name
console.log(response.queryModel);     // AI model used for querying
console.log(response.indexModel);     // AI model used for indexing
console.log(response.embeddingSize);  // number of stored embeddings
console.log(response.dimension);      // vector dimension
console.log(response.predicateIndices); // indexed predicate keys
console.log(response.dbInfo);         // optional DB store info (when AI is connected to DB)

Create Store

import { CreateStore } from "ahnlich-client-node/grpc/ai/query_pb";
import { AIModel } from "ahnlich-client-node/grpc/ai/models_pb";

await client.createStore(
  new CreateStore({
    store: "ai_store",
    queryModel: AIModel.ALL_MINI_LM_L6_V2,
    indexModel: AIModel.ALL_MINI_LM_L6_V2,
    predicates: ["brand"],
    errorIfExists: true,
    storeOriginal: true,
  }),
);

Set

import { Set } from "ahnlich-client-node/grpc/ai/query_pb";
import { AiStoreEntry, StoreInput, StoreValue } from "ahnlich-client-node/grpc/keyval_pb";
import { MetadataValue } from "ahnlich-client-node/grpc/metadata_pb";
import { PreprocessAction } from "ahnlich-client-node/grpc/ai/preprocess_pb";

await client.set(
  new Set({
    store: "ai_store",
    inputs: [
      new AiStoreEntry({
        key: new StoreInput({ value: { case: "rawString", value: "Jordan One" } }),
        value: new StoreValue({
          value: {
            brand: new MetadataValue({ value: { case: "rawString", value: "Nike" } }),
          },
        }),
      }),
    ],
    preprocessAction: PreprocessAction.NO_PREPROCESSING,
  }),
);

Get Sim N

import { GetSimN } from "ahnlich-client-node/grpc/ai/query_pb";
import { StoreInput } from "ahnlich-client-node/grpc/keyval_pb";
import { Algorithm } from "ahnlich-client-node/grpc/algorithm/algorithm_pb";

const response = await client.getSimN(
  new GetSimN({
    store: "ai_store",
    searchInput: new StoreInput({ value: { case: "rawString", value: "Jordan" } }),
    closestN: 3,
    algorithm: Algorithm.COSINE_SIMILARITY,
  }),
);
console.log(response.entries);

Get By Predicate

import { GetPred } from "ahnlich-client-node/grpc/ai/query_pb";

const response = await client.getPred(
  new GetPred({
    store: "ai_store",
    condition: /* PredicateCondition — same structure as DB */,
  }),
);

Create Predicate Index

import { CreatePredIndex } from "ahnlich-client-node/grpc/ai/query_pb";

await client.createPredIndex(
  new CreatePredIndex({ store: "ai_store", predicates: ["brand"] }),
);

Drop Predicate Index

import { DropPredIndex } from "ahnlich-client-node/grpc/ai/query_pb";

await client.dropPredIndex(
  new DropPredIndex({ store: "ai_store", predicates: ["brand"], errorIfNotExists: true }),
);

Create Non Linear Algorithm Index

import { CreateNonLinearAlgorithmIndex } from "ahnlich-client-node/grpc/ai/query_pb";
import { NonLinearIndex, KDTreeConfig, HNSWConfig } from "ahnlich-client-node/grpc/algorithm/nonlinear_pb";

// Create a KDTree index
await client.createNonLinearAlgorithmIndex(
  new CreateNonLinearAlgorithmIndex({
    store: "ai_store",
    nonLinearIndices: [new NonLinearIndex({ index: { case: "kdtree", value: new KDTreeConfig() } })],
  }),
);

// Or create an HNSW index (with optional config)
await client.createNonLinearAlgorithmIndex(
  new CreateNonLinearAlgorithmIndex({
    store: "ai_store",
    nonLinearIndices: [new NonLinearIndex({ index: { case: "hnsw", value: new HNSWConfig() } })],
  }),
);

Drop Non Linear Algorithm Index

import { DropNonLinearAlgorithmIndex } from "ahnlich-client-node/grpc/ai/query_pb";
import { NonLinearAlgorithm } from "ahnlich-client-node/grpc/algorithm/nonlinear_pb";

await client.dropNonLinearAlgorithmIndex(
  new DropNonLinearAlgorithmIndex({
    store: "ai_store",
    nonLinearIndices: [NonLinearAlgorithm.KDTree],
    errorIfNotExists: true,
  }),
);

Delete Key

import { DelKey } from "ahnlich-client-node/grpc/ai/query_pb";
import { StoreInput } from "ahnlich-client-node/grpc/keyval_pb";

await client.delKey(
  new DelKey({
    store: "ai_store",
    keys: [new StoreInput({ value: { case: "rawString", value: "Jordan One" } })],
  }),
);

Drop Store

import { DropStore } from "ahnlich-client-node/grpc/ai/query_pb";

await client.dropStore(new DropStore({ store: "ai_store", errorIfNotExists: true }));

Convert Store Input To Embeddings

Converts raw inputs (text, images, audio) into embeddings without storing them.

Basic Example:

import { createPromiseClient } from "@connectrpc/connect";
import { createConnectTransport } from "@connectrpc/connect-node";
import { AiService } from "./grpc/services/ai_service_connect";
import { AiModel, PreprocessAction } from "./grpc/ai";

const transport = createConnectTransport({
  baseUrl: "http://localhost:1370",
  httpVersion: "2",
});

const client = createPromiseClient(AiService, transport);

const inputs = [{ rawString: "Hello world" }];

const response = await client.convertStoreInputToEmbeddings({
  storeInputs: inputs,
  preprocessAction: PreprocessAction.NO_PREPROCESSING,
  model: AiModel.ALL_MINI_LM_L6_V2,
});

// Access embeddings
for (const item of response.values) {
  if (item.variant.case === "single") {
    const embedding = item.variant.value.embedding;
    console.log(`Embedding size: ${embedding.key.length}`);
  }
}

Face Detection with Bounding Box Metadata (v0.2.1+):

Buffalo-L and SFace models return normalized bounding boxes (0-1 range) and confidence scores:

import { createPromiseClient } from "@connectrpc/connect";
import { createConnectTransport } from "@connectrpc/connect-node";
import { AiService } from "./grpc/services/ai_service_connect";
import { AiModel, PreprocessAction } from "./grpc/ai";
import * as fs from "fs";

const transport = createConnectTransport({
  baseUrl: "http://localhost:1370",
  httpVersion: "2",
});

const client = createPromiseClient(AiService, transport);

// Load image
const imageBytes = fs.readFileSync("group_photo.jpg");
const inputs = [{ image: imageBytes }];

const response = await client.convertStoreInputToEmbeddings({
  storeInputs: inputs,
  preprocessAction: PreprocessAction.MODEL_PREPROCESSING,
  model: AiModel.BUFFALO_L,
});

// Process detected faces with metadata
for (const item of response.values) {
  if (item.variant.case === "multiple") {
    const faces = item.variant.value.embeddings;
    console.log(`Detected ${faces.length} faces`);
    
    for (const [i, faceData] of faces.entries()) {
      // Access embedding
      const embedding = faceData.embedding!.key; // Float32Array
      console.log(`Face ${i}: ${embedding.length}-dim embedding`);
      
      // Access bounding box metadata
      if (faceData.metadata) {
        const metadata = faceData.metadata.value;
        
        const bboxX1 = parseFloat(metadata.bbox_x1!.value!.value as string);
        const bboxY1 = parseFloat(metadata.bbox_y1!.value!.value as string);
        const bboxX2 = parseFloat(metadata.bbox_x2!.value!.value as string);
        const bboxY2 = parseFloat(metadata.bbox_y2!.value!.value as string);
        const confidence = parseFloat(metadata.confidence!.value!.value as string);
        
        console.log(`  BBox: (${bboxX1.toFixed(3)}, ${bboxY1.toFixed(3)}) ` +
                    `to (${bboxX2.toFixed(3)}, ${bboxY2.toFixed(3)})`);
        console.log(`  Confidence: ${confidence.toFixed(3)}`);
      }
    }
  }
}

Metadata Fields:

  • bbox_x1, bbox_y1, bbox_x2, bbox_y2: Normalized coordinates (0.0-1.0)
  • confidence: Detection confidence score (0.0-1.0)

To convert to pixel coordinates:

import sharp from "sharp";
const img = sharp("photo.jpg");
const { width, height } = await img.metadata();
const pixelX1 = Math.round(bboxX1 * width!);
const pixelY1 = Math.round(bboxY1 * height!);

Tracing

Pass a W3C trace ID to correlate requests across services:

const client = createDbClient("127.0.0.1:1369", {
  traceId: "00-80e1afed08e019fc1110464cfa66635c-7a085853722dc6d2-01",
});

This sets the ahnlich-trace-id header on every request.


Development & Testing

make install-dependencies
make generate    # regenerate TypeScript protobuf code from proto definitions (requires buf)
make format
make lint-check
make test

Deploy to npm

Bump the version field in package.json. When your PR is merged into main, the CI will detect the version change and automatically publish to npm.

Type Meanings

  • StoreKey: A one-dimensional float32 vector of fixed dimension
  • StoreValue: A map of string keys to MetadataValue (text or binary)
  • StoreInput: A raw string or binary blob accepted by the AI proxy
  • Predicates: Filter conditions for stored values (Equals, NotEquals, In, etc.)
  • PredicateCondition: Combines one or more predicates with AND, OR, or Value
  • AIModel: Supported embedding models (ALL_MINI_LM_L6_V2, RESNET50, BUFFALO_L, etc.)

Change Log

Version Description
0.1.0 Initial Node.js/TypeScript SDK release via gRPC