Merge pull request #3868 from BerriAI/litellm_show_updated_created_models

[Feat] Show Created at, Created by on `Models` Page
This commit is contained in:
Ishaan Jaff 2024-05-27 16:32:29 -07:00 committed by GitHub
commit d71bb96047
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 149 additions and 59 deletions

View file

@ -21,6 +21,7 @@ Features:
- ✅ Don't log/store specific requests to Langfuse, Sentry, etc. (eg confidential LLM requests) - ✅ Don't log/store specific requests to Langfuse, Sentry, etc. (eg confidential LLM requests)
- ✅ Tracking Spend for Custom Tags - ✅ Tracking Spend for Custom Tags
- ✅ Custom Branding + Routes on Swagger Docs - ✅ Custom Branding + Routes on Swagger Docs
- ✅ Audit Logs for `Created At, Created By` when Models Added
## Content Moderation ## Content Moderation

View file

@ -2790,6 +2790,13 @@ class ProxyConfig:
model.model_info["id"] = _id model.model_info["id"] = _id
model.model_info["db_model"] = True model.model_info["db_model"] = True
if premium_user is True:
# seeing "created_at", "updated_at", "created_by", "updated_by" is a LiteLLM Enterprise Feature
model.model_info["created_at"] = getattr(model, "created_at", None)
model.model_info["updated_at"] = getattr(model, "updated_at", None)
model.model_info["created_by"] = getattr(model, "created_by", None)
model.model_info["updated_by"] = getattr(model, "updated_by", None)
if model.model_info is not None and isinstance(model.model_info, dict): if model.model_info is not None and isinstance(model.model_info, dict):
if "id" not in model.model_info: if "id" not in model.model_info:
model.model_info["id"] = model.model_id model.model_info["id"] = model.model_id
@ -3075,10 +3082,9 @@ class ProxyConfig:
try: try:
if master_key is None or not isinstance(master_key, str): if master_key is None or not isinstance(master_key, str):
raise Exception( raise ValueError(
f"Master key is not initialized or formatted. master_key={master_key}" f"Master key is not initialized or formatted. master_key={master_key}"
) )
verbose_proxy_logger.debug(f"llm_router: {llm_router}")
new_models = await prisma_client.db.litellm_proxymodeltable.find_many() new_models = await prisma_client.db.litellm_proxymodeltable.find_many()
# update llm router # update llm router
await self._update_llm_router( await self._update_llm_router(

View file

@ -1,9 +1,15 @@
"""
litellm.Router Types - includes RouterConfig, UpdateRouterConfig, ModelInfo etc
"""
from typing import List, Optional, Union, Dict, Tuple, Literal, TypedDict from typing import List, Optional, Union, Dict, Tuple, Literal, TypedDict
import uuid
import enum
import httpx import httpx
from pydantic import BaseModel, validator, Field from pydantic import BaseModel, Field
import datetime
from .completion import CompletionRequest from .completion import CompletionRequest
from .embedding import EmbeddingRequest from .embedding import EmbeddingRequest
import uuid, enum
class ModelConfig(BaseModel): class ModelConfig(BaseModel):
@ -76,6 +82,12 @@ class ModelInfo(BaseModel):
db_model: bool = ( db_model: bool = (
False # used for proxy - to separate models which are stored in the db vs. config. False # used for proxy - to separate models which are stored in the db vs. config.
) )
updated_at: Optional[datetime.datetime] = None
updated_by: Optional[str] = None
created_at: Optional[datetime.datetime] = None
created_by: Optional[str] = None
base_model: Optional[str] = ( base_model: Optional[str] = (
None # specify if the base model is azure/gpt-3.5-turbo etc for accurate cost tracking None # specify if the base model is azure/gpt-3.5-turbo etc for accurate cost tracking
) )

View file

@ -86,6 +86,8 @@ import type { UploadProps } from "antd";
import { Upload } from "antd"; import { Upload } from "antd";
import TimeToFirstToken from "./model_metrics/time_to_first_token"; import TimeToFirstToken from "./model_metrics/time_to_first_token";
import DynamicFields from "./model_add/dynamic_form"; import DynamicFields from "./model_add/dynamic_form";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
interface ModelDashboardProps { interface ModelDashboardProps {
accessToken: string | null; accessToken: string | null;
token: string | null; token: string | null;
@ -269,6 +271,8 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
const [selectedProvider, setSelectedProvider] = useState<String>("OpenAI"); const [selectedProvider, setSelectedProvider] = useState<String>("OpenAI");
const [healthCheckResponse, setHealthCheckResponse] = useState<string>(""); const [healthCheckResponse, setHealthCheckResponse] = useState<string>("");
const [editModalVisible, setEditModalVisible] = useState<boolean>(false); const [editModalVisible, setEditModalVisible] = useState<boolean>(false);
const [infoModalVisible, setInfoModalVisible] = useState<boolean>(false);
const [selectedModel, setSelectedModel] = useState<any>(null); const [selectedModel, setSelectedModel] = useState<any>(null);
const [availableModelGroups, setAvailableModelGroups] = useState< const [availableModelGroups, setAvailableModelGroups] = useState<
Array<string> Array<string>
@ -297,6 +301,15 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
useState<RetryPolicyObject | null>(null); useState<RetryPolicyObject | null>(null);
const [defaultRetry, setDefaultRetry] = useState<number>(0); const [defaultRetry, setDefaultRetry] = useState<number>(0);
function formatCreatedAt(createdAt: string | null) {
if (createdAt) {
const date = new Date(createdAt);
const options = { month: 'long', day: 'numeric', year: 'numeric' };
return date.toLocaleDateString('en-US');
}
return null;
}
const EditModelModal: React.FC<EditModelModalProps> = ({ const EditModelModal: React.FC<EditModelModalProps> = ({
visible, visible,
onCancel, onCancel,
@ -423,11 +436,21 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
setEditModalVisible(true); setEditModalVisible(true);
}; };
const handleInfoClick = (model: any) => {
setSelectedModel(model);
setInfoModalVisible(true);
};
const handleEditCancel = () => { const handleEditCancel = () => {
setEditModalVisible(false); setEditModalVisible(false);
setSelectedModel(null); setSelectedModel(null);
}; };
const handleInfoCancel = () => {
setInfoModalVisible(false);
setSelectedModel(null);
};
const handleEditSubmit = async (formValues: Record<string, any>) => { const handleEditSubmit = async (formValues: Record<string, any>) => {
// Call API to update team with teamId and values // Call API to update team with teamId and values
@ -1039,7 +1062,6 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
</div> </div>
<Card> <Card>
<Table <Table
className="mt-5"
style={{ maxWidth: "1500px", width: "100%" }} style={{ maxWidth: "1500px", width: "100%" }}
> >
<TableHead> <TableHead>
@ -1049,6 +1071,7 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
maxWidth: "150px", maxWidth: "150px",
whiteSpace: "normal", whiteSpace: "normal",
wordBreak: "break-word", wordBreak: "break-word",
fontSize: "11px"
}} }}
> >
Public Model Name Public Model Name
@ -1058,6 +1081,7 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
maxWidth: "100px", maxWidth: "100px",
whiteSpace: "normal", whiteSpace: "normal",
wordBreak: "break-word", wordBreak: "break-word",
fontSize: "11px"
}} }}
> >
Provider Provider
@ -1068,25 +1092,18 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
maxWidth: "150px", maxWidth: "150px",
whiteSpace: "normal", whiteSpace: "normal",
wordBreak: "break-word", wordBreak: "break-word",
fontSize: "11px"
}} }}
> >
API Base API Base
</TableHeaderCell> </TableHeaderCell>
)} )}
<TableHeaderCell
style={{
maxWidth: "200px",
whiteSpace: "normal",
wordBreak: "break-word",
}}
>
Extra litellm Params
</TableHeaderCell>
<TableHeaderCell <TableHeaderCell
style={{ style={{
maxWidth: "85px", maxWidth: "85px",
whiteSpace: "normal", whiteSpace: "normal",
wordBreak: "break-word", wordBreak: "break-word",
fontSize: "11px"
}} }}
> >
Input Price{" "} Input Price{" "}
@ -1099,6 +1116,7 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
maxWidth: "85px", maxWidth: "85px",
whiteSpace: "normal", whiteSpace: "normal",
wordBreak: "break-word", wordBreak: "break-word",
fontSize: "11px"
}} }}
> >
Output Price{" "} Output Price{" "}
@ -1106,24 +1124,45 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
/1M Tokens ($) /1M Tokens ($)
</p> </p>
</TableHeaderCell> </TableHeaderCell>
<TableHeaderCell <TableHeaderCell
style={{ style={{
maxWidth: "120px", maxWidth: "100px",
whiteSpace: "normal", whiteSpace: "normal",
wordBreak: "break-word", wordBreak: "break-word",
fontSize: "11px"
}} }}
> >
Max Tokens {
premiumUser ? "Created At" : <a href="https://forms.gle/W3U4PZpJGFHWtHyA9" target="_blank" style={{color: "#72bcd4" }}> Created At</a>
}
</TableHeaderCell>
<TableHeaderCell
style={{
maxWidth: "100px",
whiteSpace: "normal",
wordBreak: "break-word",
fontSize: "11px"
}}
>
{
premiumUser ? "Created By" : <a href="https://forms.gle/W3U4PZpJGFHWtHyA9" target="_blank" style={{color: "#72bcd4" }}> Created By</a>
}
</TableHeaderCell> </TableHeaderCell>
<TableHeaderCell <TableHeaderCell
style={{ style={{
maxWidth: "50px", maxWidth: "50px",
whiteSpace: "normal", whiteSpace: "normal",
wordBreak: "break-word", wordBreak: "break-word",
fontSize: "11px"
}} }}
> >
Status Status
</TableHeaderCell> </TableHeaderCell>
<TableHeaderCell>
</TableHeaderCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
@ -1137,15 +1176,17 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
selectedModelGroup === "" selectedModelGroup === ""
) )
.map((model: any, index: number) => ( .map((model: any, index: number) => (
<TableRow key={index}> <TableRow key={index} style={{ maxHeight: "1px", minHeight: "1px" }}>
<TableCell <TableCell
style={{ style={{
maxWidth: "150px", maxWidth: "100px",
whiteSpace: "normal", whiteSpace: "normal",
wordBreak: "break-word", wordBreak: "break-word",
}} }}
> >
<Text>{model.model_name}</Text> <p style={{ fontSize: "10px" }}>
{model.model_name || "-"}
</p>
</TableCell> </TableCell>
<TableCell <TableCell
style={{ style={{
@ -1154,41 +1195,34 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
wordBreak: "break-word", wordBreak: "break-word",
}} }}
> >
{model.provider} <p style={{ fontSize: "10px" }}>
{model.provider || "-"}
</p>
</TableCell> </TableCell>
{userRole === "Admin" && ( {userRole === "Admin" && (
<TableCell <TableCell
style={{
maxWidth: "150px",
whiteSpace: "normal",
wordBreak: "break-word",
}}
>
<Tooltip title={model && model.api_base}>
<pre
style={{ style={{
maxWidth: "150px", maxWidth: "150px",
whiteSpace: "normal", whiteSpace: "normal",
wordBreak: "break-word", wordBreak: "break-word",
fontSize: "10px",
}} }}
title={model && model.api_base ? model.api_base : ""}
> >
{model.api_base} {model && model.api_base ? model.api_base.slice(0, 20) : "-"}
</TableCell> </pre>
</Tooltip>
</TableCell>
)} )}
<TableCell
style={{
maxWidth: "200px",
whiteSpace: "normal",
wordBreak: "break-word",
}}
>
<Accordion>
<AccordionHeader>
<Text>Litellm params</Text>
</AccordionHeader>
<AccordionBody>
<pre>
{JSON.stringify(
model.cleanedLitellmParams,
null,
2
)}
</pre>
</AccordionBody>
</Accordion>
</TableCell>
<TableCell <TableCell
style={{ style={{
maxWidth: "80px", maxWidth: "80px",
@ -1196,9 +1230,11 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
wordBreak: "break-word", wordBreak: "break-word",
}} }}
> >
<pre style={{ fontSize: "10px" }}>
{model.input_cost || {model.input_cost ||
model.litellm_params.input_cost_per_token || model.litellm_params.input_cost_per_token ||
null} "-"}
</pre>
</TableCell> </TableCell>
<TableCell <TableCell
style={{ style={{
@ -1207,20 +1243,25 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
wordBreak: "break-word", wordBreak: "break-word",
}} }}
> >
<pre style={{ fontSize: "10px" }}>
{model.output_cost || {model.output_cost ||
model.litellm_params.output_cost_per_token || model.litellm_params.output_cost_per_token ||
null} "-"}
</pre>
</TableCell> </TableCell>
<TableCell <TableCell>
style={{ <p style={{ fontSize: "10px" }}>
maxWidth: "120px", {
whiteSpace: "normal", premiumUser ? formatCreatedAt(model.model_info.created_at) || "-" : "-"
wordBreak: "break-word", }
}} </p>
>
<p style={{ fontSize: "10px" }}> </TableCell>
Max Tokens: {model.max_tokens} <br></br> <TableCell>
Max Input Tokens: {model.max_input_tokens} <p style={{ fontSize: "10px" }}>
{
premiumUser ? model.model_info.created_by || "-" : "-"
}
</p> </p>
</TableCell> </TableCell>
<TableCell <TableCell
@ -1236,7 +1277,7 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
size="xs" size="xs"
className="text-white" className="text-white"
> >
<p style={{ fontSize: "10px" }}>DB Model</p> <p style={{ fontSize: "8px" }}>DB Model</p>
</Badge> </Badge>
) : ( ) : (
<Badge <Badge
@ -1244,26 +1285,42 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
size="xs" size="xs"
className="text-black" className="text-black"
> >
<p style={{ fontSize: "10px" }}>Config Model</p> <p style={{ fontSize: "8px" }}>Config Model</p>
</Badge> </Badge>
)} )}
</TableCell> </TableCell>
<TableCell <TableCell
style={{ style={{
maxWidth: "100px", maxWidth: "150px",
whiteSpace: "normal", whiteSpace: "normal",
wordBreak: "break-word", wordBreak: "break-word",
}} }}
> >
<Grid numItems={3}>
<Col>
<Icon
icon={InformationCircleIcon}
size="sm"
onClick={() => handleInfoClick(model)}
/>
</Col>
<Col>
<Icon <Icon
icon={PencilAltIcon} icon={PencilAltIcon}
size="sm" size="sm"
onClick={() => handleEditClick(model)} onClick={() => handleEditClick(model)}
/> />
</Col>
<Col>
<DeleteModelButton <DeleteModelButton
modelID={model.model_info.id} modelID={model.model_info.id}
accessToken={accessToken} accessToken={accessToken}
/> />
</Col>
</Grid>
</TableCell> </TableCell>
</TableRow> </TableRow>
))} ))}
@ -1277,6 +1334,20 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
model={selectedModel} model={selectedModel}
onSubmit={handleEditSubmit} onSubmit={handleEditSubmit}
/> />
<Modal
title={selectedModel && selectedModel.model_name}
visible={infoModalVisible}
width={800}
footer={null}
onCancel={handleInfoCancel}
>
<Title>Model Info</Title>
<SyntaxHighlighter language="json" >
{selectedModel && JSON.stringify(selectedModel, null, 2)}
</SyntaxHighlighter>
</Modal>
</TabPanel> </TabPanel>
<TabPanel className="h-full"> <TabPanel className="h-full">
<Title2 level={2}>Add new model</Title2> <Title2 level={2}>Add new model</Title2>