mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-17 23:51:30 +00:00
Merge pull request #9267 from BerriAI/litellm_dev_03_14_2025_p1
Support reusing existing model credentials
This commit is contained in:
commit
dbc17a8c65
12 changed files with 871 additions and 243 deletions
|
|
@ -719,25 +719,9 @@ class Logging(LiteLLMLoggingBaseClass):
|
|||
|
||||
Masks the headers of the request sent from LiteLLM
|
||||
"""
|
||||
sensitive_keywords = [
|
||||
"authorization",
|
||||
"token",
|
||||
"key",
|
||||
"secret",
|
||||
]
|
||||
return {
|
||||
k: (
|
||||
(v[:-44] + "*" * 44)
|
||||
if (isinstance(v, str) and len(v) > 44)
|
||||
else "*****"
|
||||
)
|
||||
for k, v in headers.items()
|
||||
if not ignore_sensitive_headers
|
||||
or not any(
|
||||
sensitive_keyword in k.lower()
|
||||
for sensitive_keyword in sensitive_keywords
|
||||
)
|
||||
}
|
||||
return _get_masked_values(
|
||||
headers, ignore_sensitive_values=ignore_sensitive_headers
|
||||
)
|
||||
|
||||
def post_call(
|
||||
self, original_response, input=None, api_key=None, additional_args={}
|
||||
|
|
@ -2413,6 +2397,58 @@ class Logging(LiteLLMLoggingBaseClass):
|
|||
return result
|
||||
|
||||
|
||||
def _get_masked_values(
|
||||
sensitive_object: dict,
|
||||
ignore_sensitive_values: bool = False,
|
||||
mask_all_values: bool = False,
|
||||
unmasked_length: int = 4,
|
||||
number_of_asterisks: Optional[int] = 4,
|
||||
) -> dict:
|
||||
"""
|
||||
Internal debugging helper function
|
||||
|
||||
Masks the headers of the request sent from LiteLLM
|
||||
|
||||
Args:
|
||||
masked_length: Optional length for the masked portion (number of *). If set, will use exactly this many *
|
||||
regardless of original string length. The total length will be unmasked_length + masked_length.
|
||||
"""
|
||||
sensitive_keywords = [
|
||||
"authorization",
|
||||
"token",
|
||||
"key",
|
||||
"secret",
|
||||
]
|
||||
return {
|
||||
k: (
|
||||
(
|
||||
v[: unmasked_length // 2]
|
||||
+ "*" * number_of_asterisks
|
||||
+ v[-unmasked_length // 2 :]
|
||||
)
|
||||
if (
|
||||
isinstance(v, str)
|
||||
and len(v) > unmasked_length
|
||||
and number_of_asterisks is not None
|
||||
)
|
||||
else (
|
||||
(
|
||||
v[: unmasked_length // 2]
|
||||
+ "*" * (len(v) - unmasked_length)
|
||||
+ v[-unmasked_length // 2 :]
|
||||
)
|
||||
if (isinstance(v, str) and len(v) > unmasked_length)
|
||||
else "*****"
|
||||
)
|
||||
)
|
||||
for k, v in sensitive_object.items()
|
||||
if not ignore_sensitive_values
|
||||
or not any(
|
||||
sensitive_keyword in k.lower() for sensitive_keyword in sensitive_keywords
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
def set_callbacks(callback_list, function_id=None): # noqa: PLR0915
|
||||
"""
|
||||
Globally sets the callback client
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -2,16 +2,19 @@
|
|||
CRUD endpoints for storing reusable credentials.
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, Response
|
||||
|
||||
import litellm
|
||||
from litellm._logging import verbose_proxy_logger
|
||||
from litellm.litellm_core_utils.credential_accessor import CredentialAccessor
|
||||
from litellm.litellm_core_utils.litellm_logging import _get_masked_values
|
||||
from litellm.proxy._types import CommonProxyErrors, UserAPIKeyAuth
|
||||
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
|
||||
from litellm.proxy.common_utils.encrypt_decrypt_utils import encrypt_value_helper
|
||||
from litellm.proxy.utils import handle_exception_on_proxy, jsonify_object
|
||||
from litellm.types.utils import CredentialItem
|
||||
from litellm.types.utils import CreateCredentialItem, CredentialItem
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
|
@ -35,7 +38,7 @@ class CredentialHelperUtils:
|
|||
async def create_credential(
|
||||
request: Request,
|
||||
fastapi_response: Response,
|
||||
credential: CredentialItem,
|
||||
credential: CreateCredentialItem,
|
||||
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
|
||||
):
|
||||
"""
|
||||
|
|
@ -43,7 +46,7 @@ async def create_credential(
|
|||
Stores credential in DB.
|
||||
Reloads credentials in memory.
|
||||
"""
|
||||
from litellm.proxy.proxy_server import prisma_client
|
||||
from litellm.proxy.proxy_server import llm_router, prisma_client
|
||||
|
||||
try:
|
||||
if prisma_client is None:
|
||||
|
|
@ -51,9 +54,35 @@ async def create_credential(
|
|||
status_code=500,
|
||||
detail={"error": CommonProxyErrors.db_not_connected_error.value},
|
||||
)
|
||||
if credential.model_id:
|
||||
if llm_router is None:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="LLM router not found. Please ensure you have a valid router instance.",
|
||||
)
|
||||
# get model from router
|
||||
model = llm_router.get_deployment(credential.model_id)
|
||||
if model is None:
|
||||
raise HTTPException(status_code=404, detail="Model not found")
|
||||
credential_values = llm_router.get_deployment_credentials(
|
||||
credential.model_id
|
||||
)
|
||||
if credential_values is None:
|
||||
raise HTTPException(status_code=404, detail="Model not found")
|
||||
credential.credential_values = credential_values
|
||||
|
||||
if credential.credential_values is None:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Credential values are required. Unable to infer credential values from model ID.",
|
||||
)
|
||||
processed_credential = CredentialItem(
|
||||
credential_name=credential.credential_name,
|
||||
credential_values=credential.credential_values,
|
||||
credential_info=credential.credential_info,
|
||||
)
|
||||
encrypted_credential = CredentialHelperUtils.encrypt_credential_values(
|
||||
credential
|
||||
processed_credential
|
||||
)
|
||||
credentials_dict = encrypted_credential.model_dump()
|
||||
credentials_dict_jsonified = jsonify_object(credentials_dict)
|
||||
|
|
@ -66,7 +95,7 @@ async def create_credential(
|
|||
)
|
||||
|
||||
## ADD TO LITELLM ##
|
||||
CredentialAccessor.upsert_credentials([credential])
|
||||
CredentialAccessor.upsert_credentials([processed_credential])
|
||||
|
||||
return {"success": True, "message": "Credential created successfully"}
|
||||
except Exception as e:
|
||||
|
|
@ -91,6 +120,7 @@ async def get_credentials(
|
|||
masked_credentials = [
|
||||
{
|
||||
"credential_name": credential.credential_name,
|
||||
"credential_values": _get_masked_values(credential.credential_values),
|
||||
"credential_info": credential.credential_info,
|
||||
}
|
||||
for credential in litellm.credential_list
|
||||
|
|
@ -101,30 +131,75 @@ async def get_credentials(
|
|||
|
||||
|
||||
@router.get(
|
||||
"/credentials/{credential_name}",
|
||||
"/credentials/by_name/{credential_name}",
|
||||
dependencies=[Depends(user_api_key_auth)],
|
||||
tags=["credential management"],
|
||||
response_model=CredentialItem,
|
||||
)
|
||||
@router.get(
|
||||
"/credentials/by_model/{model_id}",
|
||||
dependencies=[Depends(user_api_key_auth)],
|
||||
tags=["credential management"],
|
||||
response_model=CredentialItem,
|
||||
)
|
||||
async def get_credential(
|
||||
request: Request,
|
||||
fastapi_response: Response,
|
||||
credential_name: str,
|
||||
credential_name: Optional[str] = None,
|
||||
model_id: Optional[str] = None,
|
||||
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
|
||||
):
|
||||
"""
|
||||
[BETA] endpoint. This might change unexpectedly.
|
||||
"""
|
||||
from litellm.proxy.proxy_server import llm_router
|
||||
|
||||
try:
|
||||
for credential in litellm.credential_list:
|
||||
if credential.credential_name == credential_name:
|
||||
masked_credential = {
|
||||
"credential_name": credential.credential_name,
|
||||
"credential_values": credential.credential_values,
|
||||
}
|
||||
return {"success": True, "credential": masked_credential}
|
||||
return {"success": False, "message": "Credential not found"}
|
||||
if model_id:
|
||||
if llm_router is None:
|
||||
raise HTTPException(status_code=500, detail="LLM router not found")
|
||||
model = llm_router.get_deployment(model_id)
|
||||
if model is None:
|
||||
raise HTTPException(status_code=404, detail="Model not found")
|
||||
credential_values = llm_router.get_deployment_credentials(model_id)
|
||||
if credential_values is None:
|
||||
raise HTTPException(status_code=404, detail="Model not found")
|
||||
masked_credential_values = _get_masked_values(
|
||||
credential_values,
|
||||
unmasked_length=4,
|
||||
number_of_asterisks=4,
|
||||
)
|
||||
credential = CredentialItem(
|
||||
credential_name="{}-credential-{}".format(model.model_name, model_id),
|
||||
credential_values=masked_credential_values,
|
||||
credential_info={},
|
||||
)
|
||||
# return credential object
|
||||
return credential
|
||||
elif credential_name:
|
||||
for credential in litellm.credential_list:
|
||||
if credential.credential_name == credential_name:
|
||||
masked_credential = CredentialItem(
|
||||
credential_name=credential.credential_name,
|
||||
credential_values=_get_masked_values(
|
||||
credential.credential_values,
|
||||
unmasked_length=4,
|
||||
number_of_asterisks=4,
|
||||
),
|
||||
credential_info=credential.credential_info,
|
||||
)
|
||||
return masked_credential
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="Credential not found. Got credential name: " + credential_name,
|
||||
)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=404, detail="Credential name or model ID required"
|
||||
)
|
||||
except Exception as e:
|
||||
return handle_exception_on_proxy(e)
|
||||
verbose_proxy_logger.exception(e)
|
||||
raise handle_exception_on_proxy(e)
|
||||
|
||||
|
||||
@router.delete(
|
||||
|
|
@ -164,7 +239,45 @@ async def delete_credential(
|
|||
return handle_exception_on_proxy(e)
|
||||
|
||||
|
||||
@router.put(
|
||||
def update_db_credential(
|
||||
db_credential: CredentialItem, updated_patch: CredentialItem
|
||||
) -> CredentialItem:
|
||||
"""
|
||||
Update a credential in the DB.
|
||||
"""
|
||||
merged_credential = CredentialItem(
|
||||
credential_name=db_credential.credential_name,
|
||||
credential_info=db_credential.credential_info,
|
||||
credential_values=db_credential.credential_values,
|
||||
)
|
||||
|
||||
encrypted_credential = CredentialHelperUtils.encrypt_credential_values(
|
||||
updated_patch
|
||||
)
|
||||
# update model name
|
||||
if encrypted_credential.credential_name:
|
||||
merged_credential.credential_name = encrypted_credential.credential_name
|
||||
|
||||
# update litellm params
|
||||
if encrypted_credential.credential_values:
|
||||
# Encrypt any sensitive values
|
||||
encrypted_params = {
|
||||
k: v for k, v in encrypted_credential.credential_values.items()
|
||||
}
|
||||
|
||||
merged_credential.credential_values.update(encrypted_params)
|
||||
|
||||
# update model info
|
||||
if encrypted_credential.credential_info:
|
||||
"""Update credential info"""
|
||||
if "credential_info" not in merged_credential.credential_info:
|
||||
merged_credential.credential_info = {}
|
||||
merged_credential.credential_info.update(encrypted_credential.credential_info)
|
||||
|
||||
return merged_credential
|
||||
|
||||
|
||||
@router.patch(
|
||||
"/credentials/{credential_name}",
|
||||
dependencies=[Depends(user_api_key_auth)],
|
||||
tags=["credential management"],
|
||||
|
|
@ -187,7 +300,13 @@ async def update_credential(
|
|||
status_code=500,
|
||||
detail={"error": CommonProxyErrors.db_not_connected_error.value},
|
||||
)
|
||||
credential_object_jsonified = jsonify_object(credential.model_dump())
|
||||
db_credential = await prisma_client.db.litellm_credentialstable.find_unique(
|
||||
where={"credential_name": credential_name},
|
||||
)
|
||||
if db_credential is None:
|
||||
raise HTTPException(status_code=404, detail="Credential not found in DB.")
|
||||
merged_credential = update_db_credential(db_credential, credential)
|
||||
credential_object_jsonified = jsonify_object(merged_credential.model_dump())
|
||||
await prisma_client.db.litellm_credentialstable.update(
|
||||
where={"credential_name": credential_name},
|
||||
data={
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ from litellm.types.router import (
|
|||
AlertingConfig,
|
||||
AllowedFailsPolicy,
|
||||
AssistantsTypedDict,
|
||||
CredentialLiteLLMParams,
|
||||
CustomRoutingStrategyBase,
|
||||
Deployment,
|
||||
DeploymentTypedDict,
|
||||
|
|
@ -636,29 +637,6 @@ class Router:
|
|||
if self.cache.redis_cache is None:
|
||||
self.cache.redis_cache = cache
|
||||
|
||||
def initialize_assistants_endpoint(self):
|
||||
## INITIALIZE PASS THROUGH ASSISTANTS ENDPOINT ##
|
||||
self.acreate_assistants = self.factory_function(litellm.acreate_assistants)
|
||||
self.adelete_assistant = self.factory_function(litellm.adelete_assistant)
|
||||
self.aget_assistants = self.factory_function(litellm.aget_assistants)
|
||||
self.acreate_thread = self.factory_function(litellm.acreate_thread)
|
||||
self.aget_thread = self.factory_function(litellm.aget_thread)
|
||||
self.a_add_message = self.factory_function(litellm.a_add_message)
|
||||
self.aget_messages = self.factory_function(litellm.aget_messages)
|
||||
self.arun_thread = self.factory_function(litellm.arun_thread)
|
||||
|
||||
def initialize_router_endpoints(self):
|
||||
self.amoderation = self.factory_function(
|
||||
litellm.amoderation, call_type="moderation"
|
||||
)
|
||||
self.aanthropic_messages = self.factory_function(
|
||||
litellm.anthropic_messages, call_type="anthropic_messages"
|
||||
)
|
||||
self.aresponses = self.factory_function(
|
||||
litellm.aresponses, call_type="aresponses"
|
||||
)
|
||||
self.responses = self.factory_function(litellm.responses, call_type="responses")
|
||||
|
||||
def routing_strategy_init(
|
||||
self, routing_strategy: Union[RoutingStrategy, str], routing_strategy_args: dict
|
||||
):
|
||||
|
|
@ -724,6 +702,29 @@ class Router:
|
|||
else:
|
||||
pass
|
||||
|
||||
def initialize_assistants_endpoint(self):
|
||||
## INITIALIZE PASS THROUGH ASSISTANTS ENDPOINT ##
|
||||
self.acreate_assistants = self.factory_function(litellm.acreate_assistants)
|
||||
self.adelete_assistant = self.factory_function(litellm.adelete_assistant)
|
||||
self.aget_assistants = self.factory_function(litellm.aget_assistants)
|
||||
self.acreate_thread = self.factory_function(litellm.acreate_thread)
|
||||
self.aget_thread = self.factory_function(litellm.aget_thread)
|
||||
self.a_add_message = self.factory_function(litellm.a_add_message)
|
||||
self.aget_messages = self.factory_function(litellm.aget_messages)
|
||||
self.arun_thread = self.factory_function(litellm.arun_thread)
|
||||
|
||||
def initialize_router_endpoints(self):
|
||||
self.amoderation = self.factory_function(
|
||||
litellm.amoderation, call_type="moderation"
|
||||
)
|
||||
self.aanthropic_messages = self.factory_function(
|
||||
litellm.anthropic_messages, call_type="anthropic_messages"
|
||||
)
|
||||
self.aresponses = self.factory_function(
|
||||
litellm.aresponses, call_type="aresponses"
|
||||
)
|
||||
self.responses = self.factory_function(litellm.responses, call_type="responses")
|
||||
|
||||
def validate_fallbacks(self, fallback_param: Optional[List]):
|
||||
"""
|
||||
Validate the fallbacks parameter.
|
||||
|
|
@ -4625,6 +4626,17 @@ class Router:
|
|||
raise Exception("Model invalid format - {}".format(type(model)))
|
||||
return None
|
||||
|
||||
def get_deployment_credentials(self, model_id: str) -> Optional[dict]:
|
||||
"""
|
||||
Returns -> dict of credentials for a given model id
|
||||
"""
|
||||
deployment = self.get_deployment(model_id=model_id)
|
||||
if deployment is None:
|
||||
return None
|
||||
return CredentialLiteLLMParams(
|
||||
**deployment.litellm_params.model_dump(exclude_none=True)
|
||||
).model_dump(exclude_none=True)
|
||||
|
||||
def get_deployment_by_model_group_name(
|
||||
self, model_group_name: str
|
||||
) -> Optional[Deployment]:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from typing import Iterable, List, Optional, Union
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, validator
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from typing_extensions import Literal, Required, TypedDict
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -144,7 +144,26 @@ class ModelInfo(BaseModel):
|
|||
setattr(self, key, value)
|
||||
|
||||
|
||||
class GenericLiteLLMParams(BaseModel):
|
||||
class CredentialLiteLLMParams(BaseModel):
|
||||
api_key: Optional[str] = None
|
||||
api_base: Optional[str] = None
|
||||
api_version: Optional[str] = None
|
||||
## VERTEX AI ##
|
||||
vertex_project: Optional[str] = None
|
||||
vertex_location: Optional[str] = None
|
||||
vertex_credentials: Optional[Union[str, dict]] = None
|
||||
## UNIFIED PROJECT/REGION ##
|
||||
region_name: Optional[str] = None
|
||||
|
||||
## AWS BEDROCK / SAGEMAKER ##
|
||||
aws_access_key_id: Optional[str] = None
|
||||
aws_secret_access_key: Optional[str] = None
|
||||
aws_region_name: Optional[str] = None
|
||||
## IBM WATSONX ##
|
||||
watsonx_region_name: Optional[str] = None
|
||||
|
||||
|
||||
class GenericLiteLLMParams(CredentialLiteLLMParams):
|
||||
"""
|
||||
LiteLLM Params without 'model' arg (used across completion / assistants api)
|
||||
"""
|
||||
|
|
@ -152,9 +171,6 @@ class GenericLiteLLMParams(BaseModel):
|
|||
custom_llm_provider: Optional[str] = None
|
||||
tpm: Optional[int] = None
|
||||
rpm: Optional[int] = None
|
||||
api_key: Optional[str] = None
|
||||
api_base: Optional[str] = None
|
||||
api_version: Optional[str] = None
|
||||
timeout: Optional[Union[float, str, httpx.Timeout]] = (
|
||||
None # if str, pass in as os.environ/
|
||||
)
|
||||
|
|
@ -167,18 +183,7 @@ class GenericLiteLLMParams(BaseModel):
|
|||
|
||||
## LOGGING PARAMS ##
|
||||
litellm_trace_id: Optional[str] = None
|
||||
## UNIFIED PROJECT/REGION ##
|
||||
region_name: Optional[str] = None
|
||||
## VERTEX AI ##
|
||||
vertex_project: Optional[str] = None
|
||||
vertex_location: Optional[str] = None
|
||||
vertex_credentials: Optional[Union[str, dict]] = None
|
||||
## AWS BEDROCK / SAGEMAKER ##
|
||||
aws_access_key_id: Optional[str] = None
|
||||
aws_secret_access_key: Optional[str] = None
|
||||
aws_region_name: Optional[str] = None
|
||||
## IBM WATSONX ##
|
||||
watsonx_region_name: Optional[str] = None
|
||||
|
||||
## CUSTOM PRICING ##
|
||||
input_cost_per_token: Optional[float] = None
|
||||
output_cost_per_token: Optional[float] = None
|
||||
|
|
@ -245,7 +250,11 @@ class GenericLiteLLMParams(BaseModel):
|
|||
args.pop("__class__", None)
|
||||
if max_retries is not None and isinstance(max_retries, str):
|
||||
max_retries = int(max_retries) # cast to int
|
||||
super().__init__(max_retries=max_retries, **args, **params)
|
||||
# We need to keep max_retries in args since it's a parameter of GenericLiteLLMParams
|
||||
args["max_retries"] = (
|
||||
max_retries # Put max_retries back in args after popping it
|
||||
)
|
||||
super().__init__(**args, **params)
|
||||
|
||||
def __contains__(self, key):
|
||||
# Define custom behavior for the 'in' operator
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ from openai.types.moderation import (
|
|||
CategoryScores,
|
||||
)
|
||||
from openai.types.moderation_create_response import Moderation, ModerationCreateResponse
|
||||
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr
|
||||
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr, model_validator
|
||||
from typing_extensions import Callable, Dict, Required, TypedDict, override
|
||||
|
||||
import litellm
|
||||
|
|
@ -2053,7 +2053,22 @@ class RawRequestTypedDict(TypedDict, total=False):
|
|||
error: Optional[str]
|
||||
|
||||
|
||||
class CredentialItem(BaseModel):
|
||||
class CredentialBase(BaseModel):
|
||||
credential_name: str
|
||||
credential_values: dict
|
||||
credential_info: dict
|
||||
|
||||
|
||||
class CredentialItem(CredentialBase):
|
||||
credential_values: dict
|
||||
|
||||
|
||||
class CreateCredentialItem(CredentialBase):
|
||||
credential_values: Optional[dict] = None
|
||||
model_id: Optional[str] = None
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
def check_credential_params(cls, values):
|
||||
if not values.get("credential_values") and not values.get("model_id"):
|
||||
raise ValueError("Either credential_values or model_id must be set")
|
||||
return values
|
||||
|
|
|
|||
|
|
@ -3,132 +3,392 @@ import { Form, Select } from "antd";
|
|||
import { TextInput, Text } from "@tremor/react";
|
||||
import { Row, Col, Typography, Button as Button2, Upload, UploadProps } from "antd";
|
||||
import { UploadOutlined } from "@ant-design/icons";
|
||||
import { Providers } from "../provider_info_helpers";
|
||||
import { provider_map, Providers } from "../provider_info_helpers";
|
||||
import { CredentialItem } from "../networking";
|
||||
const { Link } = Typography;
|
||||
|
||||
|
||||
interface ProviderSpecificFieldsProps {
|
||||
selectedProvider: Providers;
|
||||
uploadProps?: UploadProps;
|
||||
}
|
||||
|
||||
interface ProviderCredentialField {
|
||||
key: string;
|
||||
label: string;
|
||||
placeholder?: string;
|
||||
tooltip?: string;
|
||||
required?: boolean;
|
||||
type?: "text" | "password" | "select" | "upload";
|
||||
options?: string[];
|
||||
defaultValue?: string;
|
||||
}
|
||||
|
||||
export interface CredentialValues {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
|
||||
export const createCredentialFromModel = (provider: string, modelData: any): CredentialItem => {
|
||||
console.log("provider", provider);
|
||||
console.log("modelData", modelData);
|
||||
const enumKey = Object.keys(provider_map).find(
|
||||
key => provider_map[key].toLowerCase() === provider.toLowerCase()
|
||||
);
|
||||
if (!enumKey) {
|
||||
throw new Error(`Provider ${provider} not found in provider_map`);
|
||||
}
|
||||
const providerEnum = Providers[enumKey as keyof typeof Providers];
|
||||
const providerFields = PROVIDER_CREDENTIAL_FIELDS[providerEnum] || [];
|
||||
const credentialValues: object = {};
|
||||
|
||||
console.log("providerFields", providerFields);
|
||||
|
||||
// Go through each field defined for this provider
|
||||
providerFields.forEach(field => {
|
||||
const value = modelData.litellm_params[field.key];
|
||||
console.log("field", field);
|
||||
console.log("value", value);
|
||||
if (value !== undefined) {
|
||||
(credentialValues as Record<string, string>)[field.key] = value.toString();
|
||||
}
|
||||
});
|
||||
|
||||
const credential: CredentialItem = {
|
||||
credential_name: `${provider}-credential-${Math.floor(Math.random() * 1000000)}`,
|
||||
credential_values: credentialValues,
|
||||
credential_info: {
|
||||
custom_llm_provider: provider,
|
||||
description: `Credential for ${provider}. Created from model ${modelData.model_name}`,
|
||||
}
|
||||
}
|
||||
|
||||
return credential;
|
||||
};
|
||||
|
||||
const PROVIDER_CREDENTIAL_FIELDS: Record<Providers, ProviderCredentialField[]> = {
|
||||
[Providers.OpenAI]: [
|
||||
{
|
||||
key: "api_base",
|
||||
label: "API Base",
|
||||
type: "select",
|
||||
options: [
|
||||
"https://api.openai.com/v1",
|
||||
"https://eu.api.openai.com"
|
||||
],
|
||||
defaultValue: "https://api.openai.com/v1"
|
||||
},
|
||||
{
|
||||
key: "organization",
|
||||
label: "OpenAI Organization ID",
|
||||
placeholder: "[OPTIONAL] my-unique-org"
|
||||
},
|
||||
{
|
||||
key: "api_key",
|
||||
label: "OpenAI API Key",
|
||||
type: "password",
|
||||
required: true
|
||||
}
|
||||
],
|
||||
[Providers.OpenAI_Text]: [
|
||||
{
|
||||
key: "api_base",
|
||||
label: "API Base",
|
||||
type: "select",
|
||||
options: [
|
||||
"https://api.openai.com/v1",
|
||||
"https://eu.api.openai.com"
|
||||
],
|
||||
defaultValue: "https://api.openai.com/v1"
|
||||
},
|
||||
{
|
||||
key: "organization",
|
||||
label: "OpenAI Organization ID",
|
||||
placeholder: "[OPTIONAL] my-unique-org"
|
||||
},
|
||||
{
|
||||
key: "api_key",
|
||||
label: "OpenAI API Key",
|
||||
type: "password",
|
||||
required: true
|
||||
}
|
||||
],
|
||||
[Providers.Vertex_AI]: [
|
||||
{
|
||||
key: "vertex_project",
|
||||
label: "Vertex Project",
|
||||
placeholder: "adroit-cadet-1234..",
|
||||
required: true
|
||||
},
|
||||
{
|
||||
key: "vertex_location",
|
||||
label: "Vertex Location",
|
||||
placeholder: "us-east-1",
|
||||
required: true
|
||||
},
|
||||
{
|
||||
key: "vertex_credentials",
|
||||
label: "Vertex Credentials",
|
||||
required: true,
|
||||
type: "upload"
|
||||
}
|
||||
],
|
||||
[Providers.AssemblyAI]: [
|
||||
{
|
||||
key: "api_base",
|
||||
label: "API Base",
|
||||
type: "select",
|
||||
required: true,
|
||||
options: [
|
||||
"https://api.assemblyai.com",
|
||||
"https://api.eu.assemblyai.com"
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "api_key",
|
||||
label: "AssemblyAI API Key",
|
||||
type: "password",
|
||||
required: true
|
||||
}
|
||||
],
|
||||
[Providers.Azure]: [
|
||||
{
|
||||
key: "api_base",
|
||||
label: "API Base",
|
||||
placeholder: "https://...",
|
||||
required: true
|
||||
},
|
||||
{
|
||||
key: "api_version",
|
||||
label: "API Version",
|
||||
placeholder: "2023-07-01-preview",
|
||||
tooltip: "By default litellm will use the latest version. If you want to use a different version, you can specify it here"
|
||||
},
|
||||
{
|
||||
key: "base_model",
|
||||
label: "Base Model",
|
||||
placeholder: "azure/gpt-3.5-turbo"
|
||||
},
|
||||
{
|
||||
key: "api_key",
|
||||
label: "Azure API Key",
|
||||
type: "password",
|
||||
required: true
|
||||
}
|
||||
],
|
||||
[Providers.Azure_AI_Studio]: [
|
||||
{
|
||||
key: "api_base",
|
||||
label: "API Base",
|
||||
placeholder: "https://...",
|
||||
required: true
|
||||
},
|
||||
{
|
||||
key: "api_key",
|
||||
label: "Azure API Key",
|
||||
type: "password",
|
||||
required: true
|
||||
}
|
||||
],
|
||||
[Providers.OpenAI_Compatible]: [
|
||||
{
|
||||
key: "api_base",
|
||||
label: "API Base",
|
||||
placeholder: "https://...",
|
||||
required: true
|
||||
},
|
||||
{
|
||||
key: "api_key",
|
||||
label: "OpenAI API Key",
|
||||
type: "password",
|
||||
required: true
|
||||
}
|
||||
],
|
||||
[Providers.OpenAI_Text_Compatible]: [
|
||||
{
|
||||
key: "api_base",
|
||||
label: "API Base",
|
||||
placeholder: "https://...",
|
||||
required: true
|
||||
},
|
||||
{
|
||||
key: "api_key",
|
||||
label: "OpenAI API Key",
|
||||
type: "password",
|
||||
required: true
|
||||
}
|
||||
],
|
||||
[Providers.Bedrock]: [
|
||||
{
|
||||
key: "aws_access_key_id",
|
||||
label: "AWS Access Key ID",
|
||||
required: true,
|
||||
tooltip: "You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`)."
|
||||
},
|
||||
{
|
||||
key: "aws_secret_access_key",
|
||||
label: "AWS Secret Access Key",
|
||||
required: true,
|
||||
tooltip: "You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`)."
|
||||
},
|
||||
{
|
||||
key: "aws_region_name",
|
||||
label: "AWS Region Name",
|
||||
placeholder: "us-east-1",
|
||||
required: true,
|
||||
tooltip: "You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`)."
|
||||
}
|
||||
],
|
||||
[Providers.Ollama]: [], // No specific fields needed
|
||||
[Providers.Anthropic]: [{
|
||||
key: "api_key",
|
||||
label: "API Key",
|
||||
placeholder: "sk-",
|
||||
type: "password",
|
||||
required: true
|
||||
}],
|
||||
[Providers.Google_AI_Studio]: [{
|
||||
key: "api_key",
|
||||
label: "API Key",
|
||||
placeholder: "aig-",
|
||||
type: "password",
|
||||
required: true
|
||||
}],
|
||||
[Providers.Groq]: [{
|
||||
key: "api_key",
|
||||
label: "API Key",
|
||||
type: "password",
|
||||
required: true
|
||||
}],
|
||||
[Providers.MistralAI]: [{
|
||||
key: "api_key",
|
||||
label: "API Key",
|
||||
type: "password",
|
||||
required: true
|
||||
}],
|
||||
[Providers.Deepseek]: [{
|
||||
key: "api_key",
|
||||
label: "API Key",
|
||||
type: "password",
|
||||
required: true
|
||||
}],
|
||||
[Providers.Cohere]: [{
|
||||
key: "api_key",
|
||||
label: "API Key",
|
||||
type: "password",
|
||||
required: true
|
||||
}],
|
||||
[Providers.Databricks]: [{
|
||||
key: "api_key",
|
||||
label: "API Key",
|
||||
type: "password",
|
||||
required: true
|
||||
}],
|
||||
[Providers.xAI]: [{
|
||||
key: "api_key",
|
||||
label: "API Key",
|
||||
type: "password",
|
||||
required: true
|
||||
}],
|
||||
[Providers.Cerebras]: [{
|
||||
key: "api_key",
|
||||
label: "API Key",
|
||||
type: "password",
|
||||
required: true
|
||||
}],
|
||||
[Providers.Sambanova]: [{
|
||||
key: "api_key",
|
||||
label: "API Key",
|
||||
type: "password",
|
||||
required: true
|
||||
}],
|
||||
[Providers.Perplexity]: [{
|
||||
key: "api_key",
|
||||
label: "API Key",
|
||||
type: "password",
|
||||
required: true
|
||||
}],
|
||||
[Providers.TogetherAI]: [{
|
||||
key: "api_key",
|
||||
label: "API Key",
|
||||
type: "password",
|
||||
required: true
|
||||
}],
|
||||
[Providers.Openrouter]: [{
|
||||
key: "api_key",
|
||||
label: "API Key",
|
||||
type: "password",
|
||||
required: true
|
||||
}],
|
||||
[Providers.FireworksAI]: [{
|
||||
key: "api_key",
|
||||
label: "API Key",
|
||||
type: "password",
|
||||
required: true
|
||||
}]
|
||||
};
|
||||
|
||||
const ProviderSpecificFields: React.FC<ProviderSpecificFieldsProps> = ({
|
||||
selectedProvider,
|
||||
uploadProps
|
||||
}) => {
|
||||
console.log(`Selected provider: ${selectedProvider}`);
|
||||
console.log(`type of selectedProvider: ${typeof selectedProvider}`);
|
||||
// cast selectedProvider to Providers
|
||||
const selectedProviderEnum = Providers[selectedProvider as keyof typeof Providers] as Providers;
|
||||
console.log(`selectedProviderEnum: ${selectedProviderEnum}`);
|
||||
console.log(`type of selectedProviderEnum: ${typeof selectedProviderEnum}`);
|
||||
|
||||
// Simply use the fields as defined in PROVIDER_CREDENTIAL_FIELDS
|
||||
const allFields = React.useMemo(() => {
|
||||
return PROVIDER_CREDENTIAL_FIELDS[selectedProviderEnum] || [];
|
||||
}, [selectedProviderEnum]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{selectedProviderEnum === Providers.OpenAI || selectedProviderEnum === Providers.OpenAI_Text && (
|
||||
<>
|
||||
{allFields.map((field) => (
|
||||
<React.Fragment key={field.key}>
|
||||
<Form.Item
|
||||
label="API Base"
|
||||
name="api_base"
|
||||
label={field.label}
|
||||
name={field.key}
|
||||
rules={field.required ? [{ required: true, message: "Required" }] : undefined}
|
||||
tooltip={field.tooltip}
|
||||
className={field.key === "vertex_credentials" ? "mb-0" : undefined}
|
||||
>
|
||||
<Select placeholder="Select API Base" defaultValue="https://api.openai.com/v1">
|
||||
<Select.Option value="https://api.openai.com/v1">https://api.openai.com/v1</Select.Option>
|
||||
<Select.Option value="https://eu.api.openai.com">https://eu.api.openai.com</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="OpenAI Organization ID" name="organization">
|
||||
<TextInput placeholder="[OPTIONAL] my-unique-org" />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
|
||||
{selectedProviderEnum === Providers.Vertex_AI && (
|
||||
<>
|
||||
<Form.Item
|
||||
rules={[{ required: true, message: "Required" }]}
|
||||
label="Vertex Project"
|
||||
name="vertex_project"
|
||||
>
|
||||
<TextInput placeholder="adroit-cadet-1234.." />
|
||||
{field.type === "select" ? (
|
||||
<Select
|
||||
placeholder={field.placeholder}
|
||||
defaultValue={field.defaultValue}
|
||||
>
|
||||
{field.options?.map((option) => (
|
||||
<Select.Option key={option} value={option}>
|
||||
{option}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
) : field.type === "upload" ? (
|
||||
<Upload {...uploadProps}>
|
||||
<Button2 icon={<UploadOutlined />}>Click to Upload</Button2>
|
||||
</Upload>
|
||||
) : (
|
||||
<TextInput
|
||||
placeholder={field.placeholder}
|
||||
type={field.type === "password" ? "password" : "text"}
|
||||
/>
|
||||
)}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
rules={[{ required: true, message: "Required" }]}
|
||||
label="Vertex Location"
|
||||
name="vertex_location"
|
||||
>
|
||||
<TextInput placeholder="us-east-1" />
|
||||
</Form.Item>
|
||||
{/* Special case for Vertex Credentials help text */}
|
||||
{field.key === "vertex_credentials" && (
|
||||
<Row>
|
||||
<Col span={10}></Col>
|
||||
<Col span={10}>
|
||||
<Text className="mb-3 mt-1">
|
||||
Give litellm a gcp service account(.json file), so it
|
||||
can make the relevant calls
|
||||
</Text>
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
|
||||
<Form.Item
|
||||
rules={[{ required: true, message: "Required" }]}
|
||||
label="Vertex Credentials"
|
||||
name="vertex_credentials"
|
||||
className="mb-0"
|
||||
>
|
||||
<Upload {...uploadProps}>
|
||||
<Button2 icon={<UploadOutlined />}>
|
||||
Click to Upload
|
||||
</Button2>
|
||||
</Upload>
|
||||
</Form.Item>
|
||||
|
||||
<Row>
|
||||
<Col span={10}></Col>
|
||||
<Col span={10}>
|
||||
<Text className="mb-3 mt-1">
|
||||
Give litellm a gcp service account(.json file), so it
|
||||
can make the relevant calls
|
||||
</Text>
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
)}
|
||||
|
||||
{selectedProviderEnum === Providers.AssemblyAI && (
|
||||
<Form.Item
|
||||
rules={[{ required: true, message: "Required" }]}
|
||||
label="API Base"
|
||||
name="api_base"
|
||||
>
|
||||
<Select placeholder="Select API Base">
|
||||
<Select.Option value="https://api.assemblyai.com">https://api.assemblyai.com</Select.Option>
|
||||
<Select.Option value="https://api.eu.assemblyai.com">https://api.eu.assemblyai.com</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
{(selectedProviderEnum === Providers.Azure ||
|
||||
selectedProviderEnum === Providers.Azure_AI_Studio ||
|
||||
selectedProviderEnum === Providers.OpenAI_Compatible ||
|
||||
selectedProviderEnum === Providers.OpenAI_Text_Compatible
|
||||
) && (
|
||||
<Form.Item
|
||||
rules={[{ required: true, message: "Required" }]}
|
||||
label="API Base"
|
||||
name="api_base"
|
||||
>
|
||||
<TextInput placeholder="https://..." />
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
{selectedProviderEnum === Providers.Azure && (
|
||||
<>
|
||||
<Form.Item
|
||||
label="API Version"
|
||||
name="api_version"
|
||||
tooltip="By default litellm will use the latest version. If you want to use a different version, you can specify it here"
|
||||
>
|
||||
<TextInput placeholder="2023-07-01-preview" />
|
||||
</Form.Item>
|
||||
|
||||
<div>
|
||||
<Form.Item
|
||||
label="Base Model"
|
||||
name="base_model"
|
||||
className="mb-0"
|
||||
>
|
||||
<TextInput placeholder="azure/gpt-3.5-turbo" />
|
||||
</Form.Item>
|
||||
{/* Special case for Azure Base Model help text */}
|
||||
{field.key === "base_model" && (
|
||||
<Row>
|
||||
<Col span={10}></Col>
|
||||
<Col span={10}>
|
||||
|
|
@ -144,54 +404,9 @@ const ProviderSpecificFields: React.FC<ProviderSpecificFieldsProps> = ({
|
|||
</Text>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{selectedProviderEnum === Providers.Bedrock && (
|
||||
<>
|
||||
<Form.Item
|
||||
rules={[{ required: true, message: "Required" }]}
|
||||
label="AWS Access Key ID"
|
||||
name="aws_access_key_id"
|
||||
tooltip="You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`)."
|
||||
>
|
||||
<TextInput placeholder="" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
rules={[{ required: true, message: "Required" }]}
|
||||
label="AWS Secret Access Key"
|
||||
name="aws_secret_access_key"
|
||||
tooltip="You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`)."
|
||||
>
|
||||
<TextInput placeholder="" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
rules={[{ required: true, message: "Required" }]}
|
||||
label="AWS Region Name"
|
||||
name="aws_region_name"
|
||||
tooltip="You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`)."
|
||||
>
|
||||
<TextInput placeholder="us-east-1" />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
|
||||
{selectedProviderEnum != Providers.Bedrock &&
|
||||
selectedProviderEnum != Providers.Vertex_AI &&
|
||||
selectedProviderEnum != Providers.Ollama &&
|
||||
(
|
||||
<Form.Item
|
||||
rules={[{ required: true, message: "Required" }]}
|
||||
label="API Key"
|
||||
name="api_key"
|
||||
tooltip="LLM API Credentials"
|
||||
>
|
||||
<TextInput placeholder="sk-" type="password" />
|
||||
</Form.Item>
|
||||
)}
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -69,7 +69,6 @@ const menuItems: MenuItem[] = [
|
|||
{ 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: "18", page: "transform-request", label: "Playground", icon: <ThunderboltOutlined />, roles: all_admin_roles },
|
||||
{ key: "19", page: "credentials", label: "Credentials", icon: <LockOutlined />, roles: all_admin_roles },
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,119 @@
|
|||
import React, { useState } from "react";
|
||||
import {
|
||||
Card,
|
||||
Form,
|
||||
Button,
|
||||
Tooltip,
|
||||
Typography,
|
||||
Select as AntdSelect,
|
||||
Input,
|
||||
Switch,
|
||||
Modal
|
||||
} from "antd";
|
||||
import type { UploadProps } from "antd/es/upload";
|
||||
import { Providers, providerLogoMap } from "../provider_info_helpers";
|
||||
import type { FormInstance } from "antd";
|
||||
import ProviderSpecificFields from "../add_model/provider_specific_fields";
|
||||
import { TextInput } from "@tremor/react";
|
||||
import { CredentialItem } from "../networking";
|
||||
const { Title, Link } = Typography;
|
||||
|
||||
interface ReuseCredentialsModalProps {
|
||||
isVisible: boolean;
|
||||
onCancel: () => void;
|
||||
onAddCredential: (values: any) => void;
|
||||
existingCredential: CredentialItem | null;
|
||||
setIsCredentialModalOpen: (isVisible: boolean) => void;
|
||||
}
|
||||
|
||||
const ReuseCredentialsModal: React.FC<ReuseCredentialsModalProps> = ({
|
||||
isVisible,
|
||||
onCancel,
|
||||
onAddCredential,
|
||||
existingCredential,
|
||||
setIsCredentialModalOpen
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
console.log(`existingCredential in add credentials tab: ${JSON.stringify(existingCredential)}`);
|
||||
|
||||
const handleSubmit = (values: any) => {
|
||||
onAddCredential(values);
|
||||
form.resetFields();
|
||||
setIsCredentialModalOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="Reuse Credentials"
|
||||
visible={isVisible}
|
||||
onCancel={() => {
|
||||
onCancel();
|
||||
form.resetFields();
|
||||
}}
|
||||
footer={null}
|
||||
width={600}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={handleSubmit}
|
||||
layout="vertical"
|
||||
>
|
||||
{/* Credential Name */}
|
||||
<Form.Item
|
||||
label="Credential Name:"
|
||||
name="credential_name"
|
||||
rules={[{ required: true, message: "Credential name is required" }]}
|
||||
initialValue={existingCredential?.credential_name}
|
||||
>
|
||||
<TextInput
|
||||
placeholder="Enter a friendly name for these credentials"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
{/* Display Credential Values of existingCredential, don't allow user to edit. Credential values is a dictionary */}
|
||||
{Object.entries(existingCredential?.credential_values || {}).map(([key, value]) => (
|
||||
<Form.Item
|
||||
key={key}
|
||||
label={key}
|
||||
name={key}
|
||||
initialValue={value}
|
||||
>
|
||||
<TextInput
|
||||
placeholder={`Enter ${key}`}
|
||||
disabled={true}
|
||||
/>
|
||||
</Form.Item>
|
||||
))}
|
||||
|
||||
{/* Modal Footer */}
|
||||
<div className="flex justify-between items-center">
|
||||
<Tooltip title="Get help on our github">
|
||||
<Link href="https://github.com/BerriAI/litellm/issues">
|
||||
Need Help?
|
||||
</Link>
|
||||
</Tooltip>
|
||||
|
||||
<div>
|
||||
<Button
|
||||
onClick={() => {
|
||||
onCancel();
|
||||
form.resetFields();
|
||||
}}
|
||||
style={{ marginRight: 10 }}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
htmlType="submit"
|
||||
>
|
||||
Reuse Credentials
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReuseCredentialsModal;
|
||||
|
|
@ -14,13 +14,15 @@ import {
|
|||
TextInput,
|
||||
NumberInput,
|
||||
} from "@tremor/react";
|
||||
import { ArrowLeftIcon, TrashIcon } from "@heroicons/react/outline";
|
||||
import { modelDeleteCall, modelUpdateCall } from "./networking";
|
||||
import { Button, Form, Input, InputNumber, message, Select } from "antd";
|
||||
import { ArrowLeftIcon, TrashIcon, KeyIcon } from "@heroicons/react/outline";
|
||||
import { modelDeleteCall, modelUpdateCall, CredentialItem, credentialGetCall, credentialCreateCall } from "./networking";
|
||||
import { Button, Form, Input, InputNumber, message, Select, Modal } from "antd";
|
||||
import EditModelModal from "./edit_model/edit_model_modal";
|
||||
import { handleEditModelSubmit } from "./edit_model/edit_model_modal";
|
||||
import { getProviderLogoAndName } from "./provider_info_helpers";
|
||||
import { getDisplayModelName } from "./view_model/model_name_display";
|
||||
import AddCredentialsModal from "./model_add/add_credentials_tab";
|
||||
import ReuseCredentialsModal from "./model_add/reuse_credentials";
|
||||
|
||||
interface ModelInfoViewProps {
|
||||
modelId: string;
|
||||
|
|
@ -48,11 +50,51 @@ export default function ModelInfoView({
|
|||
const [form] = Form.useForm();
|
||||
const [localModelData, setLocalModelData] = useState(modelData);
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [isCredentialModalOpen, setIsCredentialModalOpen] = useState(false);
|
||||
const [isDirty, setIsDirty] = useState(false);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [existingCredential, setExistingCredential] = useState<CredentialItem | null>(null);
|
||||
|
||||
const canEditModel = userRole === "Admin";
|
||||
const isAdmin = userRole === "Admin";
|
||||
|
||||
const usingExistingCredential = modelData.litellm_params?.litellm_credential_name != null && modelData.litellm_params?.litellm_credential_name != undefined;
|
||||
console.log("usingExistingCredential, ", usingExistingCredential);
|
||||
console.log("modelData.litellm_params.litellm_credential_name, ", modelData.litellm_params.litellm_credential_name);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const getExistingCredential = async () => {
|
||||
console.log("accessToken, ", accessToken);
|
||||
if (!accessToken) return;
|
||||
if (usingExistingCredential) return;
|
||||
let existingCredentialResponse = await credentialGetCall(accessToken, null, modelId);
|
||||
console.log("existingCredentialResponse, ", existingCredentialResponse);
|
||||
setExistingCredential({
|
||||
credential_name: existingCredentialResponse["credential_name"],
|
||||
credential_values: existingCredentialResponse["credential_values"],
|
||||
credential_info: existingCredentialResponse["credential_info"]
|
||||
});
|
||||
}
|
||||
getExistingCredential();
|
||||
}, [accessToken, modelId]);
|
||||
|
||||
const handleReuseCredential = async (values: any) => {
|
||||
console.log("values, ", values);
|
||||
if (!accessToken) return;
|
||||
let credentialItem = {
|
||||
credential_name: values.credential_name,
|
||||
model_id: modelId,
|
||||
credential_info: {
|
||||
"custom_llm_provider": localModelData.litellm_params?.custom_llm_provider,
|
||||
}
|
||||
}
|
||||
message.info("Storing credential..");
|
||||
let credentialResponse = await credentialCreateCall(accessToken, credentialItem);
|
||||
console.log("credentialResponse, ", credentialResponse);
|
||||
message.success("Credential stored successfully");
|
||||
}
|
||||
|
||||
const handleModelUpdate = async (values: any) => {
|
||||
try {
|
||||
|
|
@ -143,8 +185,16 @@ export default function ModelInfoView({
|
|||
<Title>Public Model Name: {getDisplayModelName(modelData)}</Title>
|
||||
<Text className="text-gray-500 font-mono">{modelData.model_info.id}</Text>
|
||||
</div>
|
||||
{canEditModel && (
|
||||
{isAdmin && (
|
||||
<div className="flex gap-2">
|
||||
<TremorButton
|
||||
icon={KeyIcon}
|
||||
variant="secondary"
|
||||
onClick={() => setIsCredentialModalOpen(true)}
|
||||
className="flex items-center"
|
||||
>
|
||||
Re-use Credentials
|
||||
</TremorButton>
|
||||
<TremorButton
|
||||
icon={TrashIcon}
|
||||
variant="secondary"
|
||||
|
|
@ -507,6 +557,25 @@ export default function ModelInfoView({
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isCredentialModalOpen &&
|
||||
!usingExistingCredential ? (
|
||||
<ReuseCredentialsModal
|
||||
isVisible={isCredentialModalOpen}
|
||||
onCancel={() => setIsCredentialModalOpen(false)}
|
||||
onAddCredential={handleReuseCredential}
|
||||
existingCredential={existingCredential}
|
||||
setIsCredentialModalOpen={setIsCredentialModalOpen}
|
||||
/>
|
||||
): (
|
||||
<Modal
|
||||
open={isCredentialModalOpen}
|
||||
onCancel={() => setIsCredentialModalOpen(false)}
|
||||
title="Using Existing Credential"
|
||||
>
|
||||
<Text>{modelData.litellm_params.litellm_credential_name}</Text>
|
||||
</Modal>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -2652,6 +2652,42 @@ export const credentialListCall = async (
|
|||
}
|
||||
};
|
||||
|
||||
export const credentialGetCall = async (accessToken: String, credentialName: String | null, modelId: String | null) => {
|
||||
try {
|
||||
let url = proxyBaseUrl ? `${proxyBaseUrl}/credentials` : `/credentials`;
|
||||
|
||||
if (credentialName) {
|
||||
url += `/by_name/${credentialName}`;
|
||||
} else if (modelId) {
|
||||
url += `/by_model/${modelId}`;
|
||||
}
|
||||
|
||||
console.log("in credentialListCall");
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.text();
|
||||
handleError(errorData);
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log("/credentials API Response:", data);
|
||||
return data;
|
||||
// Handle success - you might want to update some state or UI based on the created key
|
||||
} catch (error) {
|
||||
console.error("Failed to create key:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const credentialDeleteCall = async (accessToken: String, credentialName: String) => {
|
||||
try {
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/credentials/${credentialName}` : `/credentials/${credentialName}`;
|
||||
|
|
@ -2698,7 +2734,7 @@ export const credentialUpdateCall = async (
|
|||
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/credentials/${credentialName}` : `/credentials/${credentialName}`;
|
||||
const response = await fetch(url, {
|
||||
method: "PUT",
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue