diff --git a/README.md b/README.md index 28e1f1d..f2ad558 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ func main() { }, } - results, err := index.Query(queryVector, nil, nil, 10, filter, 128, false) + results, err := index.Query(queryVector, nil, nil, 10, filter, 128, false, nil) if err != nil { log.Fatal(err) } @@ -313,10 +313,13 @@ if err != nil { queryVector := []float32{/* your query vector */} results, err := index.Query( queryVector, // query vector + nil, // sparseIndices (optional, for hybrid search) + nil, // sparseValues (optional, for hybrid search) 5, // top_k - number of results (max 512) nil, // filter (optional) 128, // ef - runtime parameter (max 1024) true, // include_vectors + nil, // filterParams (optional, for advanced filtering) ) if err != nil { log.Fatal(err) @@ -331,10 +334,15 @@ for _, result := range results { **Query Parameters:** - `vector`: Query vector (must match index dimension) +- `sparseIndices`: Sparse vector indices (optional, for hybrid search) +- `sparseValues`: Sparse vector values (optional, for hybrid search) - `k`: Number of nearest neighbors to return (max 512, default: 10) - `filter`: Optional filter criteria (map[string]interface{}) - `ef`: Runtime search parameter - higher values improve recall but increase latency (max 1024, default: 128) - `includeVectors`: Whether to return the actual vector data in results (default: false) +- `filterParams`: Advanced filter parameters (optional, *FilterParams): + - `BoostPercentage`: Expand candidate pool by X% during filtered search (0-100, default: 0) + - `PrefilterThreshold`: Switch to brute-force when matches < threshold (1000-1000000, default: 10000) **Result Fields:** @@ -368,7 +376,7 @@ filter := map[string]interface{}{ }, } -results, err := index.Query(queryVector, 5, filter, 128, true) +results, err := index.Query(queryVector, nil, nil, 5, filter, 128, true, nil) if err != nil { log.Fatal(err) } @@ -527,7 +535,7 @@ index, err := client.GetIndexWithContext(ctx, "my_index") err = index.UpsertWithContext(ctx, vectors) -results, err := index.QueryWithContext(ctx, queryVector, 10, nil, 128, false) +results, err := index.QueryWithContext(ctx, queryVector, nil, nil, 10, nil, 128, false, nil) err = client.DeleteIndexWithContext(ctx, "my_index") ``` @@ -551,7 +559,7 @@ err = client.DeleteIndexWithContext(ctx, "my_index") | Method | Description | |--------|-------------| | `Upsert(vectors []VectorItem) error` | Insert or update vectors (max 1000 per batch) | -| `Query(vector, sparseIndices, sparseValues, k, filter, ef, includeVectors) ([]QueryResult, error)` | Search for similar vectors | +| `Query(vector, sparseIndices, sparseValues, k, filter, ef, includeVectors, filterParams) ([]QueryResult, error)` | Search for similar vectors | | `DeleteVectorById(id string) (string, error)` | Delete a vector by ID | | `DeleteVectorByFilter(filter map[string]interface{}) (string, error)` | Delete vectors matching a specific filter | | `GetVector(id string) (VectorItem, error)` | Get a specific vector by ID | diff --git a/index.go b/index.go index ac5f837..b52bd15 100644 --- a/index.go +++ b/index.go @@ -72,15 +72,22 @@ type QueryResult struct { Vector []float32 `json:"vector,omitempty"` } +// FilterParams represents advanced filtering parameters for HNSW search +type FilterParams struct { + BoostPercentage int `json:"boost_percentage,omitempty"` + PrefilterThreshold int `json:"prefilter_threshold,omitempty"` +} + // QueryRequest represents the search request payload type QueryRequest struct { - Vector []float32 `json:"vector,omitempty"` - SparseIndices []int `json:"sparse_indices,omitempty"` - SparseValues []float32 `json:"sparse_values,omitempty"` - TopK int `json:"k"` - Ef int `json:"ef"` - IncludeVectors bool `json:"include_vectors"` - Filter string `json:"filter,omitempty"` + Vector []float32 `json:"vector,omitempty"` + SparseIndices []int `json:"sparse_indices,omitempty"` + SparseValues []float32 `json:"sparse_values,omitempty"` + TopK int `json:"k"` + Ef int `json:"ef"` + IncludeVectors bool `json:"include_vectors"` + Filter string `json:"filter,omitempty"` + FilterParams *FilterParams `json:"filter_params,omitempty"` } // NewIndex creates a new Index instance similar to Python's __init__ @@ -388,12 +395,12 @@ func (i *Index) GetInfo() string { i.Name, i.Dimension, i.SparseDim, i.SpaceType, i.Count, i.Precision, i.M) } -func (i *Index) Query(vector []float32, sparseIndices []int, sparseValues []float32, k int, filter map[string]interface{}, ef int, includeVectors bool) ([]QueryResult, error) { - return i.QueryWithContext(context.Background(), vector, sparseIndices, sparseValues, k, filter, ef, includeVectors) +func (i *Index) Query(vector []float32, sparseIndices []int, sparseValues []float32, k int, filter map[string]interface{}, ef int, includeVectors bool, filterParams *FilterParams) ([]QueryResult, error) { + return i.QueryWithContext(context.Background(), vector, sparseIndices, sparseValues, k, filter, ef, includeVectors, filterParams) } // QueryWithContext performs vector similarity search with context support -func (i *Index) QueryWithContext(ctx context.Context, vector []float32, sparseIndices []int, sparseValues []float32, k int, filter map[string]interface{}, ef int, includeVectors bool) ([]QueryResult, error) { +func (i *Index) QueryWithContext(ctx context.Context, vector []float32, sparseIndices []int, sparseValues []float32, k int, filter map[string]interface{}, ef int, includeVectors bool, filterParams *FilterParams) ([]QueryResult, error) { // Validate parameters if k <= 0 || k > MaxTopKAllowed { return nil, fmt.Errorf("top_k must be between 1 and %d", MaxTopKAllowed) @@ -420,6 +427,17 @@ func (i *Index) QueryWithContext(ctx context.Context, vector []float32, sparseIn return nil, fmt.Errorf("sparse_indices and sparse_values must have the same length") } + // Validate filter parameters if provided + if filterParams != nil { + if filterParams.BoostPercentage < 0 || filterParams.BoostPercentage > 100 { + return nil, fmt.Errorf("filter_boost_percentage must be between 0 and 100") + } + if filterParams.PrefilterThreshold != 0 && + (filterParams.PrefilterThreshold < 1000 || filterParams.PrefilterThreshold > 1000000) { + return nil, fmt.Errorf("prefilter_cardinality_threshold must be between 1,000 and 1,000,000") + } + } + // Normalize query vector normalizedVector, norm, err := i.normalizeVector(vector) if err != nil { @@ -435,6 +453,7 @@ func (i *Index) QueryWithContext(ctx context.Context, vector []float32, sparseIn TopK: k, Ef: ef, IncludeVectors: includeVectors, + FilterParams: filterParams, } // Add filter if provided