Go SDK

The official Go SDK for brinicle provides an idiomatic HTTP client with context support, functional options, and comprehensive type definitions.

Installation

go get github.com/bicardinal/brinicle-go

Quick Start

package main

import (
    "context"
    "fmt"
    "log"

    brinicle "github.com/bicardinal/brinicle-go"
)

func main() {
    ctx := context.Background()

    // Create a client
    client := brinicle.NewClient("http://localhost:1984",
        brinicle.WithTimeout(30*time.Second),
    )

    // Create an index
    params := &brinicle.HNSWParams{
        M:              48,
        EfConstruction: 1024,
        EfSearch:       512,
        RngSeed:        0,
    }
    _, err := client.CreateIndex(ctx, "products", 384, 0.10, params)
    if err != nil {
        log.Fatal(err)
    }

    // Initialize and ingest
    _, err = client.Init(ctx, "products", brinicle.IngestModeBuild)
    if err != nil {
        log.Fatal(err)
    }

    vector := make([]float32, 384)
    err = client.Ingest(ctx, "products", "p1", vector)
    if err != nil {
        log.Fatal(err)
    }

    _, err = client.Finalize(ctx, "products", true, nil)
    if err != nil {
        log.Fatal(err)
    }

    // Search
    queryVector := make([]float32, 384)
    results, err := client.Search(ctx, "products", queryVector, 10, 64)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(results) // [p1]
}

Configuration

Functional Options

client := brinicle.NewClient(
    "http://localhost:1984",
    brinicle.WithTimeout(60*time.Second),
    brinicle.WithHTTPClient(&http.Client{
        Timeout: 60 * time.Second,
    }),
)
OptionDescription
WithTimeout(d time.Duration)Set the request timeout
WithHTTPClient(c *http.Client)Use a custom HTTP client

API Reference

All methods accept a context.Context as the first argument, enabling cancellation and timeout control.

HealthCheck

resp, err := client.HealthCheck(ctx)
// *SuccessResponse { Success: true, Message: "..." }

CreateIndex

resp, err := client.CreateIndex(ctx, "my_index", 384, 0.10, &brinicle.HNSWParams{
    M:              48,
    EfConstruction: 1024,
    EfSearch:       512,
})

ListIndexes

resp, err := client.ListIndexes(ctx)
// *ListIndexesResponse { Indexes: []string{"my_index"}, Count: 1 }

DeleteIndex

// Close the index (data preserved)
resp, err := client.DeleteIndex(ctx, "my_index", false)

// Permanently destroy
resp, err := client.DeleteIndex(ctx, "my_index", true)

GetIndexStatus

status, err := client.GetIndexStatus(ctx, "my_index")
// *IndexStatusResponse { IndexName: "my_index", Dim: 384, HasIndex: true, NeedsRebuild: false }

LoadIndex

resp, err := client.LoadIndex(ctx, "my_index")

Init

resp, err := client.Init(ctx, "my_index", brinicle.IngestModeBuild)
resp, err := client.Init(ctx, "my_index", brinicle.IngestModeInsert)
resp, err := client.Init(ctx, "my_index", brinicle.IngestModeUpsert)

Ingest

vector := []float32{0.1, 0.2, 0.3, 0.4}
err := client.Ingest(ctx, "my_index", "item_001", vector)

IngestBatch

ids := []string{"item_001", "item_002", "item_003"}
vectors := [][]float32{
    {0.1, 0.2, 0.3, 0.4},
    {0.5, 0.6, 0.7, 0.8},
    {0.9, 1.0, 1.1, 1.2},
}
resp, err := client.IngestBatch(ctx, "my_index", ids, vectors, 4)
// *BatchIngestResponse { Success: true, Count: 3 }

Finalize

resp, err := client.Finalize(ctx, "my_index", true, nil) // optimize=true
resp, err := client.Finalize(ctx, "my_index", true, &brinicle.HNSWParams{M: 48})

DeleteItems

resp, err := client.DeleteItems(ctx, "my_index", []string{"id1", "id2"}, true)
// *DeleteResponse { DeletedCount: 2, NotFound: []string{} }

Rebuild

resp, err := client.Rebuild(ctx, "my_index", &brinicle.HNSWParams{M: 48})
results, err := client.Search(ctx, "my_index", []float32{0.1, 0.2, 0.3, 0.4}, 10, 64)
// []string{"item_001", "item_042"}

Optimize

resp, err := client.Optimize(ctx, "my_index")

ItemSearch

