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 ¶
- Constants
- Variables
- type APIClient
- type Store
- func (s *Store) CheckDocuments(ctx context.Context, space vectorstore.SpaceID, docs []*document.Document) error
- func (s *Store) CheckQuery(ctx context.Context, space vectorstore.SpaceID, opts vectorstore.SearchOptions) error
- func (s *Store) DeleteIDs(ctx context.Context, ids []string) error
- func (s *Store) DeleteMatching(ctx context.Context, predicate filter.Predicate, budget vectorstore.ScanBudget) error
- func (s *Store) Query(ctx context.Context, q *vectorstore.Query) (*vectorstore.SearchResponse, error)
- func (s *Store) QueryByScan(ctx context.Context, q *vectorstore.Query, budget vectorstore.ScanBudget) (*vectorstore.SearchResponse, error)
- func (s *Store) Upsert(ctx context.Context, batch *vectorstore.Batch) error
- type StoreConfig
Examples ¶
Constants ¶
const (
Provider = "OpenSearch"
)
Variables ¶
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
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)
}
}
Output:
func (*Store) CheckDocuments ¶ added in v0.59.0
func (*Store) CheckQuery ¶ added in v0.59.0
func (s *Store) CheckQuery(ctx context.Context, space vectorstore.SpaceID, opts vectorstore.SearchOptions) 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 (s *Store) Query(ctx context.Context, q *vectorstore.Query) (*vectorstore.SearchResponse, error)
func (*Store) QueryByScan ¶ added in v0.60.0
func (s *Store) QueryByScan(ctx context.Context, q *vectorstore.Query, budget vectorstore.ScanBudget) (*vectorstore.SearchResponse, 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