Rust SDK
Idiomatic async Rust client for the public VectorAmp API. Async/await on top of reqwest and tokio, with a small Transport trait so a different stack (gRPC, mocks) can be plugged in.
Install
# Cargo.toml
[dependencies]
vectoramp = "0.1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
Configure
Client::new reads the API key you pass in; the only required input is the key, commonly read from VECTORAMP_API_KEY. Auth uses X-API-Key.
use vectoramp::Client;
let client = Client::new(std::env::var("VECTORAMP_API_KEY").unwrap());
let client = Client::builder()
.api_key(std::env::var("VECTORAMP_API_KEY").unwrap())
.build()?;
Quickstart
A minimal init, a one-call create, then object → method calls. Only a name is required: the SDK defaults the embedding to VectorAmp-Embedding-4B (provider vectoramp), infers the dimension (2560), defaults the metric to cosine, and always uses the SABLE index.
use vectoramp::Client;
#[tokio::main]
async fn main() -> vectoramp::Result<()> {
let client = Client::new(std::env::var("VECTORAMP_API_KEY").unwrap());
// One-call create.
let dataset = client.datasets().create("product-docs").await?;
// Object -> method: operate on the returned dataset directly.
dataset
.add_texts(vec!["VectorAmp is a high-performance vector database."])
.await?;
let results = dataset.search("How does VectorAmp search documents?").await?;
let answer = dataset.ask("What index does VectorAmp use?").await?;
println!("{:?}", results.results);
println!("{}", answer.answer);
Ok(())
}
CreateDatasetRequest has no index_type field. The SDK always sends index_type: "sable", and the create body field is dim (never dimension).
Creating datasets
use vectoramp::CreateDatasetRequest;
// Minimal: name only (embedding + dim + metric defaulted/inferred).
let dataset = client.datasets().create("docs").await?;
// Hybrid (dense + sparse) index.
let hybrid = client
.datasets()
.create(CreateDatasetRequest::builder("docs").hybrid(true))
.await?;
// OpenAI embedding ("small" → 1536, "large" → 3072 inferred).
let openai = client
.datasets()
.create(CreateDatasetRequest::builder("docs").openai("small"))
.await?;
// Custom / unknown model requires an explicit dim.
let custom = client
.datasets()
.create(
CreateDatasetRequest::builder("docs")
.embedding(vectoramp::EmbeddingConfig {
provider: Some("acme".into()),
model: Some("acme-embed".into()),
})
.dim(1024),
)
.await?;
Object and service styles
create, get, and list return Dataset resource handles bound to the originating client. Both the object → method and service styles work everywhere. Pagination accepts (), (limit, offset), or a bare limit.
// Object -> method style (preferred)
let page = client.datasets().list(()).await?;
let dataset = client.datasets().get(&page.datasets[0].id).await?;
let resp = dataset.search("release notes").await?;
dataset.delete().await?;
// Service style
let resp = client.datasets().search("dataset-id", "release notes").await?;
client.datasets().delete("dataset-id").await?;
Insert, add text, and search
Vector ids accept a string or an integer. Integer ids are serialized as JSON numbers so the API preserves them exactly.
use vectoramp::{TextDocument, Vector};
dataset
.insert(vec![
Vector::new(1, vec![0.1, 0.2, 0.3]), // numeric id → JSON number
Vector::new("doc-2", vec![0.4, 0.5, 0.6]), // string id → JSON string
])
.await?;
// add_texts embeds through the dataset model, copies the text into metadata.text, and inserts.
dataset.add_texts(vec!["Hello world", "Machine learning notes"]).await?;
dataset
.add_texts(vec![
TextDocument { id: "doc-1".into(), text: "Hello world".into(), metadata: None },
TextDocument { id: 2.into(), text: "Numeric id".into(), metadata: None },
])
.await?;
String queries default to top_k: 10. rerank: true expands to the full model-rerank object. Hybrid search accepts sparse_query/alpha via SearchRequest.
use vectoramp::{SearchInput, SearchOptions};
let resp = dataset.search("machine learning best practices").await?;
let resp = dataset
.search_with(
"machine learning best practices",
SearchOptions {
top_k: Some(10),
include_documents: Some(true),
..Default::default()
}
.with_rerank(true), // expands to vectoramp / VectorAmp-Rerank-v1
)
.await?;
// Vector search:
let resp = dataset.search(SearchInput::Vector(vec![0.1, 0.2, 0.3])).await?;
SearchOptions::with_rerank(true) enables the optional model reranker after initial retrieval. The separate SearchRequest::rerank_depth_override only tunes SABLE's vector-index candidate depth and is not the model reranker.
Organization secrets and vector deletion
Store external provider credentials as organization secrets, then reference the secret from datasets. The default OpenAI BYOM reference is emb:openai:api_key.
// One-step convenience: saves/updates the OpenAI org secret, then creates
// the dataset with embedding.secret_ref = "emb:openai:api_key".
let dataset = client
.datasets()
.create_with_openai_api_key(CreateDatasetRequest::new("openai-docs"), std::env::var("OPENAI_API_KEY")?)
.await?;
// Or manage the secret explicitly.
client.org_secrets().put_openai_api_key(std::env::var("OPENAI_API_KEY")?).await?;
// Delete individual vector ids from search.
client.datasets().delete_vectors(&dataset.id, vec!["doc-001".into(), 42.into()]).await?;
Ingestion
Typed builders fill in source_type, sensible defaults, and a generated name. Supported types: web, s3, gcs, gdrive, jira, confluence, file_upload, plus GenericSource as an escape hatch.
use vectoramp::{ConfluenceSource, S3Source, WebSource};
let web = client
.sources()
.create_web(WebSource {
start_urls: vec!["https://docs.example.com".into()],
max_depth: Some(2),
..Default::default()
})
.await?;
let s3 = client
.sources()
.create_s3(S3Source {
bucket: "my-bucket".into(),
prefix: Some("docs/".into()),
region: Some("us-east-1".into()),
access_key_id: Some(std::env::var("AWS_ACCESS_KEY_ID").unwrap()),
secret_access_key: Some(std::env::var("AWS_SECRET_ACCESS_KEY").unwrap()),
..Default::default()
})
.await?;
let confluence = client
.sources()
.create_confluence(ConfluenceSource {
base_url: Some("https://acme.atlassian.net".into()),
username: Some("bot@acme.com".into()),
api_token: Some(std::env::var("CONFLUENCE_API_TOKEN").unwrap()),
spaces: vec!["ENG".into()],
..Default::default()
})
.await?;
create_source accepts any builder directly, and the per-type create_web/create_s3/create_gcs/create_google_drive/create_jira/create_confluence/create_file_upload/create_generic helpers are thin wrappers over it.
use vectoramp::StartIngestionRequest;
let dataset = client.datasets().get("dataset-id").await?;
// Create a source and start a job in one call.
let job = dataset
.ingest_new_source(WebSource {
start_urls: vec!["https://example.com/releases".into()],
..Default::default()
})
.await?;
// Start a job from an existing source.
let job = dataset.ingest_source("source-id").await?;
let jobs = client.ingestion().list_jobs(Some("dataset-id"), ()).await?;
let job = client.ingestion().get_job(&job.job_id).await?;
Manage and clean up sources
Validate a config, delete a source, or find and remove sources nothing is using.
// Validate without persisting.
client.sources().validate("web", serde_json::json!({ "start_urls": ["https://docs.example.com"] })).await?;
// See what's using a source (active schedules + in-flight jobs).
let refs = client.sources().get_references("source-id").await?; // refs.schedules, refs.schedule_count, refs.active_job_count, refs.in_use
// Delete. Returns Error::Api(409) if in use unless force is true (force also disables its schedules).
client.sources().delete("source-id", false).await?;
client.sources().delete("source-id", true).await?;
// Find unused sources, or remove all of them in one call.
let unused = client.sources().list_unused().await?;
let result = client.sources().cleanup_unused().await?; // result.deleted, result.count
Authenticated sources & connections
Authenticate a gdrive/gcs/confluence/jira source with a headless service account, or a reusable connection granted once in the browser.
use vectoramp::{CreateConnectionRequest, GoogleDriveSource};
// Option 1 — service account (headless, no browser).
let drive = client
.sources()
.create_google_drive(GoogleDriveSource {
folder_ids: vec!["google-drive-folder-id".into()],
service_account_json: Some(std::env::var("GOOGLE_SERVICE_ACCOUNT_JSON").unwrap()),
..Default::default()
})
.await?;
// Option 2 — connection (interactive once, reused server-side).
// connect() creates the grant, prints/opens authorization_url, and polls until connected.
let conn = client
.connections()
.connect(CreateConnectionRequest { provider: "google".into(), source_type: Some("gdrive".into()) })
.await?;
let drive = client
.sources()
.create_google_drive(GoogleDriveSource {
folder_ids: vec!["google-drive-folder-id".into()],
connection_id: Some(conn.id.clone()),
..Default::default()
})
.await?;
Drive the grant flow yourself when you need to:
let conn = client
.connections()
.create(CreateConnectionRequest { provider: "google".into(), source_type: Some("gdrive".into()) })
.await?;
println!("Open in a browser: {}", conn.authorization_url.unwrap()); // conn.status == "pending"
let conn = client.connections().get(&conn.id).await?; // poll until conn.status == "connected"
let list = client.connections().list(Some("google")).await?;
client.connections().delete(&conn.id).await?;
Minimal file upload ingestion
ingest_files hides the presigned-upload flow: it creates a file_upload source, initializes presigned uploads, PUTs the bytes, and completes the job.
use std::path::PathBuf;
let job = dataset
.ingest_files(vec![PathBuf::from("./docs/guide.pdf")], None)
.await?;
Dataset documents
List retained source documents and download originals when download_available is true. Pagination is cursor-based; keep passing next_cursor until it is None.
use vectoramp::DocumentListOptions;
let page = dataset
.list_documents(DocumentListOptions {
limit: Some(50),
status: Some("ready".into()),
..Default::default()
})
.await?;
for doc in &page.documents {
if !doc.download_available { continue; }
let bytes = dataset.download_document(&doc.id).await?;
if let Some(name) = &doc.file_name {
std::fs::write(name, bytes)?;
}
}
The SDK follows download redirects and returns raw bytes. Storage buckets, object keys, and payload references are never exposed.
Ask / RAG
ask defaults top_k = 5, include_sources = true, and dataset scope "all" when unscoped.
use vectoramp::AskOptions;
// Unscoped (defaults to all datasets).
let answer = client.ask("What are the key product features?").await?;
// Scoped to a dataset.
let answer = dataset.ask("What are the key product features?").await?;
// Explicit options.
let answer = client
.ask_with(
"What changed in the latest release?",
AskOptions::default().with_all_datasets().with_top_k(8),
)
.await?;
// Streaming SSE.
let mut stream = dataset.ask_stream("Summarize the launch plan").await?;
while let Some(event) = stream.next_event().await? {
if event.chunk_type == "text" {
print!("{}", event.content);
}
}
Persistent Intelligence sessions
Durable RAG conversations persisted server-side instead of resending history.
let session = client.intelligence().create_session("Launch planning").await?;
client
.intelligence()
.append_message(&session.id, "user", "What is our launch date?")
.await?;
let messages = client.intelligence().list_messages(&session.id, ()).await?;
let sessions = client.intelligence().list_sessions(()).await?;
let one = client.intelligence().get_session(&session.id).await?;
answer.sources contains source-level citations. Inline [1] markers refer to sources[0]; preview_ref is an opaque preview token, never a raw storage key.
Errors
Non-2xx responses surface as Error::Api(ApiError).
match client.datasets().get("missing").await {
Ok(dataset) => { let _ = dataset; }
Err(vectoramp::Error::Api(err)) => {
eprintln!("api error {}: {}", err.status, err.message);
}
Err(err) => eprintln!("transport error: {err}"),
}
Method reference
R = required, O = optional. Pagination arguments accept (), (limit, offset), or a bare limit.
client.datasets() — DatasetService (and the bound Dataset object)
| Method | Args | Returns |
|---|---|---|
list(pagination) | pagination (O) | DatasetList |
get(id) | id (R) | Dataset |
create(req) | name/builder/request (R); dim?, metric?, embedding?, hybrid? | Dataset |
delete(id) / Dataset::delete() | id (R) | () |
search(id, input) / Dataset::search(input) | id (R), text|vector|SearchRequest (R) | SearchResponse |
search_with(id, input, opts) / Dataset::search_with(input, opts) | + SearchOptions (O) | SearchResponse |
insert(id, vectors) / Dataset::insert(vectors) (+ insert_vectors) | id (R), Vec<Vector> (R, string or numeric id) | InsertVectorsResponse |
embed(id, req) / Dataset::embed(req) | id (R), EmbedRequest (R) | EmbedResponse |
add_texts(id, input) / Dataset::add_texts(input) | id (R), texts/docs (R) | AddTextsResponse |
add_texts_with(id, input, opts) / Dataset::add_texts_with(input, opts) | + AddTextsOptions (O) | AddTextsResponse |
list_documents(id, opts) / Dataset::list_documents(opts) | id (R), DocumentListOptions (O) | DatasetDocumentList |
download_document(id, docId) / Dataset::download_document(docId) | id (R), docId (R) | Vec<u8> |
ingest_source(id, srcId) / Dataset::ingest_source(srcId) | id (R), srcId (R) | Job |
ingest_new_source(id, builder) / Dataset::ingest_new_source(builder) | id (R), source builder (R) | Job |
ingest_files(id, paths, opts) / Dataset::ingest_files(paths, opts) | id (R), paths (R), IngestFilesOptions (O) | Job |
ask(id, query) / Dataset::ask(query) | id (R), query (R) | AskResponse |
ask_with(id, query, opts) / Dataset::ask_with(query, opts) | + AskOptions (O) | AskResponse |
ask_stream(id, query) / Dataset::ask_stream(query) | id (R), query (R) | AskStream |
Embedding helper: CreateDatasetRequest::builder(name).openai("small"|"large").
client.ask* (top level)
ask(query), ask_with(query, opts), ask_stream(query).
client.ingestion() / client.sources() — IngestionService
| Method | Args | Returns |
|---|---|---|
list_sources(pagination) | pagination (O) | SourceList |
get_source(id) | id (R) | Source |
create_source(builder) | source builder (R) | Source |
create_web/_s3/_gcs/_google_drive/_jira/_confluence/_file_upload/_generic(source) | typed builder (R) | Source |
validate(source_type, config) | source_type (R), config (R) | ValidateResult |
get_references(id) | id (R) | SourceReferences (schedules, schedule_count, active_job_count, in_use) |
delete(id, force) | id (R), force (R) | () |
list_unused() | — | SourceList |
cleanup_unused() | — | CleanupResult (deleted, count) |
start_job(req) | StartIngestionRequest (R) | Job |
list_jobs(dataset_id, pagination) | dataset_id (O), pagination (O) | JobList |
get_job(id) / retry_job(id) | id (R) | Job |
ingest_files(dataset_id, paths, opts) | dataset_id (R), paths (R), opts (O) | Job |
Source builders: WebSource, S3Source, GcsSource, GoogleDriveSource (service_account_json or connection_id for auth), JiraSource, ConfluenceSource, FileUploadSource, GenericSource.
client.connections() — ConnectionService
| Method | Args | Returns |
|---|---|---|
list(provider) | provider (O) | ConnectionList |
create(req) | CreateConnectionRequest (R) | Connection (status: "pending", authorization_url) |
get(id) | id (R) | Connection (poll until status == "connected") |
delete(id) | id (R) | () |
connect(req) | CreateConnectionRequest (R) | Connection — create + open authorization_url + poll until connected |
client.intelligence() — IntelligenceService
| Method | Args | Returns |
|---|---|---|
ask(query) / ask_with(query, opts) | query (R), AskOptions (O) | AskResponse |
stream(query, opts) | query (R), AskOptions (O) | AskStream |
create_session(req) | title/CreateSessionRequest (O) | IntelligenceSession |
list_sessions(pagination) | pagination (O) | SessionList |
get_session(id) / delete_session(id) | id (R) | IntelligenceSession / () |
append_message(id, role, content) | id (R), role (R), content (R) | SessionMessage |
append_message_with(id, req) | id (R), AppendMessageRequest (R) | SessionMessage |
list_messages(id, pagination) | id (R), pagination (O) | MessageList |
client.schedules() — ScheduleService
list(pagination), get(id), create(req), update(id, req), delete(id), trigger(id).