vector_amp 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b5cca5e94ec6fe5fa062e6e6034a5576db8264eefb4ebc306ed489ad8946ccd6
4
- data.tar.gz: ba79880edd034128d92b4a57b3a7fe46be310e0b190d3cb93f8c7a6b00d80db4
3
+ metadata.gz: 7147209a61689dee14e2d37581f33cad58fb5291543edb086fcafdf841e71c0a
4
+ data.tar.gz: c399a9373b4820d6c141362b29e2b81bc34de71a02ecb0d69c7780febdbd3674
5
5
  SHA512:
6
- metadata.gz: 8a1cf8517e4862b4a240b6dc6b4da146bb2fe1499d9b2b6a98fbdefe870be57ad24c78b5defffc4986ca3babea7d8533128450d1bfc54468305c23e54bcabcc5
7
- data.tar.gz: aca7d1ef6aa30ff9477fdcd30d1deab9831111a8fd759f521e0cbd2334f724d2b870e57eee393a9d0d987f19ced7c6b9a776054c54a44bd6f5b4a6f6f20c3aa1
6
+ metadata.gz: 6b2f983acc42db9c33801bcbe726537975af6d6ec9eab12a542a860ac5836fa417e07bb35ddfd6e4f7a55f0b6311c4ee5793d192427e5182c5fff11aae9849c4
7
+ data.tar.gz: 2d720c1922917177c98cf7ddab94b9f2f20ef0654e31f673d47f4283459cca5c9808851875bf2a28a40ff9f6d63a7634bd852b0a74539453c6e7dff9d4dd58b1
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  <div align="center">
2
2
  <a href="https://vectoramp.com/">
3
3
  <picture>
4
- <source media="(prefers-color-scheme: light)" srcset=".github/images/logo-full-light.svg">
5
- <source media="(prefers-color-scheme: dark)" srcset=".github/images/logo-full-dark.svg">
6
- <img alt="VectorAmp Logo" src=".github/images/logo-full-dark.svg" width="50%">
4
+ <source media="(prefers-color-scheme: light)" srcset="https://vectoramp.com/logo-full-light.svg">
5
+ <source media="(prefers-color-scheme: dark)" srcset="https://vectoramp.com/logo-full-dark.svg">
6
+ <img alt="VectorAmp Logo" src="https://vectoramp.com/logo-full-dark.svg" width="50%">
7
7
  </picture>
8
8
  </a>
9
9
  </div>
@@ -64,14 +64,23 @@ answer = dataset.ask("What powers VectorAmp?")
64
64
  ### Creating datasets
65
65
 
66
66
  ```ruby
67
- # Minimal: name only.
67
+ # Minimal: name only. Embedding config is omitted so VectorAmp uses
68
+ # the managed VectorAmp-Embedding-4B model and infers dim 2560.
68
69
  client.datasets.create(name: "docs")
69
70
 
70
71
  # Enable hybrid (dense + sparse) search.
71
72
  client.datasets.create(name: "docs", hybrid: true)
72
73
 
73
- # Use an OpenAI embedding model (dim inferred from the model).
74
- client.datasets.create(name: "docs", embedding: VectorAmp::Embedding.openai("large"))
74
+ # Optional BYOM: use OpenAI only when you intentionally want that provider.
75
+ # VectorAmp::Embedding.openai uses embedding.secret_ref "emb:openai:api_key" by default.
76
+ client.datasets.create(name: "openai-docs", embedding: VectorAmp::Embedding.openai("large"))
77
+
78
+ # Store/update the org OpenAI key, then create a dataset that references it.
79
+ client.datasets.create_with_openai_api_key(
80
+ name: "openai-docs",
81
+ api_key: ENV.fetch("OPENAI_API_KEY"),
82
+ size: "small"
83
+ )
75
84
 
76
85
  # Custom/unknown model: pass dim explicitly.
77
86
  client.datasets.create(name: "docs", embedding: { provider: "acme", model: "acme-embed" }, dim: 1024)
@@ -111,6 +120,7 @@ dataset.search(
111
120
  rerank: true # expands to vectoramp / VectorAmp-Rerank-v1
112
121
  )
113
122
  dataset.insert(vectors: [{ id: "sku-1", values: [0.1, 0.2], metadata: { category: "electronics" } }])
123
+ dataset.delete_vectors(ids: ["sku-1"])
114
124
  dataset.add_texts(["Wireless headphones"], metadata: { category: "electronics" })
115
125
  dataset.ask("Which headphones should I buy?")
116
126
  dataset.ingest_source("source-uuid")
@@ -123,10 +133,40 @@ dataset.search("wireless headphones", hybrid: true, sparse_query: "headphones",
123
133
  client.datasets.stats("dataset-uuid")
124
134
  client.datasets.search("dataset-uuid", "wireless headphones", top_k: 10)
125
135
  client.datasets.insert("dataset-uuid", vectors: [])
136
+ client.datasets.delete_vectors("dataset-uuid", ids: ["sku-1", 42])
126
137
  ```