The ItemSearch API enables structured search over items with lexical fields, combining vector similarity with metadata filtering. Unlike the basic vector search which operates on raw embeddings, ItemSearch lets you ingest items with human-readable fields such as titles, descriptions, and categories, then perform filtered searches that combine full-text matching with vector similarity. This makes it ideal for product catalogs, document collections, and any dataset where you need to search by both content and structured attributes. Each item index is backed by its own vector index, so you still benefit from brinicle’s high-performance HNSW graph for similarity lookups. The difference is that ItemSearch adds a lexical configuration layer that controls how text fields are tokenized and matched, allowing you to fine-tune relevance for your specific domain. The Go SDK provides strongly-typed structs for all ItemSearch operations, including filters, results, and configuration objects.

CreateItemIndex

Create a new item search index with an optional lexical configuration. The LexicalConfig struct controls how text fields are analyzed during ingestion and search — you can specify which fields are searchable, how they are tokenized, and whether stemming or stop-word removal should be applied. If nil, a default configuration is used that treats all text fields as searchable with standard tokenization. Like all Go SDK methods, this accepts a context.Context as the first argument for cancellation and timeout control.
lexicalConfig := &brinicle.LexicalConfig{
    SearchableFields: []string{"title", "description", "category"},
    Tokenizer:        "standard",
    Lowercase:        true,
    Stem:             false,
    RemoveStopWords:  true,
}

resp, err := client.CreateItemIndex(ctx, "products", 384, 0.10, &brinicle.HNSWParams{
    M:              48,
    EfConstruction: 1024,
    EfSearch:       512,
}, lexicalConfig)
if err != nil {
    log.Fatal(err)
}

InitItemIngest

Initialize an ingest session for an item index. This works identically to the vector engine’s Init method, accepting an IngestMode constant. You must call this before ingesting any items, and you must call Finalize after all items have been ingested. The build mode creates a fresh index from scratch, while insert and upsert modes allow you to add or update items in an existing index.
resp, err := client.InitItemIngest(ctx, "products", brinicle.IngestModeBuild)
resp, err := client.InitItemIngest(ctx, "products", brinicle.IngestModeInsert)
resp, err := client.InitItemIngest(ctx, "products", brinicle.IngestModeUpsert)

IngestItem

Ingest a single item with its external ID, vector, and associated metadata fields. The Item struct provides the structure for the item parameter, including the ExternalID, Vector, and Fields fields. Fields not listed in the SearchableFields of the LexicalConfig are stored but not indexed for text search. The vector must match the dimension specified during index creation.
item1 := &brinicle.Item{
    ExternalID: "p1",
    Vector:     make([]float32, 384),
    Fields: map[string]string{
        "title":       "Apple iPhone 15 Pro Max",
        "description": "Latest iPhone with A17 Pro chip",
        "category":    "Electronics",
        "price":       "1199",
    },
}

err := client.IngestItem(ctx, "products", item1)
if err != nil {
    log.Fatal(err)
}

item2 := &brinicle.Item{
    ExternalID: "p2",
    Vector:     make([]float32, 384),
    Fields: map[string]string{
        "title":       "Samsung Galaxy S24 Ultra",
        "description": "Flagship Android with AI features",
        "category":    "Electronics",
        "price":       "1299",
    },
}

err = client.IngestItem(ctx, "products", item2)
if err != nil {
    log.Fatal(err)
}

SearchItems

Search for items using a combination of vector similarity and structured filters. The query parameter provides the text query which is matched against the searchable fields defined in the lexical configuration. The filters parameter accepts an ItemSearchFilters struct with Must and MustNot conditions that narrow results by exact field values or ranges. The response is a typed ItemSearchResponse containing an array of ItemSearchResult structs with item IDs, scores, and matched fields.
// Simple text search
resp, err := client.SearchItems(ctx, "products", &brinicle.ItemSearchParams{
    Query: "iphone",
    K:     10,
})
if err != nil {
    log.Fatal(err)
}

// Search with filters
resp, err = client.SearchItems(ctx, "products", &brinicle.ItemSearchParams{
    Query: "smartphone",
    Filters: &brinicle.ItemSearchFilters{
        Must:    map[string]string{"category": "Electronics"},
        MustNot: map[string]string{"price": "0"},
    },
    K:   10,
    Efs: 64,
})
if err != nil {
    log.Fatal(err)
}

// Access results
for _, result := range resp.Results {
    fmt.Printf("%s (score: %.4f)\n", result.ExternalID, result.Score)
    fmt.Printf("Fields: %v\n", result.Fields)
}

DeleteItemIndex

Delete or close an item search index. When called with destroy: false, the index is closed but its data remains on disk and can be reloaded later. When called with destroy: true, the index and all its data are permanently removed. This method is identical in behavior to DeleteIndex but operates on item search indexes specifically.
// Close the index (data preserved)
resp, err := client.DeleteItemIndex(ctx, "products", false)

// Permanently destroy
resp, err = client.DeleteItemIndex(ctx, "products", true)

GetItemIndexStatus

