opensearch

package module
v0.61.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 10, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

Documentation

Overview

Package opensearch implements the Core vector-store capabilities using a borrowed native OpenSearch SDK client. The host provisions the concrete index and owns authentication, topology, retries, timeouts and client lifetime. NewStore validates native mapping and settings without reading documents; it never creates or changes an index. Native mapping owns vector dimensions, space, engine, method and encoding; native KNN owns candidate tuning.

The index must declare exactly content (text), embedding (FLOAT32 knn_vector with explicit dimensions and method engine), and metadata_json (keyword with index:false and doc_values:false), under dynamic:strict. Native KNN must be enabled. Full stored _source is required; both index.derived_source.enabled and index.knn.derived_source.enabled must be false. Aliases, custom routing, dynamic templates, runtime or derived fields, and default ingest or search pipelines are incompatible. The host preserves these namespace and policy guarantees while Store is in use. Trained model_id fields are unsupported because their dimension and space are not declared by the mapping.

Native Lucene and Faiss support l2, cosinesimil and innerproduct. Existing NMSLib mappings also support l1 and linf, but their filtered KNN returns errors.ErrUnsupported before I/O because native NMSLib cannot accept a KNN filter. HNSW and declared Faiss IVF methods are recognized; provision and training belong to the host. Omitted native space follows its documented l2 default. Store never compares native policy against a Scope-configured metric.

The adapter checks FLOAT32 coordinates, declared dimensions and the native cosine prohibition on zero vectors. Faiss SQ FP16 and automatic 2x encoding also require indexed coordinates in [-65504,65504] unless native clipping is enabled. Query vectors retain their native FLOAT32 contract; an index encoder does not impose its coordinate limit on queries. Scope never clips or silently repairs an embedding. Native scores are projected through Core; group merging uses native scores before Core normalization so saturated scores preserve rank.

Every source has exactly three fields; native _id is its sole identity. Metadata is one Core JSON string rather than native mapped terms. Nil and empty metadata remain distinct. Arbitrary Core-supported keys, nested values, large integers and decimals preserve their meaning. Media is unsupported. Native IDs are limited to 512 UTF-8 bytes. Existing deployments must provision the current schema and reindex source documents; obsolete configuration and record formats are not accepted.

The native mapping must bind _meta.scope_vector_space to the Host's immutable encoder space. Changing that declaration is a rebuild, not permission to reuse an old index. The Host must coordinate physical index replacement; HTTP configuration checks and data requests are distinct operations.

Shared framing, receipt attribution, document identity and scroll lifetimes belong to vectorstores/protocol/elastic. Its defaults are the canonical bulk, response and index defaults; this adapter owns the native schema and query DSL.

Upsert prepares all records and checks their float32 representations before publishing NDJSON in independent MaxBulkBytes groups. A WriteError records verified item acknowledgments, explicit rejections, groups not submitted and uncertain IDs. Successful ingestion is not a guarantee of search visibility. Upsert, DeleteIDs and DeleteMatching share the encoded MaxBulkBytes limit, including action framing and conditional-delete tokens. Every action is admitted before the first mutation, so an oversized later action cannot partially publish an otherwise invalid batch.

Query only sends native KNN; it never enumerates source records. The native mapping deliberately preserves full metadata as opaque unindexed bytes, so a filtered Query returns vectorstore.ErrUnsupportedFilter before rank I/O. A Host with authority over the entire physical index can explicitly call QueryByScan for interpreted predicates. QueryByScan bounds source records, cumulative encoded scroll response bytes (excluding ranking and cleanup), and elapsed time. Exceeding a budget returns no partial ranking. The adapter checks the native result window, every returned record and every shard acknowledgment. Scroll selection and native ranking are separate reads, not a cross-RPC snapshot.

DeleteMatching requires the same explicit scan budget, completes selection first, and uses each selected sequence number and primary term in its bulk conditional delete. A conflict is an error; no blind identity deletion or framework retry follows. DeleteIDs expresses unconditional identity intent. The Host owns the native transport. Offline tests exercise HTTP protocol fixtures; tagged tests require a separately provisioned isolated service.

Index

Examples

Constants

View Source
const (
	Provider = "OpenSearch"
)

Variables

View Source
var (
	ErrIndexMissing      = errors.New("opensearch: index not found")
	ErrIncompatibleIndex = errors.New("opensearch: native index is incompatible")
)

Functions

This section is empty.

Types

type APIClient added in v0.43.0

