Creating and Organizing Vector Stores
What a Vector Store Actually Is
A vector store is the hosted container that holds your documents in a form the file_search tool can search: chunked text, embeddings for each chunk, and any metadata you attach. Conceptually it plays the same role as an index in a search engine — you build it once (or update it incrementally), and then queries run against the index rather than against the raw documents.
It helps to be precise about what "vector" means here. Each chunk of text is converted into a list of floating-point numbers — an embedding — by an embedding model such as text-embedding-4. Two chunks whose embeddings are numerically close (by cosine similarity) are semantically close in meaning, even if they don't share many words. The vector store's job is to store these embeddings efficiently and answer "which stored vectors are closest to this query vector" quickly, even across millions of chunks.
You interact with a vector store through its ID (a string starting with vs_), and you attach one or more vector store IDs to a file_search tool call, as you saw in Lesson 1.
Creating a Vector Store
from openai import OpenAI
client = OpenAI()
vector_store = client.vector_stores.create(
name="product-docs-v1",
)
print(vector_store.id)
print(vector_store.status)
Note: The exact resource name (
client.vector_stores), method signatures, and default field values are specific to the SDK version in use. Confirm the current vector store API surface against official OpenAI documentation before relying on exact field names in production code.
What this does: it creates an empty vector store and returns an object describing it, including a unique id you'll use everywhere else — attaching files to it, referencing it in file_search tool calls, checking its processing status, and eventually deleting it. The name field is not used by retrieval logic at all; it exists purely so that you, as the developer, can recognize the store in logs, dashboards, or your own database. Giving it a meaningful, versioned name (like product-docs-v1 rather than store1) pays off the moment you have more than two or three stores, which happens quickly in any real project.
Immediately after creation, a vector store is empty and has no files. Adding files, discussed in depth in Lesson 3, is a separate step, and it is asynchronous — the files go through a processing pipeline (chunking and embedding) before they become searchable.
Why Organize Documents Into Separate Stores at All
You could, in principle, put every document your application will ever need into one giant vector store. In practice, this is almost always a mistake once you have more than one distinct topic, audience, or access boundary. There are three main reasons to split documents across multiple vector stores rather than using a single one:
- Relevance precision. A
file_searchcall retrieves the top-K most similar chunks within the stores it's given. If a "billing FAQ" vector store also contains your engineering runbooks, an ambiguous query might surface an irrelevant engineering chunk purely because it's the least-bad match among a bad candidate set. Narrowing the searchable universe to only relevant documents improves precision more reliably than any downstream filtering. - Access control and data boundaries. If different users, teams, or customers should only see certain documents, separate vector stores give you a clean, structural way to enforce that: a support agent's assistant is wired to the internal-only stores; a customer-facing assistant is wired only to public documentation stores. This is a stronger and simpler boundary than trying to filter a shared store by metadata for security purposes (metadata filtering, covered in Lesson 5, is best treated as a relevance tool, not a security control).
- Lifecycle management. Documents that update on different schedules (a rarely-changing legal policy versus a weekly-updated product changelog) are easier to refresh independently when they live in separate stores — you can rebuild or prune one without touching the other.
A Practical Organizing Pattern
A pattern that works well for most applications is to organize vector stores by domain and audience, not by document type or file format:
stores_to_create = [
"support-kb-public", # customer-facing help center content
"support-kb-internal", # internal-only troubleshooting docs
"legal-policies", # terms, privacy policy, compliance docs
"product-release-notes", # changelog-style, frequently updated
]
created = {}
for name in stores_to_create:
store = client.vector_stores.create(name=name)
created[name] = store.id
for name, store_id in created.items():
print(f"{name}: {store_id}")
This example loops over a list of logical store names and creates one vector store per name, keeping a dictionary that maps the human-readable name to the store's actual ID. In a real application, you would persist this mapping — in a database, a config file, or environment variables — rather than recreating the stores on every run, because vector_stores.create always creates a new store; it does not look up an existing one by name. A common early mistake is calling this kind of setup code on every application startup, silently accumulating dozens of duplicate, mostly-empty vector stores over time.
Naming Conventions and Versioning
Because vector store names are just labels, adopting a consistent convention early prevents confusion later. A useful pattern is:
{domain}-{purpose}-v{version}
For example: support-kb-public-v3. The version suffix matters more than it looks: when you substantially change how a document set is chunked, cleaned, or curated (see Lesson 7), you often want to build a new vector store rather than mutating the old one in place, so you can compare retrieval quality side by side before cutting your application over. Keeping the old version around during that comparison, then deleting it once you've confirmed the new one performs better, is far safer than editing a live store your production traffic depends on.
Checking Vector Store Status
store = client.vector_stores.retrieve("vs_68f2a1c9e4b8...")
print(store.status) # e.g. "completed", "in_progress"
print(store.file_counts.total)
print(store.file_counts.completed)
print(store.file_counts.failed)
Note: Field names such as
file_countsand the exact set of status values are subject to change between API versions — verify them against current documentation.
This code retrieves the current state of a vector store, which matters because file ingestion is asynchronous. A store can exist and have an ID immediately, but its files may still be processing in the background. file_counts breaks down how many files have finished processing successfully versus failed, which is essential for catching silent ingestion failures — a malformed PDF that fails to parse, for instance, will show up in failed rather than raising an exception at upload time. Checking this after every batch upload, rather than assuming success, is the difference between finding out about a bad document during development versus finding out when a user asks about content that was never actually indexed.
Common Mistakes
Creating one vector store per file instead of grouping related files into a shared store, which happens when developers treat vector stores like individual document uploads — this defeats the purpose of file_search, since a file_search call is scoped to entire stores, and searching across dozens of single-file stores requires attaching all of them and loses any benefit of a curated, topic-scoped corpus.
Never checking file_counts.failed, which causes documents to silently never be searchable, because ingestion happens asynchronously and a failed parse does not throw an exception in your calling code — always poll or inspect status after uploading a batch.
Reusing one vector store across unrelated features indefinitely, which happens because it's the path of least resistance early in a project, but degrades retrieval precision over time as unrelated content accumulates and starts competing in similarity search.
Best Practices
Name every vector store with a domain, purpose, and version, so that six months into a project you can tell what a store contains without opening it and inspecting files one by one.
Treat vector store creation as infrastructure, not a runtime operation. Create stores through a setup script or migration, not inline in request-handling code, and persist their IDs in configuration rather than recreating them on every deploy.
Keep the number of stores attached to a single file_search call small and purposeful. Attaching every store you have "just in case" reduces the relevance of any individual retrieval; attach only the stores that are actually relevant to that assistant's scope.