vectoramp 0.1.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.3.0/CHANGELOG.md +28 -0
- {vectoramp-0.1.0 → vectoramp-0.3.0}/PKG-INFO +49 -8
- {vectoramp-0.1.0 → vectoramp-0.3.0}/README.md +48 -7
- {vectoramp-0.1.0 → vectoramp-0.3.0}/pyproject.toml +1 -1
- {vectoramp-0.1.0 → vectoramp-0.3.0}/src/vectoramp/__init__.py +6 -1
- {vectoramp-0.1.0 → vectoramp-0.3.0}/src/vectoramp/client.py +9 -1
- {vectoramp-0.1.0 → vectoramp-0.3.0}/src/vectoramp/resources.py +193 -6
- {vectoramp-0.1.0 → vectoramp-0.3.0}/src/vectoramp/transport.py +1 -1
- {vectoramp-0.1.0 → vectoramp-0.3.0}/src/vectoramp/types.py +12 -0
- {vectoramp-0.1.0 → vectoramp-0.3.0}/tests/test_client.py +135 -4
- vectoramp-0.1.0/CHANGELOG.md +0 -12
- {vectoramp-0.1.0 → vectoramp-0.3.0}/.github/images/logo-full-dark.svg +0 -0
- {vectoramp-0.1.0 → vectoramp-0.3.0}/.github/images/logo-full-light.svg +0 -0
- {vectoramp-0.1.0 → vectoramp-0.3.0}/.github/workflows/ci.yml +0 -0
- {vectoramp-0.1.0 → vectoramp-0.3.0}/.github/workflows/publish.yml +0 -0
- {vectoramp-0.1.0 → vectoramp-0.3.0}/.gitignore +0 -0
- {vectoramp-0.1.0 → vectoramp-0.3.0}/CONTRIBUTING.md +0 -0
- {vectoramp-0.1.0 → vectoramp-0.3.0}/LICENSE +0 -0
- {vectoramp-0.1.0 → vectoramp-0.3.0}/NOTICE +0 -0
- {vectoramp-0.1.0 → vectoramp-0.3.0}/SECURITY.md +0 -0
- {vectoramp-0.1.0 → vectoramp-0.3.0}/src/vectoramp/connections.py +0 -0
- {vectoramp-0.1.0 → vectoramp-0.3.0}/src/vectoramp/embeddings.py +0 -0
- {vectoramp-0.1.0 → vectoramp-0.3.0}/src/vectoramp/exceptions.py +0 -0
- {vectoramp-0.1.0 → vectoramp-0.3.0}/src/vectoramp/py.typed +0 -0
- {vectoramp-0.1.0 → vectoramp-0.3.0}/src/vectoramp/sources.py +0 -0
- {vectoramp-0.1.0 → vectoramp-0.3.0}/tests/test_connections_and_sources.py +0 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
This project follows semantic versioning.
|
|
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
|
+
|
|
15
|
+
## [0.2.0] - 2026-07-14
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Add dataset/vector deletion helpers for deleting vectors by id.
|
|
20
|
+
- Add organization secret helpers and OpenAI-backed dataset creation convenience flow.
|
|
21
|
+
- Document OpenAI secret setup and vector deletion examples.
|
|
22
|
+
|
|
23
|
+
## [0.1.0] - 2026-07-02
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- Initial public-ready package baseline for VectorAmp SDK/CLI migration to GitHub.
|
|
28
|
+
- GitHub Actions CI workflow.
|
|
@@ -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
|
|
@@ -31,9 +31,9 @@ Description-Content-Type: text/markdown
|
|
|
31
31
|
<div align="center">
|
|
32
32
|
<a href="https://vectoramp.com/">
|
|
33
33
|
<picture>
|
|
34
|
-
<source media="(prefers-color-scheme: light)" srcset=".
|
|
35
|
-
<source media="(prefers-color-scheme: dark)" srcset=".
|
|
36
|
-
<img alt="VectorAmp Logo" src=".
|
|
34
|
+
<source media="(prefers-color-scheme: light)" srcset="https://vectoramp.com/logo-full-light.svg">
|
|
35
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://vectoramp.com/logo-full-dark.svg">
|
|
36
|
+
<img alt="VectorAmp Logo" src="https://vectoramp.com/logo-full-dark.svg" width="50%">
|
|
37
37
|
</picture>
|
|
38
38
|
</a>
|
|
39
39
|
</div>
|
|
@@ -87,7 +87,7 @@ client = VectorAmp(api_key="va_...", base_url="http://localhost:8080")
|
|
|
87
87
|
|
|
88
88
|
## Datasets
|
|
89
89
|
|
|
90
|
-
Dataset creation always requests the SABLE index. The SDK intentionally does **not** expose an `index_type` option. Built-in helpers infer dimensions for
|
|
90
|
+
Dataset creation always requests the SABLE index. The SDK intentionally does **not** expose an `index_type` option. For normal use, omit embedding config and VectorAmp uses the managed `VectorAmp-Embedding-4B` model with inferred dim `2560`. Built-in helpers also infer dimensions for optional OpenAI BYOM models.
|
|
91
91
|
|
|
92
92
|
The only required argument is `name`. The default embedding is `VectorAmp-Embedding-4B` (`dim=2560`, `metric="cosine"`):
|
|
93
93
|
|
|
@@ -103,17 +103,48 @@ Enable hybrid (dense + sparse) indexing with `hybrid=True`:
|
|
|
103
103
|
dataset = client.datasets.create("product-docs", hybrid=True)
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
Optional BYOM: use the `openai` helper only when you intentionally want an OpenAI embedding model (dimension is inferred):
|
|
107
107
|
|
|
108
108
|
```python
|
|
109
109
|
from vectoramp import openai
|
|
110
110
|
|
|
111
111
|
dataset = client.datasets.create(
|
|
112
|
-
"
|
|
112
|
+
"openai-docs",
|
|
113
113
|
embedding=openai("small"), # or openai("large")
|
|
114
114
|
)
|
|
115
115
|
```
|
|
116
116
|
|
|
117
|
+
To save or rotate the OpenAI API key as an organization secret first, then
|
|
118
|
+
create the dataset with `embedding.secret_ref`, use the helper:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
dataset = client.datasets.create_openai(
|
|
122
|
+
name="product-docs",
|
|
123
|
+
api_key="sk-...",
|
|
124
|
+
secret_ref="emb:openai:api_key", # default
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Or upsert the secret directly.
|
|
128
|
+
client.secrets.put_openai_api_key("sk-...")
|
|
129
|
+
client.org_secrets.update("emb:openai:api_key", "sk-rotated")
|
|
130
|
+
```
|
|
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
|
+
|
|
117
148
|
For a custom/unknown model you must pass `dim` explicitly:
|
|
118
149
|
|
|
119
150
|
```python
|
|
@@ -184,6 +215,13 @@ dataset.add_texts(
|
|
|
184
215
|
)
|
|
185
216
|
```
|
|
186
217
|
|
|
218
|
+
Delete vectors by id:
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
dataset.delete_vectors(["doc-001", 42])
|
|
222
|
+
client.datasets.delete_vectors(dataset_id, ["doc-001", 42])
|
|
223
|
+
```
|
|
224
|
+
|
|
187
225
|
## Search
|
|
188
226
|
|
|
189
227
|
Search by text:
|
|
@@ -412,13 +450,16 @@ listed first; optional arguments show their default.
|
|
|
412
450
|
|
|
413
451
|
### Datasets (`client.datasets` / `Dataset`)
|
|
414
452
|
|
|
415
|
-
- `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)` → `Dataset`. Always SABLE. `dim` inferred for built-in models; required for custom models.
|
|
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.
|
|
416
454
|
- `list(*, limit=50, offset=0)` → page with `Dataset` objects.
|
|
417
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`.
|
|
418
458
|
- `delete(dataset_id)` / `dataset.delete()`.
|
|
419
459
|
- `stats(dataset_id)` / `dataset.stats()`.
|
|
420
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.
|
|
421
461
|
- `insert(dataset_id, vectors)` and `insert_vectors(dataset_id, vectors)` / `dataset.insert(vectors)`. Record `id` may be `str` or `int` (integers stay JSON numbers).
|
|
462
|
+
- `delete_vectors(dataset_id, ids, *, write_concern=None)` / `dataset.delete_vectors(ids)`.
|
|
422
463
|
- `embed(dataset_id, *, text=None, texts=None)` / `dataset.embed(…)`.
|
|
423
464
|
- `add_texts(dataset_id, texts, *, ids=None, metadatas=None)` / `dataset.add_texts(texts, …)`. Single string or list; ids auto-generated when omitted; copies the text into `metadata.text`.
|
|
424
465
|
- `list_documents(dataset_id, *, limit=50, cursor=None, status=None)` / `dataset.list_documents(…)`.
|
|
@@ -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=".
|
|
5
|
-
<source media="(prefers-color-scheme: dark)" srcset=".
|
|
6
|
-
<img alt="VectorAmp Logo" src=".
|
|
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>
|
|
@@ -57,7 +57,7 @@ client = VectorAmp(api_key="va_...", base_url="http://localhost:8080")
|
|
|
57
57
|
|
|
58
58
|
## Datasets
|
|
59
59
|
|
|
60
|
-
Dataset creation always requests the SABLE index. The SDK intentionally does **not** expose an `index_type` option. Built-in helpers infer dimensions for
|
|
60
|
+
Dataset creation always requests the SABLE index. The SDK intentionally does **not** expose an `index_type` option. For normal use, omit embedding config and VectorAmp uses the managed `VectorAmp-Embedding-4B` model with inferred dim `2560`. Built-in helpers also infer dimensions for optional OpenAI BYOM models.
|
|
61
61
|
|
|
62
62
|
The only required argument is `name`. The default embedding is `VectorAmp-Embedding-4B` (`dim=2560`, `metric="cosine"`):
|
|
63
63
|
|
|
@@ -73,17 +73,48 @@ Enable hybrid (dense + sparse) indexing with `hybrid=True`:
|
|
|
73
73
|
dataset = client.datasets.create("product-docs", hybrid=True)
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
Optional BYOM: use the `openai` helper only when you intentionally want an OpenAI embedding model (dimension is inferred):
|
|
77
77
|
|
|
78
78
|
```python
|
|
79
79
|
from vectoramp import openai
|
|
80
80
|
|
|
81
81
|
dataset = client.datasets.create(
|
|
82
|
-
"
|
|
82
|
+
"openai-docs",
|
|
83
83
|
embedding=openai("small"), # or openai("large")
|
|
84
84
|
)
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
+
To save or rotate the OpenAI API key as an organization secret first, then
|
|
88
|
+
create the dataset with `embedding.secret_ref`, use the helper:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
dataset = client.datasets.create_openai(
|
|
92
|
+
name="product-docs",
|
|
93
|
+
api_key="sk-...",
|
|
94
|
+
secret_ref="emb:openai:api_key", # default
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Or upsert the secret directly.
|
|
98
|
+
client.secrets.put_openai_api_key("sk-...")
|
|
99
|
+
client.org_secrets.update("emb:openai:api_key", "sk-rotated")
|
|
100
|
+
```
|
|
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
|
+
|
|
87
118
|
For a custom/unknown model you must pass `dim` explicitly:
|
|
88
119
|
|
|
89
120
|
```python
|
|
@@ -154,6 +185,13 @@ dataset.add_texts(
|
|
|
154
185
|
)
|
|
155
186
|
```
|
|
156
187
|
|
|
188
|
+
Delete vectors by id:
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
dataset.delete_vectors(["doc-001", 42])
|
|
192
|
+
client.datasets.delete_vectors(dataset_id, ["doc-001", 42])
|
|
193
|
+
```
|
|
194
|
+
|
|
157
195
|
## Search
|
|
158
196
|
|
|
159
197
|
Search by text:
|
|
@@ -382,13 +420,16 @@ listed first; optional arguments show their default.
|
|
|
382
420
|
|
|
383
421
|
### Datasets (`client.datasets` / `Dataset`)
|
|
384
422
|
|
|
385
|
-
- `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)` → `Dataset`. Always SABLE. `dim` inferred for built-in models; required for custom models.
|
|
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.
|
|
386
424
|
- `list(*, limit=50, offset=0)` → page with `Dataset` objects.
|
|
387
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`.
|
|
388
428
|
- `delete(dataset_id)` / `dataset.delete()`.
|
|
389
429
|
- `stats(dataset_id)` / `dataset.stats()`.
|
|
390
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.
|
|
391
431
|
- `insert(dataset_id, vectors)` and `insert_vectors(dataset_id, vectors)` / `dataset.insert(vectors)`. Record `id` may be `str` or `int` (integers stay JSON numbers).
|
|
432
|
+
- `delete_vectors(dataset_id, ids, *, write_concern=None)` / `dataset.delete_vectors(ids)`.
|
|
392
433
|
- `embed(dataset_id, *, text=None, texts=None)` / `dataset.embed(…)`.
|
|
393
434
|
- `add_texts(dataset_id, texts, *, ids=None, metadatas=None)` / `dataset.add_texts(texts, …)`. Single string or list; ids auto-generated when omitted; copies the text into `metadata.text`.
|
|
394
435
|
- `list_documents(dataset_id, *, limit=50, cursor=None, status=None)` / `dataset.list_documents(…)`.
|
|
@@ -10,7 +10,7 @@ from .embeddings import (
|
|
|
10
10
|
openai,
|
|
11
11
|
)
|
|
12
12
|
from .exceptions import APIError, AuthenticationError, VectorAmpError
|
|
13
|
-
from .resources import Dataset
|
|
13
|
+
from .resources import Dataset, OrgSecretsResource
|
|
14
14
|
from .sources import (
|
|
15
15
|
ConfluenceSource,
|
|
16
16
|
FileUploadSource,
|
|
@@ -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,8 +35,12 @@ __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",
|
|
43
|
+
"OrgSecretsResource",
|
|
39
44
|
"S3Source",
|
|
40
45
|
"VectorAmp",
|
|
41
46
|
"VECTORAMP_EMBEDDING_4B",
|
|
@@ -9,7 +9,13 @@ from typing import Iterator, Optional, Type
|
|
|
9
9
|
import httpx
|
|
10
10
|
|
|
11
11
|
from .connections import ConnectionsResource
|
|
12
|
-
from .resources import
|
|
12
|
+
from .resources import (
|
|
13
|
+
DatasetsResource,
|
|
14
|
+
IngestionResource,
|
|
15
|
+
IntelligenceResource,
|
|
16
|
+
OrgSecretsResource,
|
|
17
|
+
SchedulesResource,
|
|
18
|
+
)
|
|
13
19
|
from .transport import BaseTransport, RestTransport
|
|
14
20
|
from .types import JSON, ConversationTurn
|
|
15
21
|
|
|
@@ -54,6 +60,8 @@ class VectorAmp:
|
|
|
54
60
|
self.ingestion = IngestionResource(self.transport)
|
|
55
61
|
self.sources = self.ingestion
|
|
56
62
|
self.connections = ConnectionsResource(self.transport)
|
|
63
|
+
self.secrets = OrgSecretsResource(self.transport)
|
|
64
|
+
self.org_secrets = self.secrets
|
|
57
65
|
self.schedules = SchedulesResource(self.transport)
|
|
58
66
|
self.intelligence = IntelligenceResource(self.transport)
|
|
59
67
|
|
|
@@ -6,9 +6,16 @@ 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
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
from typing import Any, Dict, Iterator, List, Mapping, Optional, Sequence, Union
|
|
10
|
+
from urllib.parse import quote
|
|
11
|
+
|
|
12
|
+
from .embeddings import (
|
|
13
|
+
EMBEDDING_DIMENSIONS,
|
|
14
|
+
OPENAI_TEXT_EMBEDDING_3_SMALL,
|
|
15
|
+
VECTORAMP_EMBEDDING_4B,
|
|
16
|
+
OpenAIEmbeddingSize,
|
|
17
|
+
openai,
|
|
18
|
+
)
|
|
12
19
|
from .sources import (
|
|
13
20
|
ConfluenceSource,
|
|
14
21
|
FileUploadSource,
|
|
@@ -21,7 +28,17 @@ from .sources import (
|
|
|
21
28
|
WebSource,
|
|
22
29
|
)
|
|
23
30
|
from .transport import BaseTransport, RestTransport
|
|
24
|
-
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
|
+
)
|
|
25
42
|
|
|
26
43
|
PathLike = Union[str, Path]
|
|
27
44
|
|
|
@@ -137,6 +154,15 @@ class Dataset:
|
|
|
137
154
|
"""
|
|
138
155
|
return self.service.insert_vectors(self.id, vectors)
|
|
139
156
|
|
|
157
|
+
def delete_vectors(
|
|
158
|
+
self,
|
|
159
|
+
ids: Sequence[VectorId],
|
|
160
|
+
*,
|
|
161
|
+
write_concern: Optional[Mapping[str, Any]] = None,
|
|
162
|
+
) -> JSON:
|
|
163
|
+
"""Delete vectors from this dataset by id."""
|
|
164
|
+
return self.service.delete_vectors(self.id, ids, write_concern=write_concern)
|
|
165
|
+
|
|
140
166
|
def add_texts(
|
|
141
167
|
self,
|
|
142
168
|
texts: Union[str, Sequence[str]],
|
|
@@ -274,6 +300,14 @@ class Dataset:
|
|
|
274
300
|
pipeline_id=pipeline_id,
|
|
275
301
|
)
|
|
276
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
|
+
|
|
277
311
|
def get(self, key: str, default: Any = None) -> Any:
|
|
278
312
|
"""Return a value from the raw dataset payload, or ``default``."""
|
|
279
313
|
return self.raw_data.get(key, default)
|
|
@@ -360,8 +394,11 @@ class DatasetsResource:
|
|
|
360
394
|
embedding_model: str = VECTORAMP_EMBEDDING_4B,
|
|
361
395
|
hybrid: bool = False,
|
|
362
396
|
filters: Optional[Mapping[str, Any]] = None,
|
|
363
|
-
metadata_schema: Optional[
|
|
397
|
+
metadata_schema: Optional[MetadataSchemaInput] = None,
|
|
364
398
|
tuning: Optional[Mapping[str, Any]] = None,
|
|
399
|
+
openai_api_key: Optional[str] = None,
|
|
400
|
+
openai_secret_ref: str = "emb:openai:api_key",
|
|
401
|
+
validate_openai_key: bool = False,
|
|
365
402
|
) -> Dataset:
|
|
366
403
|
"""Create a SABLE dataset.
|
|
367
404
|
|
|
@@ -384,13 +421,38 @@ class DatasetsResource:
|
|
|
384
421
|
filters: Optional filter schema/configuration.
|
|
385
422
|
metadata_schema: Optional metadata schema.
|
|
386
423
|
tuning: Optional SABLE tuning parameters.
|
|
424
|
+
openai_api_key: Optional OpenAI API key to store as an organization
|
|
425
|
+
secret before creating the dataset. When provided, and no
|
|
426
|
+
explicit embedding is passed, the dataset uses OpenAI
|
|
427
|
+
``text-embedding-3-small`` with ``secret_ref`` set to
|
|
428
|
+
``openai_secret_ref``.
|
|
429
|
+
openai_secret_ref: Organization-secret name used for the OpenAI key.
|
|
430
|
+
validate_openai_key: Ask the API to validate the OpenAI key before
|
|
431
|
+
storing it. Defaults to ``False``.
|
|
387
432
|
|
|
388
433
|
Returns:
|
|
389
434
|
Created ``Dataset`` object.
|
|
390
435
|
"""
|
|
436
|
+
if openai_api_key is not None:
|
|
437
|
+
if self.client is None or not hasattr(self.client, "secrets"):
|
|
438
|
+
raise TypeError("openai_api_key requires a DatasetResource owned by VectorAmp.")
|
|
439
|
+
validation_model = str(
|
|
440
|
+
(embedding or {}).get("model") or OPENAI_TEXT_EMBEDDING_3_SMALL
|
|
441
|
+
)
|
|
442
|
+
self.client.secrets.put(
|
|
443
|
+
openai_secret_ref,
|
|
444
|
+
openai_api_key,
|
|
445
|
+
validate=validate_openai_key,
|
|
446
|
+
model=validation_model,
|
|
447
|
+
)
|
|
448
|
+
if embedding is None and embedding_provider == "vectoramp":
|
|
449
|
+
embedding = openai("small")
|
|
450
|
+
|
|
391
451
|
embedding_config = {"provider": embedding_provider, "model": embedding_model}
|
|
392
452
|
if embedding is not None:
|
|
393
453
|
embedding_config.update(dict(embedding))
|
|
454
|
+
if openai_api_key is not None and embedding_config.get("provider") == "openai":
|
|
455
|
+
embedding_config["secret_ref"] = openai_secret_ref
|
|
394
456
|
resolved_dim = dim or EMBEDDING_DIMENSIONS.get(str(embedding_config.get("model")))
|
|
395
457
|
if resolved_dim is None:
|
|
396
458
|
raise ValueError("dim is required for custom embedding models")
|
|
@@ -407,11 +469,67 @@ class DatasetsResource:
|
|
|
407
469
|
if filters is not None:
|
|
408
470
|
body["filters"] = dict(filters)
|
|
409
471
|
if metadata_schema is not None:
|
|
410
|
-
body["
|
|
472
|
+
body["schema"] = self._normalize_metadata_schema(metadata_schema)
|
|
411
473
|
if tuning is not None:
|
|
412
474
|
body["tuning"] = dict(tuning)
|
|
413
475
|
return self._to_dataset(self._transport.request("POST", "/datasets", json_body=body))
|
|
414
476
|
|
|
477
|
+
def create_openai(
|
|
478
|
+
self,
|
|
479
|
+
*,
|
|
480
|
+
name: str,
|
|
481
|
+
api_key: str,
|
|
482
|
+
size: OpenAIEmbeddingSize = "small",
|
|
483
|
+
secret_ref: str = "emb:openai:api_key",
|
|
484
|
+
validate: bool = False,
|
|
485
|
+
dim: Optional[int] = None,
|
|
486
|
+
metric: Metric = "cosine",
|
|
487
|
+
hybrid: bool = False,
|
|
488
|
+
filters: Optional[Mapping[str, Any]] = None,
|
|
489
|
+
metadata_schema: Optional[Mapping[str, Any]] = None,
|
|
490
|
+
tuning: Optional[Mapping[str, Any]] = None,
|
|
491
|
+
) -> Dataset:
|
|
492
|
+
"""Store an OpenAI org secret, then create an OpenAI-backed dataset."""
|
|
493
|
+
embedding_config = openai(size)
|
|
494
|
+
embedding_config["secret_ref"] = secret_ref
|
|
495
|
+
return self.create(
|
|
496
|
+
name=name,
|
|
497
|
+
dim=dim,
|
|
498
|
+
metric=metric,
|
|
499
|
+
embedding=embedding_config,
|
|
500
|
+
hybrid=hybrid,
|
|
501
|
+
filters=filters,
|
|
502
|
+
metadata_schema=metadata_schema,
|
|
503
|
+
tuning=tuning,
|
|
504
|
+
openai_api_key=api_key,
|
|
505
|
+
openai_secret_ref=secret_ref,
|
|
506
|
+
validate_openai_key=validate,
|
|
507
|
+
)
|
|
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
|
+
|
|
415
533
|
def delete(self, dataset_id: str) -> Any:
|
|
416
534
|
"""Delete a dataset and return the API response.
|
|
417
535
|
|
|
@@ -578,6 +696,23 @@ class DatasetsResource:
|
|
|
578
696
|
"""Alias for ``insert_vectors``."""
|
|
579
697
|
return self.insert_vectors(dataset_id, vectors)
|
|
580
698
|
|
|
699
|
+
def delete_vectors(
|
|
700
|
+
self,
|
|
701
|
+
dataset_id: str,
|
|
702
|
+
ids: Sequence[VectorId],
|
|
703
|
+
*,
|
|
704
|
+
write_concern: Optional[Mapping[str, Any]] = None,
|
|
705
|
+
) -> JSON:
|
|
706
|
+
"""Delete vectors from a dataset by id."""
|
|
707
|
+
if not ids:
|
|
708
|
+
raise ValueError("ids must contain at least one vector id.")
|
|
709
|
+
body: JSON = {"ids": list(ids)}
|
|
710
|
+
if write_concern is not None:
|
|
711
|
+
body["write_concern"] = dict(write_concern)
|
|
712
|
+
return self._transport.request(
|
|
713
|
+
"DELETE", f"/api/v1/datasets/{dataset_id}/vectors", json_body=body
|
|
714
|
+
)
|
|
715
|
+
|
|
581
716
|
def embed(
|
|
582
717
|
self,
|
|
583
718
|
dataset_id: str,
|
|
@@ -657,6 +792,58 @@ class DatasetsResource:
|
|
|
657
792
|
return Dataset(self, raw_data)
|
|
658
793
|
|
|
659
794
|
|
|
795
|
+
class OrgSecretsResource:
|
|
796
|
+
"""Organization secret helpers."""
|
|
797
|
+
|
|
798
|
+
def __init__(self, transport: BaseTransport) -> None:
|
|
799
|
+
self._transport = transport
|
|
800
|
+
|
|
801
|
+
def put(
|
|
802
|
+
self,
|
|
803
|
+
name: str,
|
|
804
|
+
value: str,
|
|
805
|
+
*,
|
|
806
|
+
validate: bool = False,
|
|
807
|
+
model: str = OPENAI_TEXT_EMBEDDING_3_SMALL,
|
|
808
|
+
) -> Any:
|
|
809
|
+
"""Create or update an organization secret.
|
|
810
|
+
|
|
811
|
+
Stores provider credentials through the generic organization-secret
|
|
812
|
+
REST endpoint. Plaintext is sent once and is never returned by reads.
|
|
813
|
+
"""
|
|
814
|
+
if not name:
|
|
815
|
+
raise ValueError("name is required.")
|
|
816
|
+
if not value:
|
|
817
|
+
raise ValueError("value is required.")
|
|
818
|
+
return self._transport.request(
|
|
819
|
+
"PUT",
|
|
820
|
+
f"/org-secrets/{quote(name, safe='')}",
|
|
821
|
+
json_body={"value": value},
|
|
822
|
+
)
|
|
823
|
+
|
|
824
|
+
def update(
|
|
825
|
+
self,
|
|
826
|
+
name: str,
|
|
827
|
+
value: str,
|
|
828
|
+
*,
|
|
829
|
+
validate: bool = False,
|
|
830
|
+
model: str = OPENAI_TEXT_EMBEDDING_3_SMALL,
|
|
831
|
+
) -> Any:
|
|
832
|
+
"""Alias for :meth:`put`; org secrets are upserted by name."""
|
|
833
|
+
return self.put(name, value, validate=validate, model=model)
|
|
834
|
+
|
|
835
|
+
def put_openai_api_key(
|
|
836
|
+
self,
|
|
837
|
+
api_key: str,
|
|
838
|
+
*,
|
|
839
|
+
secret_ref: str = "emb:openai:api_key",
|
|
840
|
+
validate: bool = False,
|
|
841
|
+
model: str = OPENAI_TEXT_EMBEDDING_3_SMALL,
|
|
842
|
+
) -> Any:
|
|
843
|
+
"""Create or update the OpenAI API key organization secret."""
|
|
844
|
+
return self.put(secret_ref, api_key, validate=validate, model=model)
|
|
845
|
+
|
|
846
|
+
|
|
660
847
|
class IngestionResource:
|
|
661
848
|
"""Sources, upload sessions, and ingestion jobs."""
|
|
662
849
|
|
|
@@ -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:
|
|
@@ -133,6 +174,96 @@ def test_dataset_create_openai_helper_infers_dimension() -> None:
|
|
|
133
174
|
}
|
|
134
175
|
|
|
135
176
|
|
|
177
|
+
def test_dataset_create_openai_stores_secret_then_uses_secret_ref() -> None:
|
|
178
|
+
calls: list[tuple[str, str, dict[str, Any]]] = []
|
|
179
|
+
|
|
180
|
+
def handler(request: httpx.Request) -> httpx.Response:
|
|
181
|
+
body = json.loads(request.content) if request.content else {}
|
|
182
|
+
calls.append((request.method, request.url.path, body))
|
|
183
|
+
if request.url.path == "/org-secrets/emb%3Aopenai%3Aapi_key":
|
|
184
|
+
return httpx.Response(204)
|
|
185
|
+
return json_response({"id": "ds_openai", "index_type": "sable"}, 201)
|
|
186
|
+
|
|
187
|
+
client = make_client(handler)
|
|
188
|
+
dataset = client.datasets.create_openai(
|
|
189
|
+
name="openai-docs",
|
|
190
|
+
api_key="sk-test",
|
|
191
|
+
secret_ref="emb:openai:customer-a",
|
|
192
|
+
validate=True,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
assert dataset.id == "ds_openai"
|
|
196
|
+
assert calls == [
|
|
197
|
+
(
|
|
198
|
+
"PUT",
|
|
199
|
+
"/org-secrets/emb:openai:customer-a",
|
|
200
|
+
{"value": "sk-test"},
|
|
201
|
+
),
|
|
202
|
+
(
|
|
203
|
+
"POST",
|
|
204
|
+
"/datasets",
|
|
205
|
+
{
|
|
206
|
+
"name": "openai-docs",
|
|
207
|
+
"dim": 1536,
|
|
208
|
+
"metric": "cosine",
|
|
209
|
+
"embedding": {
|
|
210
|
+
"provider": "openai",
|
|
211
|
+
"model": "text-embedding-3-small",
|
|
212
|
+
"secret_ref": "emb:openai:customer-a",
|
|
213
|
+
},
|
|
214
|
+
"index_type": "sable",
|
|
215
|
+
},
|
|
216
|
+
),
|
|
217
|
+
]
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def test_org_secrets_put_update_openai_key() -> None:
|
|
221
|
+
calls: list[dict[str, Any]] = []
|
|
222
|
+
|
|
223
|
+
def handler(request: httpx.Request) -> httpx.Response:
|
|
224
|
+
calls.append(json.loads(request.content))
|
|
225
|
+
return httpx.Response(204)
|
|
226
|
+
|
|
227
|
+
client = make_client(handler)
|
|
228
|
+
assert client.secrets.put("emb:openai:api_key", "sk-new") is None
|
|
229
|
+
assert client.org_secrets.update("emb:openai:api_key", "sk-rotated") is None
|
|
230
|
+
|
|
231
|
+
assert calls[0]["value"] == "sk-new"
|
|
232
|
+
assert calls[1]["value"] == "sk-rotated"
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def test_delete_vectors_payload_and_dataset_helper() -> None:
|
|
236
|
+
calls: list[tuple[str, str, dict[str, Any]]] = []
|
|
237
|
+
|
|
238
|
+
def handler(request: httpx.Request) -> httpx.Response:
|
|
239
|
+
body = json.loads(request.content) if request.content else {}
|
|
240
|
+
calls.append((request.method, request.url.path, body))
|
|
241
|
+
return json_response({"deleted": 2, "ids": body.get("ids", [])})
|
|
242
|
+
|
|
243
|
+
client = make_client(handler)
|
|
244
|
+
result = client.datasets.delete_vectors(
|
|
245
|
+
"ds_1", ["vec-a", 42], write_concern={"wait": True}
|
|
246
|
+
)
|
|
247
|
+
dataset = Dataset(client.datasets, {"id": "ds_1"})
|
|
248
|
+
dataset.delete_vectors(["vec-c"])
|
|
249
|
+
|
|
250
|
+
assert result["deleted"] == 2
|
|
251
|
+
assert calls == [
|
|
252
|
+
(
|
|
253
|
+
"DELETE",
|
|
254
|
+
"/api/v1/datasets/ds_1/vectors",
|
|
255
|
+
{"ids": ["vec-a", 42], "write_concern": {"wait": True}},
|
|
256
|
+
),
|
|
257
|
+
("DELETE", "/api/v1/datasets/ds_1/vectors", {"ids": ["vec-c"]}),
|
|
258
|
+
]
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def test_delete_vectors_requires_ids() -> None:
|
|
262
|
+
client = make_client(lambda request: json_response({}))
|
|
263
|
+
with pytest.raises(ValueError):
|
|
264
|
+
client.datasets.delete_vectors("ds_1", [])
|
|
265
|
+
|
|
266
|
+
|
|
136
267
|
def test_list_get_delete_and_stats() -> None:
|
|
137
268
|
calls = []
|
|
138
269
|
|
vectoramp-0.1.0/CHANGELOG.md
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file.
|
|
4
|
-
|
|
5
|
-
This project follows semantic versioning.
|
|
6
|
-
|
|
7
|
-
## [0.1.0] - 2026-07-02
|
|
8
|
-
|
|
9
|
-
### Added
|
|
10
|
-
|
|
11
|
-
- Initial public-ready package baseline for VectorAmp SDK/CLI migration to GitHub.
|
|
12
|
-
- GitHub Actions CI workflow.
|
|
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
|