diff --git a/.circleci/config.yml b/.circleci/config.yml
index acf8612eacd..2ee3f24a08d 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -105,7 +105,7 @@ jobs:
command: |
pwd
ls
- python -m pytest -vv tests/local_testing --cov=litellm --cov-report=xml -x --junitxml=test-results/junit.xml --durations=5 -k "not test_python_38.py and not test_basic_python_version.py and not router and not assistants and not langfuse and not caching and not cache" -n 4
+ python -m pytest -vv tests/local_testing --cov=litellm --cov-report=xml -x --junitxml=test-results/junit.xml --durations=5 -k "not test_python_38.py and not router and not assistants and not langfuse and not caching and not cache" -n 4
no_output_timeout: 120m
- run:
name: Rename the coverage files
@@ -895,7 +895,6 @@ jobs:
pip install "pytest-retry==1.6.3"
pip install "pytest-asyncio==0.21.1"
pip install "pytest-cov==5.0.0"
- pip install "tomli==2.2.1"
- run:
name: Run tests
command: |
diff --git a/README.md b/README.md
index c7ea44cf462..b1e6c9db384 100644
--- a/README.md
+++ b/README.md
@@ -358,7 +358,7 @@ poetry install -E extra_proxy -E proxy
Step 3: Test your change:
```
-cd tests # pwd: Documents/litellm/litellm/tests
+cd litellm/tests # pwd: Documents/litellm/litellm/tests
poetry run flake8
poetry run pytest .
```
diff --git a/dist/litellm-1.57.6.tar.gz b/dist/litellm-1.57.6.tar.gz
deleted file mode 100644
index 01a039cf6ee..00000000000
Binary files a/dist/litellm-1.57.6.tar.gz and /dev/null differ
diff --git a/docs/my-website/docs/secret.md b/docs/my-website/docs/secret.md
index a65c696f367..22bad6fec18 100644
--- a/docs/my-website/docs/secret.md
+++ b/docs/my-website/docs/secret.md
@@ -72,20 +72,6 @@ general_settings:
prefix_for_stored_virtual_keys: "litellm/" # OPTIONAL. If set, this prefix will be used for stored virtual keys in the secret manager
access_mode: "write_only" # Literal["read_only", "write_only", "read_and_write"]
```
-
-
-
-```yaml
-general_settings:
- master_key: os.environ/litellm_master_key
- key_management_system: "aws_secret_manager" # 👈 KEY CHANGE
- key_management_settings:
- store_virtual_keys: true # OPTIONAL. Defaults to False, when True will store virtual keys in secret manager
- prefix_for_stored_virtual_keys: "litellm/" # OPTIONAL. If set, this prefix will be used for stored virtual keys in the secret manager
- access_mode: "read_and_write" # Literal["read_only", "write_only", "read_and_write"]
- hosted_keys: ["litellm_master_key"] # OPTIONAL. Specify which env keys you stored on AWS
-```
-
@@ -200,6 +186,34 @@ LiteLLM stores secret under the `prefix_for_stored_virtual_keys` path (default:
## Azure Key Vault
+
#### Usage with LiteLLM Proxy Server
diff --git a/litellm/proxy/_experimental/out/404.html b/litellm/proxy/_experimental/out/404.html
new file mode 100644
index 00000000000..3bbcc888404
--- /dev/null
+++ b/litellm/proxy/_experimental/out/404.html
@@ -0,0 +1 @@
+
404: This page could not be found.LiteLLM Dashboard404
This page could not be found.
\ No newline at end of file
diff --git a/litellm/proxy/_experimental/out/model_hub.html b/litellm/proxy/_experimental/out/model_hub.html
new file mode 100644
index 00000000000..8a63b4419fa
--- /dev/null
+++ b/litellm/proxy/_experimental/out/model_hub.html
@@ -0,0 +1 @@
+LiteLLM Dashboard
\ No newline at end of file
diff --git a/litellm/proxy/_experimental/out/onboarding.html b/litellm/proxy/_experimental/out/onboarding.html
new file mode 100644
index 00000000000..070f19d8e3f
--- /dev/null
+++ b/litellm/proxy/_experimental/out/onboarding.html
@@ -0,0 +1 @@
+LiteLLM Dashboard
\ No newline at end of file
diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py
index 3461952ed6d..bad7c6c1178 100644
--- a/litellm/proxy/_types.py
+++ b/litellm/proxy/_types.py
@@ -2097,13 +2097,12 @@ class TeamMemberDeleteRequest(MemberDeleteRequest):
class TeamMemberUpdateRequest(TeamMemberDeleteRequest):
- max_budget_in_team: Optional[float] = None
- role: Optional[Literal["admin", "user"]] = None
+ max_budget_in_team: float
class TeamMemberUpdateResponse(MemberUpdateResponse):
team_id: str
- max_budget_in_team: Optional[float] = None
+ max_budget_in_team: float
# Organization Member Requests
diff --git a/litellm/proxy/management_endpoints/team_endpoints.py b/litellm/proxy/management_endpoints/team_endpoints.py
index 6ebfed0eef1..1d21e315b37 100644
--- a/litellm/proxy/management_endpoints/team_endpoints.py
+++ b/litellm/proxy/management_endpoints/team_endpoints.py
@@ -920,7 +920,7 @@ async def team_member_update(
"""
[BETA]
- Update team member budgets and team member role
+ Update team member budgets
"""
from litellm.proxy.proxy_server import prisma_client
@@ -970,8 +970,6 @@ async def team_member_update(
user_api_key_dict=user_api_key_dict,
)
- team_table = returned_team_info["team_info"]
-
## get user id
received_user_id: Optional[str] = None
if data.user_id is not None:
@@ -997,50 +995,26 @@ async def team_member_update(
break
### upsert new budget
- if data.max_budget_in_team is not None:
- if identified_budget_id is None:
- new_budget = await prisma_client.db.litellm_budgettable.create(
- data={
- "max_budget": data.max_budget_in_team,
- "created_by": user_api_key_dict.user_id or "",
- "updated_by": user_api_key_dict.user_id or "",
- }
- )
+ if identified_budget_id is None:
+ new_budget = await prisma_client.db.litellm_budgettable.create(
+ data={
+ "max_budget": data.max_budget_in_team,
+ "created_by": user_api_key_dict.user_id or "",
+ "updated_by": user_api_key_dict.user_id or "",
+ }
+ )
- await prisma_client.db.litellm_teammembership.create(
- data={
- "team_id": data.team_id,
- "user_id": received_user_id,
- "budget_id": new_budget.budget_id,
- },
- )
- elif identified_budget_id is not None:
- await prisma_client.db.litellm_budgettable.update(
- where={"budget_id": identified_budget_id},
- data={"max_budget": data.max_budget_in_team},
- )
-
- ### update team member role
- if data.role is not None:
- team_members: List[Member] = []
- for member in team_table.members_with_roles:
- if member.user_id == received_user_id:
- team_members.append(
- Member(
- user_id=member.user_id,
- role=data.role,
- user_email=data.user_email or member.user_email,
- )
- )
- else:
- team_members.append(member)
-
- team_table.members_with_roles = team_members
-
- _db_team_members: List[dict] = [m.model_dump() for m in team_members]
- await prisma_client.db.litellm_teamtable.update(
- where={"team_id": data.team_id},
- data={"members_with_roles": json.dumps(_db_team_members)}, # type: ignore
+ await prisma_client.db.litellm_teammembership.create(
+ data={
+ "team_id": data.team_id,
+ "user_id": received_user_id,
+ "budget_id": new_budget.budget_id,
+ },
+ )
+ else:
+ await prisma_client.db.litellm_budgettable.update(
+ where={"budget_id": identified_budget_id},
+ data={"max_budget": data.max_budget_in_team},
)
return TeamMemberUpdateResponse(
diff --git a/tests/local_testing/test_basic_python_version.py b/tests/local_testing/test_basic_python_version.py
index c629ef3df8e..5fa48f09699 100644
--- a/tests/local_testing/test_basic_python_version.py
+++ b/tests/local_testing/test_basic_python_version.py
@@ -37,51 +37,6 @@ def test_litellm_proxy_server():
assert True
-def test_package_dependencies():
- try:
- import tomli
- import pathlib
- import litellm
-
- # Get the litellm package root path
- litellm_path = pathlib.Path(litellm.__file__).parent.parent
- pyproject_path = litellm_path / "pyproject.toml"
-
- # Read and parse pyproject.toml
- with open(pyproject_path, "rb") as f:
- pyproject = tomli.load(f)
-
- # Get all optional dependencies from poetry.dependencies
- poetry_deps = pyproject["tool"]["poetry"]["dependencies"]
- optional_deps = {
- name.lower()
- for name, value in poetry_deps.items()
- if isinstance(value, dict) and value.get("optional", False)
- }
- print(optional_deps)
- # Get all packages listed in extras
- extras = pyproject["tool"]["poetry"]["extras"]
- all_extra_deps = set()
- for extra_group in extras.values():
- all_extra_deps.update(dep.lower() for dep in extra_group)
- print(all_extra_deps)
- # Check that all optional dependencies are in some extras group
- missing_from_extras = optional_deps - all_extra_deps
- assert (
- not missing_from_extras
- ), f"Optional dependencies missing from extras: {missing_from_extras}"
-
- print(
- f"All {len(optional_deps)} optional dependencies are correctly specified in extras"
- )
-
- except Exception as e:
- pytest.fail(
- f"Error occurred while checking dependencies: {str(e)}\n"
- + traceback.format_exc()
- )
-
-
import os
import subprocess
import time
diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx
index f219b563f9a..a0ed6d702a6 100644
--- a/ui/litellm-dashboard/src/components/networking.tsx
+++ b/ui/litellm-dashboard/src/components/networking.tsx
@@ -2321,47 +2321,6 @@ export const teamMemberAddCall = async (
}
};
-export const teamMemberUpdateCall = async (
- accessToken: string,
- teamId: string,
- formValues: Member // Assuming formValues is an object
-) => {
- try {
- console.log("Form Values in teamMemberAddCall:", formValues); // Log the form values before making the API call
-
- const url = proxyBaseUrl
- ? `${proxyBaseUrl}/team/member_update`
- : `/team/member_update`;
- const response = await fetch(url, {
- method: "POST",
- headers: {
- [globalLitellmHeaderName]: `Bearer ${accessToken}`,
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- team_id: teamId,
- role: formValues.role,
- user_id: formValues.user_id,
- }),
- });
-
- if (!response.ok) {
- const errorData = await response.text();
- handleError(errorData);
- console.error("Error response from the server:", errorData);
- throw new Error("Network response was not ok");
- }
-
- const data = await response.json();
- console.log("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 organizationMemberAddCall = async (
accessToken: string,
organizationId: string,
diff --git a/ui/litellm-dashboard/src/components/teams.tsx b/ui/litellm-dashboard/src/components/teams.tsx
index e988b65e3a7..8979a96a3b4 100644
--- a/ui/litellm-dashboard/src/components/teams.tsx
+++ b/ui/litellm-dashboard/src/components/teams.tsx
@@ -2,7 +2,6 @@ import React, { useState, useEffect } from "react";
import Link from "next/link";
import { Typography } from "antd";
import { teamDeleteCall, teamUpdateCall, teamInfoCall } from "./networking";
-import TeamMemberModal, { TeamMember } from "@/components/team/edit_membership";
import {
InformationCircleIcon,
PencilAltIcon,
@@ -66,12 +65,12 @@ interface EditTeamModalProps {
import {
teamCreateCall,
teamMemberAddCall,
- teamMemberUpdateCall,
Member,
modelAvailableCall,
teamListCall
} from "./networking";
+
const Team: React.FC = ({
teams,
searchParams,
@@ -113,12 +112,11 @@ const Team: React.FC = ({
const [isTeamModalVisible, setIsTeamModalVisible] = useState(false);
const [isAddMemberModalVisible, setIsAddMemberModalVisible] = useState(false);
- const [isEditMemberModalVisible, setIsEditMemberModalVisible] = useState(false);
const [userModels, setUserModels] = useState([]);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [teamToDelete, setTeamToDelete] = useState(null);
- const [selectedEditMember, setSelectedEditMember] = useState(null);
+ // store team info as {"team_id": team_info_object}
const [perTeamInfo, setPerTeamInfo] = useState>({});
const EditTeamModal: React.FC = ({
@@ -259,19 +257,16 @@ const Team: React.FC = ({
const handleMemberOk = () => {
setIsAddMemberModalVisible(false);
- setIsEditMemberModalVisible(false);
memberForm.resetFields();
};
const handleCancel = () => {
setIsTeamModalVisible(false);
-
form.resetFields();
};
const handleMemberCancel = () => {
setIsAddMemberModalVisible(false);
- setIsEditMemberModalVisible(false);
memberForm.resetFields();
};
@@ -417,7 +412,7 @@ const Team: React.FC = ({
return false;
}
- const _common_member_update_call = async (formValues: Record, callType: "add" | "edit") => {
+ const handleMemberCreate = async (formValues: Record) => {
try {
if (accessToken != null && teams != null) {
message.info("Adding Member");
@@ -426,27 +421,13 @@ const Team: React.FC = ({
user_email: formValues.user_email,
user_id: formValues.user_id,
};
- let response: any;
- if (callType == "add") {
- response = await teamMemberAddCall(
- accessToken,
- selectedTeam["team_id"],
- user_role
- );
- message.success("Member added");
- } else {
- response = await teamMemberUpdateCall(
- accessToken,
- selectedTeam["team_id"],
- {
- "role": formValues.role,
- "user_id": formValues.id,
- "user_email": formValues.email
- }
- );
- message.success("Member updated");
- }
-
+ const response: any = await teamMemberAddCall(
+ accessToken,
+ selectedTeam["team_id"],
+ user_role
+ );
+ message.success("Member added");
+ console.log(`response for team create call: ${response["data"]}`);
// Checking if the team exists in the list and updating or adding accordingly
const foundIndex = teams.findIndex((team) => {
console.log(
@@ -468,15 +449,7 @@ const Team: React.FC = ({
} catch (error) {
console.error("Error creating the team:", error);
}
- }
-
- const handleMemberCreate = async (formValues: Record) => {
- _common_member_update_call(formValues, "add");
};
-
- const handleMemberUpdate = async (formValues: Record) => {
- _common_member_update_call(formValues, "edit");
- }
return (
@@ -858,29 +831,6 @@ const Team: React.FC = ({
: null}
{member["role"]}
-
- {userRole == "Admin" ? (
- <>
- {
- setIsEditMemberModalVisible(true);
- setSelectedEditMember({
- "id": member["user_id"],
- "email": member["user_email"],
- "role": member["role"]
- })
- }}
- />
- {}}
- icon={TrashIcon}
- size="sm"
- />
- >
- ) : null}
-
)
)
@@ -888,13 +838,6 @@ const Team: React.FC = ({
-
{selectedTeam && (
= ({
if (cachedUserModels) {
setUserModels(JSON.parse(cachedUserModels));
} else {
- const fetchTeams = async () => {
- let givenTeams;
- if (userRole != "Admin" && userRole != "Admin Viewer") {
- givenTeams = await teamListCall(accessToken, userID)
- } else {
- givenTeams = await teamListCall(accessToken)
- }
-
- console.log(`givenTeams: ${givenTeams}`)
-
- setTeams(givenTeams)
- }
const fetchData = async () => {
try {
const proxy_settings: ProxySettings = await getProxyUISettings(accessToken);
@@ -207,6 +194,7 @@ const UserDashboard: React.FC = ({
setUserSpendData(response["user_info"]);
console.log(`userSpendData: ${JSON.stringify(userSpendData)}`)
setKeys(response["keys"]); // Assuming this is the correct path to your data
+ setTeams(response["teams"]);
const teamsArray = [...response["teams"]];
if (teamsArray.length > 0) {
console.log(`response['teams']: ${teamsArray}`);
@@ -247,7 +235,6 @@ const UserDashboard: React.FC = ({
}
};
fetchData();
- fetchTeams();
}
}
}, [userID, token, accessToken, keys, userRole]);
diff --git a/ui/litellm-dashboard/src/components/view_key_table.tsx b/ui/litellm-dashboard/src/components/view_key_table.tsx
index 144c30c0d20..5ee3007195c 100644
--- a/ui/litellm-dashboard/src/components/view_key_table.tsx
+++ b/ui/litellm-dashboard/src/components/view_key_table.tsx
@@ -131,57 +131,6 @@ const ViewKeyTable: React.FC = ({
const [knownTeamIDs, setKnownTeamIDs] = useState(initialKnownTeamIDs);
- // Function to check if user is admin of a team
- const isUserTeamAdmin = (team: any) => {
- if (!team.members_with_roles) return false;
- return team.members_with_roles.some(
- (member: any) => member.role === "admin" && member.user_id === userID
- );
- };
-
- // Combine all keys that user should have access to
- const all_keys_to_display = React.useMemo(() => {
- let allKeys: any[] = [];
-
- // If no teams, return personal keys
- if (!teams || teams.length === 0) {
- return data;
- }
-
- teams.forEach(team => {
- // For default team or when user is not admin, use personal keys (data)
- if (team.team_id === "default-team" || !isUserTeamAdmin(team)) {
- if (selectedTeam && selectedTeam.team_id === team.team_id && data) {
- allKeys = [...allKeys, ...data.filter(key => key.team_id === team.team_id)];
- }
- }
- // For teams where user is admin, use team keys
- else if (isUserTeamAdmin(team)) {
- if (selectedTeam && selectedTeam.team_id === team.team_id) {
- allKeys = [...allKeys, ...(team.keys || [])];
- }
- }
- });
-
- // If no team is selected, show all accessible keys
- if (!selectedTeam && data) {
- const personalKeys = data.filter(key => !key.team_id || key.team_id === "default-team");
- const adminTeamKeys = teams
- .filter(team => isUserTeamAdmin(team))
- .flatMap(team => team.keys || []);
- allKeys = [...personalKeys, ...adminTeamKeys];
- }
-
- // Filter out litellm-dashboard keys
- allKeys = allKeys.filter(key => key.team_id !== "litellm-dashboard");
-
- // Remove duplicates based on token
- const uniqueKeys = Array.from(
- new Map(allKeys.map(key => [key.token, key])).values()
- );
-
- return uniqueKeys;
- }, [data, teams, selectedTeam, userID]);
useEffect(() => {
const calculateNewExpiryTime = (duration: string | undefined) => {
@@ -909,7 +858,7 @@ const ViewKeyTable: React.FC = ({
- {all_keys_to_display && all_keys_to_display.map((item) => {
+ {data.map((item) => {
console.log(item);
// skip item if item.team_id == "litellm-dashboard"
if (item.team_id === "litellm-dashboard") {