Check the status of an item search index. Returns the index name, dimension, whether the HNSW graph has been built, and whether a rebuild is needed after recent ingestions. This is useful for monitoring the health of your indexes and determining when a rebuild or optimization might be necessary.
status, err := client.GetItemIndexStatus(ctx, "products")
if err != nil {
    log.Fatal(err)
}
// &ItemIndexStatusResponse{IndexName: "products", Dim: 384, HasIndex: true, NeedsRebuild: false}

Autocomplete

The Autocomplete API provides prefix-based search for type-ahead and suggestion use cases. Autocomplete indexes are optimized for fast prefix matching, making them ideal for search boxes, tag suggestion, and any interface where users type partial queries and expect instant results. Unlike ItemSearch which combines vector similarity with text matching, Autocomplete focuses on sub-millisecond prefix lookups over string keys. Each autocomplete index stores a set of string keys and their associated vectors. When a user types a partial query, the engine searches for keys that begin with the typed prefix and returns the closest matches ranked by vector similarity. This allows you to build autocomplete experiences that are both fast and semantically relevant — for example, typing “iph” could suggest “iPhone 15 Pro Max” based on both the prefix match and the semantic proximity to other electronics products.

CreateAutocompleteIndex

Create a new autocomplete index with an optional autocomplete configuration. The AutocompleteConfig struct controls how suggestions are matched and ranked — for example, you can set the minimum prefix length before suggestions are returned, the maximum number of suggestions, and whether to apply fuzzy matching for typo tolerance. If nil, sensible defaults are used that work well for most use cases.
autocompleteConfig := &brinicle.AutocompleteConfig{
    MinPrefixLength: 1,
    MaxSuggestions:  10,
    FuzzyMatch:      true,
    FuzzyDistance:    2,
}

resp, err := client.CreateAutocompleteIndex(ctx, "product_suggestions", 384, 0.10, &brinicle.HNSWParams{
    M:              48,
    EfConstruction: 1024,
    EfSearch:       512,
}, autocompleteConfig)
if err != nil {
    log.Fatal(err)
}

InitAutocompleteIngest

Initialize an ingest session for an autocomplete index. Like the vector engine’s Init method, this must be called before ingesting any autocomplete entries. The mode controls whether you are building a new index from scratch or adding entries to an existing one. After ingesting all entries, you must call Finalize to commit the data and build the prefix index.
resp, err := client.InitAutocompleteIngest(ctx, "product_suggestions", brinicle.IngestModeBuild)
resp, err = client.InitAutocompleteIngest(ctx, "product_suggestions", brinicle.IngestModeInsert)
resp, err = client.InitAutocompleteIngest(ctx, "product_suggestions", brinicle.IngestModeUpsert)

IngestAutocomplete

Ingest a single autocomplete entry with its key string and associated vector. The key is the string that users will type as a prefix — for example, a product name like “iPhone 15 Pro Max”. The vector provides the semantic representation used to rank suggestions when multiple prefix matches exist. Keys should be lowercase if the AutocompleteConfig specifies Lowercase: true, as the engine will not automatically normalize keys during ingestion.
vector1 := make([]float32, 384)
err := client.IngestAutocomplete(ctx, "product_suggestions", "iphone 15 pro max", vector1)
if err != nil {
    log.Fatal(err)
}

vector2 := make([]float32, 384)
err = client.IngestAutocomplete(ctx, "product_suggestions", "samsung galaxy s24 ultra", vector2)
if err != nil {
    log.Fatal(err)
}

SearchAutocomplete

Search for autocomplete suggestions matching a given prefix. The query parameter is the partial string typed by the user, and the k parameter controls how many suggestions to return. Results are ranked by a combination of prefix match quality and vector similarity, ensuring that the most relevant suggestions appear first. This method is designed for sub-millisecond response times, making it suitable for interactive type-ahead interfaces.
suggestions, err := client.SearchAutocomplete(ctx, "product_suggestions", "iph", 5)
if err != nil {
    log.Fatal(err)
}
// []string{"iphone 15 pro max", "iphone 15 case", "iphone charger"}

DeleteAutocompleteIndex

Delete or close an autocomplete index. When called with destroy: false, the index is closed but its data remains on disk. When called with destroy: true, the index and all associated data are permanently removed from disk. Use this method to clean up indexes that are no longer needed or to force a full rebuild by destroying and recreating an index.
// Close the index (data preserved)
resp, err := client.DeleteAutocompleteIndex(ctx, "product_suggestions", false)

// Permanently destroy
resp, err = client.DeleteAutocompleteIndex(ctx, "product_suggestions", true)

GetAutocompleteIndexStatus

Check the status of an autocomplete index. Returns the index name, vector dimension, whether the prefix index has been built, and whether a rebuild is needed after recent ingestions. Monitoring index status is important for autocomplete indexes because the prefix index must be rebuilt after ingestion to reflect new entries.
status, err := client.GetAutocompleteIndexStatus(ctx, "product_suggestions")
if err != nil {
    log.Fatal(err)
}
// &AutocompleteIndexStatusResponse{IndexName: "product_suggestions", Dim: 384, HasIndex: true, NeedsRebuild: false}

