mirror of
https://github.com/BerriAI/litellm.git
synced 2025-04-26 11:14:04 +00:00
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:
commit
d71bb96047
4 changed files with 149 additions and 59 deletions
|
@ -21,6 +21,7 @@ Features:
|
|||
- ✅ Don't log/store specific requests to Langfuse, Sentry, etc. (eg confidential LLM requests)
|
||||
- ✅ Tracking Spend for Custom Tags
|
||||
- ✅ Custom Branding + Routes on Swagger Docs
|
||||
- ✅ Audit Logs for `Created At, Created By` when Models Added
|
||||
|
||||
|
||||
## Content Moderation
|
||||
|
|
|
@ -2790,6 +2790,13 @@ class ProxyConfig:
|
|||
model.model_info["id"] = _id
|
||||
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 "id" not in model.model_info:
|
||||
model.model_info["id"] = model.model_id
|
||||
|
@ -3075,10 +3082,9 @@ class ProxyConfig:
|
|||
|
||||
try:
|
||||
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}"
|
||||
)
|
||||
verbose_proxy_logger.debug(f"llm_router: {llm_router}")
|
||||
new_models = await prisma_client.db.litellm_proxymodeltable.find_many()
|
||||
# update llm router
|
||||
await self._update_llm_router(
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
"""
|
||||
litellm.Router Types - includes RouterConfig, UpdateRouterConfig, ModelInfo etc
|
||||
"""
|
||||
|
||||
from typing import List, Optional, Union, Dict, Tuple, Literal, TypedDict
|
||||
import uuid
|
||||
import enum
|
||||
import httpx
|
||||
from pydantic import BaseModel, validator, Field
|
||||
from pydantic import BaseModel, Field
|
||||
import datetime
|
||||
from .completion import CompletionRequest
|
||||
from .embedding import EmbeddingRequest
|
||||
import uuid, enum
|
||||
|
||||
|
||||
class ModelConfig(BaseModel):
|
||||
|
@ -76,6 +82,12 @@ class ModelInfo(BaseModel):
|
|||
db_model: bool = (
|
||||
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] = (
|
||||
None # specify if the base model is azure/gpt-3.5-turbo etc for accurate cost tracking
|
||||
)
|
||||
|
|
|
@ -86,6 +86,8 @@ import type { UploadProps } from "antd";
|
|||
import { Upload } from "antd";
|
||||
import TimeToFirstToken from "./model_metrics/time_to_first_token";
|
||||
import DynamicFields from "./model_add/dynamic_form";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
|
||||
interface ModelDashboardProps {
|
||||
accessToken: string | null;
|
||||
token: string | null;
|
||||
|
@ -269,6 +271,8 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
|||
const [selectedProvider, setSelectedProvider] = useState<String>("OpenAI");
|
||||
const [healthCheckResponse, setHealthCheckResponse] = useState<string>("");
|
||||
const [editModalVisible, setEditModalVisible] = useState<boolean>(false);
|
||||
const [infoModalVisible, setInfoModalVisible] = useState<boolean>(false);
|
||||
|
||||
const [selectedModel, setSelectedModel] = useState<any>(null);
|
||||
const [availableModelGroups, setAvailableModelGroups] = useState<
|
||||
Array<string>
|
||||
|
@ -297,6 +301,15 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
|||
useState<RetryPolicyObject | null>(null);
|
||||
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> = ({
|
||||
visible,
|
||||
onCancel,
|
||||
|
@ -423,11 +436,21 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
|||
setEditModalVisible(true);
|
||||
};
|
||||
|
||||
const handleInfoClick = (model: any) => {
|
||||
setSelectedModel(model);
|
||||
setInfoModalVisible(true);
|
||||
};
|
||||
|
||||
const handleEditCancel = () => {
|
||||
setEditModalVisible(false);
|
||||
setSelectedModel(null);
|
||||
};
|
||||
|
||||
const handleInfoCancel = () => {
|
||||
setInfoModalVisible(false);
|
||||
setSelectedModel(null);
|
||||
};
|
||||
|
||||
const handleEditSubmit = async (formValues: Record<string, any>) => {
|
||||
// Call API to update team with teamId and values
|
||||
|
||||
|
@ -1039,7 +1062,6 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
|||
</div>
|
||||
<Card>
|
||||
<Table
|
||||
className="mt-5"
|
||||
style={{ maxWidth: "1500px", width: "100%" }}
|
||||
>
|
||||
<TableHead>
|
||||
|
@ -1049,6 +1071,7 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
|||
maxWidth: "150px",
|
||||
whiteSpace: "normal",
|
||||
wordBreak: "break-word",
|
||||
fontSize: "11px"
|
||||
}}
|
||||
>
|
||||
Public Model Name
|
||||
|
@ -1058,6 +1081,7 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
|||
maxWidth: "100px",
|
||||
whiteSpace: "normal",
|
||||
wordBreak: "break-word",
|
||||
fontSize: "11px"
|
||||
}}
|
||||
>
|
||||
Provider
|
||||
|
@ -1068,25 +1092,18 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
|||
maxWidth: "150px",
|
||||
whiteSpace: "normal",
|
||||
wordBreak: "break-word",
|
||||
fontSize: "11px"
|
||||
}}
|
||||
>
|
||||
API Base
|
||||
</TableHeaderCell>
|
||||
)}
|
||||
<TableHeaderCell
|
||||
style={{
|
||||
maxWidth: "200px",
|
||||
whiteSpace: "normal",
|
||||
wordBreak: "break-word",
|
||||
}}
|
||||
>
|
||||
Extra litellm Params
|
||||
</TableHeaderCell>
|
||||
<TableHeaderCell
|
||||
style={{
|
||||
maxWidth: "85px",
|
||||
whiteSpace: "normal",
|
||||
wordBreak: "break-word",
|
||||
fontSize: "11px"
|
||||
}}
|
||||
>
|
||||
Input Price{" "}
|
||||
|
@ -1099,6 +1116,7 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
|||
maxWidth: "85px",
|
||||
whiteSpace: "normal",
|
||||
wordBreak: "break-word",
|
||||
fontSize: "11px"
|
||||
}}
|
||||
>
|
||||
Output Price{" "}
|
||||
|
@ -1106,24 +1124,45 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
|||
/1M Tokens ($)
|
||||
</p>
|
||||
</TableHeaderCell>
|
||||
|
||||
<TableHeaderCell
|
||||
style={{
|
||||
maxWidth: "120px",
|
||||
maxWidth: "100px",
|
||||
whiteSpace: "normal",
|
||||
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
|
||||
style={{
|
||||
maxWidth: "50px",
|
||||
whiteSpace: "normal",
|
||||
wordBreak: "break-word",
|
||||
fontSize: "11px"
|
||||
}}
|
||||
>
|
||||
Status
|
||||
</TableHeaderCell>
|
||||
<TableHeaderCell>
|
||||
|
||||
</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
|
@ -1137,15 +1176,17 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
|||
selectedModelGroup === ""
|
||||
)
|
||||
.map((model: any, index: number) => (
|
||||
<TableRow key={index}>
|
||||
<TableRow key={index} style={{ maxHeight: "1px", minHeight: "1px" }}>
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "150px",
|
||||
maxWidth: "100px",
|
||||
whiteSpace: "normal",
|
||||
wordBreak: "break-word",
|
||||
}}
|
||||
>
|
||||
<Text>{model.model_name}</Text>
|
||||
<p style={{ fontSize: "10px" }}>
|
||||
{model.model_name || "-"}
|
||||
</p>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{
|
||||
|
@ -1154,41 +1195,34 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
|||
wordBreak: "break-word",
|
||||
}}
|
||||
>
|
||||
{model.provider}
|
||||
<p style={{ fontSize: "10px" }}>
|
||||
{model.provider || "-"}
|
||||
</p>
|
||||
</TableCell>
|
||||
{userRole === "Admin" && (
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "150px",
|
||||
whiteSpace: "normal",
|
||||
wordBreak: "break-word",
|
||||
}}
|
||||
>
|
||||
<Tooltip title={model && model.api_base}>
|
||||
<pre
|
||||
style={{
|
||||
maxWidth: "150px",
|
||||
whiteSpace: "normal",
|
||||
wordBreak: "break-word",
|
||||
fontSize: "10px",
|
||||
}}
|
||||
title={model && model.api_base ? model.api_base : ""}
|
||||
>
|
||||
{model.api_base}
|
||||
</TableCell>
|
||||
{model && model.api_base ? model.api_base.slice(0, 20) : "-"}
|
||||
</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
|
||||
style={{
|
||||
maxWidth: "80px",
|
||||
|
@ -1196,9 +1230,11 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
|||
wordBreak: "break-word",
|
||||
}}
|
||||
>
|
||||
<pre style={{ fontSize: "10px" }}>
|
||||
{model.input_cost ||
|
||||
model.litellm_params.input_cost_per_token ||
|
||||
null}
|
||||
"-"}
|
||||
</pre>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{
|
||||
|
@ -1207,20 +1243,25 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
|||
wordBreak: "break-word",
|
||||
}}
|
||||
>
|
||||
<pre style={{ fontSize: "10px" }}>
|
||||
{model.output_cost ||
|
||||
model.litellm_params.output_cost_per_token ||
|
||||
null}
|
||||
"-"}
|
||||
</pre>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "120px",
|
||||
whiteSpace: "normal",
|
||||
wordBreak: "break-word",
|
||||
}}
|
||||
>
|
||||
<p style={{ fontSize: "10px" }}>
|
||||
Max Tokens: {model.max_tokens} <br></br>
|
||||
Max Input Tokens: {model.max_input_tokens}
|
||||
<TableCell>
|
||||
<p style={{ fontSize: "10px" }}>
|
||||
{
|
||||
premiumUser ? formatCreatedAt(model.model_info.created_at) || "-" : "-"
|
||||
}
|
||||
</p>
|
||||
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<p style={{ fontSize: "10px" }}>
|
||||
{
|
||||
premiumUser ? model.model_info.created_by || "-" : "-"
|
||||
}
|
||||
</p>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
|
@ -1236,7 +1277,7 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
|||
size="xs"
|
||||
className="text-white"
|
||||
>
|
||||
<p style={{ fontSize: "10px" }}>DB Model</p>
|
||||
<p style={{ fontSize: "8px" }}>DB Model</p>
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge
|
||||
|
@ -1244,26 +1285,42 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
|||
size="xs"
|
||||
className="text-black"
|
||||
>
|
||||
<p style={{ fontSize: "10px" }}>Config Model</p>
|
||||
<p style={{ fontSize: "8px" }}>Config Model</p>
|
||||
</Badge>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "100px",
|
||||
maxWidth: "150px",
|
||||
whiteSpace: "normal",
|
||||
wordBreak: "break-word",
|
||||
}}
|
||||
>
|
||||
<Grid numItems={3}>
|
||||
<Col>
|
||||
<Icon
|
||||
icon={InformationCircleIcon}
|
||||
size="sm"
|
||||
onClick={() => handleInfoClick(model)}
|
||||
/>
|
||||
</Col>
|
||||
<Col>
|
||||
<Icon
|
||||
icon={PencilAltIcon}
|
||||
size="sm"
|
||||
onClick={() => handleEditClick(model)}
|
||||
/>
|
||||
</Col>
|
||||
|
||||
<Col>
|
||||
<DeleteModelButton
|
||||
modelID={model.model_info.id}
|
||||
accessToken={accessToken}
|
||||
/>
|
||||
</Col>
|
||||
|
||||
</Grid>
|
||||
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
|
@ -1277,6 +1334,20 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
|||
model={selectedModel}
|
||||
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 className="h-full">
|
||||
<Title2 level={2}>Add new model</Title2>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue