mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-10 03:28:53 +00:00
[Feat] Vector Stores/KnowledgeBases - Allow defining Vector Store Configs (#10448)
* init vector store configs * working kb init * add vector store endpoints * use litellm_credential_name * working CRUD vector stores litellm * working creds with vector DB * ui cleanup * clean up vector store id * fix delete button * refactored vector store component * working selector for KBs * ui vector stores * add vector store tool calls usage on chat ui * fixes for vector stores litellm * test fix * docs Knowledge Bases * fixes for vector stores litellm * fix linting * add managed vectorstores * fix orjson ci/cd test * fix linting * add types.tsx file
This commit is contained in:
parent
7c679abe85
commit
be885e4a17
27 changed files with 1716 additions and 41 deletions
|
|
@ -1,10 +1,10 @@
|
|||
# Using Knowledge Bases with LiteLLM
|
||||
# Using Vector Stores (Knowledge Bases) with LiteLLM
|
||||
|
||||
LiteLLM integrates with AWS Bedrock Knowledge Bases, allowing your models to access your organization's data for more accurate and contextually relevant responses.
|
||||
|
||||
## Quick Start
|
||||
|
||||
In order to use a Bedrock Knowledge Base with LiteLLM, you need to pass `knowledge_bases` as a parameter to the completion request. Where `knowledge_bases` is a list of Bedrock Knowledge Base IDs.
|
||||
In order to use a Bedrock Knowledge Base with LiteLLM, you need to pass `vector_store_ids` as a parameter to the completion request. Where `vector_store_ids` is a list of Bedrock Knowledge Base IDs.
|
||||
|
||||
### LiteLLM Python SDK
|
||||
|
||||
|
|
@ -13,11 +13,11 @@ import os
|
|||
import litellm
|
||||
|
||||
|
||||
# Make a completion request with knowledge_bases parameter
|
||||
# Make a completion request with vector_store_ids parameter
|
||||
response = await litellm.acompletion(
|
||||
model="anthropic/claude-3-5-sonnet",
|
||||
messages=[{"role": "user", "content": "What is litellm?"}],
|
||||
knowledge_bases=["YOUR_KNOWLEDGE_BASE_ID"] # e.g., "T37J8R4WTM"
|
||||
vector_store_ids=["YOUR_KNOWLEDGE_BASE_ID"] # e.g., "T37J8R4WTM"
|
||||
)
|
||||
|
||||
print(response.choices[0].message.content)
|
||||
|
|
@ -36,7 +36,7 @@ model_list:
|
|||
|
||||
```
|
||||
|
||||
#### 2. Make a request with knowledge_bases parameter
|
||||
#### 2. Make a request with vector_store_ids parameter
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
|
@ -51,7 +51,7 @@ curl http://localhost:4000/v1/chat/completions \
|
|||
-d '{
|
||||
"model": "claude-3-5-sonnet",
|
||||
"messages": [{"role": "user", "content": "What is litellm?"}],
|
||||
"knowledge_bases": ["YOUR_KNOWLEDGE_BASE_ID"]
|
||||
"vector_store_ids": ["YOUR_KNOWLEDGE_BASE_ID"]
|
||||
}'
|
||||
```
|
||||
|
||||
|
|
@ -68,11 +68,11 @@ client = OpenAI(
|
|||
api_key="your-litellm-api-key"
|
||||
)
|
||||
|
||||
# Make a completion request with knowledge_bases parameter
|
||||
# Make a completion request with vector_store_ids parameter
|
||||
response = client.chat.completions.create(
|
||||
model="claude-3-5-sonnet",
|
||||
messages=[{"role": "user", "content": "What is litellm?"}],
|
||||
extra_body={"knowledge_bases": ["YOUR_KNOWLEDGE_BASE_ID"]}
|
||||
extra_body={"vector_store_ids": ["YOUR_KNOWLEDGE_BASE_ID"]}
|
||||
)
|
||||
|
||||
print(response.choices[0].message.content)
|
||||
|
|
@ -85,7 +85,7 @@ print(response.choices[0].message.content)
|
|||
|
||||
LiteLLM implements a `BedrockKnowledgeBaseHook` that intercepts your completion requests for handling the integration with Bedrock Knowledge Bases.
|
||||
|
||||
1. You make a completion request with the `knowledge_bases` parameter
|
||||
1. You make a completion request with the `vector_store_ids` parameter
|
||||
2. LiteLLM automatically:
|
||||
- Uses your last message as the query to retrieve relevant information from the Knowledge Base
|
||||
- Adds the retrieved context to your conversation
|
||||
|
|
@ -93,7 +93,7 @@ LiteLLM implements a `BedrockKnowledgeBaseHook` that intercepts your completion
|
|||
|
||||
### Example Transformation
|
||||
|
||||
When you pass `knowledge_bases=["YOUR_KNOWLEDGE_BASE_ID"]`, your request flows through these steps:
|
||||
When you pass `vector_store_ids=["YOUR_KNOWLEDGE_BASE_ID"]`, your request flows through these steps:
|
||||
|
||||
**1. Original Request to LiteLLM:**
|
||||
```json
|
||||
|
|
@ -102,7 +102,7 @@ When you pass `knowledge_bases=["YOUR_KNOWLEDGE_BASE_ID"]`, your request flows t
|
|||
"messages": [
|
||||
{"role": "user", "content": "What is litellm?"}
|
||||
],
|
||||
"knowledge_bases": ["YOUR_KNOWLEDGE_BASE_ID"]
|
||||
"vector_store_ids": ["YOUR_KNOWLEDGE_BASE_ID"]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -127,7 +127,7 @@ This is sent to: `https://bedrock-agent-runtime.{aws_region}.amazonaws.com/knowl
|
|||
}
|
||||
```
|
||||
|
||||
This process happens automatically whenever you include the `knowledge_bases` parameter in your request.
|
||||
This process happens automatically whenever you include the `vector_store_ids` parameter in your request.
|
||||
|
||||
## API Reference
|
||||
|
||||
|
|
@ -137,4 +137,4 @@ When using the Knowledge Base integration with LiteLLM, you can include the foll
|
|||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `knowledge_bases` | List[str] | List of Bedrock Knowledge Base IDs to query |
|
||||
| `vector_store_ids` | List[str] | List of Bedrock Knowledge Base IDs to query |
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
from fastapi import APIRouter
|
||||
from fastapi.responses import Response
|
||||
from .utils import _should_block_robots
|
||||
from .vector_stores.endpoints import router as vector_stores_router
|
||||
|
||||
router = APIRouter()
|
||||
router.include_router(vector_stores_router)
|
||||
|
||||
|
||||
@router.get("/robots.txt")
|
||||
|
|
@ -14,4 +16,4 @@ async def get_robots():
|
|||
if _should_block_robots():
|
||||
return Response(content="User-agent: *\nDisallow: /", media_type="text/plain")
|
||||
else:
|
||||
return Response(status_code=404)
|
||||
return Response(status_code=404)
|
||||
|
|
|
|||
180
enterprise/proxy/vector_stores/endpoints.py
Normal file
180
enterprise/proxy/vector_stores/endpoints.py
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
"""
|
||||
VECTOR STORE MANAGEMENT
|
||||
|
||||
All /vector_store management endpoints
|
||||
|
||||
/vector_store/new
|
||||
/vector_store/delete
|
||||
/vector_store/list
|
||||
"""
|
||||
import copy
|
||||
from typing import List
|
||||
from litellm.litellm_core_utils.safe_json_dumps import safe_dumps
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from litellm._logging import verbose_proxy_logger
|
||||
from litellm.proxy._types import UserAPIKeyAuth
|
||||
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
|
||||
from litellm.proxy.vector_stores.vector_store_registry import global_vector_store_manager
|
||||
from litellm.types.vector_stores import (
|
||||
LiteLLM_ManagedVectorStoreListResponse,
|
||||
LiteLLM_ManagedVectorStore,
|
||||
VectorStoreDeleteRequest,
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/vector_store/new",
|
||||
tags=["vector store management"],
|
||||
dependencies=[Depends(user_api_key_auth)],
|
||||
)
|
||||
async def new_vector_store(
|
||||
vector_store: LiteLLM_ManagedVectorStore,
|
||||
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
|
||||
):
|
||||
"""
|
||||
Create a new vector store.
|
||||
|
||||
Parameters:
|
||||
- vector_store_id: str - Unique identifier for the vector store
|
||||
- custom_llm_provider: str - Provider of the vector store
|
||||
- vector_store_name: Optional[str] - Name of the vector store
|
||||
- vector_store_description: Optional[str] - Description of the vector store
|
||||
- vector_store_metadata: Optional[Dict] - Additional metadata for the vector store
|
||||
"""
|
||||
from litellm.proxy.proxy_server import prisma_client
|
||||
|
||||
if prisma_client is None:
|
||||
raise HTTPException(status_code=500, detail="Database not connected")
|
||||
|
||||
try:
|
||||
# Check if vector store already exists
|
||||
existing_vector_store = await prisma_client.db.litellm_managedvectorstorestable.find_unique(
|
||||
where={"vector_store_id": vector_store.get("vector_store_id")}
|
||||
)
|
||||
if existing_vector_store is not None:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Vector store with ID {vector_store.get('vector_store_id')} already exists"
|
||||
)
|
||||
|
||||
if vector_store.get("vector_store_metadata") is not None:
|
||||
vector_store["vector_store_metadata"] = safe_dumps(vector_store.get("vector_store_metadata"))
|
||||
|
||||
new_vector_store = await prisma_client.db.litellm_managedvectorstorestable.create(
|
||||
data=vector_store
|
||||
)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": f"Vector store {vector_store.get('vector_store_id')} created successfully",
|
||||
"vector_store": new_vector_store,
|
||||
}
|
||||
except Exception as e:
|
||||
verbose_proxy_logger.exception(f"Error creating vector store: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
|
||||
@router.get(
|
||||
"/vector_store/list",
|
||||
tags=["vector store management"],
|
||||
dependencies=[Depends(user_api_key_auth)],
|
||||
response_model=LiteLLM_ManagedVectorStoreListResponse,
|
||||
)
|
||||
async def list_vector_stores(
|
||||
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
|
||||
page: int = 1,
|
||||
page_size: int = 100,
|
||||
):
|
||||
"""
|
||||
List all available vector stores with optional filtering and pagination.
|
||||
Combines both in-memory vector stores and those stored in the database.
|
||||
|
||||
Parameters:
|
||||
- page: int - Page number for pagination (default: 1)
|
||||
- page_size: int - Number of items per page (default: 100)
|
||||
"""
|
||||
from litellm.proxy.proxy_server import prisma_client
|
||||
|
||||
try:
|
||||
# Get in-memory vector stores
|
||||
in_memory_vector_stores: List[LiteLLM_ManagedVectorStore] = copy.deepcopy(global_vector_store_manager.vector_stores)
|
||||
|
||||
# Get vector stores from database
|
||||
vector_stores_from_db: List[LiteLLM_ManagedVectorStore] = []
|
||||
if prisma_client is not None:
|
||||
_vector_stores_from_db = await prisma_client.db.litellm_managedvectorstorestable.find_many(
|
||||
order={
|
||||
"created_at": "desc"
|
||||
},
|
||||
)
|
||||
for vector_store in _vector_stores_from_db:
|
||||
_dict_vector_store = dict(vector_store)
|
||||
_litellm_managed_vector_store = LiteLLM_ManagedVectorStore(**_dict_vector_store)
|
||||
vector_stores_from_db.append(_litellm_managed_vector_store)
|
||||
|
||||
# Combine in-memory and database vector stores
|
||||
combined_vector_stores: List[LiteLLM_ManagedVectorStore] = in_memory_vector_stores + vector_stores_from_db
|
||||
|
||||
total_count = len(combined_vector_stores)
|
||||
total_pages = (total_count + page_size - 1) // page_size
|
||||
|
||||
# Format response using LiteLLM_ManagedVectorStoreListResponse
|
||||
response = LiteLLM_ManagedVectorStoreListResponse(
|
||||
object="list",
|
||||
data=combined_vector_stores,
|
||||
total_count=total_count,
|
||||
current_page=page,
|
||||
total_pages=total_pages,
|
||||
)
|
||||
|
||||
return response
|
||||
except Exception as e:
|
||||
verbose_proxy_logger.exception(f"Error listing vector stores: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/vector_store/delete",
|
||||
tags=["vector store management"],
|
||||
dependencies=[Depends(user_api_key_auth)],
|
||||
)
|
||||
async def delete_vector_store(
|
||||
data: VectorStoreDeleteRequest,
|
||||
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
|
||||
):
|
||||
"""
|
||||
Delete a vector store.
|
||||
|
||||
Parameters:
|
||||
- vector_store_id: str - ID of the vector store to delete
|
||||
"""
|
||||
from litellm.proxy.proxy_server import prisma_client
|
||||
|
||||
if prisma_client is None:
|
||||
raise HTTPException(status_code=500, detail="Database not connected")
|
||||
|
||||
try:
|
||||
# Check if vector store exists
|
||||
existing_vector_store = await prisma_client.db.litellm_managedvectorstorestable.find_unique(
|
||||
where={"vector_store_id": data.vector_store_id}
|
||||
)
|
||||
if existing_vector_store is None:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Vector store with ID {data.vector_store_id} not found"
|
||||
)
|
||||
|
||||
# Delete vector store
|
||||
await prisma_client.db.litellm_managedvectorstorestable.delete(
|
||||
where={"vector_store_id": data.vector_store_id}
|
||||
)
|
||||
|
||||
return {"message": f"Vector store {data.vector_store_id} deleted successfully"}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
-- CreateTable
|
||||
CREATE TABLE "LiteLLM_ManagedVectorStoresTable" (
|
||||
"vector_store_id" TEXT NOT NULL,
|
||||
"custom_llm_provider" TEXT NOT NULL,
|
||||
"vector_store_name" TEXT,
|
||||
"vector_store_description" TEXT,
|
||||
"vector_store_metadata" JSONB,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
"litellm_credential_name" TEXT,
|
||||
|
||||
CONSTRAINT "LiteLLM_ManagedVectorStoresTable_pkey" PRIMARY KEY ("vector_store_id")
|
||||
);
|
||||
|
||||
|
|
@ -425,3 +425,14 @@ model LiteLLM_ManagedFileTable {
|
|||
@@index([unified_file_id])
|
||||
}
|
||||
|
||||
|
||||
model LiteLLM_ManagedVectorStoresTable {
|
||||
vector_store_id String @id
|
||||
custom_llm_provider String
|
||||
vector_store_name String?
|
||||
vector_store_description String?
|
||||
vector_store_metadata Json?
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
litellm_credential_name String?
|
||||
}
|
||||
|
|
@ -62,11 +62,11 @@ class BedrockKnowledgeBaseHook(CustomPromptManagement, BaseAWSLLM):
|
|||
"""
|
||||
Retrieves the context from the Bedrock Knowledge Base and appends it to the messages.
|
||||
"""
|
||||
knowledge_bases = non_default_params.pop("knowledge_bases", None)
|
||||
if knowledge_bases:
|
||||
for knowledge_base in knowledge_bases:
|
||||
vector_store_ids = non_default_params.pop("vector_store_ids", None)
|
||||
if vector_store_ids:
|
||||
for vector_store_id in vector_store_ids:
|
||||
response = await self.make_bedrock_kb_retrieve_request(
|
||||
knowledge_base_id=knowledge_base,
|
||||
knowledge_base_id=vector_store_id,
|
||||
query=self._get_kb_query_from_messages(messages),
|
||||
)
|
||||
verbose_logger.debug(f"Bedrock Knowledge Base Response: {response}")
|
||||
|
|
@ -231,7 +231,7 @@ class BedrockKnowledgeBaseHook(CustomPromptManagement, BaseAWSLLM):
|
|||
|
||||
@staticmethod
|
||||
def should_use_prompt_management_hook(non_default_params: Dict) -> bool:
|
||||
if non_default_params.get("knowledge_bases", None):
|
||||
if non_default_params.get("vector_store_ids", None):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,22 @@
|
|||
model_list:
|
||||
- model_name: computer-user-agent
|
||||
- model_name: openai/gpt-4o
|
||||
litellm_params:
|
||||
model: azure/computer-use-preview
|
||||
truncation: auto
|
||||
api_key: os.environ/AZURE_RESPONSES_OPENAI_API_KEY
|
||||
api_base: os.environ/AZURE_RESPONSES_OPENAI_ENDPOINT
|
||||
api_version: os.environ/AZURE_RESPONSES_OPENAI_API_VERSION
|
||||
general_settings:
|
||||
store_prompts_in_spend_logs: true
|
||||
block_robots: true
|
||||
model: openai/gpt-4o
|
||||
api_key: os.environ/OPENAI_API_KEY
|
||||
- model_name: claude-3-5-sonnet-with-vector-store
|
||||
litellm_params:
|
||||
model: anthropic/claude-3-5-sonnet-latest
|
||||
api_key: os.environ/ANTHROPIC_API_KEY
|
||||
vector_store_ids: ["T37J8R4WTM"]
|
||||
|
||||
vector_stores:
|
||||
- vector_store_name: "bedrock-litellm-website-knowledgebase"
|
||||
litellm_params:
|
||||
custom_llm_provider: "bedrock"
|
||||
vector_store_id: "T37J8R4WTM"
|
||||
vector_store_description: "Bedrock vector store for the Litellm website knowledgebase"
|
||||
vector_store_metadata:
|
||||
source: "https://www.litellm.com/docs"
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1998,6 +1998,19 @@ class ProxyConfig:
|
|||
all_guardrails=guardrails_v2, config_file_path=config_file_path
|
||||
)
|
||||
|
||||
## CREDENTIALS
|
||||
credential_list_dict = self.load_credential_list(config=config)
|
||||
litellm.credential_list = credential_list_dict
|
||||
|
||||
## NON-LLM CONFIGS eg. MCP tools, vector stores, etc.
|
||||
self._init_non_llm_configs(config=config)
|
||||
|
||||
return router, router.get_model_list(), general_settings
|
||||
|
||||
def _init_non_llm_configs(self, config: dict):
|
||||
"""
|
||||
Initialize non-LLM configs eg. MCP tools, vector stores, etc.
|
||||
"""
|
||||
## MCP TOOLS
|
||||
mcp_tools_config = config.get("mcp_tools", None)
|
||||
if mcp_tools_config:
|
||||
|
|
@ -2011,10 +2024,17 @@ class ProxyConfig:
|
|||
|
||||
global_mcp_server_manager.load_servers_from_config(mcp_servers_config)
|
||||
|
||||
## CREDENTIALS
|
||||
credential_list_dict = self.load_credential_list(config=config)
|
||||
litellm.credential_list = credential_list_dict
|
||||
return router, router.get_model_list(), general_settings
|
||||
## VECTOR STORES
|
||||
vector_stores_config = config.get("vector_stores", None)
|
||||
if vector_stores_config:
|
||||
from litellm.proxy.vector_stores.vector_store_registry import (
|
||||
global_vector_store_manager,
|
||||
)
|
||||
|
||||
global_vector_store_manager.load_vector_stores_from_config(
|
||||
vector_stores_config
|
||||
)
|
||||
pass
|
||||
|
||||
def _load_alerting_settings(self, general_settings: dict):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -425,3 +425,14 @@ model LiteLLM_ManagedFileTable {
|
|||
@@index([unified_file_id])
|
||||
}
|
||||
|
||||
|
||||
model LiteLLM_ManagedVectorStoresTable {
|
||||
vector_store_id String @id
|
||||
custom_llm_provider String
|
||||
vector_store_name String?
|
||||
vector_store_description String?
|
||||
vector_store_metadata Json?
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
litellm_credential_name String?
|
||||
}
|
||||
79
litellm/proxy/vector_stores/vector_store_registry.py
Normal file
79
litellm/proxy/vector_stores/vector_store_registry.py
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# litellm/proxy/vector_stores/vector_store_registry.py
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.types.vector_stores import (
|
||||
LiteLLM_ManagedVectorStore,
|
||||
LiteLLM_ManagedVectorStoreListResponse,
|
||||
LiteLLM_VectorStoreConfig,
|
||||
)
|
||||
|
||||
|
||||
class VectorStoreManager:
|
||||
def __init__(self):
|
||||
self.vector_stores: List[LiteLLM_ManagedVectorStore] = []
|
||||
|
||||
def load_vector_stores_from_config(self, vector_stores_config: List[Dict]):
|
||||
for vector_store_config in vector_stores_config:
|
||||
# cast to VectorStoreConfig
|
||||
litellm_vector_store_config = LiteLLM_VectorStoreConfig(
|
||||
**vector_store_config
|
||||
)
|
||||
vector_store_name = litellm_vector_store_config.get("vector_store_name")
|
||||
vector_store_litellm_params: Dict[str, Any] = (
|
||||
litellm_vector_store_config.get("litellm_params") or {}
|
||||
)
|
||||
|
||||
vector_store_id = vector_store_litellm_params.get("vector_store_id")
|
||||
if vector_store_id is None:
|
||||
raise ValueError(
|
||||
f"vector_store_id is required for initializing vector store, got vector_store_id={vector_store_id}"
|
||||
)
|
||||
custom_llm_provider = vector_store_litellm_params.get("custom_llm_provider")
|
||||
if custom_llm_provider is None:
|
||||
raise ValueError(
|
||||
f"custom_llm_provider is required for initializing vector store, got custom_llm_provider={custom_llm_provider}"
|
||||
)
|
||||
|
||||
litellm_managed_vector_store = LiteLLM_ManagedVectorStore(
|
||||
vector_store_id=vector_store_id,
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
vector_store_name=vector_store_name,
|
||||
vector_store_description=vector_store_litellm_params.get(
|
||||
"vector_store_description"
|
||||
),
|
||||
vector_store_metadata=vector_store_litellm_params.get(
|
||||
"vector_store_metadata"
|
||||
),
|
||||
created_at=datetime.now(timezone.utc),
|
||||
updated_at=datetime.now(timezone.utc),
|
||||
)
|
||||
self.vector_stores.append(litellm_managed_vector_store)
|
||||
|
||||
verbose_logger.debug(
|
||||
"all loaded vector stores = %s",
|
||||
json.dumps(self.vector_stores, indent=4, default=str),
|
||||
)
|
||||
|
||||
def list_all_vector_stores(self) -> LiteLLM_ManagedVectorStoreListResponse:
|
||||
"""
|
||||
List all vector stores in the required format
|
||||
|
||||
Returns:
|
||||
LiteLLM_ManagedVectorStoreListResponse: A standardized response with vector store data
|
||||
"""
|
||||
# Prepare the response
|
||||
response = LiteLLM_ManagedVectorStoreListResponse(
|
||||
object="list",
|
||||
data=self.vector_stores,
|
||||
total_count=len(self.vector_stores),
|
||||
current_page=1,
|
||||
total_pages=1,
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
global_vector_store_manager = VectorStoreManager()
|
||||
|
|
@ -2272,6 +2272,7 @@ class DynamicPromptManagementParamLiteral(str, Enum):
|
|||
|
||||
CACHE_CONTROL_INJECTION_POINTS = "cache_control_injection_points"
|
||||
KNOWLEDGE_BASES = "knowledge_bases"
|
||||
VECTOR_STORE_IDS = "vector_store_ids"
|
||||
|
||||
@classmethod
|
||||
def list_all_params(cls):
|
||||
|
|
|
|||
63
litellm/types/vector_stores.py
Normal file
63
litellm/types/vector_stores.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List, Literal, Optional, Union
|
||||
|
||||
from pydantic import BaseModel
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from litellm.types.router import CredentialLiteLLMParams
|
||||
|
||||
|
||||
class SupportedVectorStoreIntegrations(str, Enum):
|
||||
"""Supported vector store integrations."""
|
||||
|
||||
BEDROCK = "bedrock"
|
||||
|
||||
|
||||
class LiteLLM_VectorStoreConfig(TypedDict, total=False):
|
||||
"""Parameters for initializing a vector store on Litellm proxy config.yaml"""
|
||||
|
||||
vector_store_name: str
|
||||
litellm_params: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class LiteLLM_ManagedVectorStore(TypedDict, total=False):
|
||||
"""LiteLLM managed vector store object - this is is the object stored in the database"""
|
||||
|
||||
vector_store_id: str
|
||||
custom_llm_provider: str
|
||||
|
||||
vector_store_name: Optional[str]
|
||||
vector_store_description: Optional[str]
|
||||
vector_store_metadata: Optional[Union[Dict[str, Any], str]]
|
||||
created_at: Optional[datetime]
|
||||
updated_at: Optional[datetime]
|
||||
|
||||
# credential fields
|
||||
litellm_credential_name: Optional[str]
|
||||
|
||||
|
||||
class LiteLLM_ManagedVectorStoreListResponse(TypedDict, total=False):
|
||||
"""Response format for listing vector stores"""
|
||||
|
||||
object: Literal["list"] # Always "list"
|
||||
data: List[LiteLLM_ManagedVectorStore]
|
||||
total_count: Optional[int]
|
||||
current_page: Optional[int]
|
||||
total_pages: Optional[int]
|
||||
|
||||
|
||||
class VectorStoreUpdateRequest(BaseModel):
|
||||
vector_store_id: str
|
||||
custom_llm_provider: Optional[str] = None
|
||||
vector_store_name: Optional[str] = None
|
||||
vector_store_description: Optional[str] = None
|
||||
vector_store_metadata: Optional[Dict] = None
|
||||
|
||||
|
||||
class VectorStoreDeleteRequest(BaseModel):
|
||||
vector_store_id: str
|
||||
|
||||
|
||||
class VectorStoreInfoRequest(BaseModel):
|
||||
vector_store_id: str
|
||||
|
|
@ -425,3 +425,14 @@ model LiteLLM_ManagedFileTable {
|
|||
@@index([unified_file_id])
|
||||
}
|
||||
|
||||
|
||||
model LiteLLM_ManagedVectorStoresTable {
|
||||
vector_store_id String @id
|
||||
custom_llm_provider String
|
||||
vector_store_name String?
|
||||
vector_store_description String?
|
||||
vector_store_metadata Json?
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
litellm_credential_name String?
|
||||
}
|
||||
|
|
@ -49,7 +49,7 @@ async def test_e2e_bedrock_knowledgebase_retrieval_with_completion():
|
|||
response = await litellm.acompletion(
|
||||
model="anthropic/claude-3.5-sonnet",
|
||||
messages=[{"role": "user", "content": "what is litellm?"}],
|
||||
knowledge_bases = [
|
||||
vector_store_ids = [
|
||||
"T37J8R4WTM"
|
||||
],
|
||||
client=client
|
||||
|
|
@ -90,7 +90,7 @@ async def test_e2e_bedrock_knowledgebase_retrieval_with_llm_api_call():
|
|||
response = await litellm.acompletion(
|
||||
model="anthropic/claude-3-5-haiku-latest",
|
||||
messages=[{"role": "user", "content": "what is litellm?"}],
|
||||
knowledge_bases = [
|
||||
vector_store_ids = [
|
||||
"T37J8R4WTM"
|
||||
],
|
||||
client=async_client
|
||||
|
|
@ -116,7 +116,7 @@ async def test_openai_with_knowledge_base_mock_openai():
|
|||
await litellm.acompletion(
|
||||
model="gpt-4",
|
||||
messages=[{"role": "user", "content": "what is litellm?"}],
|
||||
knowledge_bases = [
|
||||
vector_store_ids = [
|
||||
"T37J8R4WTM"
|
||||
],
|
||||
client=client,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import { fetchUserModels } from "@/components/create_key_button";
|
|||
import { fetchTeams } from "@/components/common_components/fetch_teams";
|
||||
import MCPToolsViewer from "@/components/mcp_tools";
|
||||
import TagManagement from "@/components/tag_management";
|
||||
import VectorStoreManagement from "@/components/vector_store_management";
|
||||
import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner";
|
||||
import { cx } from '@/lib/cva.config';
|
||||
|
||||
|
|
@ -408,6 +409,12 @@ export default function CreateKeyPage() {
|
|||
userRole={userRole}
|
||||
userID={userID}
|
||||
/>
|
||||
) : page == "vector-stores" ? (
|
||||
<VectorStoreManagement
|
||||
accessToken={accessToken}
|
||||
userRole={userRole}
|
||||
userID={userID}
|
||||
/>
|
||||
) : page == "new_usage" ? (
|
||||
<NewUsagePage
|
||||
userID={userID}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
|||
import { coy } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||
import EndpointSelector from "./chat_ui/EndpointSelector";
|
||||
import TagSelector from "./tag_management/TagSelector";
|
||||
import VectorStoreSelector from "./vector_store_management/VectorStoreSelector";
|
||||
import { determineEndpointType } from "./chat_ui/EndpointUtils";
|
||||
import { MessageType } from "./chat_ui/types";
|
||||
import ReasoningContent from "./chat_ui/ReasoningContent";
|
||||
|
|
@ -47,7 +48,9 @@ import {
|
|||
UserOutlined,
|
||||
DeleteOutlined,
|
||||
LoadingOutlined,
|
||||
TagsOutlined
|
||||
TagsOutlined,
|
||||
DatabaseOutlined,
|
||||
InfoCircleOutlined
|
||||
} from "@ant-design/icons";
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
|
@ -83,6 +86,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
|
|||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const abortControllerRef = useRef<AbortController | null>(null);
|
||||
const [selectedTags, setSelectedTags] = useState<string[]>([]);
|
||||
const [selectedVectorStores, setSelectedVectorStores] = useState<string[]>([]);
|
||||
const [messageTraceId, setMessageTraceId] = useState<string | null>(null);
|
||||
|
||||
const chatEndRef = useRef<HTMLDivElement>(null);
|
||||
|
|
@ -328,7 +332,8 @@ const ChatUI: React.FC<ChatUIProps> = ({
|
|||
updateReasoningContent,
|
||||
updateTimingData,
|
||||
updateUsageData,
|
||||
traceId
|
||||
traceId,
|
||||
selectedVectorStores.length > 0 ? selectedVectorStores : undefined
|
||||
);
|
||||
} else if (endpointType === EndpointType.IMAGE) {
|
||||
// For image generation
|
||||
|
|
@ -354,7 +359,8 @@ const ChatUI: React.FC<ChatUIProps> = ({
|
|||
updateReasoningContent,
|
||||
updateTimingData,
|
||||
updateUsageData,
|
||||
traceId
|
||||
traceId,
|
||||
selectedVectorStores.length > 0 ? selectedVectorStores : undefined
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -503,6 +509,27 @@ const ChatUI: React.FC<ChatUIProps> = ({
|
|||
accessToken={accessToken || ""}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="font-medium block mb-2 text-gray-700 flex items-center">
|
||||
<DatabaseOutlined className="mr-2" /> Vector Store
|
||||
<Tooltip
|
||||
className="ml-1"
|
||||
title={
|
||||
<span>
|
||||
Select vector store(s) to use for this LLM API call. You can set up your vector store <a href="?page=vector-stores" style={{ color: '#1890ff' }}>here</a>.
|
||||
</span>
|
||||
}>
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
</Text>
|
||||
<VectorStoreSelector
|
||||
value={selectedVectorStores}
|
||||
onChange={setSelectedVectorStores}
|
||||
className="mb-4"
|
||||
accessToken={accessToken || ""}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={clearChatHistory}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ export async function makeOpenAIChatCompletionRequest(
|
|||
onReasoningContent?: (content: string) => void,
|
||||
onTimingData?: (timeToFirstToken: number) => void,
|
||||
onUsageData?: (usage: TokenUsage) => void,
|
||||
traceId?: string
|
||||
traceId?: string,
|
||||
vector_store_ids?: string[]
|
||||
) {
|
||||
// base url should be the current base_url
|
||||
const isLocal = process.env.NODE_ENV === "development";
|
||||
|
|
@ -56,6 +57,7 @@ export async function makeOpenAIChatCompletionRequest(
|
|||
},
|
||||
litellm_trace_id: traceId,
|
||||
messages: chatHistory as ChatCompletionMessageParam[],
|
||||
...(vector_store_ids ? { vector_store_ids } : {}),
|
||||
}, { signal });
|
||||
|
||||
for await (const chunk of response) {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ export async function makeOpenAIResponsesRequest(
|
|||
onReasoningContent?: (content: string) => void,
|
||||
onTimingData?: (timeToFirstToken: number) => void,
|
||||
onUsageData?: (usage: TokenUsage) => void,
|
||||
traceId?: string
|
||||
traceId?: string,
|
||||
vector_store_ids?: string[]
|
||||
) {
|
||||
if (!accessToken) {
|
||||
throw new Error("API key is required");
|
||||
|
|
@ -60,6 +61,7 @@ export async function makeOpenAIResponsesRequest(
|
|||
input: formattedInput,
|
||||
stream: true,
|
||||
litellm_trace_id: traceId,
|
||||
...(vector_store_ids ? { vector_store_ids } : {}),
|
||||
}, { signal });
|
||||
|
||||
for await (const event of response) {
|
||||
|
|
|
|||
|
|
@ -73,10 +73,11 @@ const Sidebar: React.FC<SidebarProps> = ({
|
|||
{ key: "9", page: "caching", label: "Caching", icon: <DatabaseOutlined />, roles: all_admin_roles },
|
||||
{ key: "10", page: "budgets", label: "Budgets", icon: <BankOutlined />, roles: all_admin_roles },
|
||||
{ key: "11", page: "guardrails", label: "Guardrails", icon: <SafetyOutlined />, roles: all_admin_roles },
|
||||
{ key: "4", page: "usage", label: "Old Usage", icon: <BarChartOutlined /> },
|
||||
{ key: "20", page: "transform-request", label: "API Playground", icon: <ApiOutlined />, roles: [...all_admin_roles, ...internalUserRoles] },
|
||||
{ key: "18", page: "mcp-tools", label: "MCP Tools", icon: <ToolOutlined />, roles: all_admin_roles },
|
||||
{ key: "19", page: "tag-management", label: "Tag Management", icon: <TagsOutlined />, roles: all_admin_roles },
|
||||
{ key: "21", page: "vector-stores", label: "Vector Stores", icon: <DatabaseOutlined />, roles: all_admin_roles },
|
||||
{ key: "4", page: "usage", label: "Old Usage", icon: <BarChartOutlined /> },
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4791,4 +4791,125 @@ export const sessionSpendLogsCall = async (
|
|||
console.error("Failed to fetch session logs:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const vectorStoreCreateCall = async (
|
||||
accessToken: string,
|
||||
formValues: Record<string, any>
|
||||
): Promise<void> => {
|
||||
try {
|
||||
let url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/vector_store/new`
|
||||
: `/vector_store/new`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${accessToken}`
|
||||
},
|
||||
body: JSON.stringify(formValues)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.detail || 'Failed to create vector store');
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Error creating vector store:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const vectorStoreListCall = async (
|
||||
accessToken: string,
|
||||
page: number = 1,
|
||||
page_size: number = 100
|
||||
): Promise<any> => {
|
||||
try {
|
||||
let url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/vector_store/list`
|
||||
: `/vector_store/list`;
|
||||
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${accessToken}`
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.detail || 'Failed to list vector stores');
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Error listing vector stores:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const vectorStoreDeleteCall = async (
|
||||
accessToken: string,
|
||||
vectorStoreId: string
|
||||
): Promise<void> => {
|
||||
try {
|
||||
let url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/vector_store/delete`
|
||||
: `/vector_store/delete`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${accessToken}`
|
||||
},
|
||||
body: JSON.stringify({ vector_store_id: vectorStoreId })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.detail || 'Failed to delete vector store');
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Error deleting vector store:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const vectorStoreInfoCall = async (
|
||||
accessToken: string,
|
||||
vectorStoreId: string
|
||||
): Promise<any> => {
|
||||
try {
|
||||
let url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/vector_store/info`
|
||||
: `/vector_store/info`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${accessToken}`
|
||||
},
|
||||
body: JSON.stringify({ vector_store_id: vectorStoreId })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.detail || 'Failed to get vector store info');
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Error getting vector store info:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import React from "react";
|
||||
import { Modal } from "antd";
|
||||
import { Button as TremorButton } from "@tremor/react";
|
||||
|
||||
interface DeleteModalProps {
|
||||
isVisible: boolean;
|
||||
onCancel: () => void;
|
||||
onConfirm: () => void;
|
||||
}
|
||||
|
||||
const DeleteModal: React.FC<DeleteModalProps> = ({
|
||||
isVisible,
|
||||
onCancel,
|
||||
onConfirm,
|
||||
}) => {
|
||||
return (
|
||||
<Modal
|
||||
title="Delete Vector Store"
|
||||
visible={isVisible}
|
||||
footer={null}
|
||||
onCancel={onCancel}
|
||||
>
|
||||
<p>Are you sure you want to delete this vector store? This action cannot be undone.</p>
|
||||
<div className="px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
||||
<TremorButton
|
||||
onClick={onConfirm}
|
||||
color="red"
|
||||
className="ml-2"
|
||||
>
|
||||
Delete
|
||||
</TremorButton>
|
||||
<TremorButton onClick={onCancel} variant="primary">Cancel</TremorButton>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteModal;
|
||||
|
|
@ -0,0 +1,229 @@
|
|||
import React, { useState } from "react";
|
||||
import {
|
||||
TextInput,
|
||||
Icon,
|
||||
Button as TremorButton,
|
||||
Text,
|
||||
} from "@tremor/react";
|
||||
import {
|
||||
Modal,
|
||||
Form,
|
||||
Select,
|
||||
message,
|
||||
Tooltip,
|
||||
Input,
|
||||
} from "antd";
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import { CredentialItem, vectorStoreCreateCall } from "../networking";
|
||||
import { Providers, providerLogoMap, provider_map } from "../provider_info_helpers";
|
||||
|
||||
interface VectorStoreFormProps {
|
||||
isVisible: boolean;
|
||||
onCancel: () => void;
|
||||
onSuccess: () => void;
|
||||
accessToken: string | null;
|
||||
credentials: CredentialItem[];
|
||||
}
|
||||
|
||||
const VectorStoreForm: React.FC<VectorStoreFormProps> = ({
|
||||
isVisible,
|
||||
onCancel,
|
||||
onSuccess,
|
||||
accessToken,
|
||||
credentials,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [metadataJson, setMetadataJson] = useState("{}");
|
||||
|
||||
const handleCreate = async (formValues: any) => {
|
||||
if (!accessToken) return;
|
||||
try {
|
||||
// Parse metadata JSON
|
||||
let metadata = {};
|
||||
try {
|
||||
metadata = metadataJson.trim() ? JSON.parse(metadataJson) : {};
|
||||
} catch (e) {
|
||||
message.error("Invalid JSON in metadata field");
|
||||
return;
|
||||
}
|
||||
|
||||
await vectorStoreCreateCall(accessToken, {
|
||||
vector_store_id: formValues.vector_store_id,
|
||||
custom_llm_provider: formValues.custom_llm_provider,
|
||||
vector_store_name: formValues.vector_store_name,
|
||||
vector_store_description: formValues.vector_store_description,
|
||||
vector_store_metadata: metadata,
|
||||
});
|
||||
message.success("Vector store created successfully");
|
||||
form.resetFields();
|
||||
setMetadataJson("{}");
|
||||
onSuccess();
|
||||
} catch (error) {
|
||||
console.error("Error creating vector store:", error);
|
||||
message.error("Error creating vector store: " + error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
form.resetFields();
|
||||
setMetadataJson("{}");
|
||||
onCancel();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="Create New Vector Store"
|
||||
visible={isVisible}
|
||||
width={800}
|
||||
footer={null}
|
||||
onCancel={handleCancel}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={handleCreate}
|
||||
labelCol={{ span: 8 }}
|
||||
wrapperCol={{ span: 16 }}
|
||||
labelAlign="left"
|
||||
>
|
||||
<Form.Item
|
||||
label={
|
||||
<span>
|
||||
Provider{' '}
|
||||
<Tooltip title="Select the provider for this vector store">
|
||||
<InfoCircleOutlined style={{ marginLeft: '4px' }} />
|
||||
</Tooltip>
|
||||
</span>
|
||||
}
|
||||
name="custom_llm_provider"
|
||||
rules={[{ required: true, message: "Please select a provider" }]}
|
||||
initialValue="bedrock"
|
||||
>
|
||||
<Select>
|
||||
{Object.entries(Providers).map(([providerEnum, providerDisplayName]) => {
|
||||
// Currently only showing Bedrock since it's the only supported provider
|
||||
if (providerEnum === 'Bedrock') {
|
||||
return (
|
||||
<Select.Option key={providerEnum} value={provider_map[providerEnum]}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<img
|
||||
src={providerLogoMap[providerDisplayName]}
|
||||
alt={`${providerEnum} logo`}
|
||||
className="w-5 h-5"
|
||||
onError={(e) => {
|
||||
// Create a div with provider initial as fallback
|
||||
const target = e.target as HTMLImageElement;
|
||||
const parent = target.parentElement;
|
||||
if (parent) {
|
||||
const fallbackDiv = document.createElement('div');
|
||||
fallbackDiv.className = 'w-5 h-5 rounded-full bg-gray-200 flex items-center justify-center text-xs';
|
||||
fallbackDiv.textContent = providerDisplayName.charAt(0);
|
||||
parent.replaceChild(fallbackDiv, target);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<span>{providerDisplayName}</span>
|
||||
</div>
|
||||
</Select.Option>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={
|
||||
<span>
|
||||
Vector Store ID{' '}
|
||||
<Tooltip title="Enter the vector store ID from your api provider">
|
||||
<InfoCircleOutlined style={{ marginLeft: '4px' }} />
|
||||
</Tooltip>
|
||||
</span>
|
||||
}
|
||||
name="vector_store_id"
|
||||
rules={[{ required: true, message: "Please input the vector store ID from your api provider" }]}
|
||||
>
|
||||
<TextInput />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={
|
||||
<span>
|
||||
Vector Store Name{' '}
|
||||
<Tooltip title="Custom name you want to give to the vector store, this name will be rendered on the LiteLLM UI">
|
||||
<InfoCircleOutlined style={{ marginLeft: '4px' }} />
|
||||
</Tooltip>
|
||||
</span>
|
||||
}
|
||||
name="vector_store_name"
|
||||
>
|
||||
<TextInput />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Description"
|
||||
name="vector_store_description"
|
||||
>
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={
|
||||
<span>
|
||||
Existing Credentials{' '}
|
||||
<Tooltip title="Optionally select API provider credentials for this vector store eg. Bedrock API KEY">
|
||||
<InfoCircleOutlined style={{ marginLeft: '4px' }} />
|
||||
</Tooltip>
|
||||
</span>
|
||||
}
|
||||
name="litellm_credential_name"
|
||||
>
|
||||
<Select
|
||||
showSearch
|
||||
placeholder="Select or search for existing credentials"
|
||||
optionFilterProp="children"
|
||||
filterOption={(input, option) =>
|
||||
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
||||
}
|
||||
options={[
|
||||
{ value: null, label: 'None' },
|
||||
...credentials.map((credential) => ({
|
||||
value: credential.credential_name,
|
||||
label: credential.credential_name
|
||||
}))
|
||||
]}
|
||||
allowClear
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={
|
||||
<span>
|
||||
Metadata{' '}
|
||||
<Tooltip title="JSON metadata for the vector store (optional)">
|
||||
<InfoCircleOutlined style={{ marginLeft: '4px' }} />
|
||||
</Tooltip>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<Input.TextArea
|
||||
rows={4}
|
||||
value={metadataJson}
|
||||
onChange={(e) => setMetadataJson(e.target.value)}
|
||||
placeholder='{"key": "value"}'
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<div className="flex justify-end space-x-3">
|
||||
<TremorButton onClick={handleCancel} variant="secondary">
|
||||
Cancel
|
||||
</TremorButton>
|
||||
<TremorButton variant="primary" type="submit">
|
||||
Create
|
||||
</TremorButton>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default VectorStoreForm;
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { Select, Typography, Tooltip } from 'antd';
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import { VectorStore } from './types';
|
||||
import { vectorStoreListCall } from '../networking';
|
||||
interface VectorStoreSelectorProps {
|
||||
onChange: (selectedVectorStores: string[]) => void;
|
||||
value?: string[];
|
||||
className?: string;
|
||||
accessToken: string;
|
||||
}
|
||||
|
||||
const VectorStoreSelector: React.FC<VectorStoreSelectorProps> = ({
|
||||
onChange,
|
||||
value,
|
||||
className,
|
||||
accessToken
|
||||
}) => {
|
||||
const [vectorStores, setVectorStores] = useState<VectorStore[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchVectorStores = async () => {
|
||||
if (!accessToken) return;
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await vectorStoreListCall(accessToken);
|
||||
if (response.data) {
|
||||
setVectorStores(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching vector stores:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchVectorStores();
|
||||
}, [accessToken]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder="Select vector stores"
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
loading={loading}
|
||||
className={className}
|
||||
options={vectorStores.map(store => ({
|
||||
label: `${store.vector_store_name || store.vector_store_id} (${store.vector_store_id})`,
|
||||
value: store.vector_store_id,
|
||||
title: store.vector_store_description || store.vector_store_id,
|
||||
}))}
|
||||
optionFilterProp="label"
|
||||
showSearch
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VectorStoreSelector;
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
import React from "react";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeaderCell,
|
||||
TableRow,
|
||||
Icon,
|
||||
Text,
|
||||
Badge,
|
||||
} from "@tremor/react";
|
||||
import {
|
||||
TrashIcon,
|
||||
SwitchVerticalIcon,
|
||||
ChevronUpIcon,
|
||||
ChevronDownIcon,
|
||||
} from "@heroicons/react/outline";
|
||||
import { Tooltip } from "antd";
|
||||
import {
|
||||
ColumnDef,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getSortedRowModel,
|
||||
SortingState,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import { VectorStore } from "./types";
|
||||
import { getProviderLogoAndName } from "../provider_info_helpers";
|
||||
|
||||
interface VectorStoreTableProps {
|
||||
data: VectorStore[];
|
||||
onDelete: (vectorStoreId: string) => void;
|
||||
}
|
||||
|
||||
const VectorStoreTable: React.FC<VectorStoreTableProps> = ({
|
||||
data,
|
||||
onDelete,
|
||||
}) => {
|
||||
const [sorting, setSorting] = React.useState<SortingState>([
|
||||
{ id: "created_at", desc: true }
|
||||
]);
|
||||
|
||||
const columns: ColumnDef<VectorStore>[] = [
|
||||
{
|
||||
header: "Vector Store ID",
|
||||
accessorKey: "vector_store_id",
|
||||
cell: ({ row }) => {
|
||||
const vectorStore = row.original;
|
||||
return (
|
||||
<div className="overflow-hidden">
|
||||
<Tooltip title={vectorStore.vector_store_id}>
|
||||
<span className="font-mono text-blue-500 text-xs font-normal">
|
||||
{vectorStore.vector_store_id}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Name",
|
||||
accessorKey: "vector_store_name",
|
||||
cell: ({ row }) => {
|
||||
const vectorStore = row.original;
|
||||
return (
|
||||
<Tooltip title={vectorStore.vector_store_name}>
|
||||
<span className="text-xs">
|
||||
{vectorStore.vector_store_name || "-"}
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Description",
|
||||
accessorKey: "vector_store_description",
|
||||
cell: ({ row }) => {
|
||||
const vectorStore = row.original;
|
||||
return (
|
||||
<Tooltip title={vectorStore.vector_store_description}>
|
||||
<span className="text-xs">
|
||||
{vectorStore.vector_store_description || "-"}
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Provider",
|
||||
accessorKey: "custom_llm_provider",
|
||||
cell: ({ row }) => {
|
||||
const vectorStore = row.original;
|
||||
const { displayName, logo } = getProviderLogoAndName(vectorStore.custom_llm_provider);
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
{logo && <img src={logo} alt={displayName} className="h-4 w-4" />}
|
||||
<span className="text-xs">{displayName}</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Created",
|
||||
accessorKey: "created_at",
|
||||
sortingFn: "datetime",
|
||||
cell: ({ row }) => {
|
||||
const vectorStore = row.original;
|
||||
return (
|
||||
<span className="text-xs">
|
||||
{new Date(vectorStore.created_at).toLocaleDateString()}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
header: "",
|
||||
cell: ({ row }) => {
|
||||
const vectorStore = row.original;
|
||||
return (
|
||||
<div className="flex space-x-2">
|
||||
<Icon
|
||||
icon={TrashIcon}
|
||||
size="sm"
|
||||
onClick={() => onDelete(vectorStore.vector_store_id)}
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns,
|
||||
state: {
|
||||
sorting,
|
||||
},
|
||||
onSortingChange: setSorting,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
enableSorting: true,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="rounded-lg custom-border relative">
|
||||
<div className="overflow-x-auto">
|
||||
<Table className="[&_td]:py-0.5 [&_th]:py-1">
|
||||
<TableHead>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => (
|
||||
<TableHeaderCell
|
||||
key={header.id}
|
||||
className={`py-1 h-8 ${
|
||||
header.id === 'actions'
|
||||
? 'sticky right-0 bg-white shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)]'
|
||||
: ''
|
||||
}`}
|
||||
onClick={header.column.getToggleSortingHandler()}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex items-center">
|
||||
{header.isPlaceholder ? null : (
|
||||
flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
{header.id !== 'actions' && (
|
||||
<div className="w-4">
|
||||
{header.column.getIsSorted() ? (
|
||||
{
|
||||
asc: <ChevronUpIcon className="h-4 w-4 text-blue-500" />,
|
||||
desc: <ChevronDownIcon className="h-4 w-4 text-blue-500" />
|
||||
}[header.column.getIsSorted() as string]
|
||||
) : (
|
||||
<SwitchVerticalIcon className="h-4 w-4 text-gray-400" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TableHeaderCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows.length > 0 ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow key={row.id} className="h-8">
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell
|
||||
key={cell.id}
|
||||
className={`py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap ${
|
||||
cell.column.id === 'actions'
|
||||
? 'sticky right-0 bg-white shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)]'
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-8 text-center">
|
||||
<div className="text-center text-gray-500">
|
||||
<p>No vector stores found</p>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VectorStoreTable;
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
Card,
|
||||
Icon,
|
||||
Button as TremorButton,
|
||||
Col,
|
||||
Text,
|
||||
Grid,
|
||||
} from "@tremor/react";
|
||||
import {
|
||||
InformationCircleIcon,
|
||||
RefreshIcon,
|
||||
} from "@heroicons/react/outline";
|
||||
import { message } from "antd";
|
||||
import { vectorStoreListCall, vectorStoreDeleteCall, credentialListCall, CredentialItem } from "../networking";
|
||||
import { VectorStore } from "./types";
|
||||
import VectorStoreTable from "./VectorStoreTable";
|
||||
import VectorStoreForm from "./VectorStoreForm";
|
||||
import DeleteModal from "./DeleteModal";
|
||||
|
||||
interface VectorStoreProps {
|
||||
accessToken: string | null;
|
||||
userID: string | null;
|
||||
userRole: string | null;
|
||||
}
|
||||
|
||||
const VectorStoreManagement: React.FC<VectorStoreProps> = ({
|
||||
accessToken,
|
||||
userID,
|
||||
userRole,
|
||||
}) => {
|
||||
const [vectorStores, setVectorStores] = useState<VectorStore[]>([]);
|
||||
const [isCreateModalVisible, setIsCreateModalVisible] = useState(false);
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [vectorStoreToDelete, setVectorStoreToDelete] = useState<string | null>(null);
|
||||
const [lastRefreshed, setLastRefreshed] = useState("");
|
||||
const [credentials, setCredentials] = useState<CredentialItem[]>([]);
|
||||
|
||||
const fetchVectorStores = async () => {
|
||||
if (!accessToken) return;
|
||||
try {
|
||||
const response = await vectorStoreListCall(accessToken);
|
||||
console.log("List vector stores response:", response);
|
||||
setVectorStores(response.data || []);
|
||||
} catch (error) {
|
||||
console.error("Error fetching vector stores:", error);
|
||||
message.error("Error fetching vector stores: " + error);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchCredentials = async () => {
|
||||
if (!accessToken) return;
|
||||
try {
|
||||
const response = await credentialListCall(accessToken);
|
||||
console.log("List credentials response:", response);
|
||||
setCredentials(response.credentials || []);
|
||||
} catch (error) {
|
||||
console.error("Error fetching credentials:", error);
|
||||
message.error("Error fetching credentials: " + error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRefreshClick = () => {
|
||||
fetchVectorStores();
|
||||
fetchCredentials();
|
||||
const currentDate = new Date();
|
||||
setLastRefreshed(currentDate.toLocaleString());
|
||||
};
|
||||
|
||||
const handleDelete = async (vectorStoreId: string) => {
|
||||
setVectorStoreToDelete(vectorStoreId);
|
||||
setIsDeleteModalOpen(true);
|
||||
};
|
||||
|
||||
const confirmDelete = async () => {
|
||||
if (!accessToken || !vectorStoreToDelete) return;
|
||||
try {
|
||||
await vectorStoreDeleteCall(accessToken, vectorStoreToDelete);
|
||||
message.success("Vector store deleted successfully");
|
||||
fetchVectorStores();
|
||||
} catch (error) {
|
||||
console.error("Error deleting vector store:", error);
|
||||
message.error("Error deleting vector store: " + error);
|
||||
}
|
||||
setIsDeleteModalOpen(false);
|
||||
setVectorStoreToDelete(null);
|
||||
};
|
||||
|
||||
const handleCreateSuccess = () => {
|
||||
setIsCreateModalVisible(false);
|
||||
fetchVectorStores();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchVectorStores();
|
||||
fetchCredentials();
|
||||
}, [accessToken]);
|
||||
|
||||
return (
|
||||
<div className="w-full mx-4 h-[75vh]">
|
||||
<div className="gap-2 p-8 h-[75vh] w-full mt-2">
|
||||
<div className="flex justify-between mt-2 w-full items-center mb-4">
|
||||
<h1>Vector Store Management</h1>
|
||||
<div className="flex items-center space-x-2">
|
||||
{lastRefreshed && <Text>Last Refreshed: {lastRefreshed}</Text>}
|
||||
<Icon
|
||||
icon={RefreshIcon}
|
||||
variant="shadow"
|
||||
size="xs"
|
||||
className="self-center cursor-pointer"
|
||||
onClick={handleRefreshClick}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Text className="mb-4">
|
||||
<p>You can use vector stores to store and retrieve LLM embeddings. Currently, we support Amazon Bedrock vector stores.</p>
|
||||
</Text>
|
||||
|
||||
<TremorButton
|
||||
className="mb-4"
|
||||
onClick={() => setIsCreateModalVisible(true)}
|
||||
>
|
||||
+ Create Vector Store
|
||||
</TremorButton>
|
||||
|
||||
<Grid numItems={1} className="gap-2 pt-2 pb-2 h-[75vh] w-full mt-2">
|
||||
<Col numColSpan={1}>
|
||||
<VectorStoreTable
|
||||
data={vectorStores}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
</Col>
|
||||
</Grid>
|
||||
|
||||
{/* Create Vector Store Modal */}
|
||||
<VectorStoreForm
|
||||
isVisible={isCreateModalVisible}
|
||||
onCancel={() => setIsCreateModalVisible(false)}
|
||||
onSuccess={handleCreateSuccess}
|
||||
accessToken={accessToken}
|
||||
credentials={credentials}
|
||||
/>
|
||||
|
||||
{/* Delete Confirmation Modal */}
|
||||
<DeleteModal
|
||||
isVisible={isDeleteModalOpen}
|
||||
onCancel={() => setIsDeleteModalOpen(false)}
|
||||
onConfirm={confirmDelete}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VectorStoreManagement;
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
export interface VectorStore {
|
||||
vector_store_id: string;
|
||||
custom_llm_provider: string;
|
||||
vector_store_name?: string;
|
||||
vector_store_description?: string;
|
||||
vector_store_metadata?: Record<string, any>;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
created_by?: string;
|
||||
updated_by?: string;
|
||||
}
|
||||
|
||||
export interface VectorStoreInfoRequest {
|
||||
vector_store_id: string;
|
||||
}
|
||||
|
||||
export interface VectorStoreNewRequest {
|
||||
vector_store_id: string;
|
||||
custom_llm_provider: string;
|
||||
vector_store_name?: string;
|
||||
vector_store_description?: string;
|
||||
vector_store_metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface VectorStoreUpdateRequest {
|
||||
vector_store_id: string;
|
||||
custom_llm_provider?: string;
|
||||
vector_store_name?: string;
|
||||
vector_store_description?: string;
|
||||
vector_store_metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface VectorStoreDeleteRequest {
|
||||
vector_store_id: string;
|
||||
}
|
||||
|
||||
export interface VectorStoreListResponse {
|
||||
object: string;
|
||||
data: VectorStore[];
|
||||
total_count: number;
|
||||
current_page: number;
|
||||
total_pages: number;
|
||||
}
|
||||
|
|
@ -0,0 +1,357 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
Card,
|
||||
Text,
|
||||
Title,
|
||||
Button,
|
||||
Badge,
|
||||
} from "@tremor/react";
|
||||
import {
|
||||
Form,
|
||||
Input,
|
||||
Select as Select2,
|
||||
message,
|
||||
Tooltip,
|
||||
Button as AntButton,
|
||||
} from "antd";
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import { vectorStoreInfoCall, credentialListCall, CredentialItem } from "../networking";
|
||||
import { VectorStore } from "./types";
|
||||
import { Providers, providerLogoMap, provider_map } from "../provider_info_helpers";
|
||||
|
||||
interface VectorStoreInfoViewProps {
|
||||
vectorStoreId: string;
|
||||
onClose: () => void;
|
||||
accessToken: string | null;
|
||||
is_admin: boolean;
|
||||
editVectorStore: boolean;
|
||||
}
|
||||
|
||||
const VectorStoreInfoView: React.FC<VectorStoreInfoViewProps> = ({
|
||||
vectorStoreId,
|
||||
onClose,
|
||||
accessToken,
|
||||
is_admin,
|
||||
editVectorStore,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [vectorStoreDetails, setVectorStoreDetails] = useState<VectorStore | null>(null);
|
||||
const [isEditing, setIsEditing] = useState<boolean>(editVectorStore);
|
||||
const [metadataString, setMetadataString] = useState<string>("{}");
|
||||
const [credentials, setCredentials] = useState<CredentialItem[]>([]);
|
||||
|
||||
const fetchVectorStoreDetails = async () => {
|
||||
if (!accessToken) return;
|
||||
try {
|
||||
const response = await vectorStoreInfoCall(accessToken, vectorStoreId);
|
||||
if (response && response.vector_store) {
|
||||
setVectorStoreDetails(response.vector_store);
|
||||
|
||||
// If metadata exists and is an object, stringify it for display/editing
|
||||
if (response.vector_store.vector_store_metadata) {
|
||||
const metadata = typeof response.vector_store.vector_store_metadata === 'string'
|
||||
? JSON.parse(response.vector_store.vector_store_metadata)
|
||||
: response.vector_store.vector_store_metadata;
|
||||
setMetadataString(JSON.stringify(metadata, null, 2));
|
||||
}
|
||||
|
||||
if (editVectorStore) {
|
||||
form.setFieldsValue({
|
||||
vector_store_id: response.vector_store.vector_store_id,
|
||||
custom_llm_provider: response.vector_store.custom_llm_provider,
|
||||
vector_store_name: response.vector_store.vector_store_name,
|
||||
vector_store_description: response.vector_store.vector_store_description,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching vector store details:", error);
|
||||
message.error("Error fetching vector store details: " + error);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchCredentials = async () => {
|
||||
if (!accessToken) return;
|
||||
try {
|
||||
const response = await credentialListCall(accessToken);
|
||||
console.log("List credentials response:", response);
|
||||
setCredentials(response.credentials || []);
|
||||
} catch (error) {
|
||||
console.error("Error fetching credentials:", error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchVectorStoreDetails();
|
||||
fetchCredentials();
|
||||
}, [vectorStoreId, accessToken]);
|
||||
|
||||
const handleSave = async (values: any) => {
|
||||
if (!accessToken) return;
|
||||
try {
|
||||
// Parse the metadata JSON string
|
||||
let metadata = {};
|
||||
try {
|
||||
metadata = metadataString ? JSON.parse(metadataString) : {};
|
||||
} catch (e) {
|
||||
message.error("Invalid JSON in metadata field");
|
||||
return;
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
vector_store_id: values.vector_store_id,
|
||||
custom_llm_provider: values.custom_llm_provider,
|
||||
vector_store_name: values.vector_store_name,
|
||||
vector_store_description: values.vector_store_description,
|
||||
vector_store_metadata: metadata,
|
||||
};
|
||||
|
||||
// Use the updated data to call an update endpoint
|
||||
// await vectorStoreUpdateCall(accessToken, updateData);
|
||||
message.success("Vector store updated successfully");
|
||||
setIsEditing(false);
|
||||
fetchVectorStoreDetails();
|
||||
} catch (error) {
|
||||
console.error("Error updating vector store:", error);
|
||||
message.error("Error updating vector store: " + error);
|
||||
}
|
||||
};
|
||||
|
||||
if (!vectorStoreDetails) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<div>
|
||||
<Button onClick={onClose} className="mb-4">← Back to Vector Stores</Button>
|
||||
<Title>Vector Store ID: {vectorStoreDetails.vector_store_id}</Title>
|
||||
<Text className="text-gray-500">{vectorStoreDetails.vector_store_description || "No description"}</Text>
|
||||
</div>
|
||||
{is_admin && !isEditing && (
|
||||
<Button onClick={() => setIsEditing(true)}>Edit Vector Store</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isEditing ? (
|
||||
<Card>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={handleSave}
|
||||
layout="vertical"
|
||||
initialValues={vectorStoreDetails}
|
||||
>
|
||||
<Form.Item
|
||||
label="Vector Store ID"
|
||||
name="vector_store_id"
|
||||
rules={[{ required: true, message: "Please input a vector store ID" }]}
|
||||
>
|
||||
<Input disabled />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Vector Store Name"
|
||||
name="vector_store_name"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Description"
|
||||
name="vector_store_description"
|
||||
>
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={
|
||||
<span>
|
||||
Provider{' '}
|
||||
<Tooltip title="Select the provider for this vector store">
|
||||
<InfoCircleOutlined style={{ marginLeft: '4px' }} />
|
||||
</Tooltip>
|
||||
</span>
|
||||
}
|
||||
name="custom_llm_provider"
|
||||
rules={[{ required: true, message: "Please select a provider" }]}
|
||||
>
|
||||
<Select2>
|
||||
{Object.entries(Providers).map(([providerEnum, providerDisplayName]) => {
|
||||
// Currently only showing Bedrock since it's the only supported provider
|
||||
if (providerEnum === 'Bedrock') {
|
||||
return (
|
||||
<Select2.Option key={providerEnum} value={provider_map[providerEnum]}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<img
|
||||
src={providerLogoMap[providerDisplayName]}
|
||||
alt={`${providerEnum} logo`}
|
||||
className="w-5 h-5"
|
||||
onError={(e) => {
|
||||
// Create a div with provider initial as fallback
|
||||
const target = e.target as HTMLImageElement;
|
||||
const parent = target.parentElement;
|
||||
if (parent) {
|
||||
const fallbackDiv = document.createElement('div');
|
||||
fallbackDiv.className = 'w-5 h-5 rounded-full bg-gray-200 flex items-center justify-center text-xs';
|
||||
fallbackDiv.textContent = providerDisplayName.charAt(0);
|
||||
parent.replaceChild(fallbackDiv, target);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<span>{providerDisplayName}</span>
|
||||
</div>
|
||||
</Select2.Option>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
</Select2>
|
||||
</Form.Item>
|
||||
|
||||
{/* Credentials */}
|
||||
<div className="mb-4">
|
||||
<Text className="text-sm text-gray-500 mb-2">
|
||||
Either select existing credentials OR enter provider credentials below
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<Form.Item
|
||||
label="Existing Credentials"
|
||||
name="litellm_credential_name"
|
||||
>
|
||||
<Select2
|
||||
showSearch
|
||||
placeholder="Select or search for existing credentials"
|
||||
optionFilterProp="children"
|
||||
filterOption={(input, option) =>
|
||||
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
||||
}
|
||||
options={[
|
||||
{ value: null, label: 'None' },
|
||||
...credentials.map((credential) => ({
|
||||
value: credential.credential_name,
|
||||
label: credential.credential_name
|
||||
}))
|
||||
]}
|
||||
allowClear
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<div className="flex items-center my-4">
|
||||
<div className="flex-grow border-t border-gray-200"></div>
|
||||
<span className="px-4 text-gray-500 text-sm">OR</span>
|
||||
<div className="flex-grow border-t border-gray-200"></div>
|
||||
</div>
|
||||
|
||||
<Form.Item
|
||||
label={
|
||||
<span>
|
||||
Metadata{' '}
|
||||
<Tooltip title="JSON metadata for the vector store">
|
||||
<InfoCircleOutlined style={{ marginLeft: '4px' }} />
|
||||
</Tooltip>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<Input.TextArea
|
||||
rows={4}
|
||||
value={metadataString}
|
||||
onChange={(e) => setMetadataString(e.target.value)}
|
||||
placeholder='{"key": "value"}'
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<div className="flex justify-end space-x-2">
|
||||
<AntButton onClick={() => setIsEditing(false)}>Cancel</AntButton>
|
||||
<AntButton type="primary" htmlType="submit">Save Changes</AntButton>
|
||||
</div>
|
||||
</Form>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<Title>Vector Store Details</Title>
|
||||
<div className="space-y-4 mt-4">
|
||||
<div>
|
||||
<Text className="font-medium">ID</Text>
|
||||
<Text>{vectorStoreDetails.vector_store_id}</Text>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="font-medium">Name</Text>
|
||||
<Text>{vectorStoreDetails.vector_store_name || "-"}</Text>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="font-medium">Description</Text>
|
||||
<Text>{vectorStoreDetails.vector_store_description || "-"}</Text>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="font-medium">Provider</Text>
|
||||
<div className="flex items-center space-x-2 mt-1">
|
||||
{(() => {
|
||||
const provider = vectorStoreDetails.custom_llm_provider || "bedrock";
|
||||
const { displayName, logo } = (() => {
|
||||
// Find the enum key by matching provider_map values
|
||||
const enumKey = Object.keys(provider_map).find(
|
||||
key => provider_map[key].toLowerCase() === provider.toLowerCase()
|
||||
);
|
||||
|
||||
if (!enumKey) {
|
||||
return { displayName: provider, logo: "" };
|
||||
}
|
||||
|
||||
// Get the display name from Providers enum and logo from map
|
||||
const displayName = Providers[enumKey as keyof typeof Providers];
|
||||
const logo = providerLogoMap[displayName];
|
||||
|
||||
return { displayName, logo };
|
||||
})();
|
||||
|
||||
return (
|
||||
<>
|
||||
{logo && (
|
||||
<img
|
||||
src={logo}
|
||||
alt={`${displayName} logo`}
|
||||
className="w-5 h-5"
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
const parent = target.parentElement;
|
||||
if (parent) {
|
||||
const fallbackDiv = document.createElement('div');
|
||||
fallbackDiv.className = 'w-5 h-5 rounded-full bg-gray-200 flex items-center justify-center text-xs';
|
||||
fallbackDiv.textContent = displayName.charAt(0);
|
||||
parent.replaceChild(fallbackDiv, target);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Badge color="blue">{displayName}</Badge>
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="font-medium">Metadata</Text>
|
||||
<div className="bg-gray-50 p-3 rounded mt-2 font-mono text-xs overflow-auto max-h-48">
|
||||
<pre>{metadataString}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="font-medium">Created</Text>
|
||||
<Text>{vectorStoreDetails.created_at ? new Date(vectorStoreDetails.created_at).toLocaleString() : "-"}</Text>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="font-medium">Last Updated</Text>
|
||||
<Text>{vectorStoreDetails.updated_at ? new Date(vectorStoreDetails.updated_at).toLocaleString() : "-"}</Text>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VectorStoreInfoView;
|
||||
Loading…
Add table
Reference in a new issue