Error Handling

import "github.com/bicardinal/brinicle-go"

resp, err := client.CreateIndex(ctx, "my_index", 128, 0.10, nil)
if err != nil {
    var brinicleErr *brinicle.BrinicleError
    if errors.As(err, &brinicleErr) {
        switch {
        case brinicleErr.StatusCode == 404:
            log.Printf("Not found: %s", brinicleErr.Message)
        case brinicleErr.StatusCode == 409:
            log.Printf("Conflict: %s", brinicleErr.Message)
        case brinicleErr.StatusCode == 400:
            log.Printf("Validation error: %s", brinicleErr.Message)
        default:
            log.Printf("API error (%d): %s", brinicleErr.StatusCode, brinicleErr.Message)
        }
    } else {
        log.Printf("Unexpected error: %v", err)
    }
}

Context and Cancellation

All operations support Go’s context.Context for cancellation and timeout:
// With timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

results, err := client.Search(ctx, "my_index", queryVector, 10, 64)

// With cancellation
ctx, cancel := context.WithCancel(context.Background())
go func() {
    time.Sleep(2 * time.Second)
    cancel() // Cancel after 2 seconds
}()

results, err := client.Search(ctx, "my_index", queryVector, 10, 64)
if errors.Is(err, context.Canceled) {
    log.Println("Search was canceled")
}

Type Reference

type HNSWParams struct {
    M              int `json:"M"`
    EfConstruction int `json:"ef_construction"`
    EfSearch       int `json:"ef_search"`
    RngSeed        int `json:"rng_seed"`
}

type SuccessResponse struct {
    Success   bool   `json:"success"`
    Message   string `json:"message,omitempty"`
    IndexName string `json:"index_name,omitempty"`
}

type ListIndexesResponse struct {
    Indexes []string `json:"indexes"`
    Count   int      `json:"count"`
}

type IndexStatusResponse struct {
    IndexName    string `json:"index_name"`
    Dim          int    `json:"dim"`
    HasIndex     bool   `json:"has_index"`
    NeedsRebuild bool   `json:"needs_rebuild"`
}

type DeleteResponse struct {
    DeletedCount int      `json:"deleted_count"`
    NotFound     []string `json:"not_found,omitempty"`
}

type BatchIngestResponse struct {
    Success bool `json:"success"`
    Count   int  `json:"count"`
}

type IngestMode string

const (
    IngestModeBuild  IngestMode = "build"
    IngestModeInsert IngestMode = "insert"
    IngestModeUpsert IngestMode = "upsert"
)

ItemSearch Types

The following types are used by the ItemSearch API. The Item struct represents a single item to ingest, with its ID, vector, and metadata fields. The ItemSearchFilters struct provides structured filtering with Must and MustNot conditions. The ItemSearchResult struct represents a single search result with score and matched fields, and ItemSearchResponse wraps the full response including result count.
type Item struct {
    ExternalID string            `json:"external_id"`
    Vector     []float32         `json:"vector"`
    Fields     map[string]string `json:"fields"`
}

type ItemSearchFilters struct {
    Must    map[string]string `json:"must,omitempty"`
    MustNot map[string]string `json:"must_not,omitempty"`
}

type ItemSearchParams struct {
    Query   string             `json:"query"`
    Filters *ItemSearchFilters `json:"filters,omitempty"`
    K       int                `json:"k"`
    Efs     int                `json:"efs,omitempty"`
}

type ItemSearchResult struct {
    ExternalID string            `json:"external_id"`
    Score      float64           `json:"score"`
    Fields     map[string]string `json:"fields"`
}

type ItemSearchResponse struct {
    Results []*ItemSearchResult `json:"results"`
    Total   int                 `json:"total"`
}

type LexicalConfig struct {
    SearchableFields []string `json:"searchable_fields,omitempty"`
    Tokenizer        string   `json:"tokenizer,omitempty"`
    Lowercase        bool     `json:"lowercase,omitempty"`
    Stem             bool     `json:"stem,omitempty"`
    RemoveStopWords  bool     `json:"remove_stop_words,omitempty"`
}

Autocomplete Types

The following types are used by the Autocomplete API. The AutocompleteConfig struct controls how the autocomplete engine matches prefixes and ranks suggestions, including options for fuzzy matching and minimum prefix length.
type AutocompleteConfig struct {
    MinPrefixLength int  `json:"min_prefix_length,omitempty"`
    MaxSuggestions  int  `json:"max_suggestions,omitempty"`
    FuzzyMatch      bool `json:"fuzzy_match,omitempty"`
    FuzzyDistance    int  `json:"fuzzy_distance,omitempty"`
}