forked from phoenix/litellm-mirror
517 lines
20 KiB
Python
517 lines
20 KiB
Python
"""
|
|
Admin sets proxy url + allowed email subdomain
|
|
"""
|
|
from dotenv import load_dotenv
|
|
|
|
load_dotenv()
|
|
import streamlit as st
|
|
import base64, os, json, uuid, requests
|
|
import pandas as pd
|
|
import plotly.express as px
|
|
import click
|
|
|
|
# Replace your_base_url with the actual URL where the proxy auth app is hosted
|
|
your_base_url = os.getenv("BASE_URL") # Example base URL
|
|
|
|
|
|
# Function to encode the configuration
|
|
def encode_config(proxy_url, allowed_email_subdomain, admin_emails):
|
|
combined_string = f"proxy_url={proxy_url}&accepted_email_subdomain={allowed_email_subdomain}&admin_emails={admin_emails}"
|
|
return base64.b64encode(combined_string.encode("utf-8")).decode("utf-8")
|
|
|
|
|
|
# Simple function to update config values
|
|
def update_config_values(proxy_url, allowed_email_subdomain, admin_emails):
|
|
st.session_state["proxy_url"] = proxy_url
|
|
st.session_state["allowed_email_subdomain"] = allowed_email_subdomain
|
|
st.session_state["admin_emails"] = admin_emails
|
|
if your_base_url.endswith("/"):
|
|
st.session_state[
|
|
"user_auth_url"
|
|
] = f"{your_base_url}user?page={encode_config(proxy_url=proxy_url, allowed_email_subdomain=allowed_email_subdomain, admin_emails=admin_emails)}"
|
|
else:
|
|
st.session_state[
|
|
"user_auth_url"
|
|
] = f"{your_base_url}/user?page={encode_config(proxy_url=proxy_url, allowed_email_subdomain=allowed_email_subdomain, admin_emails=admin_emails)}"
|
|
st.session_state["is_admin"] = True
|
|
|
|
|
|
def proxy_setup():
|
|
# Create a configuration placeholder
|
|
st.session_state.setdefault("proxy_url", "http://example.com")
|
|
st.session_state.setdefault("allowed_email_subdomain", "example.com")
|
|
st.session_state.setdefault("admin_emails", "admin@example.com")
|
|
st.session_state.setdefault("user_auth_url", "NOT_GIVEN")
|
|
|
|
with st.form("config_form", clear_on_submit=False):
|
|
proxy_url = st.text_input("Set Proxy URL", st.session_state["proxy_url"])
|
|
allowed_email_subdomain = st.text_input(
|
|
"Set Allowed Email Subdomain", st.session_state["allowed_email_subdomain"]
|
|
)
|
|
admin_emails = st.text_input(
|
|
"Allowed Admin Emails (add ',' to separate multiple emails)",
|
|
st.session_state["admin_emails"],
|
|
)
|
|
submitted = st.form_submit_button("Save")
|
|
|
|
if submitted:
|
|
update_config_values(
|
|
proxy_url=proxy_url,
|
|
allowed_email_subdomain=allowed_email_subdomain,
|
|
admin_emails=admin_emails,
|
|
)
|
|
|
|
# Display the current configuration
|
|
st.write(f"Current Proxy URL: {st.session_state['proxy_url']}")
|
|
st.write(
|
|
f"Current Allowed Email Subdomain: {st.session_state['allowed_email_subdomain']}"
|
|
)
|
|
st.write(f"Current User Auth URL: {st.session_state['user_auth_url']}")
|
|
|
|
|
|
def add_new_model():
|
|
import streamlit as st
|
|
import json, requests, uuid
|
|
|
|
if (
|
|
st.session_state.get("api_url", None) is None
|
|
and st.session_state.get("proxy_key", None) is None
|
|
):
|
|
st.warning(
|
|
f"Please configure the Proxy Endpoint and Proxy Key on the Proxy Setup page. Currently set Proxy Endpoint: {st.session_state.get('api_url', None)} and Proxy Key: {st.session_state.get('proxy_key', None)}"
|
|
)
|
|
|
|
model_name = st.text_input(
|
|
"Model Name - user-facing model name", placeholder="gpt-3.5-turbo"
|
|
)
|
|
st.subheader("LiteLLM Params")
|
|
litellm_model_name = st.text_input(
|
|
"Model", placeholder="azure/gpt-35-turbo-us-east"
|
|
)
|
|
litellm_api_key = st.text_input("API Key")
|
|
litellm_api_base = st.text_input(
|
|
"API Base",
|
|
placeholder="https://my-endpoint.openai.azure.com",
|
|
)
|
|
litellm_api_version = st.text_input("API Version", placeholder="2023-07-01-preview")
|
|
litellm_params = json.loads(
|
|
st.text_area(
|
|
"Additional Litellm Params (JSON dictionary). [See all possible inputs](https://github.com/BerriAI/litellm/blob/3f15d7230fe8e7492c95a752963e7fbdcaf7bf98/litellm/main.py#L293)",
|
|
value={},
|
|
)
|
|
)
|
|
st.subheader("Model Info")
|
|
mode_options = ("completion", "embedding", "image generation")
|
|
mode_selected = st.selectbox("Mode", mode_options)
|
|
model_info = json.loads(
|
|
st.text_area(
|
|
"Additional Model Info (JSON dictionary)",
|
|
value={},
|
|
)
|
|
)
|
|
|
|
if st.button("Submit"):
|
|
try:
|
|
model_info = {
|
|
"model_name": model_name,
|
|
"litellm_params": {
|
|
"model": litellm_model_name,
|
|
"api_key": litellm_api_key,
|
|
"api_base": litellm_api_base,
|
|
"api_version": litellm_api_version,
|
|
},
|
|
"model_info": {
|
|
"id": str(uuid.uuid4()),
|
|
"mode": mode_selected,
|
|
},
|
|
}
|
|
# Make the POST request to the specified URL
|
|
complete_url = ""
|
|
if st.session_state["api_url"].endswith("/"):
|
|
complete_url = f"{st.session_state['api_url']}model/new"
|
|
else:
|
|
complete_url = f"{st.session_state['api_url']}/model/new"
|
|
|
|
headers = {"Authorization": f"Bearer {st.session_state['proxy_key']}"}
|
|
response = requests.post(complete_url, json=model_info, headers=headers)
|
|
|
|
if response.status_code == 200:
|
|
st.success("Model added successfully!")
|
|
else:
|
|
st.error(f"Failed to add model. Status code: {response.status_code}")
|
|
|
|
st.success("Form submitted successfully!")
|
|
except Exception as e:
|
|
raise e
|
|
|
|
|
|
def list_models():
|
|
import streamlit as st
|
|
import requests
|
|
|
|
# Check if the necessary configuration is available
|
|
if (
|
|
st.session_state.get("api_url", None) is not None
|
|
and st.session_state.get("proxy_key", None) is not None
|
|
):
|
|
# Make the GET request
|
|
try:
|
|
complete_url = ""
|
|
if isinstance(st.session_state["api_url"], str) and st.session_state[
|
|
"api_url"
|
|
].endswith("/"):
|
|
complete_url = f"{st.session_state['api_url']}models"
|
|
else:
|
|
complete_url = f"{st.session_state['api_url']}/models"
|
|
response = requests.get(
|
|
complete_url,
|
|
headers={"Authorization": f"Bearer {st.session_state['proxy_key']}"},
|
|
)
|
|
# Check if the request was successful
|
|
if response.status_code == 200:
|
|
models = response.json()
|
|
st.write(models) # or st.json(models) to pretty print the JSON
|
|
else:
|
|
st.error(f"Failed to get models. Status code: {response.status_code}")
|
|
except Exception as e:
|
|
st.error(f"An error occurred while requesting models: {e}")
|
|
else:
|
|
st.warning(
|
|
f"Please configure the Proxy Endpoint and Proxy Key on the Proxy Setup page. Currently set Proxy Endpoint: {st.session_state.get('api_url', None)} and Proxy Key: {st.session_state.get('proxy_key', None)}"
|
|
)
|
|
|
|
|
|
def spend_per_key():
|
|
import streamlit as st
|
|
import requests
|
|
|
|
# Check if the necessary configuration is available
|
|
if (
|
|
st.session_state.get("api_url", None) is not None
|
|
and st.session_state.get("proxy_key", None) is not None
|
|
):
|
|
# Make the GET request
|
|
try:
|
|
complete_url = ""
|
|
if isinstance(st.session_state["api_url"], str) and st.session_state[
|
|
"api_url"
|
|
].endswith("/"):
|
|
complete_url = f"{st.session_state['api_url']}/spend/keys"
|
|
else:
|
|
complete_url = f"{st.session_state['api_url']}/spend/keys"
|
|
response = requests.get(
|
|
complete_url,
|
|
headers={"Authorization": f"Bearer {st.session_state['proxy_key']}"},
|
|
)
|
|
# Check if the request was successful
|
|
if response.status_code == 200:
|
|
spend_per_key = response.json()
|
|
# Create DataFrame
|
|
spend_df = pd.DataFrame(spend_per_key)
|
|
|
|
# Display the spend per key as a graph
|
|
st.header("Spend ($) per API Key:")
|
|
top_10_df = spend_df.nlargest(10, "spend")
|
|
fig = px.bar(
|
|
top_10_df,
|
|
x="token",
|
|
y="spend",
|
|
title="Top 10 Spend per Key",
|
|
height=550, # Adjust the height
|
|
width=1200, # Adjust the width)
|
|
hover_data=["token", "spend", "user_id", "team_id"],
|
|
)
|
|
st.plotly_chart(fig)
|
|
|
|
# Display the spend per key as a table
|
|
st.write("Spend per Key - Full Table:")
|
|
st.table(spend_df)
|
|
|
|
else:
|
|
st.error(f"Failed to get models. Status code: {response.status_code}")
|
|
except Exception as e:
|
|
st.error(f"An error occurred while requesting models: {e}")
|
|
else:
|
|
st.warning(
|
|
f"Please configure the Proxy Endpoint and Proxy Key on the Proxy Setup page. Currently set Proxy Endpoint: {st.session_state.get('api_url', None)} and Proxy Key: {st.session_state.get('proxy_key', None)}"
|
|
)
|
|
|
|
|
|
def spend_per_user():
|
|
import streamlit as st
|
|
import requests
|
|
|
|
# Check if the necessary configuration is available
|
|
if (
|
|
st.session_state.get("api_url", None) is not None
|
|
and st.session_state.get("proxy_key", None) is not None
|
|
):
|
|
# Make the GET request
|
|
try:
|
|
complete_url = ""
|
|
if isinstance(st.session_state["api_url"], str) and st.session_state[
|
|
"api_url"
|
|
].endswith("/"):
|
|
complete_url = f"{st.session_state['api_url']}/spend/users"
|
|
else:
|
|
complete_url = f"{st.session_state['api_url']}/spend/users"
|
|
response = requests.get(
|
|
complete_url,
|
|
headers={"Authorization": f"Bearer {st.session_state['proxy_key']}"},
|
|
)
|
|
# Check if the request was successful
|
|
if response.status_code == 200:
|
|
spend_per_user = response.json()
|
|
# Create DataFrame
|
|
spend_df = pd.DataFrame(spend_per_user)
|
|
|
|
# Display the spend per key as a graph
|
|
st.header("Spend ($) per User:")
|
|
top_10_df = spend_df.nlargest(10, "spend")
|
|
fig = px.bar(
|
|
top_10_df,
|
|
x="user_id",
|
|
y="spend",
|
|
title="Top 10 Spend per Key",
|
|
height=550, # Adjust the height
|
|
width=1200, # Adjust the width)
|
|
# hover_data=["token", "spend", "user_id", "team_id"],
|
|
)
|
|
st.plotly_chart(fig)
|
|
|
|
# Display the spend per key as a table
|
|
st.write("Spend per User - Full Table:")
|
|
st.table(spend_df)
|
|
|
|
else:
|
|
st.error(f"Failed to get models. Status code: {response.status_code}")
|
|
except Exception as e:
|
|
st.error(f"An error occurred while requesting models: {e}")
|
|
else:
|
|
st.warning(
|
|
f"Please configure the Proxy Endpoint and Proxy Key on the Proxy Setup page. Currently set Proxy Endpoint: {st.session_state.get('api_url', None)} and Proxy Key: {st.session_state.get('proxy_key', None)}"
|
|
)
|
|
|
|
|
|
def create_key():
|
|
import streamlit as st
|
|
import json, requests, uuid
|
|
|
|
if (
|
|
st.session_state.get("api_url", None) is None
|
|
and st.session_state.get("proxy_key", None) is None
|
|
):
|
|
st.warning(
|
|
f"Please configure the Proxy Endpoint and Proxy Key on the Proxy Setup page. Currently set Proxy Endpoint: {st.session_state.get('api_url', None)} and Proxy Key: {st.session_state.get('proxy_key', None)}"
|
|
)
|
|
|
|
duration = st.text_input("Duration - Can be in (h,m,s)", placeholder="1h")
|
|
|
|
models = st.text_input("Models it can access (separated by comma)", value="")
|
|
models = models.split(",") if models else []
|
|
|
|
additional_params = json.loads(
|
|
st.text_area(
|
|
"Additional Key Params (JSON dictionary). [See all possible inputs](https://litellm-api.up.railway.app/#/key%20management/generate_key_fn_key_generate_post)",
|
|
value={},
|
|
)
|
|
)
|
|
|
|
if st.button("Submit"):
|
|
try:
|
|
key_post_body = {
|
|
"duration": duration,
|
|
"models": models,
|
|
**additional_params,
|
|
}
|
|
# Make the POST request to the specified URL
|
|
complete_url = ""
|
|
if st.session_state["api_url"].endswith("/"):
|
|
complete_url = f"{st.session_state['api_url']}key/generate"
|
|
else:
|
|
complete_url = f"{st.session_state['api_url']}/key/generate"
|
|
|
|
headers = {"Authorization": f"Bearer {st.session_state['proxy_key']}"}
|
|
response = requests.post(complete_url, json=key_post_body, headers=headers)
|
|
|
|
if response.status_code == 200:
|
|
st.success(f"Key added successfully! - {response.json()}")
|
|
else:
|
|
st.error(f"Failed to add Key. Status code: {response.status_code}")
|
|
|
|
st.success("Form submitted successfully!")
|
|
except Exception as e:
|
|
raise e
|
|
|
|
|
|
def update_config():
|
|
if (
|
|
st.session_state.get("api_url", None) is None
|
|
and st.session_state.get("proxy_key", None) is None
|
|
):
|
|
st.warning(
|
|
f"Please configure the Proxy Endpoint and Proxy Key on the Proxy Setup page. Currently set Proxy Endpoint: {st.session_state.get('api_url', None)} and Proxy Key: {st.session_state.get('proxy_key', None)}"
|
|
)
|
|
|
|
st.markdown("#### Alerting")
|
|
input_slack_webhook = st.text_input(
|
|
"Slack Webhook URL (Optional)",
|
|
value=st.session_state.get("slack_webhook", ""),
|
|
placeholder="https://hooks.slack.com/services/...",
|
|
)
|
|
st.markdown(
|
|
"More information on Slack alerting configuration can be found in the [documentation]"
|
|
"(https://docs.litellm.ai/docs/proxy/alerting)."
|
|
)
|
|
alerting_threshold = st.text_input(
|
|
"Alerting threshold (in seconds) (Optional)",
|
|
value=st.session_state.get("alerting_threshold", 300),
|
|
placeholder=300,
|
|
)
|
|
st.markdown("How long to wait before a request is considered hanging")
|
|
st.markdown("#### Logging")
|
|
|
|
enable_langfuse_logging = st.checkbox("Enable Langfuse Logging")
|
|
if enable_langfuse_logging == True:
|
|
langfuse_host_url = st.text_input(
|
|
"Langfuse Host",
|
|
value=st.session_state.get("langfuse_host", "https://cloud.langfuse.com"),
|
|
placeholder="https://cloud.langfuse.com",
|
|
)
|
|
langfuse_public_key = st.text_input(
|
|
"Langfuse Public Key",
|
|
value=st.session_state.get("langfuse_public_key", ""),
|
|
placeholder="pk-lf-...",
|
|
)
|
|
langfuse_secret_key = st.text_input(
|
|
"Langfuse Secret Key",
|
|
value=st.session_state.get("langfuse_secret_key", ""),
|
|
placeholder="sk-lf-...",
|
|
)
|
|
# When the "Save" button is clicked, update the session state
|
|
if st.button("Save"):
|
|
try:
|
|
config_post_body = {}
|
|
if (
|
|
enable_langfuse_logging == True
|
|
and langfuse_host_url is not None
|
|
and langfuse_public_key is not None
|
|
and langfuse_secret_key is not None
|
|
):
|
|
config_post_body["litellm_settings"] = {
|
|
"success_callback": ["langfuse"]
|
|
}
|
|
config_post_body["environment_variables"] = {
|
|
"LANGFUSE_HOST": langfuse_host_url,
|
|
"LANGFUSE_PUBLIC_KEY": langfuse_public_key,
|
|
"LANGFUSE_SECRET_KEY": langfuse_secret_key,
|
|
}
|
|
if input_slack_webhook is not None and alerting_threshold is not None:
|
|
config_post_body["general_settings"] = {
|
|
"alerting": ["slack"],
|
|
"alerting_threshold": alerting_threshold,
|
|
}
|
|
config_post_body["environment_variables"] = {
|
|
"SLACK_WEBHOOK_URL": input_slack_webhook
|
|
}
|
|
|
|
# Make the POST request to the specified URL
|
|
complete_url = ""
|
|
if st.session_state["api_url"].endswith("/"):
|
|
complete_url = f"{st.session_state.get('api_url')}config/update"
|
|
else:
|
|
complete_url = f"{st.session_state.get('api_url')}/config/update"
|
|
|
|
headers = {"Authorization": f"Bearer {st.session_state['proxy_key']}"}
|
|
response = requests.post(
|
|
complete_url, json=config_post_body, headers=headers
|
|
)
|
|
|
|
if response.status_code == 200:
|
|
st.success(f"Config updated successfully! - {response.json()}")
|
|
else:
|
|
st.error(
|
|
f"Failed to update config. Status code: {response.status_code}. Error message: {response.json()['detail']}"
|
|
)
|
|
|
|
st.success("Form submitted successfully!")
|
|
except Exception as e:
|
|
raise e
|
|
|
|
|
|
def admin_page(is_admin="NOT_GIVEN", input_api_url=None, input_proxy_key=None):
|
|
# Display the form for the admin to set the proxy URL and allowed email subdomain
|
|
st.set_page_config(
|
|
layout="wide", # Use "wide" layout for more space
|
|
)
|
|
st.header("Admin Configuration")
|
|
st.session_state.setdefault("is_admin", is_admin)
|
|
# Add a navigation sidebar
|
|
st.sidebar.title("Navigation")
|
|
|
|
page = st.sidebar.radio(
|
|
"Go to",
|
|
(
|
|
"Connect to Proxy",
|
|
"View Spend Per Key",
|
|
"View Spend Per User",
|
|
"List Models",
|
|
"Update Config",
|
|
"Add Models",
|
|
"Create Key",
|
|
"End-User Auth",
|
|
),
|
|
)
|
|
# Display different pages based on navigation selection
|
|
if page == "Connect to Proxy":
|
|
# Use text inputs with intermediary variables
|
|
if input_api_url is None:
|
|
input_api_url = st.text_input(
|
|
"Proxy Endpoint",
|
|
value=st.session_state.get("api_url", ""),
|
|
placeholder="http://0.0.0.0:8000",
|
|
)
|
|
else:
|
|
st.session_state["api_url"] = input_api_url
|
|
|
|
if input_proxy_key is None:
|
|
input_proxy_key = st.text_input(
|
|
"Proxy Key",
|
|
value=st.session_state.get("proxy_key", ""),
|
|
placeholder="sk-...",
|
|
)
|
|
else:
|
|
st.session_state["proxy_key"] = input_proxy_key
|
|
# When the "Save" button is clicked, update the session state
|
|
if st.button("Save"):
|
|
st.session_state["api_url"] = input_api_url
|
|
st.session_state["proxy_key"] = input_proxy_key
|
|
st.success("Configuration saved!")
|
|
elif page == "Update Config":
|
|
update_config()
|
|
elif page == "End-User Auth":
|
|
proxy_setup()
|
|
elif page == "Add Models":
|
|
add_new_model()
|
|
elif page == "List Models":
|
|
list_models()
|
|
elif page == "Create Key":
|
|
create_key()
|
|
elif page == "View Spend Per Key":
|
|
spend_per_key()
|
|
elif page == "View Spend Per User":
|
|
spend_per_user()
|
|
|
|
|
|
# admin_page()
|
|
|
|
|
|
@click.command()
|
|
@click.option("--proxy_endpoint", type=str, help="Proxy Endpoint")
|
|
@click.option("--proxy_master_key", type=str, help="Proxy Master Key")
|
|
def main(proxy_endpoint, proxy_master_key):
|
|
admin_page(input_api_url=proxy_endpoint, input_proxy_key=proxy_master_key)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|