127
138
 
128
139
  `client.datasets.create` intentionally does not accept `index_type`; all datasets are created with SABLE.
129
140
 
141
+ ### Organization secrets
142
+
143
+ ```ruby
144
+ # Store or rotate the default OpenAI org secret used by OpenAI embedding datasets.
145
+ client.org_secrets.put_openai_api_key(api_key: ENV.fetch("OPENAI_API_KEY"))
146
+
147
+ # Optionally validate before storing, or use a custom secret_ref.
148
+ client.org_secrets.put_openai_api_key(
149
+ api_key: ENV.fetch("OPENAI_API_KEY"),
150
+ secret_ref: "emb:openai:api_key",
151
+ validate: true,
152
+ model: "text-embedding-3-small"
153
+ )
154
+ ```
155
+
156
+ Declare typed metadata at creation, then merge fields or replace the complete schema:
157
+
158
+ ```ruby
159
+ schema = [
160
+ VectorAmp::MetadataSchema.field(:category, VectorAmp::MetadataSchema::STRING),
161
+ VectorAmp::MetadataSchema.field(:price, VectorAmp::MetadataSchema::F32)
162
+ ]
163
+ dataset = client.datasets.create(name: "products", metadata_schema: schema)
164
+ dataset.patch_metadata_schema([VectorAmp::MetadataSchema.field(:inventory, VectorAmp::MetadataSchema::U32)])
165
+ dataset.replace_metadata_schema(schema) # [] removes all declared fields
166
+ ```
167
+
168
+ Canonical types are `STRING`, `U32`, `I32`, `I64`, `F32`, and `F64`.
169
+
130
170
  ### Source documents
131
171
 
132
172
  Dataset document listing is cursor-based: pass `next_cursor` into the next request and do not assume offsets or totals. Downloads return retained original bytes and follow API/storage redirects.
@@ -334,6 +374,8 @@ equivalent `datasetObj.<m>(...)` on a returned `VectorAmp::Dataset`.
334
374
  | Method | Required | Optional (defaults) |
335
375
  |---|---|---|
336
376
  | `create(name:)` | `name` | `dim` (inferred), `embedding` (vectoramp/VectorAmp-Embedding-4B), `metric` ("cosine"), `hybrid`, `filters`, `metadata_schema`, `tuning`, `metadata` |
377
+ | `patch_metadata_schema(id, schema)` | `id`, `schema` | — |
378
+ | `replace_metadata_schema(id, schema)` | `id`, `schema` | — |
337
379
  | `list` | — | `limit` (50), `offset` (0) |
338
380
  | `get(id)` | `id` | — |
339
381
  | `delete(id)` | `id` | — |
@@ -5,6 +5,7 @@ require_relative "datasets"
5
5
  require_relative "error"
6
6
  require_relative "ingestion"
7
7
  require_relative "intelligence"
8
+ require_relative "org_secrets"
8
9
  require_relative "schedules"
9
10
  require_relative "source"
10
11
  require_relative "transport/http"
@@ -19,7 +20,8 @@ module VectorAmp
19
20
  # @return [IngestionResource] ingestion/source API resource.
20
21
  # @return [IntelligenceResource] intelligence API resource.
21
22
  # @return [ConnectionsResource] managed provider connections API resource.
22
- attr_reader :base_url, :datasets, :ingestion, :intelligence, :schedules, :sources, :connections
23
+ # @return [OrgSecretsResource] organization-scoped secret helpers.
24
+ attr_reader :base_url, :datasets, :ingestion, :intelligence, :schedules, :sources, :connections, :org_secrets
23
25
 
24
26
  # Create a VectorAmp API client.
25
27
  # @param api_key [String] API key; defaults to ENV["VECTORAMP_API_KEY"].
@@ -38,6 +40,7 @@ module VectorAmp
38
40
  @schedules = SchedulesResource.new(@transport)
