mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-15 23:31:29 +00:00
feat(setup): add interactive setup wizard + install.sh
Adds `litellm --setup` — a Claude Code-style TUI onboarding wizard that guides users through provider selection, API key entry, and proxy config generation, then optionally starts the proxy immediately. - litellm/setup_wizard.py: wizard with ASCII art, numbered provider menu (OpenAI, Anthropic, Azure, Gemini, Bedrock, Ollama), API key prompts, port/master-key config, and litellm_config.yaml generation - litellm/proxy/proxy_cli.py: adds --setup flag that invokes the wizard - scripts/install.sh: curl-installable script (detect OS/Python, pip install litellm[proxy], launch wizard) Usage: curl -fsSL https://raw.githubusercontent.com/BerriAI/litellm/main/scripts/install.sh | sh litellm --setup
This commit is contained in:
parent
ef9cc33ee3
commit
db7075b080
3 changed files with 557 additions and 1 deletions
|
|
@ -468,6 +468,12 @@ class ProxyInitializationHelpers:
|
|||
type=str,
|
||||
help="Path to the logging configuration file",
|
||||
)
|
||||
@click.option(
|
||||
"--setup",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Run the interactive setup wizard to configure providers and generate a config file",
|
||||
)
|
||||
@click.option(
|
||||
"--version",
|
||||
"-v",
|
||||
|
|
@ -598,6 +604,7 @@ def run_server( # noqa: PLR0915
|
|||
num_requests,
|
||||
use_queue,
|
||||
health,
|
||||
setup,
|
||||
version,
|
||||
run_gunicorn,
|
||||
run_hypercorn,
|
||||
|
|
@ -611,6 +618,12 @@ def run_server( # noqa: PLR0915
|
|||
max_requests_before_restart,
|
||||
enforce_prisma_migration_check: bool,
|
||||
):
|
||||
if setup:
|
||||
from litellm.setup_wizard import run_setup_wizard
|
||||
|
||||
run_setup_wizard()
|
||||
return
|
||||
|
||||
args = locals()
|
||||
if local:
|
||||
from proxy_server import (
|
||||
|
|
@ -904,7 +917,7 @@ def run_server( # noqa: PLR0915
|
|||
# Auto-create PROMETHEUS_MULTIPROC_DIR for multi-worker setups
|
||||
ProxyInitializationHelpers._maybe_setup_prometheus_multiproc_dir(
|
||||
num_workers=num_workers,
|
||||
litellm_settings=litellm_settings if config else None,
|
||||
litellm_settings=litellm_settings if config else None, # type: ignore[possibly-unbound]
|
||||
)
|
||||
|
||||
# --- SEPARATE HEALTH APP LOGIC ---
|
||||
|
|
|
|||
409
litellm/setup_wizard.py
Normal file
409
litellm/setup_wizard.py
Normal file
|
|
@ -0,0 +1,409 @@
|
|||
# ruff: noqa: T201
|
||||
# flake8: noqa: T201
|
||||
"""
|
||||
LiteLLM Interactive Setup Wizard
|
||||
|
||||
Guides users through selecting LLM providers, entering API keys,
|
||||
and generating a proxy config file — mirroring the Claude Code onboarding UX.
|
||||
"""
|
||||
|
||||
import importlib.metadata
|
||||
import os
|
||||
import secrets
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Provider definitions
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
PROVIDERS = [
|
||||
{
|
||||
"id": "openai",
|
||||
"name": "OpenAI",
|
||||
"description": "GPT-4o, GPT-4o-mini, o1",
|
||||
"env_key": "OPENAI_API_KEY",
|
||||
"key_hint": "sk-...",
|
||||
"models": ["gpt-4o", "gpt-4o-mini"],
|
||||
},
|
||||
{
|
||||
"id": "anthropic",
|
||||
"name": "Anthropic",
|
||||
"description": "Claude Opus, Sonnet, Haiku",
|
||||
"env_key": "ANTHROPIC_API_KEY",
|
||||
"key_hint": "sk-ant-...",
|
||||
"models": ["claude-opus-4-5", "claude-sonnet-4-5", "claude-haiku-4-5-20251001"],
|
||||
},
|
||||
{
|
||||
"id": "azure",
|
||||
"name": "Azure OpenAI",
|
||||
"description": "GPT-4o via Azure",
|
||||
"env_key": "AZURE_API_KEY",
|
||||
"key_hint": "your-azure-key",
|
||||
"models": [],
|
||||
"needs_api_base": True,
|
||||
"api_base_hint": "https://<resource>.openai.azure.com/",
|
||||
"api_version": "2024-07-01-preview",
|
||||
},
|
||||
{
|
||||
"id": "gemini",
|
||||
"name": "Google Gemini",
|
||||
"description": "Gemini 2.0 Flash, Gemini 1.5 Pro",
|
||||
"env_key": "GEMINI_API_KEY",
|
||||
"key_hint": "AIza...",
|
||||
"models": ["gemini/gemini-2.0-flash", "gemini/gemini-1.5-pro"],
|
||||
},
|
||||
{
|
||||
"id": "bedrock",
|
||||
"name": "AWS Bedrock",
|
||||
"description": "Claude, Llama via AWS",
|
||||
"env_key": "AWS_ACCESS_KEY_ID",
|
||||
"key_hint": "AKIA...",
|
||||
"models": ["bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0"],
|
||||
"needs_extra": True,
|
||||
"extra_keys": ["AWS_SECRET_ACCESS_KEY", "AWS_REGION_NAME"],
|
||||
"extra_hints": ["your-secret-key", "us-east-1"],
|
||||
},
|
||||
{
|
||||
"id": "ollama",
|
||||
"name": "Ollama",
|
||||
"description": "Local models (llama3, mistral, etc.)",
|
||||
"env_key": None,
|
||||
"key_hint": None,
|
||||
"models": ["ollama/llama3.2", "ollama/mistral"],
|
||||
"api_base": "http://localhost:11434",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ANSI colour helpers (no external deps needed)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_ORANGE = "\033[38;2;215;119;87m"
|
||||
_DIM = "\033[2m"
|
||||
_BOLD = "\033[1m"
|
||||
_GREEN = "\033[38;2;78;186;101m"
|
||||
_BLUE = "\033[38;2;177;185;249m"
|
||||
_GREY = "\033[38;2;153;153;153m"
|
||||
_RESET = "\033[0m"
|
||||
_CHECK = "✔"
|
||||
|
||||
|
||||
def _supports_color() -> bool:
|
||||
return sys.stdout.isatty() and os.environ.get("NO_COLOR") is None
|
||||
|
||||
|
||||
def _c(code: str, text: str) -> str:
|
||||
if _supports_color():
|
||||
return f"{code}{text}{_RESET}"
|
||||
return text
|
||||
|
||||
|
||||
def orange(t: str) -> str:
|
||||
return _c(_ORANGE, t)
|
||||
|
||||
|
||||
def bold(t: str) -> str:
|
||||
return _c(_BOLD, t)
|
||||
|
||||
|
||||
def green(t: str) -> str:
|
||||
return _c(_GREEN, t)
|
||||
|
||||
|
||||
def blue(t: str) -> str:
|
||||
return _c(_BLUE, t)
|
||||
|
||||
|
||||
def grey(t: str) -> str:
|
||||
return _c(_GREY, t)
|
||||
|
||||
|
||||
def dim(t: str) -> str:
|
||||
return _c(_DIM, t)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ASCII art
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
LITELLM_ASCII = r"""
|
||||
██╗ ██╗████████╗███████╗██╗ ██╗ ███╗ ███╗
|
||||
██║ ██║╚══██╔══╝██╔════╝██║ ██║ ████╗ ████║
|
||||
██║ ██║ ██║ █████╗ ██║ ██║ ██╔████╔██║
|
||||
██║ ██║ ██║ ██╔══╝ ██║ ██║ ██║╚██╔╝██║
|
||||
███████╗██║ ██║ ███████╗███████╗███████╗██║ ╚═╝ ██║
|
||||
╚══════╝╚═╝ ╚═╝ ╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝
|
||||
"""
|
||||
|
||||
DIVIDER = dim(" " + "╌" * 74)
|
||||
|
||||
|
||||
def _print_welcome() -> None:
|
||||
try:
|
||||
version = importlib.metadata.version("litellm")
|
||||
except Exception:
|
||||
version = "unknown"
|
||||
|
||||
print()
|
||||
print(orange(LITELLM_ASCII.rstrip("\n")))
|
||||
print(f" {orange('Welcome')} to {bold('LiteLLM')} {grey('v' + version)}")
|
||||
print()
|
||||
print(DIVIDER)
|
||||
print()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Provider selection
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _print_provider_menu(selected: List[int]) -> None:
|
||||
print()
|
||||
print(f" {bold('Choose your LLM providers')}")
|
||||
print(grey(" Enter numbers separated by commas (e.g. 1,2). Press Enter to confirm."))
|
||||
print()
|
||||
for i, p in enumerate(PROVIDERS, 1):
|
||||
bullet = green(f"◉ {i}.") if (i in selected) else grey(f"○ {i}.")
|
||||
name = bold(p["name"])
|
||||
desc = grey(p["description"])
|
||||
print(f" {bullet} {name} {desc}")
|
||||
print()
|
||||
|
||||
|
||||
def _select_providers() -> List[Dict]:
|
||||
selected_nums: List[int] = []
|
||||
_print_provider_menu(selected_nums)
|
||||
|
||||
while True:
|
||||
raw = input(f" {blue('❯')} Provider(s): ").strip()
|
||||
if not raw:
|
||||
if not selected_nums:
|
||||
print(grey(" Please select at least one provider."))
|
||||
continue
|
||||
break
|
||||
try:
|
||||
nums = [int(x.strip()) for x in raw.replace(" ", ",").split(",") if x.strip()]
|
||||
valid = [n for n in nums if 1 <= n <= len(PROVIDERS)]
|
||||
if not valid:
|
||||
print(grey(f" Enter numbers between 1 and {len(PROVIDERS)}."))
|
||||
continue
|
||||
selected_nums = sorted(set(valid))
|
||||
_print_provider_menu(selected_nums)
|
||||
except ValueError:
|
||||
print(grey(" Enter numbers separated by commas, e.g. 1,3"))
|
||||
|
||||
return [PROVIDERS[i - 1] for i in selected_nums]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# API key collection
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _collect_keys(providers: List[Dict]) -> Dict[str, str]:
|
||||
env_vars: Dict[str, str] = {}
|
||||
print()
|
||||
print(DIVIDER)
|
||||
print()
|
||||
print(f" {bold('Enter your API keys')}")
|
||||
print(grey(" Keys are stored only in the generated config file."))
|
||||
print()
|
||||
|
||||
for p in providers:
|
||||
if p["env_key"] is None:
|
||||
# Ollama — no key needed
|
||||
print(f" {green(p['name'])}: {grey('no key needed (uses local Ollama)')}")
|
||||
continue
|
||||
|
||||
hint = grey(p.get("key_hint", ""))
|
||||
key = ""
|
||||
while not key:
|
||||
key = input(f" {blue('❯')} {bold(p['name'])} API key {hint}: ").strip()
|
||||
if not key:
|
||||
print(grey(" Key is required. Leave blank to skip this provider."))
|
||||
skip = input(grey(" Skip? (y/N): ")).strip().lower()
|
||||
if skip == "y":
|
||||
break
|
||||
|
||||
if key:
|
||||
env_vars[p["env_key"]] = key
|
||||
|
||||
# Extra keys (e.g. AWS secret + region)
|
||||
if p.get("needs_extra") and key:
|
||||
for extra_key, extra_hint in zip(
|
||||
p.get("extra_keys", []), p.get("extra_hints", [])
|
||||
):
|
||||
val = input(
|
||||
f" {blue('❯')} {extra_key} {grey(extra_hint)}: "
|
||||
).strip()
|
||||
if val:
|
||||
env_vars[extra_key] = val
|
||||
|
||||
# API base for Azure
|
||||
if p.get("needs_api_base") and key:
|
||||
api_base = input(
|
||||
f" {blue('❯')} Azure endpoint URL {grey(p.get('api_base_hint', ''))}: "
|
||||
).strip()
|
||||
if api_base:
|
||||
env_vars[f"_LITELLM_AZURE_API_BASE_{p['id'].upper()}"] = api_base
|
||||
|
||||
return env_vars
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Config generation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _build_config(
|
||||
providers: List[Dict],
|
||||
env_vars: Dict[str, str],
|
||||
port: int,
|
||||
master_key: str,
|
||||
) -> str:
|
||||
lines = ["model_list:"]
|
||||
|
||||
for p in providers:
|
||||
if not p["models"] and p["id"] == "azure":
|
||||
# Azure — add a generic placeholder
|
||||
models_to_add = ["azure/gpt-4o"]
|
||||
else:
|
||||
models_to_add = p["models"]
|
||||
|
||||
for model in models_to_add:
|
||||
# User-facing model name (strip provider prefix for display)
|
||||
display_name = model.split("/")[-1] if "/" in model else model
|
||||
lines.append(f" - model_name: {display_name}")
|
||||
lines.append(f" litellm_params:")
|
||||
lines.append(f" model: {model}")
|
||||
|
||||
if p["env_key"] and p["env_key"] in env_vars:
|
||||
lines.append(f" api_key: os.environ/{p['env_key']}")
|
||||
|
||||
if p.get("api_base"):
|
||||
lines.append(f" api_base: {p['api_base']}")
|
||||
elif p.get("needs_api_base"):
|
||||
azure_base_key = f"_LITELLM_AZURE_API_BASE_{p['id'].upper()}"
|
||||
if azure_base_key in env_vars:
|
||||
lines.append(f" api_base: {env_vars.pop(azure_base_key)}")
|
||||
if p.get("api_version"):
|
||||
lines.append(f" api_version: {p['api_version']}")
|
||||
|
||||
lines.append("")
|
||||
lines.append("general_settings:")
|
||||
lines.append(f" master_key: {master_key}")
|
||||
lines.append("")
|
||||
|
||||
# Write env vars inline so the config is self-contained
|
||||
real_env_vars = {k: v for k, v in env_vars.items() if not k.startswith("_LITELLM_")}
|
||||
if real_env_vars:
|
||||
lines.append("environment_variables:")
|
||||
for k, v in real_env_vars.items():
|
||||
lines.append(f" {k}: \"{v}\"")
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Proxy settings
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _proxy_settings() -> tuple[int, str]:
|
||||
print()
|
||||
print(DIVIDER)
|
||||
print()
|
||||
print(f" {bold('Proxy settings')}")
|
||||
print()
|
||||
|
||||
port_raw = input(f" {blue('❯')} Port {grey('[4000]')}: ").strip()
|
||||
port = int(port_raw) if port_raw.isdigit() else 4000
|
||||
|
||||
key_raw = input(
|
||||
f" {blue('❯')} Master key {grey('[auto-generate]')}: "
|
||||
).strip()
|
||||
master_key = key_raw if key_raw else f"sk-{secrets.token_urlsafe(32)}"
|
||||
|
||||
return port, master_key
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main wizard entrypoint
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def run_setup_wizard() -> Optional[str]:
|
||||
"""
|
||||
Run the interactive setup wizard.
|
||||
|
||||
Returns the path to the generated config file, or None if aborted.
|
||||
"""
|
||||
try:
|
||||
_run_wizard()
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
print(f"\n\n {grey('Setup cancelled.')}\n")
|
||||
return None
|
||||
return None # caller receives path via side effect (printed to stdout)
|
||||
|
||||
|
||||
def _run_wizard() -> None:
|
||||
_print_welcome()
|
||||
|
||||
print(f" {bold('Lets get started.')}")
|
||||
print()
|
||||
|
||||
# Step 1: providers
|
||||
providers = _select_providers()
|
||||
|
||||
# Step 2: API keys
|
||||
env_vars = _collect_keys(providers)
|
||||
|
||||
# Step 3: proxy settings
|
||||
port, master_key = _proxy_settings()
|
||||
|
||||
# Step 4: write config
|
||||
config_content = _build_config(providers, env_vars, port, master_key)
|
||||
|
||||
config_path = Path(os.getcwd()) / "litellm_config.yaml"
|
||||
config_path.write_text(config_content)
|
||||
|
||||
# Step 5: print success
|
||||
print()
|
||||
print(DIVIDER)
|
||||
print()
|
||||
print(f" {green(_CHECK + ' Config saved')} → {bold(str(config_path))}")
|
||||
print()
|
||||
print(f" {bold('To start your proxy:')}")
|
||||
print()
|
||||
print(f" {grey('$')} litellm --config {config_path}")
|
||||
print()
|
||||
print(f" {bold('Then set your client:')}")
|
||||
print()
|
||||
print(f" export OPENAI_BASE_URL=http://localhost:{port}")
|
||||
print(f" export OPENAI_API_KEY={master_key}")
|
||||
print()
|
||||
print(DIVIDER)
|
||||
print()
|
||||
|
||||
# Step 6: offer to start now
|
||||
start = input(f" {blue('❯')} Start the proxy now? {grey('(Y/n)')}: ").strip().lower()
|
||||
if start in ("", "y", "yes"):
|
||||
print()
|
||||
print(f" {green(_CHECK)} Starting LiteLLM proxy on port {bold(str(port))}…")
|
||||
print()
|
||||
# exec replaces this process with the proxy server
|
||||
os.execlp( # noqa: S606
|
||||
sys.executable,
|
||||
sys.executable,
|
||||
"-m",
|
||||
"litellm",
|
||||
"--config",
|
||||
str(config_path),
|
||||
"--port",
|
||||
str(port),
|
||||
)
|
||||
else:
|
||||
print()
|
||||
print(
|
||||
f" Run {bold(f'litellm --config {config_path}')} whenever you're ready."
|
||||
)
|
||||
print()
|
||||
134
scripts/install.sh
Executable file
134
scripts/install.sh
Executable file
|
|
@ -0,0 +1,134 @@
|
|||
#!/usr/bin/env bash
|
||||
# LiteLLM Installer
|
||||
# Usage: curl -fsSL https://litellm.ai/install.sh | sh
|
||||
set -euo pipefail
|
||||
|
||||
LITELLM_PACKAGE="litellm[proxy]"
|
||||
MIN_PYTHON_MAJOR=3
|
||||
MIN_PYTHON_MINOR=9
|
||||
|
||||
# ── colours ────────────────────────────────────────────────────────────────
|
||||
if [ -t 1 ] && command -v tput >/dev/null 2>&1; then
|
||||
ORANGE='\033[38;2;215;119;87m'
|
||||
BOLD='\033[1m'
|
||||
GREEN='\033[38;2;78;186;101m'
|
||||
GREY='\033[38;2;153;153;153m'
|
||||
RESET='\033[0m'
|
||||
else
|
||||
ORANGE='' BOLD='' GREEN='' GREY='' RESET=''
|
||||
fi
|
||||
|
||||
info() { printf "${GREY} %s${RESET}\n" "$*"; }
|
||||
success() { printf "${GREEN} ✔ %s${RESET}\n" "$*"; }
|
||||
header() { printf "${ORANGE} %s${RESET}\n" "$*"; }
|
||||
die() { printf "\n Error: %s\n\n" "$*" >&2; exit 1; }
|
||||
|
||||
# ── banner ─────────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
printf "${ORANGE}"
|
||||
cat << 'EOF'
|
||||
██╗ ██╗████████╗███████╗██╗ ██╗ ███╗ ███╗
|
||||
██║ ██║╚══██╔══╝██╔════╝██║ ██║ ████╗ ████║
|
||||
██║ ██║ ██║ █████╗ ██║ ██║ ██╔████╔██║
|
||||
██║ ██║ ██║ ██╔══╝ ██║ ██║ ██║╚██╔╝██║
|
||||
███████╗██║ ██║ ███████╗███████╗███████╗██║ ╚═╝ ██║
|
||||
╚══════╝╚═╝ ╚═╝ ╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝
|
||||
EOF
|
||||
printf "${RESET}"
|
||||
printf " ${BOLD}LiteLLM Installer${RESET} ${GREY}— unified gateway for 100+ LLM providers${RESET}\n\n"
|
||||
|
||||
# ── OS detection ───────────────────────────────────────────────────────────
|
||||
OS="$(uname -s)"
|
||||
ARCH="$(uname -m)"
|
||||
|
||||
case "$OS" in
|
||||
Darwin) PLATFORM="macOS ($ARCH)" ;;
|
||||
Linux) PLATFORM="Linux ($ARCH)" ;;
|
||||
*) die "Unsupported OS: $OS. LiteLLM supports macOS and Linux." ;;
|
||||
esac
|
||||
|
||||
info "Platform: $PLATFORM"
|
||||
|
||||
# ── Python detection ───────────────────────────────────────────────────────
|
||||
PYTHON_BIN=""
|
||||
for candidate in python3 python; do
|
||||
if command -v "$candidate" >/dev/null 2>&1; then
|
||||
py_ver="$("$candidate" -c 'import sys; print(sys.version_info[:2])' 2>/dev/null || true)"
|
||||
major="$("$candidate" -c 'import sys; print(sys.version_info.major)' 2>/dev/null || true)"
|
||||
minor="$("$candidate" -c 'import sys; print(sys.version_info.minor)' 2>/dev/null || true)"
|
||||
if [ "${major:-0}" -ge "$MIN_PYTHON_MAJOR" ] && [ "${minor:-0}" -ge "$MIN_PYTHON_MINOR" ]; then
|
||||
PYTHON_BIN="$candidate"
|
||||
info "Python: $("$candidate" --version 2>&1)"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$PYTHON_BIN" ]; then
|
||||
die "Python ${MIN_PYTHON_MAJOR}.${MIN_PYTHON_MINOR}+ is required but not found.
|
||||
Install it from https://python.org/downloads or via your package manager:
|
||||
macOS: brew install python@3
|
||||
Ubuntu: sudo apt install python3 python3-pip"
|
||||
fi
|
||||
|
||||
# ── pip detection ──────────────────────────────────────────────────────────
|
||||
if ! "$PYTHON_BIN" -m pip --version >/dev/null 2>&1; then
|
||||
die "pip is not available. Install it with:
|
||||
$PYTHON_BIN -m ensurepip --upgrade
|
||||
or:
|
||||
curl https://bootstrap.pypa.io/get-pip.py | $PYTHON_BIN"
|
||||
fi
|
||||
|
||||
# ── install ────────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
header "Installing ${LITELLM_PACKAGE}…"
|
||||
echo ""
|
||||
|
||||
# Use --quiet to avoid wall of pip output; keep --progress-bar off for cleaner CI
|
||||
"$PYTHON_BIN" -m pip install --quiet --progress-bar off "${LITELLM_PACKAGE}" \
|
||||
|| die "pip install failed. Try manually: $PYTHON_BIN -m pip install '${LITELLM_PACKAGE}'"
|
||||
|
||||
# Verify litellm is on PATH (or accessible as python -m litellm)
|
||||
LITELLM_BIN="$(command -v litellm 2>/dev/null || true)"
|
||||
if [ -z "$LITELLM_BIN" ]; then
|
||||
# Might be installed in a user PATH that isn't active yet
|
||||
USER_BIN="$("$PYTHON_BIN" -c 'import site,os; print(site.getuserbase())')/bin"
|
||||
if [ -x "$USER_BIN/litellm" ]; then
|
||||
LITELLM_BIN="$USER_BIN/litellm"
|
||||
info "Note: $LITELLM_BIN is not in your PATH yet."
|
||||
info "Add this to your shell profile:"
|
||||
info " export PATH=\"\$PATH:$USER_BIN\""
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
success "LiteLLM installed"
|
||||
|
||||
# ── version check ──────────────────────────────────────────────────────────
|
||||
if [ -n "$LITELLM_BIN" ]; then
|
||||
installed_ver="$("$LITELLM_BIN" --version 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || true)"
|
||||
[ -n "$installed_ver" ] && info "Version: $installed_ver"
|
||||
fi
|
||||
|
||||
# ── launch setup wizard ────────────────────────────────────────────────────
|
||||
echo ""
|
||||
printf " ${BOLD}Run the interactive setup wizard?${RESET} ${GREY}(Y/n)${RESET}: "
|
||||
read -r answer </dev/tty
|
||||
|
||||
if [ -z "$answer" ] || [ "$answer" = "y" ] || [ "$answer" = "Y" ]; then
|
||||
echo ""
|
||||
if [ -n "$LITELLM_BIN" ]; then
|
||||
exec "$LITELLM_BIN" --setup
|
||||
else
|
||||
exec "$PYTHON_BIN" -m litellm --setup
|
||||
fi
|
||||
else
|
||||
echo ""
|
||||
header "Quick start:"
|
||||
echo ""
|
||||
info " litellm --setup # interactive wizard"
|
||||
info " litellm --model gpt-4o # single-model quickstart"
|
||||
echo ""
|
||||
info "Docs: https://docs.litellm.ai"
|
||||
echo ""
|
||||
fi
|
||||
Loading…
Add table
Reference in a new issue