Vector Stores
aimock implements the 14 OpenAI Vector Stores endpoints listed below in memory. An SDK can
upload a file, attach it to a store, poll it to completed, and call
POST /v1/vector_stores/{id}/search directly without touching
api.openai.com. Progression is deterministic and driven by retrieves.
This mock does not execute the hosted file_search tool in Responses or
Assistants. Vector-store contents do not automatically produce model responses. Responses
requests need separate matching fixtures for local mock responses. In default mock mode,
an unmatched Responses request returns 404 with
no_fixture_match. Strict mode returns 503 instead, even when an
upstream is configured. With strict mode disabled, record/proxy mode forwards unmatched
requests to the configured upstream.
Endpoints
| Method | Path | Purpose |
|---|---|---|
POST |
/v1/vector_stores |
Create a store, optionally with file_ids |
GET |
/v1/vector_stores |
List stores, newest first (limit, order,
after, before)
|
GET |
/v1/vector_stores/{id} |
Retrieve a store (refreshes its derived status) |
POST |
/v1/vector_stores/{id} |
Modify name, metadata, expires_after |
DELETE |
/v1/vector_stores/{id} |
Delete a store and its files and batches |
POST |
/v1/vector_stores/{id}/files |
Attach a file_id from the Files store |
GET |
/v1/vector_stores/{id}/files |
List attached files (filter, pagination) |
GET |
/v1/vector_stores/{id}/files/{file_id} |
Retrieve a file, advancing it one poll step |
DELETE |
/v1/vector_stores/{id}/files/{file_id} |
Detach a file |
POST |
/v1/vector_stores/{id}/file_batches |
Create a file batch from file_ids |
GET |
/v1/vector_stores/{id}/file_batches/{batch_id} |
Retrieve a batch, advancing it one poll step |
POST |
/v1/vector_stores/{id}/file_batches/{batch_id}/cancel |
Cancel a non-terminal batch |
GET |
/v1/vector_stores/{id}/file_batches/{batch_id}/files |
List a batch's files (pagination) |
POST |
/v1/vector_stores/{id}/search |
Deterministic search over completed files |
Deterministic Progression
A new file starts at in_progress. The first retrieve after attaching shows
in_progress; the second lands the outcome (completed by default)
and the file is stable from then on. File batches follow the same two-poll rule. A store's
own status is derived: in_progress while any file or batch is
still live, completed once everything settles, and expired once
expires_at passes.
attach -> in_progress
retrieve -> in_progress
retrieve -> completed | failed | cancelled (stable afterwards)
batch -> in_progress
retrieve -> in_progress
retrieve -> completed | failed | cancelled
cancel -> cancelling -> cancelled (400 once terminal)
Failure Injection
Send X-AIMock-Vector-Outcome: completed | failed | cancelled on
POST …/files or POST …/file_batches to choose where the second
poll lands (default completed; anything else is a 400). A
failed file carries a
last_error: { code: "mock_ingest_failed", … } payload so error rendering
paths are testable.
Files Must Exist First
Attaching a file_id the Files store has never seen is a
404 naming the file, so a suite that forgets the upload step fails loudly
instead of indexing a ghost. usage_bytes is the real byte length from the
Files store. Upload with purpose: "assistants" (or any accepted Files
purpose) before attaching.
const file = await openai.files.create({
file: fs.createReadStream("./docs.txt"),
purpose: "assistants",
});
const store = await openai.vectorStores.create({ name: "rag" });
await openai.vectorStores.files.create(store.id, { file_id: file.id });
await openai.vectorStores.files.retrieve(store.id, file.id); // in_progress
await openai.vectorStores.files.retrieve(store.id, file.id); // completed
const hits = await fetch(`${base}/v1/vector_stores/${store.id}/search`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: "pricing", max_num_results: 5 }),
}).then((r) => r.json());
Search
Search is deterministic, not semantic: only completed files are searched,
ranking is a stable hash of query + file_id so the same query always orders
the same way, and every hit carries its file id plus a canned text snippet quoting the
query. query accepts a non-empty string or a non-empty array of non-empty
strings. Array entries are joined with a newline in request order for ranking and
snippets; a single-entry array ranks identically to its string. The response
search_query echoes the original string or array.
max_num_results is an integer in 1–50
(default 10); ranking_options.score_threshold is a number in
0–1. An empty store answers 200 with
data: []. Direct search supports tests of top-k, thresholds, and empty
results. It does not measure semantic relevance.
File attachment and batch creation accept attributes: up to 16 pairs with
keys of at most 64 characters and string values of at most 512 characters, numbers, or
booleans. Batch attributes apply to each newly attached file. Omitted or null attributes
are returned as null; an empty object stays empty. Search results include the
file's attributes.
Search filters accept comparisons
{ type: "eq", key: "category", value: "guide" } using eq,
ne, gt, gte, lt, or lte,
and nested groups { type: "and", filters: [...] } or
{ type: "or", filters: [...] }. Filters run before ranking and result limits.
Comparisons require an existing key and matching value types; missing keys do not match,
including with ne. Ordering uses numeric, lexicographic string, or
false-before-true boolean order. Malformed attributes or filters return 400.
Validation
-
A body that is not a JSON object is a
400;namemust be a string when present on create. On modify,name: nullresets the name to an empty string; omittingnamepreserves it -
metadatafollows the shared platform rule: at most 16 string pairs, keys up to 64 characters, values up to 512 -
expires_afteris{ anchor: "last_active_at", days: 1–365 }; sendingnullon modify clears the expiry -
chunking_strategyis{ type: "auto" }or{ type: "static", static: { max_chunk_size_tokens: 100–4096, chunk_overlap_tokens: 0–2048 } }with overlap at most half the chunk size -
file_idsis an array of up to 2000 non-empty strings; attaching the same file twice is a400 -
List pagination:
limitis an integer in1–100,orderisascordesc,after/beforemust name a known cursor and are mutually exclusive; a repeated scalar parameter is a400 - Unknown store, file or batch ids are
404naming the id
State & Journal
Stores live in memory for the life of the process. POST /__aimock/reset,
LLMock.reset() and clearVectorStoreStore() all clear them. Every
request is journaled exactly once with service: "vector-stores" and runs
through the chaos gate — see Chaos Testing. Prometheus labels
are normalized (/v1/vector_stores/{id},
/v1/vector_stores/{id}/files/{fileId},
/v1/vector_stores/{id}/file_batches/{batchId}[/cancel|/files],
/v1/vector_stores/{id}/search), so caller-controlled ids cannot inflate
cardinality — see Prometheus Metrics.