39
41
  @intelligence = IntelligenceResource.new(@transport)
40
42
  @connections = ConnectionsResource.new(@transport)
43
+ @org_secrets = OrgSecretsResource.new(@transport)
41
44
  @datasets = DatasetsResource.new(@transport, client: self)
42
45
  end
43
46
 
@@ -71,6 +71,14 @@ module VectorAmp
71
71
  service.insert(id, vectors: vectors)
72
72
  end
73
73
 
74
+ # Delete one or more vectors from this dataset by id.
75
+ # @param ids [Array<String,Integer>] vector ids to delete.
76
+ # @param write_concern [String, nil] optional API write concern.
77
+ # @return [Hash] delete response.
78
+ def delete_vectors(ids:, write_concern: nil)
79
+ service.delete_vectors(id, ids: ids, write_concern: write_concern)
80
+ end
81
+
74
82
  # Embed and insert texts into this dataset.
75
83
  # @param texts_arg [Array<String>, nil] positional texts for convenience.
76
84
  # @param texts [Array<String>, nil] keyword texts.
@@ -87,13 +95,22 @@ module VectorAmp
87
95
  service.delete(id)
88
96
  end
89
97
 
98
+ # Add or update typed metadata fields, retaining omitted fields.
99
+ def patch_metadata_schema(schema)
100
+ service.patch_metadata_schema(id, schema)
101
+ end
102
+
103
+ # Replace the complete typed metadata schema.
104
+ def replace_metadata_schema(schema)
105
+ service.replace_metadata_schema(id, schema)
106
+ end
107
+
90
108
  # Fetch stats for this dataset.
91
109
  # @return [Hash] dataset statistics.
92
110
  def stats
93
111
  service.stats(id)
94
112
  end
95
113
 
96
-
97
114
  # List retained source documents for this dataset using cursor pagination.
98
115
  # @param limit [Integer, nil] maximum documents to return.
99
116
  # @param cursor [String, nil] cursor from a previous response's `next_cursor`.
@@ -59,7 +59,7 @@ module VectorAmp
59
59
  # @param metric [String] distance metric; defaults to `cosine`.
60
60
  # @param hybrid [Boolean, nil] enable hybrid (dense + sparse) search; sends `hybrid: true`.
61
61
  # @param filters [Hash, nil] optional filter schema/config.
62
- # @param metadata_schema [Hash, nil] optional metadata schema.
62
+ # @param metadata_schema [Array<Hash>, Hash, nil] optional metadata schema.
63
63
  # @param tuning [Hash, nil] optional SABLE tuning parameters.
64
64
  # @param metadata [Hash, nil] optional dataset metadata.
65
65
  # @return [Dataset] created dataset.
@@ -85,13 +85,51 @@ module VectorAmp
85
85
  index_type: "sable",
86
86
  hybrid: hybrid,
87
87
  filters: filters,
88
- metadata_schema: metadata_schema,
88
+ schema: normalize_metadata_schema(metadata_schema),
89
89
  tuning: tuning,
90
90
  metadata: metadata
91
91
  )
92
92
  wrap_dataset(@transport.request(:post, "/datasets", body: body))
93
93
  end
94
94
 
95
+ # Store/update an OpenAI API key in org secrets, then create an OpenAI-backed dataset
96
+ # whose embedding config references that secret.
97
+ # @param name [String] dataset name.
98
+ # @param api_key [String] OpenAI API key to store server-side.
99
+ # @param size [String, Symbol] "small" or "large".
100
+ # @param secret_ref [String] org secret reference to write and attach to the dataset.
101
+ # @param validate [Boolean] validate the key before storing.
102
+ # @param options [Hash] forwarded to {#create}, e.g. `hybrid: true` or `metadata:`.
103
+ # @return [Dataset] created dataset.
104
+ def create_with_openai_api_key(name:, api_key:, size: "small", secret_ref: "emb:openai:api_key", validate: false, **options)
105
+ secrets = @client&.org_secrets || begin
106
+ require_relative "org_secrets"
107
+ OrgSecretsResource.new(@transport)
108
+ end
109
+ secrets.put_openai_api_key(
110
+ api_key: api_key,
111
+ secret_ref: secret_ref,
112
+ validate: validate,
113
+ model: openai_model(size)
114
+ )
115
+ create(name: name, embedding: Embedding.openai(size, secret_ref: secret_ref), **options)
116
+ end
117
+
118
+ # Add or update typed metadata fields, retaining omitted fields.
119
+ # @param dataset_id [String] dataset id.
120
+ # @param schema [Array<Hash>] fields with `name` and canonical `type`.
121
+ # @return [Dataset] updated dataset.
122
+ def patch_metadata_schema(dataset_id, schema)
123
+ update_metadata_schema(dataset_id, schema, "merge")
124
+ end
125
+
126
+ # Replace the complete typed metadata schema.
127
+ # @param dataset_id [String] dataset id.
128
+ # @param schema [Array<Hash>] complete field list; may be empty.
129
+ # @return [Dataset] updated dataset.
130
+ def replace_metadata_schema(dataset_id, schema)
131
+ update_metadata_schema(dataset_id, schema, "replace")
132
+ end
95
133
 
