vectoramp 0.2.0__tar.gz → 0.3.0__tar.gz
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.
- {vectoramp-0.2.0 → vectoramp-0.3.0}/CHANGELOG.md +8 -0
- {vectoramp-0.2.0 → vectoramp-0.3.0}/PKG-INFO +19 -1
- {vectoramp-0.2.0 → vectoramp-0.3.0}/README.md +18 -0
- {vectoramp-0.2.0 → vectoramp-0.3.0}/pyproject.toml +1 -1
- {vectoramp-0.2.0 → vectoramp-0.3.0}/src/vectoramp/__init__.py +4 -0
- {vectoramp-0.2.0 → vectoramp-0.3.0}/src/vectoramp/resources.py +46 -4
- {vectoramp-0.2.0 → vectoramp-0.3.0}/src/vectoramp/transport.py +1 -1
- {vectoramp-0.2.0 → vectoramp-0.3.0}/src/vectoramp/types.py +12 -0
- {vectoramp-0.2.0 → vectoramp-0.3.0}/tests/test_client.py +45 -4
- {vectoramp-0.2.0 → vectoramp-0.3.0}/.github/images/logo-full-dark.svg +0 -0
- {vectoramp-0.2.0 → vectoramp-0.3.0}/.github/images/logo-full-light.svg +0 -0
- {vectoramp-0.2.0 → vectoramp-0.3.0}/.github/workflows/ci.yml +0 -0
- {vectoramp-0.2.0 → vectoramp-0.3.0}/.github/workflows/publish.yml +0 -0
- {vectoramp-0.2.0 → vectoramp-0.3.0}/.gitignore +0 -0
- {vectoramp-0.2.0 → vectoramp-0.3.0}/CONTRIBUTING.md +0 -0
- {vectoramp-0.2.0 → vectoramp-0.3.0}/LICENSE +0 -0
- {vectoramp-0.2.0 → vectoramp-0.3.0}/NOTICE +0 -0
- {vectoramp-0.2.0 → vectoramp-0.3.0}/SECURITY.md +0 -0
- {vectoramp-0.2.0 → vectoramp-0.3.0}/src/vectoramp/client.py +0 -0
- {vectoramp-0.2.0 → vectoramp-0.3.0}/src/vectoramp/connections.py +0 -0
- {vectoramp-0.2.0 → vectoramp-0.3.0}/src/vectoramp/embeddings.py +0 -0
- {vectoramp-0.2.0 → vectoramp-0.3.0}/src/vectoramp/exceptions.py +0 -0
- {vectoramp-0.2.0 → vectoramp-0.3.0}/src/vectoramp/py.typed +0 -0
- {vectoramp-0.2.0 → vectoramp-0.3.0}/src/vectoramp/sources.py +0 -0
- {vectoramp-0.2.0 → vectoramp-0.3.0}/tests/test_connections_and_sources.py +0 -0
|
@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
This project follows semantic versioning.
|
|
6
6
|
|
|
7
|
+
## [0.3.0] - 2026-07-20
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Add typed metadata-schema fields when creating datasets.
|
|
12
|
+
- Add metadata-schema merge/patch and full replacement operations.
|
|
13
|
+
- Document create, merge, and replace schema workflows.
|
|
14
|
+
|
|
7
15
|
## [0.2.0] - 2026-07-14
|
|
8
16
|
|
|
9
17
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vectoramp
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Python SDK for the VectorAmp API
|
|
5
5
|
Project-URL: Homepage, https://vectoramp.com
|
|
6
6
|
Project-URL: Documentation, https://docs.vectoramp.com
|
|
@@ -129,6 +129,22 @@ client.secrets.put_openai_api_key("sk-...")
|
|
|
129
129
|
client.org_secrets.update("emb:openai:api_key", "sk-rotated")
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
+
Declare typed metadata at creation, then merge fields or replace the complete schema:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from vectoramp import MetadataSchemaField
|
|
136
|
+
|
|
137
|
+
schema: list[MetadataSchemaField] = [
|
|
138
|
+
{"name": "category", "type": "string"},
|
|
139
|
+
{"name": "price", "type": "f32"},
|
|
140
|
+
]
|
|
141
|
+
dataset = client.datasets.create(name="products", metadata_schema=schema)
|
|
142
|
+
dataset.patch_metadata_schema([{"name": "inventory", "type": "u32"}])
|
|
143
|
+
dataset.replace_metadata_schema(schema) # [] removes all declared fields
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Canonical field types are `string`, `u32`, `i32`, `i64`, `f32`, and `f64`.
|
|
147
|
+
|
|
132
148
|
For a custom/unknown model you must pass `dim` explicitly:
|
|
133
149
|
|
|
134
150
|
```python
|
|
@@ -437,6 +453,8 @@ listed first; optional arguments show their default.
|
|
|
437
453
|
- `create(name, *, dim=None, metric="cosine", embedding=None, embedding_provider="vectoramp", embedding_model="VectorAmp-Embedding-4B", hybrid=False, filters=None, metadata_schema=None, tuning=None, openai_api_key=None, openai_secret_ref="emb:openai:api_key", validate_openai_key=False)` → `Dataset`. Always SABLE. `dim` inferred for built-in models; required for custom models.
|
|
438
454
|
- `list(*, limit=50, offset=0)` → page with `Dataset` objects.
|
|
439
455
|
- `get(dataset_id)` → `Dataset`.
|
|
456
|
+
- `patch_metadata_schema(dataset_id, schema)` / `Dataset.patch_metadata_schema(schema)` → updated `Dataset`.
|
|
457
|
+
- `replace_metadata_schema(dataset_id, schema)` / `Dataset.replace_metadata_schema(schema)` → updated `Dataset`.
|
|
440
458
|
- `delete(dataset_id)` / `dataset.delete()`.
|
|
441
459
|
- `stats(dataset_id)` / `dataset.stats()`.
|
|
442
460
|
- `search(dataset_id, query=None, *, vector=None, text=None, search_text=None, top_k=10, filters=None, advanced_filters=None, embedding_provider=None, embedding_model=None, nprobe_override=None, rerank_depth_override=None, hybrid=None, sparse_query=None, alpha=None, include_embeddings=None, include_documents=None, include_metadata=None, rerank=None)` / `dataset.search(…)`. `query` accepts a string (text) or float sequence (vector); `top_k` defaults to 10.
|
|
@@ -99,6 +99,22 @@ client.secrets.put_openai_api_key("sk-...")
|
|
|
99
99
|
client.org_secrets.update("emb:openai:api_key", "sk-rotated")
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
+
Declare typed metadata at creation, then merge fields or replace the complete schema:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from vectoramp import MetadataSchemaField
|
|
106
|
+
|
|
107
|
+
schema: list[MetadataSchemaField] = [
|
|
108
|
+
{"name": "category", "type": "string"},
|
|
109
|
+
{"name": "price", "type": "f32"},
|
|
110
|
+
]
|
|
111
|
+
dataset = client.datasets.create(name="products", metadata_schema=schema)
|
|
112
|
+
dataset.patch_metadata_schema([{"name": "inventory", "type": "u32"}])
|
|
113
|
+
dataset.replace_metadata_schema(schema) # [] removes all declared fields
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Canonical field types are `string`, `u32`, `i32`, `i64`, `f32`, and `f64`.
|
|
117
|
+
|
|
102
118
|
For a custom/unknown model you must pass `dim` explicitly:
|
|
103
119
|
|
|
104
120
|
```python
|
|
@@ -407,6 +423,8 @@ listed first; optional arguments show their default.
|
|
|
407
423
|
- `create(name, *, dim=None, metric="cosine", embedding=None, embedding_provider="vectoramp", embedding_model="VectorAmp-Embedding-4B", hybrid=False, filters=None, metadata_schema=None, tuning=None, openai_api_key=None, openai_secret_ref="emb:openai:api_key", validate_openai_key=False)` → `Dataset`. Always SABLE. `dim` inferred for built-in models; required for custom models.
|
|
408
424
|
- `list(*, limit=50, offset=0)` → page with `Dataset` objects.
|
|
409
425
|
- `get(dataset_id)` → `Dataset`.
|
|
426
|
+
- `patch_metadata_schema(dataset_id, schema)` / `Dataset.patch_metadata_schema(schema)` → updated `Dataset`.
|
|
427
|
+
- `replace_metadata_schema(dataset_id, schema)` / `Dataset.replace_metadata_schema(schema)` → updated `Dataset`.
|
|
410
428
|
- `delete(dataset_id)` / `dataset.delete()`.
|
|
411
429
|
- `stats(dataset_id)` / `dataset.stats()`.
|
|
412
430
|
- `search(dataset_id, query=None, *, vector=None, text=None, search_text=None, top_k=10, filters=None, advanced_filters=None, embedding_provider=None, embedding_model=None, nprobe_override=None, rerank_depth_override=None, hybrid=None, sparse_query=None, alpha=None, include_embeddings=None, include_documents=None, include_metadata=None, rerank=None)` / `dataset.search(…)`. `query` accepts a string (text) or float sequence (vector); `top_k` defaults to 10.
|
|
@@ -21,6 +21,7 @@ from .sources import (
|
|
|
21
21
|
S3Source,
|
|
22
22
|
WebSource,
|
|
23
23
|
)
|
|
24
|
+
from .types import MetadataFieldType, MetadataSchema, MetadataSchemaField
|
|
24
25
|
|
|
25
26
|
__all__ = [
|
|
26
27
|
"APIError",
|
|
@@ -34,6 +35,9 @@ __all__ = [
|
|
|
34
35
|
"GenericSource",
|
|
35
36
|
"GoogleDriveSource",
|
|
36
37
|
"JiraSource",
|
|
38
|
+
"MetadataFieldType",
|
|
39
|
+
"MetadataSchema",
|
|
40
|
+
"MetadataSchemaField",
|
|
37
41
|
"OPENAI_TEXT_EMBEDDING_3_LARGE",
|
|
38
42
|
"OPENAI_TEXT_EMBEDDING_3_SMALL",
|
|
39
43
|
"OrgSecretsResource",
|
|
@@ -6,7 +6,7 @@ import mimetypes
|
|
|
6
6
|
import uuid
|
|
7
7
|
from collections.abc import ItemsView, KeysView, ValuesView
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import Any, Iterator, Mapping, Optional, Sequence, Union
|
|
9
|
+
from typing import Any, Dict, Iterator, List, Mapping, Optional, Sequence, Union
|
|
10
10
|
from urllib.parse import quote
|
|
11
11
|
|
|
12
12
|
from .embeddings import (
|
|
@@ -28,7 +28,17 @@ from .sources import (
|
|
|
28
28
|
WebSource,
|
|
29
29
|
)
|
|
30
30
|
from .transport import BaseTransport, RestTransport
|
|
31
|
-
from .types import
|
|
31
|
+
from .types import (
|
|
32
|
+
JSON,
|
|
33
|
+
AdvancedFilter,
|
|
34
|
+
ConversationTurn,
|
|
35
|
+
Filters,
|
|
36
|
+
MetadataSchema,
|
|
37
|
+
MetadataSchemaInput,
|
|
38
|
+
Metric,
|
|
39
|
+
Vector,
|
|
40
|
+
VectorId,
|
|
41
|
+
)
|
|
32
42
|
|
|
33
43
|
PathLike = Union[str, Path]
|
|
34
44
|
|
|
@@ -290,6 +300,14 @@ class Dataset:
|
|
|
290
300
|
pipeline_id=pipeline_id,
|
|
291
301
|
)
|
|
292
302
|
|
|
303
|
+
def patch_metadata_schema(self, schema: MetadataSchema) -> "Dataset":
|
|
304
|
+
"""Add or update typed metadata fields while retaining other fields."""
|
|
305
|
+
return self.service.patch_metadata_schema(self.id, schema)
|
|
306
|
+
|
|
307
|
+
def replace_metadata_schema(self, schema: MetadataSchema) -> "Dataset":
|
|
308
|
+
"""Replace the complete typed metadata schema."""
|
|
309
|
+
return self.service.replace_metadata_schema(self.id, schema)
|
|
310
|
+
|
|
293
311
|
def get(self, key: str, default: Any = None) -> Any:
|
|
294
312
|
"""Return a value from the raw dataset payload, or ``default``."""
|
|
295
313
|
return self.raw_data.get(key, default)
|
|
@@ -376,7 +394,7 @@ class DatasetsResource:
|
|
|
376
394
|
embedding_model: str = VECTORAMP_EMBEDDING_4B,
|
|
377
395
|
hybrid: bool = False,
|
|
378
396
|
filters: Optional[Mapping[str, Any]] = None,
|
|
379
|
-
metadata_schema: Optional[
|
|
397
|
+
metadata_schema: Optional[MetadataSchemaInput] = None,
|
|
380
398
|
tuning: Optional[Mapping[str, Any]] = None,
|
|
381
399
|
openai_api_key: Optional[str] = None,
|
|
382
400
|
openai_secret_ref: str = "emb:openai:api_key",
|
|
@@ -451,7 +469,7 @@ class DatasetsResource:
|
|
|
451
469
|
if filters is not None:
|
|
452
470
|
body["filters"] = dict(filters)
|
|
453
471
|
if metadata_schema is not None:
|
|
454
|
-
body["
|
|
472
|
+
body["schema"] = self._normalize_metadata_schema(metadata_schema)
|
|
455
473
|
if tuning is not None:
|
|
456
474
|
body["tuning"] = dict(tuning)
|
|
457
475
|
return self._to_dataset(self._transport.request("POST", "/datasets", json_body=body))
|
|
@@ -488,6 +506,30 @@ class DatasetsResource:
|
|
|
488
506
|
validate_openai_key=validate,
|
|
489
507
|
)
|
|
490
508
|
|
|
509
|
+
def patch_metadata_schema(self, dataset_id: str, schema: MetadataSchema) -> Dataset:
|
|
510
|
+
"""Add or update schema fields without removing omitted fields."""
|
|
511
|
+
body = {"schema": [dict(field) for field in schema], "mode": "merge"}
|
|
512
|
+
return self._to_dataset(
|
|
513
|
+
self._transport.request("PATCH", f"/datasets/{dataset_id}/schema", json_body=body)
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
def replace_metadata_schema(self, dataset_id: str, schema: MetadataSchema) -> Dataset:
|
|
517
|
+
"""Replace a dataset's complete typed metadata schema."""
|
|
518
|
+
body = {"schema": [dict(field) for field in schema], "mode": "replace"}
|
|
519
|
+
return self._to_dataset(
|
|
520
|
+
self._transport.request("PATCH", f"/datasets/{dataset_id}/schema", json_body=body)
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
@staticmethod
|
|
524
|
+
def _normalize_metadata_schema(schema: MetadataSchemaInput) -> List[Dict[str, Any]]:
|
|
525
|
+
"""Normalize the legacy field-name mapping and the canonical field array."""
|
|
526
|
+
if isinstance(schema, Mapping):
|
|
527
|
+
return [
|
|
528
|
+
{"name": name, **dict(config)}
|
|
529
|
+
for name, config in schema.items()
|
|
530
|
+
]
|
|
531
|
+
return [dict(field) for field in schema]
|
|
532
|
+
|
|
491
533
|
def delete(self, dataset_id: str) -> Any:
|
|
492
534
|
"""Delete a dataset and return the API response.
|
|
493
535
|
|
|
@@ -157,7 +157,7 @@ class RestTransport(BaseTransport):
|
|
|
157
157
|
return urljoin(self.base_url, path.lstrip("/"))
|
|
158
158
|
|
|
159
159
|
def _headers(self, headers: Optional[Mapping[str, str]] = None) -> Dict[str, str]:
|
|
160
|
-
merged = {"X-API-Key": self.api_key, "User-Agent": "vectoramp-python/0.
|
|
160
|
+
merged = {"X-API-Key": self.api_key, "User-Agent": "vectoramp-python/0.3.0"}
|
|
161
161
|
if headers:
|
|
162
162
|
merged.update(headers)
|
|
163
163
|
return merged
|
|
@@ -7,6 +7,18 @@ from typing import Any, Dict, List, Literal, Mapping, Optional, Sequence, TypedD
|
|
|
7
7
|
JSON = Dict[str, Any]
|
|
8
8
|
Metadata = Mapping[str, Any]
|
|
9
9
|
Metric = Literal["cosine", "dot", "euclidean"]
|
|
10
|
+
MetadataFieldType = Literal["string", "u32", "i32", "i64", "f32", "f64"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MetadataSchemaField(TypedDict):
|
|
14
|
+
"""A typed metadata field declared on a dataset."""
|
|
15
|
+
|
|
16
|
+
name: str
|
|
17
|
+
type: MetadataFieldType
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
MetadataSchema = Sequence[MetadataSchemaField]
|
|
21
|
+
MetadataSchemaInput = Union[MetadataSchema, Mapping[str, Mapping[str, Any]]]
|
|
10
22
|
|
|
11
23
|
|
|
12
24
|
class EmbeddingConfig(TypedDict):
|
|
@@ -52,7 +52,7 @@ def test_dataset_create_forces_sable_and_auth_header() -> None:
|
|
|
52
52
|
name="docs",
|
|
53
53
|
dim=2560,
|
|
54
54
|
filters={"category": "string"},
|
|
55
|
-
metadata_schema={"
|
|
55
|
+
metadata_schema=[{"name": "title", "type": "string"}],
|
|
56
56
|
tuning={"replicas": 1},
|
|
57
57
|
)
|
|
58
58
|
|
|
@@ -64,8 +64,10 @@ def test_dataset_create_forces_sable_and_auth_header() -> None:
|
|
|
64
64
|
"provider": "vectoramp",
|
|
65
65
|
"model": "VectorAmp-Embedding-4B",
|
|
66
66
|
}
|
|
67
|
+
# hybrid is not sent unless explicitly requested.
|
|
68
|
+
assert "hybrid" not in seen["body"]
|
|
67
69
|
assert seen["body"]["filters"] == {"category": "string"}
|
|
68
|
-
assert seen["body"]["
|
|
70
|
+
assert seen["body"]["schema"] == [{"name": "title", "type": "string"}]
|
|
69
71
|
assert seen["body"]["tuning"] == {"replicas": 1}
|
|
70
72
|
|
|
71
73
|
|
|
@@ -88,8 +90,47 @@ def test_dataset_create_minimal_name_only_defaults() -> None:
|
|
|
88
90
|
"provider": "vectoramp",
|
|
89
91
|
"model": "VectorAmp-Embedding-4B",
|
|
90
92
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_dataset_create_normalizes_legacy_metadata_schema_mapping() -> None:
|
|
96
|
+
seen = {}
|
|
97
|
+
|
|
98
|
+
def handler(request: httpx.Request) -> httpx.Response:
|
|
99
|
+
seen["body"] = json.loads(request.content)
|
|
100
|
+
return json_response({"id": "ds_legacy"}, 201)
|
|
101
|
+
|
|
102
|
+
client = make_client(handler)
|
|
103
|
+
client.datasets.create(
|
|
104
|
+
name="legacy",
|
|
105
|
+
metadata_schema={"title": {"type": "string"}},
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
assert seen["body"]["schema"] == [{"name": "title", "type": "string"}]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@pytest.mark.parametrize(
|
|
112
|
+
("method_name", "mode"),
|
|
113
|
+
[("patch_metadata_schema", "merge"), ("replace_metadata_schema", "replace")],
|
|
114
|
+
)
|
|
115
|
+
def test_update_metadata_schema(method_name: str, mode: str) -> None:
|
|
116
|
+
seen = {}
|
|
117
|
+
|
|
118
|
+
def handler(request: httpx.Request) -> httpx.Response:
|
|
119
|
+
seen["method"] = request.method
|
|
120
|
+
seen["url"] = str(request.url)
|
|
121
|
+
seen["body"] = json.loads(request.content)
|
|
122
|
+
return json_response({"id": "ds_1", "schema_version": 2})
|
|
123
|
+
|
|
124
|
+
client = make_client(handler)
|
|
125
|
+
schema = [{"name": "price", "type": "f32"}]
|
|
126
|
+
dataset = getattr(client.datasets, method_name)("ds_1", schema)
|
|
127
|
+
|
|
128
|
+
assert dataset.id == "ds_1"
|
|
129
|
+
assert seen == {
|
|
130
|
+
"method": "PATCH",
|
|
131
|
+
"url": "https://api.test/datasets/ds_1/schema",
|
|
132
|
+
"body": {"schema": schema, "mode": mode},
|
|
133
|
+
}
|
|
93
134
|
|
|
94
135
|
|
|
95
136
|
def test_dataset_create_hybrid() -> None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|