Zep’s document vector store lets you embed and search documents using vector similarity search, Maximum Marginal Relevance Re-Ranking, and metadata filtering.

You can manage collections, ingest documents, and search using Zep’s SDKs, LangChain, or LlamaIndex.

zep-python supports async operations.

All methods come in sync and async flavors, with async methods prefixed with a.

For instance, zep-python offers both zep_client.memory.add_memory and zep_client.memory.aadd_memory.

Key Concepts

Collections

A Collection is a group of documents that use the same embedding strategy and model. Zep automatically creates embeddings for the documents you provide.

Documents

Documents are the texts you want to embed and search. You can add documents to collections and optionally assign them a unique ID and metadata. If you add metadata, it can help filter search results.

Initializing the Zep Client

For details on initializing the Zep client, check out the SDK documentation.

Creating a Collection

client = ZepClient(api_key="zep_api_key")

collection_name = "babbagedocs" # the name of your collection. alphanum values only

collection = client.document.add_collection(
    name=collection_name,  # required
    description="Babbage's Calculating Engine",  # optional
    metadata={"foo": "bar"},  # optional metadata to associate with this collection
)

Loading an Existing Collection

collection = client.document.get_collection(collection_name)

Adding Documents to a Collection

chunks = read_chunks_from_file(file, max_chunk_size)  # your custom function to read chunks from a file

documents = [
    Document(
        content=chunk,
        document_id=f"{collection_name}-{i}",  # optional document ID
        metadata={"bar": i},  # optional metadata
    )
    for i, chunk in enumerate(chunks)
]

uuids = collection.add_documents(documents)

document_id is an optional identifier you can assign to each document. It’s handy for linking a document chunk with a specific ID you choose.

The metadata is an optional dictionary that holds metadata related to your document. Zep leverages this metadata for hybrid searches across a collection, enabling you to filter search results more effectively.

When you use collection.add_documents, it returns a list of Zep UUIDs corresponding to the documents you’ve added to the collection.

Chunking your documents

Choosing the right chunking strategy is crucial and highly dependent on your specific needs. A variety of 3rd-party libraries, including Langchain, offer support for processing documents from numerous sources and dividing them into smaller segments suitable for embedding.

We recommend experimenting with various extractors, chunking strategies, sizes, and overlaps to discover the optimal approach for your project.

Monitoring Embedding Progress

The process of embedding documents in Zep is asynchronous. To keep track of your collection’s embedding progress, you can periodically check the collection’s status:

import time

while True:
    c = client.document.get_collection(collection_name)
    print(
        "Embedding status: "
        f"{c.document_embedded_count}/{c.document_count} documents embedded"
    )
    time.sleep(1)
    if c.status == "ready":
        break

Once the collection’s status changes to ready, it means all documents have been successfully embedded and are now searchable.

Zep enables hybrid vector search across your collections, allowing you to pinpoint the most relevant documents based on semantic similarity. Additionally, you have the option to refine your search by filtering through document metadata.

You can initiate a search using either a text query or an embedding vector, depending on your needs.

Zep’s Collection and Memory search support semantic search queries, JSONPath-based metadata filters, and a combination of both. Memory search also supports querying by message creation date.

Read more about constructing search queries.

# search for documents using only a query string
query = "the moon"
results = collection.search(text=query, limit=5)

# hybrid search for documents using a query string and metadata filter
metadata_query = {
    "where": {"jsonpath": '$[*] ? (@.baz == "qux")'},
}
results = collection.search(text=query, metadata=metadata_query, limit=5)

# Search by embedding vector, rather than text query
# embedding is a list of floats
results = collection.search(
    embedding=embedding, limit=5
)

metadata is an optional dictionary of JSONPath filters used to match on metadata associated with your documents.

limit is an optional integer indicating the maximum number of results to return.

Retrieving Documents by UUID

Zep supports retrieving a list of documents by Zep UUID:

docs_to_get = uuids[40:50]
documents = collection.get_documents(docs_to_get)

Other Common Operations

This section covers additional common operations you might need to perform, such as listing all collections within your client’s scope. The examples above demonstrate how to create an index on a collection and list all collections for both Python and TypeScript.

Updating a Collection’s Description or Metadata

client.document.update_collection(
    collection_name,
    description="Charles Babbage's Babbage's Calculating Engine 2",
    metadata={"newfoo": "newbar"},
)

Update a Document’s ID or Metadata

collection.update_document(document_uuid, document_id="new_id", metadata={"foo": "bar"})

Deleting Documents

Zep supports deleting documents from a collection by UUID:

collection.delete_document(document_uuid)

Deleting a Collection

Deleting a collection will delete all documents in the collection, as well as the collection itself.

client.document.delete_collection(collection_name)