Aetherfy Vectors Python SDK
Memory for AI agents — conversations, knowledge bases, scrape logs, trading histories. Named scopes you create and search, replicated across regions, with no infrastructure to run.
🚀 Key Features
- 🧠 Memory Primitives:
Namespacefor any named scope,Threadfor conversations — not a raw collection API - 🌍 Global Performance: Low latency from anywhere in the world
- ⚡ Intelligent Caching: High cache hit rates for optimal performance
- 🛡️ Zero DevOps: No infrastructure management or regional deployment needed
- 🤝 Workspace Scoping: Multi-agent tenancy, auto-detected inside deployed agents
- 📊 Built-in Analytics: Real-time performance metrics and usage insights
- 🔧 Auto-Failover: Intelligent routing and retry mechanisms
- 🔐 Enterprise Security: API key authentication and audit logging
- 🪜 Escape Hatch:
AetherfyVectorsClientunderneath, API-compatible withqdrant-client
📦 Installation
pip install aetherfy-vectors
🏃♂️ Quick Start
Every memory lives in a scope you create by name. Two kinds: a namespace for anything with a name, a thread for a conversation. Bring your own embedding vectors — Aetherfy stores and searches them, it does not generate them.
from aetherfy_memory import MemoryClient
memory = MemoryClient() # reads AETHERFY_API_KEY; workspace auto-detected
# --- A namespace: any named scope ---------------------------------------
memory.create_namespace("customer-42")
customer = memory.namespace("customer-42")
customer.add(text="Lives in NYC, prefers email", vector=embed("Lives in NYC, prefers email"))
hits = customer.search(vector=embed("where is this customer based?"), limit=5)
# --- A thread: a conversation -------------------------------------------
memory.create_thread("conv-99")
thread = memory.thread("conv-99")
thread.add(role="user", content="hi", vector=embed("hi"))
thread.add(role="assistant", content="hello", vector=embed("hello"))
recent = thread.history(limit=20) # in message order
# Deleting a scope is atomic — it drops the whole backing collection.
thread.clear()
Scopes must be created before you write to them, so a typo raises instead of
silently creating a second store. vector_size defaults to 384; pass your
model's dimension to create_namespace / create_thread if it differs
(1536 for OpenAI small, 3072 for large, 1024 for Cohere v3).
Deployed on Aetherfy, MemoryClient() takes no arguments at all: the control
plane injects AETHERFY_API_KEY and the workspace at deploy time.
Full memory API — iteration, bulk loading, metadata — under Memory SDK below.
🧱 The Low-Level Client
AetherfyVectorsClient is the layer MemoryClient is built on, exported and
supported. Use it when you want raw collections and points rather than named
scopes, or an operation the memory layer doesn't expose.
The snippets below build on each other; each assumes the imports and client from the previous ones.
Migration from qdrant-client
Replace your existing qdrant-client code with just 2 lines changed:
# Before (qdrant-client)
from qdrant_client import QdrantClient
client = QdrantClient(host="localhost", port=6333)
# After (aetherfy-vectors) - Only 2 changes needed!
from aetherfy_vectors import AetherfyVectorsClient
client = AetherfyVectorsClient(api_key="afy_live_your_api_key_here")
# All your existing code works unchanged! 🎉
results = client.search(collection_name="my_collection", query_vector=[0.1, 0.2, 0.3])
Basic Usage Example
from aetherfy_vectors import AetherfyVectorsClient
from aetherfy_vectors.models import VectorConfig, DistanceMetric
# Initialize client
client = AetherfyVectorsClient(api_key="afy_live_your_api_key_here")
# Create a collection
client.create_collection(
"documents",
VectorConfig(size=128, distance=DistanceMetric.COSINE)
)
# Add vectors — a point id is an unsigned integer (<= 2**53 - 1) or a UUID string
points = [
{
"id": 1,
"vector": [0.1, 0.2, ...], # 128-dimensional vector
"payload": {"title": "Document 1", "category": "research"}
}
]
client.upsert("documents", points)
# Search for similar vectors
results = client.search(
collection_name="documents",
query_vector=[0.1, 0.2, ...],
limit=10,
with_payload=True
)
for result in results:
print(f"ID: {result.id}, Score: {result.score}")
print(f"Title: {result.payload['title']}")
🌟 Unique Features
Usage Against Your Plan Limits
usage = client.get_usage_stats()
print(f"Storage used: {usage.storage_bytes_used:,} bytes")
print(f"Collections: {usage.collections_count}/{usage.collections_limit}")
print(f"Tier: {usage.tier}")
print(f"Replicating to: {', '.join(usage.active_regions)}")
Intelligent Global Routing
Your requests are automatically routed to the optimal region:
# No configuration needed - routing is automatic!
# Requests from the US → the US region
# Requests from the EU → the EU region
# Requests from Asia → the Asia-Pacific region
🔧 Advanced Usage
Batch Operations
Efficient batch processing with automatic optimization:
# Bulk insert thousands of points (ids are unsigned integers or UUID strings)
large_batch = [
{"id": i, "vector": [...], "payload": {...}}
for i in range(10000)
]
# Automatically optimized batch size and routing
client.upsert("large_collection", large_batch)
Complex Filtering
Full compatibility with qdrant-client filters. Three clauses, and only
three: must, must_not, should.
results = client.search(
collection_name="products",
query_vector=[...],
query_filter={
"must": [
{"key": "category", "match": {"value": "electronics"}},
{"key": "price", "range": {"gte": 100, "lte": 1000}}
],
"must_not": [
{"key": "status", "match": {"value": "discontinued"}}
],
},
limit=20
)
The same clauses are available as a typed object, if you prefer it to a dict:
from aetherfy_vectors.models import Filter
results = client.search(
collection_name="products",
query_vector=[...],
query_filter=Filter(
must=[{"key": "category", "match": {"value": "electronics"}}],
must_not=[{"key": "status", "match": {"value": "discontinued"}}],
),
limit=20,
)
A clause name outside those three raises ValidationError before the
request leaves the process — including mustNot, which is the JavaScript
SDK's spelling of must_not. Unknown clauses are never forwarded and never
dropped: writing the wrong one is an error you see, not a filter that
quietly matches everything. The same rule applies everywhere a filter is
accepted — search, scroll, scroll_iter, count, delete.
Context Manager Support
with AetherfyVectorsClient(api_key="your_key") as client:
results = client.search("collection", [0.1, 0.2, 0.3])
# Automatic cleanup
🔁 Iterating Large Collections
For bulk reads, use scroll_iter() rather than scroll(limit=…). The
iterator pages transparently and stays within the server's per-request
caps (1000 points/call, 10 MB/response):
for point in client.scroll_iter("my_collection", batch_size=256):
process(point)
# With a filter and selective payload/vector return
for point in client.scroll_iter(
"my_collection",
batch_size=512,
scroll_filter={"must": [{"key": "status", "match": {"value": "active"}}]},
with_payload=True,
with_vectors=False,
):
process(point)
batch_size is the page size for one HTTP round trip (max 1000 server-side).
The iterator handles cursor management, page exhaustion, and pagination
errors — no offset bookkeeping in user code.
✏️ Editing Payload on Existing Points
Three operations on the payload of points that already exist — no need to re-upsert vectors:
# MERGE: add or update keys, leave others alone
client.set_payload(
"my_collection",
{"reviewed": True, "reviewer": "alice"},
[point_id],
)
# OVERWRITE: replace the entire payload object
client.overwrite_payload(
"my_collection",
{"category": "X"}, # all other keys are dropped
[point_id],
)
# DELETE: remove specific keys, leave others alone
client.delete_payload(
"my_collection",
["draft_field", "stale_score"],
[point_id],
)
Each call accepts up to 512 points in one round trip; for larger
mutations, batch on the caller side. The semantics map exactly to
qdrant's set_payload / overwrite_payload / delete_payload so
existing patterns transfer.
🧠 Memory SDK — Iter, Bulk-load, set_metadata
The Memory layer (aetherfy_memory) layers Namespace and Thread
abstractions on top of AetherfyVectorsClient. Three additions worth
knowing once you go past add() / search():
Iterating a namespace or a thread
from aetherfy_memory import MemoryClient
memory = MemoryClient(api_key="afy_live_…", workspace="my-bot")
ns = memory.namespace("customer-42")
for point in ns.iter(batch_size=256):
process(point)
# Threads have iter_history() — yields messages in ts order across the
# whole conversation. Distinct from history(limit=N), which caps at 5000
# in memory for the most-recent slice.
thread = memory.thread("conv-99")
for msg in thread.iter_history(order="asc"):
print(msg.role, msg.content)
Use history(limit=N) for "show me the last N messages" (bounded, fast).
Use iter_history() for "walk every message in this thread" (paged,
memory-bounded by the iterator).
Bulk-loading memories
add_many() and append_many() batch into a single client.upsert so
N items become 1 round trip. IDs are returned in input order; missing
IDs are auto-generated as canonical UUIDs (the same format iter() and
retrieve() yield back, so equality comparisons just work).
items = [
{"text": "first", "vector": embed("first"), "metadata": {"src": "a"}},
{"text": "second", "vector": embed("second"), "metadata": {"src": "b"}},
]
ids = ns.add_many(items) # single round trip; preserves input order
# Threads use append_many — role/content/ts payloads, ts auto-set per
# message when omitted (each message gets its own ts, not one shared).
msgs = [
{"role": "user", "content": "hi", "vector": embed("hi")},
{"role": "assistant", "content": "hello", "vector": embed("hello")},
]
ids = thread.append_many(msgs)
Threads have no add_many() — a Thread is not a Namespace subclass
(they share a scope base but declare their own write API), so there is
no add_many to call. add_many writes text/metadata payloads,
which don't fit a thread's role/content/ts schema. Use
append_many() on threads.
set_metadata — atomic replace, explicit-compose merge
set_metadata() replaces the entire metadata sub-key.
set_metadata({"tag": "x"}) nukes every other key. Use
merge_metadata() if you want additive updates that preserve existing
keys. Reserved fields (text for Namespace; role/content/ts for
Thread) are untouched either way.
ns.set_metadata(point_id, {"reviewed": True, "score": 0.92})
To merge into existing metadata via the explicit-compose pattern (race visible at the call site, no atomicity guarantee):
current = ns.retrieve([point_id])[0]["payload"].get("metadata", {})
current.update({"reviewed": True})
ns.set_metadata(point_id, current)
If two callers run this concurrently, one update wins and the other sees its read be stale — by design, you see that race in your own code rather than have the SDK hide it.
merge_metadata — atomic per-point partial merge
merge_metadata({"tag": "x"}) adds/updates the listed keys and leaves
every other key untouched. Concurrent patches to different keys all
land atomically; concurrent writes to the same key resolve via
last-writer-wins per the storage operation order. Raises
PointNotFoundError if the point doesn't exist. Reserved keys (text
on Namespace; role, content, ts on Thread) cannot appear in the
partial — raises a local ValueError before the request is sent.
ns.merge_metadata(point_id, {"reviewed": True})
ns.merge_metadata(point_id, {"score": 0.92})
# final metadata: original keys + reviewed + score
delete_metadata_keys — atomic key removal
delete_metadata_keys(point_id, ["tag", "score"]) removes the listed
keys from metadata; keys not in the list are left untouched. Raises
PointNotFoundError if the point doesn't exist. Reserved keys cannot
appear in the keys list (same set as merge_metadata).
ns.delete_metadata_keys(point_id, ["draft", "stale_score"])
📐 Limits
Two axes constrain a single call: per-request size (PRS) and requests
per minute (RPM). Both axes return a 4xx with a structured error.code
when they fire — no surprise 5xx, no silent truncation.
| Class | Endpoint | Cap |
|---|---|---|
| READS | scroll · search · retrieve |
≤ 1000 points/call · ≤ 10 MB/response |
| WRITES | upsert |
≤ 10 K vectors/call · streaming |
| payload edits · batch delete | ≤ 512 points/call |
Upserts stream — there is no body-size cap on the public upsert URL. The 10 K vectors/call is a defensive request-level ceiling, not a body limit; one call can upload millions of bytes via byte-target chunking on the receiving end. For bulk reads, use
scroll_iter()— it pages transparently and stays within both quotas.
requests_per_minute is a sliding-window minutely cap derived from your
subscription tier. When it fires, the SDK raises
RateLimitExceededError with a structured retry_after (seconds);
PRS violations raise ValidationError (400) or surface as 413
RESPONSE_TOO_LARGE for oversized response bodies.
🤝 Multi-Agent Workspaces
Workspaces let multiple agents share vector collections without name collisions. All collections created through a workspace-scoped client are automatically namespaced — agents in the same workspace see each other's collections; agents in different workspaces are fully isolated.
Creating a workspace-scoped client
from aetherfy_vectors import AetherfyVectorsClient
client = AetherfyVectorsClient(
api_key="afy_live_your_api_key_here",
workspace="invoice-pipeline", # All operations are scoped to this workspace
)
How scoping works
Collection names are automatically prefixed — you always use the short name:
# Create a collection (stored as "invoice-pipeline/documents" internally)
from aetherfy_vectors.models import VectorConfig, DistanceMetric
client.create_collection("documents", VectorConfig(size=768, distance=DistanceMetric.COSINE))
# Search — no need to know the full scoped name
results = client.search("documents", query_vector=embedding, limit=10)
# List — only returns collections in your workspace
collections = client.get_collections()
# → [Collection(name='documents', ...)] (short names, not scoped names)
Multi-agent example
# Agent A: extractor
extractor = AetherfyVectorsClient(api_key=api_key, workspace="invoice-pipeline")
extractor.create_collection("raw-invoices", VectorConfig(size=768, distance=DistanceMetric.COSINE))
extractor.upsert("raw-invoices", extracted_points)
# Agent B: classifier — same workspace, sees Agent A's collection
classifier = AetherfyVectorsClient(api_key=api_key, workspace="invoice-pipeline")
results = classifier.search("raw-invoices", query_vector=embedding, limit=20)
Workspace auto-detection from environment
Agents deployed by Aetherfy automatically have AETHERFY_WORKSPACE set. The client picks this up if no workspace is passed explicitly:
import os
# In a deployed agent, AETHERFY_WORKSPACE is injected automatically
client = AetherfyVectorsClient(api_key=os.environ["AETHERFY_API_KEY"])
# → workspace is auto-detected from AETHERFY_WORKSPACE env var
No workspace (backward-compatible)
# No workspace — collections are stored as-is, not scoped
client = AetherfyVectorsClient(api_key="afy_live_your_key")
client.create_collection("my-global-collection", VectorConfig(size=768, distance=DistanceMetric.COSINE))
Tip: Create workspaces explicitly in the Aetherfy control plane before use (
afy workspaces create invoice-pipeline). Agents deployed to a workspace automatically receive the workspace name viaAETHERFY_WORKSPACE.
🤖 Running as an Agent
Code deployed to Aetherfy as an agent gets a set of helpers in the same
distribution, under aetherfy_agent. Nothing to add to your requirements: the
standard runtime image preinstalls aetherfy-vectors, and a version you pin
yourself wins over it. A custom container installs it like any other package.
Each of these is a thin wrapper over a platform contract the docs already publish; the helper exists so the contract stops being copied into every task.
from aetherfy_agent import payload, machine, fan_out, spawn
# This run's input. A scheduled fire sends none, so {} is the normal case.
data = payload()
# The machine this run is on: whole numbers, not the strings the env carries.
shape = machine()
# An in-machine pool. Results come back in INPUT order and no failure is
# swallowed: the lowest-indexed exception is re-raised once every worker has
# finished. Width defaults to vcpus x 8 for threads, the default pool.
answers = fan_out(summarise, data.get("items", []))
# CPU-bound work wants processes. The default width follows the pool:
# threads get vcpus x 8 (they mostly wait), processes get vcpus.
digests = fan_out(hash_one, data.get("files", []), kind="processes")
# Run a different task agent, on its own machine, with its own lifecycle.
run = spawn("nightly-rollup", {"date": "2026-09-07"})
print(run.spawn_id, run.region, run.status)
fan_out prints one line to the run's logs before it starts, so how wide a run
went is visible after the fact:
aetherfy: fanning out 32 wide on 4 vCPU / 8192 MB (120 tasks)
Spawning has three outcomes worth telling apart. The payload cap and the concurrent-run cap get their own types; everything else carries the platform's stable error code, which is the thing to branch on:
from aetherfy_agent import spawn
from aetherfy_agent.exceptions import (
PayloadTooLarge,
SpawnError,
TooManyRunsInFlight,
)
try:
spawn("nightly-rollup", {"date": "2026-09-07"})
except PayloadTooLarge as exc:
# The payload is for parameters and references, not data. Write the data to
# a collection and pass its id.
print(exc.payload_bytes, "exceeds", exc.max_bytes)
except TooManyRunsInFlight as exc:
# The one refusal here worth retrying: runs are finishing all the time.
# The cap is the ACCOUNT's and is set by your plan; `limit` names which
# plan limit was hit, and `max_in_flight_runs` is None on an uncapped plan.
print("waiting on", exc.in_flight_count, "of", exc.max_in_flight_runs)
except SpawnError as exc:
print(exc.error_code)
Returning a result, and reading one back
Output on Aetherfy mirrors input. A task writes its answer with write_result;
whoever started the run reads it back off the run itself, so a parent hears
from a child in another region with no side channel between them.
from aetherfy_agent import write_result
# In the child task. The mirror of payload(): a file on the machine, nothing
# over the network. Returning nothing is the normal case, so most tasks never
# call this at all.
write_result({"rows": 128, "date": "2026-09-08"})
from aetherfy_agent import spawn, wait, result
# In the parent. wait() holds one request open instead of polling; a run that
# has not finished in time comes back exactly as it stands, which is not an
# error — read `state` and call again.
run = spawn("nightly-rollup", {"date": "2026-09-08"})
finished = wait(run.spawn_id, timeout_seconds=45)
if finished.state == "completed":
print(finished.result)
# result() is the same read without the waiting.
now = result(run.spawn_id)
print(now.state, now.has_result, now.result_error)
The result shares the payload's inline cap — one number bounding both directions — and it is for answers and references, not data. Anything larger belongs in a collection, with its id in the result:
from aetherfy_agent import write_result
from aetherfy_agent.exceptions import ResultTooLarge
try:
write_result(everything)
except ResultTooLarge as exc:
print(exc.result_bytes, "exceeds", exc.max_bytes)
write_result({"collection": "nightly-rollup", "rows": len(everything)})
Reading a run has the same shape as spawning it: the two refusals worth telling apart get their own types, and everything else carries the platform's stable error code.
from aetherfy_agent import result
from aetherfy_agent.exceptions import RunAccessDenied, RunNotFound, RunReadError
try:
result(run_id)
except RunNotFound:
print("no run has that id")
except RunAccessDenied:
print("that run belongs to another account")
except RunReadError as exc:
print(exc.error_code)
Full contract, including the environment variables behind every call: docs.aetherfy.com/agents/task-contract.
🧩 Payload Schemas
Collections can carry an optional payload schema that the SDK validates against before upsert — catching malformed payloads client-side without a round trip. Schemas are cached and automatically revalidated when they change server-side (via ETag).
from aetherfy_vectors import Schema, FieldDefinition
schema = Schema(
fields={
"title": FieldDefinition(type="string", required=True),
"price": FieldDefinition(type="float", required=True),
"tags": FieldDefinition(type="array", required=False, element_type="string"),
"in_stock": FieldDefinition(type="boolean", required=False),
},
description="Product catalog payloads",
)
# enforcement: "off" (no validation), "warn" (log warnings), "strict" (raise on violation)
etag = client.set_schema("products", schema, enforcement="strict")
# Inspect or remove the schema
current = client.get_schema("products") # returns None if no schema is defined
client.delete_schema("products")
Infer a schema from existing data
analysis = client.analyze_schema("products", sample_size=1000)
print(analysis.suggested_schema) # a Schema you can set_schema() directly
Schema violations raise SchemaValidationError (with a list of per-field errors). If the server reports a stale schema (412 Precondition Failed), the SDK auto-refreshes the cache and retries.
📊 Performance Comparison
| Feature | Local Qdrant | Aetherfy Vectors |
|---|---|---|
| Global Latency | Single region, wherever you host it | Served from the nearest region |
| Caching | None | Built in |
| DevOps Required | High | Zero |
| Auto-Failover | Manual | Automatic |
| Global Replication | Manual | Automatic |
| Analytics | Limited | Built-in |
🛠️ Environment Setup
API Key Configuration
Set your API key using environment variables (recommended):
# Either of these is read automatically
export AETHERFY_API_KEY="afy_live_your_api_key_here"
export AETHERFY_VECTORS_API_KEY="afy_live_your_api_key_here"
Or pass it directly:
client = AetherfyVectorsClient(api_key="afy_live_your_api_key_here")
Relevant environment variables:
| Variable | Purpose |
|---|---|
AETHERFY_API_KEY |
Primary API key |
AETHERFY_VECTORS_API_KEY |
Alternative API key (useful when the same process talks to multiple Aetherfy services) |
AETHERFY_WORKSPACE |
Used when the client is constructed with workspace="auto" (set automatically on deployed agents) |
AETHERFY_VECTORS_URL |
Pin the client to a specific endpoint URL. Set automatically by the control-plane on deployed agents. Wins over api_region= if both are set. |
AETHERFY_VECTORS_API_REGION |
Equivalent to api_region= constructor arg. Local-dev / debugging only. |
Local development across regions
Production agents have AETHERFY_VECTORS_URL injected by the control-plane —
that's the URL they reach the regional backend through, and it takes
precedence over api_region=. For local development (no env var injected),
you can pin a client to a specific regional endpoint:
client = AetherfyVectorsClient(
api_key="afy_test_...",
api_region="eu-central-1", # 'us-east-1' | 'eu-central-1' | 'ap-southeast-1'
)
The first call resolves api_region against GET /api/v1/regions on the
default global endpoint and caches the result on the client instance.
If both AETHERFY_VECTORS_URL and api_region= are set, the env var wins
and a warning is logged — that's the production-agent protection rule.
api_region selects which regional endpoint to connect to — it is a
transport/routing override, not where a collection's data lives. To control
collection placement, pass regions= to create_collection (see below).
Python Version Support
- Minimum: Python 3.9
- Recommended: Python 3.10+
- Tested: Python 3.9, 3.10, 3.11, 3.12
📚 Complete API Reference
Collection Management
# Create collection — returns the created Collection (with its resolved
# `regions` echoed back by the server). Pass `regions=[...]` to pin the
# collection to a subset of your scope; omit it to default to your full
# scope. (Distinct from the constructor's `api_region`, which only picks
# the endpoint to connect to.)
collection = client.create_collection(
collection_name, vectors_config, distance=None, description=None, regions=None
)
# List collections
collections = client.get_collections()
# Get collection info
info = client.get_collection(name)
# Check existence
exists = client.collection_exists(name)
# Delete collection
client.delete_collection(name)
Point Operations
# Insert/update points
client.upsert(collection_name, points)
# Retrieve points
points = client.retrieve(collection_name, ids, with_payload=True, with_vectors=False)
# Delete points
client.delete(collection_name, point_ids_or_filter)
# Count points — count_filter takes a Filter or a plain dict, same as
# search / scroll / delete
count = client.count(collection_name, count_filter=None, exact=True)
Search Operations
# Vector search
results = client.search(
collection_name,
query_vector,
limit=10,
offset=0,
query_filter=None,
with_payload=True,
with_vectors=False,
score_threshold=None,
search_params=None # engine params, sent verbatim as the body's `params`
)
# Trade latency for recall: a larger hnsw_ef makes the graph walk visit more
# candidates. Omit search_params to keep the tuned server default (hnsw_ef=100).
results = client.search(collection_name, query_vector, search_params={"hnsw_ef": 256})
search_params is passed through untranslated — the API and Qdrant own the
schema, so the SDK validates nothing and needs no release to track new params.
Note that the server cache key is derived from the request body, so the same
query at a different hnsw_ef is a separate cache entry (never a wrong hit).
Schema Management (Aetherfy-specific)
# Set, fetch, and remove a payload schema.
# enforcement defaults to "off" — pass "warn" or "strict" to have the
# server act on violations.
etag = client.set_schema(collection_name, schema, enforcement="off", description=None)
schema = client.get_schema(collection_name) # None if not set
client.delete_schema(collection_name)
# Infer a schema from existing data
analysis = client.analyze_schema(collection_name, sample_size=1000) # 100–10000
# Cache control (rarely needed)
client.refresh_schema(collection_name)
client.clear_schema_cache(collection_name=None) # None clears all
Usage statistics (Aetherfy-specific)
usage = client.get_usage_stats()
usage.storage_bytes_used # int — bytes stored across every collection
usage.storage_limit_bytes # int | None — None on an unlimited tier
usage.collections_count # int — active collections
usage.collections_limit # int | None — None on an unlimited tier
usage.tier # str — the plan's tier name
usage.active_regions # list[str] — union of your collections' regions
usage.usage_percentage # int — storage %, 0 when there is no limit
🚨 Error Handling
The SDK provides detailed error handling compatible with qdrant-client:
from aetherfy_vectors.exceptions import (
AetherfyVectorsException, # base class for all SDK errors
AuthenticationError,
CollectionNotFoundError,
PointNotFoundError,
RateLimitExceededError,
ServiceUnavailableError,
ValidationError,
RequestTimeoutError,
NetworkError,
SchemaValidationError,
SchemaNotFoundError,
CollectionInUseError,
QuotaExceededError,
)
try:
results = client.search("nonexistent", [0.1, 0.2, 0.3])
except CollectionNotFoundError as e:
print(f"Collection not found: {e}")
except AuthenticationError as e:
print(f"Invalid API key: {e}")
except RateLimitExceededError as e:
print(f"Rate limit exceeded. Retry after: {e.retry_after}s")
except SchemaValidationError as e:
for violation in e.errors:
print(violation)
except QuotaExceededError as e:
print(f"Quota '{e.quota_type}' exceeded: {e.current}/{e.limit}")
🔧 Configuration Options
client = AetherfyVectorsClient(
api_key="your_api_key", # Required (or set AETHERFY_API_KEY)
endpoint="https://vectors.aetherfy.com", # Optional: Custom endpoint
timeout=30.0, # Optional: Request timeout (seconds)
workspace=None, # Optional: None, a workspace name, or "auto"
# to read AETHERFY_WORKSPACE
)
📈 Monitoring & Observability
Built-in Dashboard Data
# Quota consumption, for a plan-usage dashboard
usage = client.get_usage_stats()
dashboard_data = {
"storage_bytes": usage.storage_bytes_used,
"storage_limit_bytes": usage.storage_limit_bytes, # None = unlimited
"storage_percent": usage.usage_percentage,
"collections": usage.collections_count,
"collections_limit": usage.collections_limit, # None = unlimited
"tier": usage.tier,
"regions": usage.active_regions,
}
The fields are the endpoint's own, verbatim — no derived percentages beyond the one it serves, and no unit conversion. Anything else you want (a collections percentage, megabytes) is a calculation at your call site, where the "unlimited" sentinels are yours to handle.
Aetherfy does not expose latency or cache-hit telemetry through this SDK. Measure request latency at your own call site — it is the only number that reflects what your application actually experienced.
Health Checks
def health_check():
try:
collections = client.get_collections()
return {"status": "healthy", "collections": len(collections)}
except Exception as e:
return {"status": "unhealthy", "error": str(e)}
🔗 Migration Guide
Step-by-Step Migration
-
Install aetherfy-vectors:
pip install aetherfy-vectors
-
Get your API key from the Aetherfy dashboard
-
Update imports:
# from qdrant_client import QdrantClient from aetherfy_vectors import AetherfyVectorsClient
-
Update initialization:
# client = QdrantClient(host="localhost", port=6333) client = AetherfyVectorsClient(api_key="your_api_key")
-
Test existing functionality (should work unchanged!)
-
Optional: Add analytics calls for insights
-
Deploy and enjoy global performance! 🚀
Migration Compatibility
| qdrant-client Method | aetherfy-vectors | Compatible |
|---|---|---|
create_collection() |
✅ | 100% |
get_collections() |
✅ | 100% |
upsert() |
✅ | 100% |
search() |
✅ | 100% |
retrieve() |
✅ | 100% |
delete() |
✅ | 100% |
count() |
✅ | 100% |
Plus additional analytics methods unique to Aetherfy!
🤝 Contributing
We welcome contributions! Open an issue or a pull request on GitHub.
Development Setup
# Clone the repository
git clone https://github.com/l-td/aetherfy-vectors-python-sdk.git
cd aetherfy-vectors-python-sdk
# Install development dependencies
pip install -r requirements-dev.txt
# Run tests
pytest
# Format code
black aetherfy_vectors/
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🆘 Support
- Documentation: https://docs.aetherfy.com/vectors
- API Reference: https://docs.aetherfy.com/vectors/api
- SDK Reference: https://docs.aetherfy.com/vectors/sdk
- Issues: GitHub Issues
- Email: developers@aetherfy.com
🌟 Why Choose Aetherfy Vectors?
For Solo Developers
- Zero setup time - start building immediately
- Predictable pricing - no surprise infrastructure costs
- Global reach - your users get fast responses worldwide
For Teams
- No DevOps overhead - focus on your product, not infrastructure
- Built-in monitoring - comprehensive analytics out of the box
- Reliable performance - automatic failover
For Enterprises
- Global scalability - handles millions of vectors effortlessly
- Security first - enterprise-grade authentication and audit logs
- Cost effective - pay only for what you use, no idle infrastructure
Ready to experience global vector search? Get your API key and migrate in minutes! 🚀
Metadata
Release files for aetherfy-vectors 1.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| aetherfy_vectors-1.1.0.tar.gz | 103.4 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| aetherfy_vectors-1.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 191.9 kB
Release files / aetherfy_vectors-1.1.0.tar.gz
| Download URL | aetherfy_vectors-1.1.0.tar.gz |
|---|---|
| Size | 103.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8cb4caf44911ce0ec7b2a4c6c3a352cce1d5b7fa5cbb5b7e68a3c4ea877f8a9c
|
|
BLAKE2b-256 checksum How to use checksums |
28b7b047c023eb7bc59a60629244c8810bd8ac11387e545d12bbafcb95cfb5e2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.21
|
Release files / aetherfy_vectors-1.1.0-py3-none-any.whl
| Download URL | aetherfy_vectors-1.1.0-py3-none-any.whl |
|---|---|
| Size | 88.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6a4904caf124b66fecac34226e4b809d6e20dab26308b83aadde0722102a18df
|
|
BLAKE2b-256 checksum How to use checksums |
39866a261e277f9d617ec2da0b2fb9d3f3bd3db19bcb3238225c058ca1a7112b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.21
|
Security for aetherfy-vectors 1.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.