type APIClient interface {
	Stream(*http.Request) (*http.Response, error)
}

APIClient borrows the native SDK's streaming transport. The host owns authentication, topology, retries, timeouts and lifetime; Store closes every response body.

type Store

type Store struct {
	// contains filtered or unexported fields
}

func NewStore

func NewStore(ctx context.Context, config StoreConfig) (*Store, error)
Example
package main

import (
	"context"
	"errors"
	"net/http"
	"os"

	"strings"
	"time"

	opensearchsdk "github.com/opensearch-project/opensearch-go/v5"

	"github.com/Tangerg/scope/core/document"

	"github.com/Tangerg/scope/core/vectorstore"
	"github.com/Tangerg/scope/vectorstores/opensearch"
)

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
	defer cancel()
	transport := http.DefaultTransport.(*http.Transport).Clone()
	defer transport.CloseIdleConnections()
	client, err := opensearchsdk.NewClient(opensearchsdk.Config{Addresses: []string{os.Getenv("SCOPE_OPENSEARCH_ENDPOINT")}, Username: os.Getenv("SCOPE_OPENSEARCH_USERNAME"), Password: os.Getenv("SCOPE_OPENSEARCH_PASSWORD"), Transport: transport})
	if err != nil {
		panic(err)
	}
	defer func() {
		if closeErr := client.Close(); closeErr != nil {
			panic(closeErr)
		}
	}()
	// Provision once through the native host API before constructing Store.
	provisionRequest, err := http.NewRequestWithContext(ctx, http.MethodPut, "/documents", strings.NewReader(`{"settings":{"index.knn":true,"index.knn.derived_source.enabled":false,"index.derived_source.enabled":false},"mappings":{"_meta":{"scope_vector_space":"paired-v1"},"dynamic":"strict","properties":{"content":{"type":"text"},"embedding":{"type":"knn_vector","dimension":2,"method":{"name":"hnsw","engine":"lucene","space_type":"cosinesimil"}},"metadata_json":{"type":"keyword","index":false,"doc_values":false}}}}`))
	if err != nil {
		panic(err)
	}
	provisionRequest.Header.Set("Content-Type", "application/json")
	created, err := client.Stream(provisionRequest)
	if err != nil {
		panic(err)
	}
	closeErr := created.Body.Close()
	if created.StatusCode < 200 || created.StatusCode >= 300 || closeErr != nil {
		panic(errors.Join(errors.New("native index provisioning failed"), closeErr))
	}

	store, err := opensearch.NewStore(ctx, opensearch.StoreConfig{Client: client, IndexName: "documents", Space: "paired-v1"})
	if err != nil {
		panic(err)
	}
	if err = store.Upsert(ctx, &vectorstore.Batch{Space: "paired-v1", Records: []vectorstore.Record{{Document: &document.Document{ID: "one", Text: "example"}, Values: []float64{1, 0}}}}); err != nil {
		panic(err)
	}
	if _, err = store.Query(ctx, &vectorstore.Query{Space: "paired-v1", Values: []float64{1, 0}}); err != nil {
		panic(err)
	}
	if err = store.DeleteIDs(ctx, []string{"one"}); err != nil {
		panic(err)
	}
}

func (*Store) CheckDocuments added in v0.59.0

func (s *Store) CheckDocuments(ctx context.Context, space vectorstore.SpaceID, docs []*document.Document) error

func (*Store) CheckQuery added in v0.59.0

func (s *Store) CheckQuery(ctx context.Context, space vectorstore.SpaceID, opts vectorstore.SearchOptions) error

func (*Store) DeleteIDs

func (s *Store) DeleteIDs(ctx context.Context, ids []string) error

func (*Store) DeleteMatching added in v0.59.0

func (s *Store) DeleteMatching(ctx context.Context, predicate filter.Predicate, budget vectorstore.ScanBudget) error

func (*Store) Query added in v0.59.0

func (*Store) QueryByScan added in v0.60.0

func (*Store) Upsert added in v0.59.0

func (s *Store) Upsert(ctx context.Context, batch *vectorstore.Batch) error

type StoreConfig

type StoreConfig struct {
	Client    APIClient
	IndexName string
	Space     vectorstore.SpaceID
	// MaxBulkBytes limits the encoded action/document lines per native write.
	MaxBulkBytes     int
	MaxResponseBytes int64
}

func (StoreConfig) Validate

func (s StoreConfig) Validate() error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL