[Feat] Proxy - New LLM API Routes /v1/vector_stores and /v1/vector_stores/vs_abc123/search (#12699)

* add Search + Create vector stores

* add new vector_stores routes

* add avector_store_search routes

* docs vector stores

* docs vector stores

* Create Vector Store

* fix - refactor

* add vector_store_router

* fix imports

* fix ruff
This commit is contained in:
Ishaan Jaff
2025-07-17 11:19:25 -07:00
committed by GitHub
parent 52a525ab9c
commit 95f7290e44
9 changed files with 860 additions and 3 deletions
@@ -0,0 +1,314 @@
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
# /vector_stores - Create Vector Store
Create a vector store which can be used to store and search document chunks for retrieval-augmented generation (RAG) use cases.
## Overview
| Feature | Supported | Notes |
|---------|-----------|-------|
| Cost Tracking | ✅ | Tracked per vector store operation |
| Logging | ✅ | Works across all integrations |
| End-user Tracking | ✅ | |
| Support LLM Providers | **OpenAI, Azure OpenAI, Bedrock, Vertex RAG Engine** | Full vector stores API support across providers |
## Usage
### LiteLLM Python SDK
<Tabs>
<TabItem value="basic" label="Basic Usage">
#### Non-streaming example
```python showLineNumbers title="Create Vector Store - Basic"
import litellm
response = await litellm.vector_stores.acreate(
name="My Document Store",
file_ids=["file-abc123", "file-def456"]
)
print(response)
```
#### Synchronous example
```python showLineNumbers title="Create Vector Store - Sync"
import litellm
response = litellm.vector_stores.create(
name="My Document Store",
file_ids=["file-abc123", "file-def456"]
)
print(response)
```
</TabItem>
<TabItem value="advanced" label="Advanced Configuration">
#### With expiration and chunking strategy
```python showLineNumbers title="Create Vector Store - Advanced"
import litellm
response = await litellm.vector_stores.acreate(
name="My Document Store",
file_ids=["file-abc123", "file-def456"],
expires_after={
"anchor": "last_active_at",
"days": 7
},
chunking_strategy={
"type": "static",
"static": {
"max_chunk_size_tokens": 800,
"chunk_overlap_tokens": 400
}
},
metadata={
"project": "rag-system",
"environment": "production"
}
)
print(response)
```
</TabItem>
<TabItem value="openai-provider" label="OpenAI Provider">
#### Using OpenAI provider explicitly
```python showLineNumbers title="Create Vector Store - OpenAI Provider"
import litellm
import os
# Set API key
os.environ["OPENAI_API_KEY"] = "your-openai-api-key"
response = await litellm.vector_stores.acreate(
name="My Document Store",
file_ids=["file-abc123", "file-def456"],
custom_llm_provider="openai"
)
print(response)
```
</TabItem>
</Tabs>
### LiteLLM Proxy Server
<Tabs>
<TabItem value="proxy-setup" label="Setup & Usage">
1. Setup config.yaml
```yaml
model_list:
- model_name: gpt-4o
litellm_params:
model: openai/gpt-4o
api_key: os.environ/OPENAI_API_KEY
general_settings:
# Vector store settings can be added here if needed
```
2. Start proxy
```bash
litellm --config /path/to/config.yaml
```
3. Test it with OpenAI SDK!
```python showLineNumbers title="OpenAI SDK via LiteLLM Proxy"
from openai import OpenAI
# Point OpenAI SDK to LiteLLM proxy
client = OpenAI(
base_url="http://0.0.0.0:4000",
api_key="sk-1234", # Your LiteLLM API key
)
vector_store = client.beta.vector_stores.create(
name="My Document Store",
file_ids=["file-abc123", "file-def456"]
)
print(vector_store)
```
</TabItem>
<TabItem value="curl-proxy" label="curl">
```bash showLineNumbers title="Create Vector Store via curl"
curl -L -X POST 'http://0.0.0.0:4000/v1/vector_stores' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer sk-1234' \
-d '{
"name": "My Document Store",
"file_ids": ["file-abc123", "file-def456"],
"expires_after": {
"anchor": "last_active_at",
"days": 7
},
"chunking_strategy": {
"type": "static",
"static": {
"max_chunk_size_tokens": 800,
"chunk_overlap_tokens": 400
}
},
"metadata": {
"project": "rag-system",
"environment": "production"
}
}'
```
</TabItem>
</Tabs>
### OpenAI SDK (Standalone)
<Tabs>
<TabItem value="openai-direct" label="Direct OpenAI Usage">
```python showLineNumbers title="OpenAI SDK Direct"
from openai import OpenAI
client = OpenAI(api_key="your-openai-api-key")
vector_store = client.beta.vector_stores.create(
name="My Document Store",
file_ids=["file-abc123", "file-def456"]
)
print(vector_store)
```
</TabItem>
</Tabs>
## Request Format
The request body follows OpenAI's vector stores API format.
#### Example request body
```json
{
"name": "My Document Store",
"file_ids": ["file-abc123", "file-def456"],
"expires_after": {
"anchor": "last_active_at",
"days": 7
},
"chunking_strategy": {
"type": "static",
"static": {
"max_chunk_size_tokens": 800,
"chunk_overlap_tokens": 400
}
},
"metadata": {
"project": "rag-system",
"environment": "production"
}
}
```
#### Optional Fields
- **name** (string): The name of the vector store.
- **file_ids** (array of strings): A list of File IDs that the vector store should use. Useful for tools like `file_search` that can access files.
- **expires_after** (object): The expiration policy for the vector store.
- **anchor** (string): Anchor timestamp after which the expiration policy applies. Supported anchors: `last_active_at`.
- **days** (integer): The number of days after the anchor time that the vector store will expire.
- **chunking_strategy** (object): The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy.
- **type** (string): Always `static`.
- **static** (object): The static chunking strategy.
- **max_chunk_size_tokens** (integer): The maximum number of tokens in each chunk. The default value is `800`. The minimum value is `100` and the maximum value is `4096`.
- **chunk_overlap_tokens** (integer): The number of tokens that overlap between chunks. The default value is `400`.
- **metadata** (object): Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a structured format. Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
## Response Format
#### Example Response
```json
{
"id": "vs_abc123",
"object": "vector_store",
"created_at": 1699061776,
"name": "My Document Store",
"bytes": 139920,
"file_counts": {
"in_progress": 0,
"completed": 2,
"failed": 0,
"cancelled": 0,
"total": 2
},
"status": "completed",
"expires_after": {
"anchor": "last_active_at",
"days": 7
},
"expires_at": null,
"last_active_at": 1699061776,
"metadata": {
"project": "rag-system",
"environment": "production"
}
}
```
#### Response Fields
- **id** (string): The identifier, which can be referenced in API endpoints.
- **object** (string): The object type, which is always `vector_store`.
- **created_at** (integer): The Unix timestamp (in seconds) for when the vector store was created.
- **name** (string): The name of the vector store.
- **bytes** (integer): The total number of bytes used by the files in the vector store.
- **file_counts** (object): The file counts for the vector store.
- **in_progress** (integer): The number of files that are currently being processed.
- **completed** (integer): The number of files that have been successfully processed.
- **failed** (integer): The number of files that failed to process.
- **cancelled** (integer): The number of files that were cancelled.
- **total** (integer): The total number of files.
- **status** (string): The status of the vector store, which can be either `expired`, `in_progress`, or `completed`. A status of `completed` indicates that the vector store is ready for use.
- **expires_after** (object or null): The expiration policy for the vector store.
- **expires_at** (integer or null): The Unix timestamp (in seconds) for when the vector store will expire.
- **last_active_at** (integer or null): The Unix timestamp (in seconds) for when the vector store was last active.
- **metadata** (object or null): Set of 16 key-value pairs that can be attached to an object.
## Mock Response Testing
For testing purposes, you can use mock responses:
```python showLineNumbers title="Mock Response Example"
import litellm
# Mock response for testing
mock_response = {
"id": "vs_mock123",
"object": "vector_store",
"created_at": 1699061776,
"name": "Mock Vector Store",
"bytes": 0,
"file_counts": {
"in_progress": 0,
"completed": 0,
"failed": 0,
"cancelled": 0,
"total": 0
},
"status": "completed"
}
response = await litellm.vector_stores.acreate(
name="Test Store",
mock_response=mock_response
)
print(response)
```
@@ -0,0 +1,368 @@
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
# /vector_stores/{vector_store_id}/search - Search Vector Store
Search a vector store for relevant chunks based on a query and file attributes filter. This is useful for retrieval-augmented generation (RAG) use cases.
## Overview
| Feature | Supported | Notes |
|---------|-----------|-------|
| Cost Tracking | ✅ | Tracked per search operation |
| Logging | ✅ | Works across all integrations |
| End-user Tracking | ✅ | |
| Support LLM Providers | **OpenAI, Azure OpenAI, Bedrock, Vertex RAG Engine** | Full vector stores API support across providers |
## Usage
### LiteLLM Python SDK
<Tabs>
<TabItem value="basic" label="Basic Usage">
#### Non-streaming example
```python showLineNumbers title="Search Vector Store - Basic"
import litellm
response = await litellm.vector_stores.asearch(
vector_store_id="vs_abc123",
query="What is the capital of France?"
)
print(response)
```
#### Synchronous example
```python showLineNumbers title="Search Vector Store - Sync"
import litellm
response = litellm.vector_stores.search(
vector_store_id="vs_abc123",
query="What is the capital of France?"
)
print(response)
```
</TabItem>
<TabItem value="advanced" label="Advanced Configuration">
#### With filters and ranking options
```python showLineNumbers title="Search Vector Store - Advanced"
import litellm
response = await litellm.vector_stores.asearch(
vector_store_id="vs_abc123",
query="What is the capital of France?",
filters={
"file_ids": ["file-abc123", "file-def456"]
},
max_num_results=5,
ranking_options={
"score_threshold": 0.7
},
rewrite_query=True
)
print(response)
```
</TabItem>
<TabItem value="multiple-queries" label="Multiple Queries">
#### Searching with multiple queries
```python showLineNumbers title="Search Vector Store - Multiple Queries"
import litellm
response = await litellm.vector_stores.asearch(
vector_store_id="vs_abc123",
query=[
"What is the capital of France?",
"What is the population of Paris?"
],
max_num_results=10
)
print(response)
```
</TabItem>
<TabItem value="openai-provider" label="OpenAI Provider">
#### Using OpenAI provider explicitly
```python showLineNumbers title="Search Vector Store - OpenAI Provider"
import litellm
import os
# Set API key
os.environ["OPENAI_API_KEY"] = "your-openai-api-key"
response = await litellm.vector_stores.asearch(
vector_store_id="vs_abc123",
query="What is the capital of France?",
custom_llm_provider="openai"
)
print(response)
```
</TabItem>
</Tabs>
### LiteLLM Proxy Server
<Tabs>
<TabItem value="proxy-setup" label="Setup & Usage">
1. Setup config.yaml
```yaml
model_list:
- model_name: gpt-4o
litellm_params:
model: openai/gpt-4o
api_key: os.environ/OPENAI_API_KEY
general_settings:
# Vector store settings can be added here if needed
```
2. Start proxy
```bash
litellm --config /path/to/config.yaml
```
3. Test it with OpenAI SDK!
```python showLineNumbers title="OpenAI SDK via LiteLLM Proxy"
from openai import OpenAI
# Point OpenAI SDK to LiteLLM proxy
client = OpenAI(
base_url="http://0.0.0.0:4000",
api_key="sk-1234", # Your LiteLLM API key
)
search_results = client.beta.vector_stores.search(
vector_store_id="vs_abc123",
query="What is the capital of France?",
max_num_results=5
)
print(search_results)
```
</TabItem>
<TabItem value="curl-proxy" label="curl">
```bash showLineNumbers title="Search Vector Store via curl"
curl -L -X POST 'http://0.0.0.0:4000/v1/vector_stores/vs_abc123/search' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer sk-1234' \
-d '{
"query": "What is the capital of France?",
"filters": {
"file_ids": ["file-abc123", "file-def456"]
},
"max_num_results": 5,
"ranking_options": {
"score_threshold": 0.7
},
"rewrite_query": true
}'
```
</TabItem>
</Tabs>
### OpenAI SDK (Standalone)
<Tabs>
<TabItem value="openai-direct" label="Direct OpenAI Usage">
```python showLineNumbers title="OpenAI SDK Direct"
from openai import OpenAI
client = OpenAI(api_key="your-openai-api-key")
search_results = client.beta.vector_stores.search(
vector_store_id="vs_abc123",
query="What is the capital of France?",
max_num_results=5
)
print(search_results)
```
</TabItem>
</Tabs>
## Request Format
The request body follows OpenAI's vector stores search API format.
#### Example request body
```json
{
"query": "What is the capital of France?",
"filters": {
"file_ids": ["file-abc123", "file-def456"]
},
"max_num_results": 5,
"ranking_options": {
"score_threshold": 0.7
},
"rewrite_query": true
}
```
#### Required Fields
- **query** (string or array of strings): A query string or array for the search. The query is used to find relevant chunks in the vector store.
#### Optional Fields
- **filters** (object): Optional filter to apply based on file attributes.
- **file_ids** (array of strings): Filter chunks based on specific file IDs.
- **max_num_results** (integer): Maximum number of results to return. Must be between 1 and 50. Default is 10.
- **ranking_options** (object): Optional ranking options for search.
- **score_threshold** (number): Minimum similarity score threshold for results.
- **rewrite_query** (boolean): Whether to rewrite the natural language query for vector search optimization. Default is true.
## Response Format
#### Example Response
```json
{
"object": "vector_store.search_results.page",
"search_query": "What is the capital of France?",
"data": [
{
"score": 0.95,
"content": [
{
"type": "text",
"text": "Paris is the capital and most populous city of France. With an official estimated population of 2,102,650 residents as of 1 January 2023 in an area of more than 105 km², Paris is the fourth-most populated city in the European Union and the 30th most densely populated city in the world in 2022."
}
]
},
{
"score": 0.87,
"content": [
{
"type": "text",
"text": "France, officially the French Republic, is a country located primarily in Western Europe. Its capital is Paris, one of the most important cultural and economic centers in Europe."
}
]
}
]
}
```
#### Response Fields
- **object** (string): The object type, which is always `vector_store.search_results.page`.
- **search_query** (string): The query that was used for the search.
- **data** (array): An array of search result objects.
- **score** (number): The similarity score of the search result, typically between 0 and 1, where 1 is the most similar.
- **content** (array): Array of content objects containing the retrieved text.
- **type** (string): The type of content, typically `text`.
- **text** (string): The actual text content that was retrieved from the vector store.
## Mock Response Testing
For testing purposes, you can use mock responses:
```python showLineNumbers title="Mock Response Example"
import litellm
# Mock response for testing
mock_results = [
{
"score": 0.95,
"content": [
{
"text": "Paris is the capital of France.",
"type": "text"
}
]
},
{
"score": 0.87,
"content": [
{
"text": "France is a country in Western Europe.",
"type": "text"
}
]
}
]
response = await litellm.vector_stores.asearch(
vector_store_id="vs_abc123",
query="What is the capital of France?",
mock_response=mock_results
)
print(response)
```
## Error Handling
Common errors you might encounter:
```python showLineNumbers title="Error Handling Example"
import litellm
try:
response = await litellm.vector_stores.asearch(
vector_store_id="vs_invalid",
query="What is the capital of France?"
)
except litellm.NotFoundError as e:
print(f"Vector store not found: {e}")
except litellm.RateLimitError as e:
print(f"Rate limit exceeded: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
```
## Best Practices
1. **Query Optimization**: Use clear, specific queries for better search results.
2. **Result Filtering**: Use file_ids filter to limit search scope when needed.
3. **Score Thresholds**: Set appropriate score thresholds to filter out irrelevant results.
4. **Batch Queries**: Use array queries when searching for multiple related topics.
5. **Error Handling**: Always implement proper error handling for production use.
```python showLineNumbers title="Best Practices Example"
import litellm
async def search_documents(vector_store_id: str, user_query: str):
"""
Search documents with best practices applied
"""
try:
response = await litellm.vector_stores.asearch(
vector_store_id=vector_store_id,
query=user_query,
max_num_results=5,
ranking_options={
"score_threshold": 0.7 # Filter out low-relevance results
},
rewrite_query=True # Optimize query for vector search
)
# Filter results by score for additional quality control
high_quality_results = [
result for result in response.data
if result.score >= 0.8
]
return high_quality_results
except Exception as e:
print(f"Search failed: {e}")
return []
# Usage
results = await search_documents("vs_abc123", "What is the capital of France?")
```
+8
View File
@@ -278,6 +278,14 @@ const sidebars = {
"text_to_speech",
]
},
{
type: "category",
label: "/vector_stores",
items: [
"vector_stores/create",
"vector_stores/search",
]
},
{
type: "category",
label: "Pass-through Endpoints (Anthropic SDK, etc.)",
@@ -11,7 +11,7 @@ All /vector_store management endpoints
import copy
from typing import List
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Request, Response
import litellm
from litellm._logging import verbose_proxy_logger
@@ -27,7 +27,9 @@ from litellm.vector_stores.vector_store_registry import VectorStoreRegistry
router = APIRouter()
########################################################
# Management Endpoints
########################################################
@router.post(
"/vector_store/new",
tags=["vector store management"],
@@ -273,6 +273,8 @@ class ProxyBaseLLMRequestProcessing:
"agenerate_content",
"agenerate_content_stream",
"allm_passthrough_route",
"avector_store_search",
"avector_store_create",
],
version: Optional[str] = None,
user_model: Optional[str] = None,
@@ -357,6 +359,8 @@ class ProxyBaseLLMRequestProcessing:
"agenerate_content",
"agenerate_content_stream",
"allm_passthrough_route",
"avector_store_search",
"avector_store_create",
],
proxy_logging_obj: ProxyLogging,
general_settings: dict,
@@ -0,0 +1,129 @@
from fastapi import APIRouter, Depends, Request, Response
from litellm.proxy._types import UserAPIKeyAuth
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
from litellm.proxy.common_request_processing import ProxyBaseLLMRequestProcessing
router = APIRouter()
########################################################
# OpenAI Compatible Endpoints
########################################################
@router.post("/v1/vector_stores/{vector_store_id}/search", dependencies=[Depends(user_api_key_auth)])
@router.post("/vector_stores/{vector_store_id}/search", dependencies=[Depends(user_api_key_auth)])
async def vector_store_search(
request: Request,
vector_store_id: str,
fastapi_response: Response,
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
):
"""
Search a vector store.
API Reference:
https://platform.openai.com/docs/api-reference/vector-stores/search
"""
from litellm.proxy.proxy_server import (
_read_request_body,
general_settings,
llm_router,
proxy_config,
proxy_logging_obj,
select_data_generator,
user_api_base,
user_max_tokens,
user_model,
user_request_timeout,
user_temperature,
version,
)
data = await _read_request_body(request=request)
if "vector_store_id" not in data:
data["vector_store_id"] = vector_store_id
processor = ProxyBaseLLMRequestProcessing(data=data)
try:
return await processor.base_process_llm_request(
request=request,
fastapi_response=fastapi_response,
user_api_key_dict=user_api_key_dict,
route_type="avector_store_search",
proxy_logging_obj=proxy_logging_obj,
llm_router=llm_router,
general_settings=general_settings,
proxy_config=proxy_config,
select_data_generator=select_data_generator,
model=None,
user_model=user_model,
user_temperature=user_temperature,
user_request_timeout=user_request_timeout,
user_max_tokens=user_max_tokens,
user_api_base=user_api_base,
version=version,
)
except Exception as e:
raise await processor._handle_llm_api_exception(
e=e,
user_api_key_dict=user_api_key_dict,
proxy_logging_obj=proxy_logging_obj,
version=version,
)
@router.post("/v1/vector_stores", dependencies=[Depends(user_api_key_auth)])
@router.post("/vector_stores", dependencies=[Depends(user_api_key_auth)])
async def vector_store_create(
request: Request,
fastapi_response: Response,
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
):
"""
Create a vector store.
API Reference:
https://platform.openai.com/docs/api-reference/vector-stores/create
"""
from litellm.proxy.proxy_server import (
_read_request_body,
general_settings,
llm_router,
proxy_config,
proxy_logging_obj,
select_data_generator,
user_api_base,
user_max_tokens,
user_model,
user_request_timeout,
user_temperature,
version,
)
data = await _read_request_body(request=request)
processor = ProxyBaseLLMRequestProcessing(data=data)
try:
return await processor.base_process_llm_request(
request=request,
fastapi_response=fastapi_response,
user_api_key_dict=user_api_key_dict,
route_type="avector_store_create",
proxy_logging_obj=proxy_logging_obj,
llm_router=llm_router,
general_settings=general_settings,
proxy_config=proxy_config,
select_data_generator=select_data_generator,
model=None,
user_model=user_model,
user_temperature=user_temperature,
user_request_timeout=user_request_timeout,
user_max_tokens=user_max_tokens,
user_api_base=user_api_base,
version=version,
)
except Exception as e:
raise await processor._handle_llm_api_exception(
e=e,
user_api_key_dict=user_api_key_dict,
proxy_logging_obj=proxy_logging_obj,
version=version,
)
+4
View File
@@ -207,6 +207,9 @@ from litellm.proxy.common_utils.openai_endpoint_utils import (
from litellm.proxy.common_utils.proxy_state import ProxyState
from litellm.proxy.common_utils.reset_budget_job import ResetBudgetJob
from litellm.proxy.common_utils.swagger_utils import ERROR_RESPONSES
from litellm.proxy.common_utils.vector_store_endpoints.endpoints import (
router as vector_store_router,
)
from litellm.proxy.credential_endpoints.endpoints import router as credential_router
from litellm.proxy.db.db_transaction_queue.spend_log_cleanup import SpendLogCleanup
from litellm.proxy.db.exception_handler import PrismaDBExceptionHandler
@@ -8708,6 +8711,7 @@ app.include_router(public_endpoints_router)
app.include_router(rerank_router)
app.include_router(image_router)
app.include_router(fine_tuning_router)
app.include_router(vector_store_router)
app.include_router(credential_router)
app.include_router(llm_passthrough_router)
app.include_router(mcp_management_router)
+2
View File
@@ -76,6 +76,8 @@ async def route_request(
"agenerate_content",
"agenerate_content_stream",
"allm_passthrough_route",
"avector_store_search",
"avector_store_create",
],
):
"""
+27 -1
View File
@@ -783,6 +783,26 @@ class Router:
litellm.allm_passthrough_route, call_type="allm_passthrough_route"
)
#########################################################
# Vector Store routes
#########################################################
from litellm.vector_stores.main import acreate, asearch, create, search
# async routes
self.avector_store_search = self.factory_function(
asearch, call_type="avector_store_search"
)
self.avector_store_create = self.factory_function(
acreate, call_type="avector_store_create"
)
# sync routes
self.vector_store_search = self.factory_function(
search, call_type="vector_store_search"
)
self.vector_store_create = self.factory_function(
create, call_type="vector_store_create"
)
#########################################################
# Gemini Native routes
#########################################################
@@ -3290,6 +3310,10 @@ class Router:
"generate_content",
"agenerate_content_stream",
"generate_content_stream",
"avector_store_search",
"avector_store_create",
"vector_store_search",
"vector_store_create",
] = "assistants",
):
"""
@@ -3300,7 +3324,7 @@ class Router:
- An asynchronous function for asynchronous call types
"""
# Handle synchronous call types
if call_type in ("responses", "generate_content", "generate_content_stream"):
if call_type in ("responses", "generate_content", "generate_content_stream", "vector_store_search", "vector_store_create"):
def sync_wrapper(
custom_llm_provider: Optional[
@@ -3346,6 +3370,8 @@ class Router:
"aimage_edit",
"agenerate_content",
"agenerate_content_stream",
"avector_store_search",
"avector_store_create",
):
return await self._ageneric_api_call_with_fallbacks(
original_function=original_function,