mirror of
https://github.com/tiennm99/litellm.git
synced 2026-07-17 06:17:39 +00:00
Merge pull request #9269 from BerriAI/litellm_rc_03_14_2025
feat(team_endpoints.py): unfurl 'all-proxy-models' on team info endpo…
This commit is contained in:
@@ -4,4 +4,6 @@ model_list:
|
||||
model: azure/chatgpt-v-2
|
||||
api_key: os.environ/AZURE_API_KEY
|
||||
api_base: os.environ/AZURE_API_BASE
|
||||
|
||||
|
||||
litellm_settings:
|
||||
callbacks: ["prometheus"]
|
||||
@@ -40,6 +40,7 @@ from litellm.proxy._types import (
|
||||
ProxyErrorTypes,
|
||||
ProxyException,
|
||||
SpecialManagementEndpointEnums,
|
||||
SpecialModelNames,
|
||||
TeamAddMemberResponse,
|
||||
TeamInfoResponseObject,
|
||||
TeamListResponseObject,
|
||||
@@ -70,6 +71,7 @@ from litellm.proxy.utils import (
|
||||
_premium_user_check,
|
||||
handle_exception_on_proxy,
|
||||
)
|
||||
from litellm.router import Router
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -1238,6 +1240,23 @@ def validate_membership(
|
||||
)
|
||||
|
||||
|
||||
def _unfurl_all_proxy_models(
|
||||
team_info: LiteLLM_TeamTable, llm_router: Router
|
||||
) -> LiteLLM_TeamTable:
|
||||
if (
|
||||
SpecialModelNames.all_proxy_models.value in team_info.models
|
||||
and llm_router is not None
|
||||
):
|
||||
team_models: set[str] = set() # make set to avoid duplicates
|
||||
for model in team_info.models:
|
||||
if model != SpecialModelNames.all_proxy_models.value:
|
||||
team_models.add(model)
|
||||
for model in llm_router.get_model_names():
|
||||
team_models.add(model)
|
||||
team_info.models = list(team_models)
|
||||
return team_info
|
||||
|
||||
|
||||
@router.get(
|
||||
"/team/info", tags=["team management"], dependencies=[Depends(user_api_key_auth)]
|
||||
)
|
||||
@@ -1260,7 +1279,7 @@ async def team_info(
|
||||
--header 'Authorization: Bearer your_api_key_here'
|
||||
```
|
||||
"""
|
||||
from litellm.proxy.proxy_server import prisma_client
|
||||
from litellm.proxy.proxy_server import llm_router, prisma_client
|
||||
|
||||
try:
|
||||
if prisma_client is None:
|
||||
@@ -1333,6 +1352,9 @@ async def team_info(
|
||||
else:
|
||||
_team_info = LiteLLM_TeamTable()
|
||||
|
||||
## UNFURL 'all-proxy-models' into the team_info.models list ##
|
||||
if llm_router is not None:
|
||||
_team_info = _unfurl_all_proxy_models(_team_info, llm_router)
|
||||
response_object = TeamInfoResponseObject(
|
||||
team_id=team_id,
|
||||
team_info=_team_info,
|
||||
@@ -1534,7 +1556,7 @@ async def list_team(
|
||||
- user_id: str - Optional. If passed will only return teams that the user_id is a member of.
|
||||
- organization_id: str - Optional. If passed will only return teams that belong to the organization_id. Pass 'default_organization' to get all teams without organization_id.
|
||||
"""
|
||||
from litellm.proxy.proxy_server import prisma_client
|
||||
from litellm.proxy.proxy_server import llm_router, prisma_client
|
||||
|
||||
if not allowed_route_check_inside_route(
|
||||
user_api_key_dict=user_api_key_dict, requested_user_id=user_id
|
||||
@@ -1593,6 +1615,11 @@ async def list_team(
|
||||
)
|
||||
|
||||
try:
|
||||
# unfurl all-proxy-models
|
||||
if llm_router is not None:
|
||||
team = _unfurl_all_proxy_models(
|
||||
LiteLLM_TeamTable(**team.model_dump()), llm_router
|
||||
)
|
||||
returned_responses.append(
|
||||
TeamListResponseObject(
|
||||
**team.model_dump(),
|
||||
|
||||
@@ -310,9 +310,7 @@ export default function CreateKeyPage() {
|
||||
<BudgetPanel accessToken={accessToken} />
|
||||
) : page == "guardrails" ? (
|
||||
<GuardrailsPanel accessToken={accessToken} />
|
||||
) : page == "transform-request" ? (
|
||||
<TransformRequestPanel accessToken={accessToken} />
|
||||
) : page == "general-settings" ? (
|
||||
): page == "general-settings" ? (
|
||||
<GeneralSettings
|
||||
userID={userID}
|
||||
userRole={userRole}
|
||||
|
||||
@@ -68,7 +68,6 @@ const menuItems: MenuItem[] = [
|
||||
{ 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: "18", page: "transform-request", label: "Playground", icon: <ThunderboltOutlined />, roles: all_admin_roles },
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
NumberInput,
|
||||
} from "@tremor/react";
|
||||
import { ArrowLeftIcon, TrashIcon, KeyIcon } from "@heroicons/react/outline";
|
||||
import { modelDeleteCall, modelUpdateCall, CredentialItem, credentialGetCall, credentialCreateCall } from "./networking";
|
||||
import { modelDeleteCall, modelUpdateCall, CredentialItem, credentialGetCall, credentialCreateCall, modelInfoCall, modelInfoV1Call } 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";
|
||||
@@ -48,7 +48,7 @@ export default function ModelInfoView({
|
||||
setSelectedModel
|
||||
}: ModelInfoViewProps) {
|
||||
const [form] = Form.useForm();
|
||||
const [localModelData, setLocalModelData] = useState(modelData);
|
||||
const [localModelData, setLocalModelData] = useState<any>(null);
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [isCredentialModalOpen, setIsCredentialModalOpen] = useState(false);
|
||||
const [isDirty, setIsDirty] = useState(false);
|
||||
@@ -77,7 +77,16 @@ export default function ModelInfoView({
|
||||
credential_info: existingCredentialResponse["credential_info"]
|
||||
});
|
||||
}
|
||||
|
||||
const getModelInfo = async () => {
|
||||
if (!accessToken) return;
|
||||
let modelInfoResponse = await modelInfoV1Call(accessToken, modelId);
|
||||
console.log("modelInfoResponse, ", modelInfoResponse);
|
||||
let specificModelData = modelInfoResponse.data[0];
|
||||
setLocalModelData(specificModelData);
|
||||
}
|
||||
getExistingCredential();
|
||||
getModelInfo();
|
||||
}, [accessToken, modelId]);
|
||||
|
||||
const handleReuseCredential = async (values: any) => {
|
||||
@@ -292,24 +301,25 @@ export default function ModelInfoView({
|
||||
</TremorButton>
|
||||
)}
|
||||
</div>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={handleModelUpdate}
|
||||
initialValues={{
|
||||
{localModelData ? (
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={handleModelUpdate}
|
||||
initialValues={{
|
||||
model_name: localModelData.model_name,
|
||||
litellm_model_name: localModelData.litellm_model_name,
|
||||
api_base: localModelData.litellm_params?.api_base,
|
||||
custom_llm_provider: localModelData.litellm_params?.custom_llm_provider,
|
||||
organization: localModelData.litellm_params?.organization,
|
||||
tpm: localModelData.litellm_params?.tpm,
|
||||
rpm: localModelData.litellm_params?.rpm,
|
||||
max_retries: localModelData.litellm_params?.max_retries,
|
||||
timeout: localModelData.litellm_params?.timeout,
|
||||
stream_timeout: localModelData.litellm_params?.stream_timeout,
|
||||
input_cost: localModelData.litellm_params?.input_cost_per_token ?
|
||||
(localModelData.litellm_params.input_cost_per_token * 1_000_000) : modelData.input_cost * 1_000_000,
|
||||
api_base: localModelData.litellm_params.api_base,
|
||||
custom_llm_provider: localModelData.litellm_params.custom_llm_provider,
|
||||
organization: localModelData.litellm_params.organization,
|
||||
tpm: localModelData.litellm_params.tpm,
|
||||
rpm: localModelData.litellm_params.rpm,
|
||||
max_retries: localModelData.litellm_params.max_retries,
|
||||
timeout: localModelData.litellm_params.timeout,
|
||||
stream_timeout: localModelData.litellm_params.stream_timeout,
|
||||
input_cost: localModelData.litellm_params.input_cost_per_token ?
|
||||
(localModelData.litellm_params.input_cost_per_token * 1_000_000) : localModelData.model_info?.input_cost_per_token * 1_000_000 || null,
|
||||
output_cost: localModelData.litellm_params?.output_cost_per_token ?
|
||||
(localModelData.litellm_params.output_cost_per_token * 1_000_000) : modelData.output_cost * 1_000_000,
|
||||
(localModelData.litellm_params.output_cost_per_token * 1_000_000) : localModelData.model_info?.output_cost_per_token * 1_000_000 || null,
|
||||
}}
|
||||
layout="vertical"
|
||||
onValuesChange={() => setIsDirty(true)}
|
||||
@@ -346,9 +356,9 @@ export default function ModelInfoView({
|
||||
</Form.Item>
|
||||
) : (
|
||||
<div className="mt-1 p-2 bg-gray-50 rounded">
|
||||
{localModelData.litellm_params?.input_cost_per_token
|
||||
? (localModelData.litellm_params.input_cost_per_token * 1_000_000).toFixed(4)
|
||||
: modelData.input_cost * 1_000_000}
|
||||
{localModelData?.litellm_params?.input_cost_per_token
|
||||
? (localModelData.litellm_params?.input_cost_per_token * 1_000_000).toFixed(4)
|
||||
: localModelData?.model_info?.input_cost_per_token ? (localModelData.model_info.input_cost_per_token * 1_000_000).toFixed(4) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -361,9 +371,9 @@ export default function ModelInfoView({
|
||||
</Form.Item>
|
||||
) : (
|
||||
<div className="mt-1 p-2 bg-gray-50 rounded">
|
||||
{localModelData.litellm_params?.output_cost_per_token
|
||||
{localModelData?.litellm_params?.output_cost_per_token
|
||||
? (localModelData.litellm_params.output_cost_per_token * 1_000_000).toFixed(4)
|
||||
: modelData.output_cost * 1_000_000}
|
||||
: localModelData?.model_info?.output_cost_per_token ? (localModelData.model_info.output_cost_per_token * 1_000_000).toFixed(4) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -502,7 +512,10 @@ export default function ModelInfoView({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Form>
|
||||
</Form>
|
||||
) : (
|
||||
<Text>Loading...</Text>
|
||||
)}
|
||||
</Card>
|
||||
</TabPanel>
|
||||
|
||||
|
||||
@@ -1254,6 +1254,36 @@ export const modelInfoCall = async (
|
||||
}
|
||||
};
|
||||
|
||||
export const modelInfoV1Call = async (accessToken: String, modelId: String) => {
|
||||
/**
|
||||
* Get all models on proxy
|
||||
*/
|
||||
try {
|
||||
let url = proxyBaseUrl ? `${proxyBaseUrl}/v1/model/info` : `/v1/model/info`;
|
||||
url += `?litellm_model_id=${modelId}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.text();
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log("modelInfoV1Call:", data);
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Failed to create key:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const modelHubCall = async (accessToken: String) => {
|
||||
/**
|
||||
* Get all models on proxy
|
||||
|
||||
@@ -122,7 +122,7 @@ const TopKeyView: React.FC<TopKeyViewProps> = ({
|
||||
<BarChart
|
||||
className="mt-4 h-40 cursor-pointer hover:opacity-90"
|
||||
data={topKeys}
|
||||
index="key"
|
||||
index="key_alias"
|
||||
categories={["spend"]}
|
||||
colors={["cyan"]}
|
||||
yAxisWidth={80}
|
||||
@@ -130,9 +130,26 @@ const TopKeyView: React.FC<TopKeyViewProps> = ({
|
||||
layout="vertical"
|
||||
showXAxis={false}
|
||||
showLegend={false}
|
||||
valueFormatter={(value) => `$${value.toFixed(2)}`}
|
||||
valueFormatter={(value) => value ? `$${value.toFixed(2)}` : "No Key Alias"}
|
||||
onValueChange={(item) => handleKeyClick(item)}
|
||||
showTooltip={true}
|
||||
customTooltip={(props) => {
|
||||
const item = props.payload?.[0]?.payload;
|
||||
return (
|
||||
<div className="p-3 bg-black/90 shadow-lg rounded-lg text-white">
|
||||
<div className="space-y-1.5">
|
||||
<div className="text-sm">
|
||||
<span className="text-gray-300">Key: </span>
|
||||
<span className="font-mono text-gray-100">{item?.key?.slice(0, 10)}...</span>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<span className="text-gray-300">Spend: </span>
|
||||
<span className="text-white font-medium">${item?.spend.toFixed(2)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user