96
134
  # List retained source documents for a dataset using cursor pagination.
97
135
  # @param dataset_id [String] dataset id.
@@ -176,6 +214,21 @@ module VectorAmp
176
214
  @transport.request(:post, "/datasets/#{dataset_id}/insert", body: { vectors: Utils.normalize_vectors(vectors) })
177
215
  end
178
216
 
217
+ # Delete one or more vectors from a dataset by id.
218
+ # @param dataset_id [String] dataset id.
219
+ # @param ids [Array<String,Integer>] vector ids to delete.
220
+ # @param write_concern [String, nil] optional API write concern.
221
+ # @return [Hash] delete response with `deleted` and `dataset_id`.
222
+ def delete_vectors(dataset_id, ids:, write_concern: nil)
223
+ raise ArgumentError, "ids must not be empty" if ids.nil? || ids.empty?
224
+
225
+ @transport.request(
226
+ :delete,
227
+ "/datasets/#{dataset_id}/vectors",
228
+ body: Utils.compact_hash(ids: ids, write_concern: write_concern)
229
+ )
230
+ end
231
+
179
232
  # Generate embeddings using the dataset embedding configuration.
180
233
  # @param dataset_id [String] dataset id.
181
234
  # @param text [String, nil] single text to embed.
@@ -221,6 +274,34 @@ module VectorAmp
221
274
 
222
275
  private
223
276
 
277
+ def openai_model(size)
278
+ case size.to_s
279
+ when "small" then "text-embedding-3-small"
280
+ when "large" then "text-embedding-3-large"
281
+ else
282
+ raise ArgumentError, %(openai size must be "small" or "large", got #{size.inspect})
283
+ end
284
+ end
285
+
286
+ def normalize_metadata_schema(schema)
287
+ return schema unless schema.is_a?(Hash)
288
+
289
+ schema.map do |name, config|
290
+ raise ArgumentError, "metadata schema field #{name.inspect} must be a Hash" unless config.is_a?(Hash)
291
+
292
+ { name: name.to_s }.merge(config)
293
+ end
294
+ end
295
+
296
+ def update_metadata_schema(dataset_id, schema, mode)
297
+ response = @transport.request(
298
+ :patch,
299
+ "/datasets/#{dataset_id}/schema",
300
+ body: { schema: schema, mode: mode }
301
+ )
302
+ wrap_dataset(response)
303
+ end
304
+
224
305
  def wrap_dataset(data)
225
306
  Dataset.new(data, service: self, client: @client)
226
307
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "utils"
4
+
3
5
  module VectorAmp
4
6
  # Embedding configuration defaults and helpers.
5
7
  #
@@ -28,16 +30,17 @@ module VectorAmp
28
30
 
29
31
  # Build an OpenAI embedding configuration.
30
32
  # @param size [String, Symbol] `"small"` (text-embedding-3-small) or `"large"` (text-embedding-3-large).
31
- # @return [Hash] `{ provider: "openai", model: ... }`
33
+ # @param secret_ref [String, nil] organization secret reference containing the OpenAI API key.
34
+ # @return [Hash] `{ provider: "openai", model: ..., secret_ref: ... }`
32
35
  # @raise [ArgumentError] when size is not "small" or "large".
33
- def openai(size = "small")
36
+ def openai(size = "small", secret_ref: "emb:openai:api_key")
34
37
  model = case size.to_s
35
38
  when "small" then "text-embedding-3-small"
36
39
  when "large" then "text-embedding-3-large"
37
40
  else
38
41
  raise ArgumentError, %(openai size must be "small" or "large", got #{size.inspect})
39
42
  end
40
- { provider: "openai", model: model }
43
+ Utils.compact_hash(provider: "openai", model: model, secret_ref: secret_ref)
41
44
  end
42
45
 
43
46
  # Normalize a user-supplied embedding value into a config hash.
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VectorAmp
4
+ # Helpers and canonical scalar types for dataset metadata schemas.
5
+ module MetadataSchema
6
+ STRING = "string"
7
+ U32 = "u32"
8
+ I32 = "i32"
9
+ I64 = "i64"
10
+ F32 = "f32"
11
+ F64 = "f64"
12
+ TYPES = [STRING, U32, I32, I64, F32, F64].freeze
13
+
14
+ def self.field(name, type)
15
+ raise ArgumentError, "unsupported metadata schema type: #{type.inspect}" unless TYPES.include?(type)
16
+
17
+ { name: name.to_s, type: type }
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ require_relative "utils"
6
+
7
+ module VectorAmp
8
+ # Organization-scoped secret helpers exposed by the public API.
9
+ class OrgSecretsResource
10
+ DEFAULT_OPENAI_SECRET_REF = "emb:openai:api_key"
11
+
12
+ # @param transport [#request] API transport.
13
+ # @return [OrgSecretsResource]
14
+ def initialize(transport)
15
+ @transport = transport
16
+ end
17
+
18
+ # Store or update an organization secret by name.
19
+ # @param name [String] org secret reference to write.
20
+ # @param value [String] plaintext value sent once to the API.
21
+ def put(name:, value:)
22
+ secret_name = name.to_s.strip
23
+ secret_value = value.to_s.strip
24
+ raise ArgumentError, "name is required" if secret_name.empty?
25
+ raise ArgumentError, "value is required" if secret_value.empty?
26
+
27
+ @transport.request(:put, "/org-secrets/#{URI.encode_www_form_component(secret_name)}", body: { value: secret_value })
28
+ end
29
+
30
+ # Check whether an organization secret exists.
31
+ def exists?(name:)
32
+ secret_name = name.to_s.strip
33
+ raise ArgumentError, "name is required" if secret_name.empty?
34
+ @transport.request(:get, "/org-secrets/#{URI.encode_www_form_component(secret_name)}")
35
+ end
36
+
37
+ # Store or update the organization OpenAI API key.
38
+ # @param api_key [String] OpenAI API key to store server-side.
39
+ # @param secret_ref [String] org secret reference to write.
40
+ # @param validate [Boolean] accepted for API compatibility; generic org-secret writes do not validate.
41
+ # @param model [String, nil] accepted for API compatibility.
42
+ # @return [Hash, nil] API response; normally nil/empty for 204.
43
+ def put_openai_api_key(api_key:, secret_ref: DEFAULT_OPENAI_SECRET_REF, validate: false, model: nil)
44
+ key = api_key.to_s.strip
45
+ raise ArgumentError, "api_key is required" if key.empty?
46
+
47
+ put(name: secret_ref, value: key)
48
+ end
49
+
50
+ alias update_openai_api_key put_openai_api_key
51
+
52
+ # Check whether the default organization OpenAI API key exists.
53
+ # @return [Hash, nil] API response; normally nil/empty for 204.
54
+ def openai_api_key?
55
+ exists?(name: DEFAULT_OPENAI_SECRET_REF)
56
+ end
57
+ end
58
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module VectorAmp
4
- VERSION = "0.1.0"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/vector_amp.rb CHANGED
@@ -4,6 +4,8 @@ require_relative "vector_amp/version"
4
4
  require_relative "vector_amp/client"
5
5
  require_relative "vector_amp/embedding"
6
6
  require_relative "vector_amp/error"
7
+ require_relative "vector_amp/org_secrets"
8
+ require_relative "vector_amp/metadata_schema"
7
9
  require_relative "vector_amp/source"
8
10
 
9
11
  module VectorAmp
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vector_amp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - VectorAmp
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-07-02 00:00:00.000000000 Z
11
+ date: 2026-07-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -114,6 +114,8 @@ files:
114
114
  - lib/vector_amp/error.rb
115
115
  - lib/vector_amp/ingestion.rb
116
116
  - lib/vector_amp/intelligence.rb
117
+ - lib/vector_amp/metadata_schema.rb
118
+ - lib/vector_amp/org_secrets.rb
117
119
  - lib/vector_amp/schedules.rb
118
120
  - lib/vector_amp/source.rb
119
121
  - lib/vector_amp/transport/base.rb