Merge pull request #2902 from BerriAI/litellm_ui_set_get_callbacks

UI view set callbacks
This commit is contained in:
Ishaan Jaff 2024-04-08 16:08:14 -07:00 committed by GitHub
commit 0d925a6c55
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 211 additions and 27 deletions

View file

@ -1809,17 +1809,15 @@ class ProxyConfig:
}
## DB
if (
prisma_client is not None
and litellm.get_secret("SAVE_CONFIG_TO_DB", False) == True
if prisma_client is not None and (
general_settings.get("store_model_in_db", False) == True
):
prisma_setup(database_url=None) # in case it's not been connected yet
_tasks = []
keys = [
"model_list",
"general_settings",
"router_settings",
"litellm_settings",
"environment_variables",
]
for k in keys:
response = prisma_client.get_generic_data(
@ -1828,6 +1826,12 @@ class ProxyConfig:
_tasks.append(response)
responses = await asyncio.gather(*_tasks)
for response in responses:
if response is not None:
param_name = getattr(response, "param_name", None)
param_value = getattr(response, "param_value", None)
if param_name is not None and param_value is not None:
config[param_name] = param_value
return config
@ -1836,11 +1840,6 @@ class ProxyConfig:
# Load existing config
backup_config = await self.get_config()
# Save the updated config
## YAML
with open(f"{user_config_file_path}", "w") as config_file:
yaml.dump(new_config, config_file, default_flow_style=False)
# update Router - verifies if this is a valid config
try:
(
@ -1862,22 +1861,17 @@ class ProxyConfig:
- Do not write restricted params like 'api_key' to the database
- if api_key is passed, save that to the local environment or connected secret manage (maybe expose `litellm.save_secret()`)
"""
if (
prisma_client is not None
and litellm.get_secret("SAVE_CONFIG_TO_DB", default_value=False) == True
if prisma_client is not None and (
general_settings.get("store_model_in_db", False) == True
):
### KEY REMOVAL ###
models = new_config.get("model_list", [])
for m in models:
if m.get("litellm_params", {}).get("api_key", None) is not None:
# pop the key
api_key = m["litellm_params"].pop("api_key")
# store in local env
key_name = f"LITELLM_MODEL_KEY_{uuid.uuid4()}"
os.environ[key_name] = api_key
# save the key name (not the value)
m["litellm_params"]["api_key"] = f"os.environ/{key_name}"
# if using - db for config - models are in ModelTable
new_config.pop("model_list", None)
await prisma_client.insert_data(data=new_config, table_name="config")
else:
# Save the updated config - if user is not using a dB
## YAML
with open(f"{user_config_file_path}", "w") as config_file:
yaml.dump(new_config, config_file, default_flow_style=False)
async def load_team_config(self, team_id: str):
"""
@ -7922,8 +7916,10 @@ async def update_config(config_info: ConfigYAML):
Currently supports modifying General Settings + LiteLLM settings
"""
global llm_router, llm_model_list, general_settings, proxy_config, proxy_logging_obj
global llm_router, llm_model_list, general_settings, proxy_config, proxy_logging_obj, master_key
try:
import base64
# Load existing config
config = await proxy_config.get_config()
@ -7943,9 +7939,17 @@ async def update_config(config_info: ConfigYAML):
if config_info.environment_variables is not None:
config.setdefault("environment_variables", {})
updated_environment_variables = config_info.environment_variables
_updated_environment_variables = config_info.environment_variables
# encrypt updated_environment_variables #
for k, v in _updated_environment_variables.items():
if isinstance(v, str):
encrypted_value = encrypt_value(value=v, master_key=master_key) # type: ignore
_updated_environment_variables[k] = base64.b64encode(
encrypted_value
).decode("utf-8")
config["environment_variables"] = {
**updated_environment_variables,
**_updated_environment_variables,
**config["environment_variables"],
}
@ -7958,6 +7962,25 @@ async def update_config(config_info: ConfigYAML):
**config["litellm_settings"],
}
# if litellm.success_callback in updated_litellm_settings and config["litellm_settings"]
if (
"success_callback" in updated_litellm_settings
and "success_callback" in config["litellm_settings"]
):
# check both success callback are lists
if isinstance(
config["litellm_settings"]["success_callback"], list
) and isinstance(updated_litellm_settings["success_callback"], list):
combined_success_callback = (
config["litellm_settings"]["success_callback"]
+ updated_litellm_settings["success_callback"]
)
combined_success_callback = list(set(combined_success_callback))
config["litellm_settings"][
"success_callback"
] = combined_success_callback
# Save the updated config
await proxy_config.save_config(new_config=config)
@ -7987,6 +8010,48 @@ async def update_config(config_info: ConfigYAML):
)
@router.get(
"/get/config/callbacks",
tags=["config.yaml"],
include_in_schema=False,
dependencies=[Depends(user_api_key_auth)],
)
async def get_config():
"""
For Admin UI - allows admin to view config via UI
"""
global llm_router, llm_model_list, general_settings, proxy_config, proxy_logging_obj, master_key
try:
config_data = await proxy_config.get_config()
_environment_variables = config_data.get("environment_variables", {})
config_data = config_data["litellm_settings"]
# only store the keys and return the values as sk...***
for key, value in _environment_variables.items():
_environment_variables[key] = value[:5] + "*****"
config_data["environment_variables"] = _environment_variables
return {"data": config_data, "status": "success"}
except Exception as e:
traceback.print_exc()
if isinstance(e, HTTPException):
raise ProxyException(
message=getattr(e, "detail", f"Authentication Error({str(e)})"),
type="auth_error",
param=getattr(e, "param", "None"),
code=getattr(e, "status_code", status.HTTP_400_BAD_REQUEST),
)
elif isinstance(e, ProxyException):
raise e
raise ProxyException(
message="Authentication Error, " + str(e),
type="auth_error",
param=getattr(e, "param", "None"),
code=status.HTTP_400_BAD_REQUEST,
)
@router.get(
"/config/yaml",
tags=["config.yaml"],

View file

@ -7,6 +7,7 @@ import ModelDashboard from "@/components/model_dashboard";
import ViewUserDashboard from "@/components/view_users";
import Teams from "@/components/teams";
import AdminPanel from "@/components/admins";
import Settings from "@/components/settings";
import ChatUI from "@/components/chat_ui";
import Sidebar from "../components/leftnav";
import Usage from "../components/usage";
@ -161,6 +162,12 @@ const CreateKeyPage = () => {
searchParams={searchParams}
accessToken={accessToken}
/>
) : page == "settings" ? (
<Settings
userID={userID}
userRole={userRole}
accessToken={accessToken}
/>
) : (
<Usage
userID={userID}

View file

@ -87,6 +87,11 @@ const Sidebar: React.FC<SidebarProps> = ({
Models
</Text>
</Menu.Item>
<Menu.Item key="8" onClick={() => setPage("settings")}>
<Text>
Settings
</Text>
</Menu.Item>
{userRole == "Admin" ? (
<Menu.Item key="7" onClick={() => setPage("admin-panel")}>
<Text>

View file

@ -1120,3 +1120,42 @@ export const slackBudgetAlertsHealthCheck = async (accessToken: String) => {
}
};
export const getCallbacksCall = async (
accessToken: String,
userID: String,
userRole: String
) => {
/**
* Get all the models user has access to
*/
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/get/config/callbacks` : `/get/config/callbacks`;
//message.info("Requesting model data");
const response = await fetch(url, {
method: "GET",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.text();
message.error(errorData);
throw new Error("Network response was not ok");
}
const data = await response.json();
//message.info("Received model 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 get callbacks:", error);
throw error;
}
};

View file

@ -0,0 +1,68 @@
import React, { useState, useEffect } from "react";
import { Card, Title, Subtitle, Table, TableHead, TableRow, Badge, TableHeaderCell, TableCell, TableBody, Metric, Text, Grid, Button, Col, } from "@tremor/react";
import { getCallbacksCall } from "./networking";
interface SettingsPageProps {
accessToken: string | null;
userRole: string | null;
userID: string | null;
}
const Settings: React.FC<SettingsPageProps> = ({
accessToken,
userRole,
userID,
}) => {
const [callbacks, setCallbacks] = useState(["None"]);
useEffect(() => {
if (!accessToken || !userRole || !userID) {
return;
}
getCallbacksCall(accessToken, userID, userRole).then((data) => {
console.log("callbacks",data);
let callbacks_data = data.data;
let callback_names = callbacks_data.success_callback // ["callback1", "callback2"]
setCallbacks(callback_names);
});
}, [accessToken, userRole, userID]);
return (
<div className="w-full mx-4">
<Grid numItems={1} className="gap-2 p-8 h-[75vh] w-full mt-2">
<Card className="h-[15vh]">
<Grid numItems={2} className="mt-2">
<Col>
<Title>Logging Callbacks</Title>
</Col>
<Col>
<div>
{callbacks.length === 0 ? (
<Badge>None</Badge>
) : (
callbacks.map((callback, index) => (
<Badge key={index} color={"sky"}>
{callback}
</Badge>
))
)}
</div>
</Col>
</Grid>
<Col>
<Button size="xs" className="mt-2">Add Callback</Button>
</Col>
</Card>
</Grid>
</div>
);
};
export default Settings;