mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-02 07:13:02 +00:00
* feat(skills): add a litellm-setup agent skill * fix(skills): run the quickstart for Docker with Postgres in litellm-setup Use scripts/quickstart.sh for the Docker plus Postgres path instead of a hand-rolled Postgres container, keep uv with the user's own Postgres, load .env as data instead of sourcing it, pin the uv Python range, chmod an existing .env, list bedrock_converse and vertex_ai rows in the cost map lookup, ask before editing the shell profile, and stop the health wait when the proxy exits * fix(skills): guide empty Postgres, docker sudo, and key fixes in litellm-setup Ask for a new empty database on the uv path and DIRECT_URL behind a pooler, add the baseline error to troubleshooting, and drop inherited database settings in the loader line. Check python3 and lsof, explain Docker's permission denied, give quickstart commands by the folder's path, and fix provider keys by recreating the container or deleting the quickstart model. Drop the structure-only test. * fix(skills): keep shell profiles untouched when litellm-setup installs uv The uv installer edits shell profiles unless UV_NO_MODIFY_PATH is set, so the skill now sets it and puts ~/.local/bin on PATH for later commands. It also says litellm --setup runs in a separate terminal and how to retire the shared master-key UI login * fix(skills): offer litellm --setup only on the uv path The Docker paths install no host litellm CLI, and the old wording also passed the skill's own step number on to the user * fix(skills): ask before using an env provider key, print model names config.yaml accepts, probe the port with curl * fix(skills): state the env key default in the question, chmod the wizard config, keep uv's bin dir on PATH * fix(skills): keep litellm-setup on its own .env and install when the shell has others * fix(skills): read the setup wizard's config without printing keys and hand off the exact restart command * fix(skills): hide the database-access dev skill from end-user skill installs * fix(skills): give the litellm --setup path a restart command that keeps its config * docs(skills): point litellm --setup users to the master key in litellm_config.yaml * fix(skills): hide every non-end-user skill, use Azure deployment names, keep the restart block out of the user's shell * test(skills): find non-end-user skills in-process instead of through git * docs(skills): price Azure deployments with base_model and put uv on PATH in the start block
68 lines
3 KiB
Python
68 lines
3 KiB
Python
from collections.abc import Iterator
|
|
from fnmatch import fnmatch
|
|
from pathlib import Path
|
|
from typing import Final
|
|
|
|
import pytest
|
|
import yaml
|
|
|
|
_REPO_ROOT: Final = Path(__file__).resolve().parents[2]
|
|
_SKILLS_DIR: Final = _REPO_ROOT / "skills"
|
|
_SKILL_DIRECTORIES: Final = tuple(sorted(skill_file.parent for skill_file in _SKILLS_DIR.glob("*/SKILL.md")))
|
|
_FULL_DEPTH_MAX_DEPTH: Final = 5
|
|
_FULL_DEPTH_SKIPPED_NAMES: Final = frozenset(("node_modules", ".git", "dist", "build", "__pycache__"))
|
|
_GITIGNORED_NAME_PATTERNS: Final = tuple(
|
|
line.strip().rstrip("/")
|
|
for line in (_REPO_ROOT / ".gitignore").read_text(encoding="utf-8").splitlines()
|
|
if line.strip() and not line.startswith(("#", "!")) and "/" not in line.strip().rstrip("/")
|
|
)
|
|
|
|
|
|
def _absent_from_a_clone_or_skipped_by_the_cli(name: str) -> bool:
|
|
return name in _FULL_DEPTH_SKIPPED_NAMES or any(fnmatch(name, pattern) for pattern in _GITIGNORED_NAME_PATTERNS)
|
|
|
|
|
|
def _full_depth_skill_directories(directory: Path, depth: int) -> Iterator[Path]:
|
|
if (directory / "SKILL.md").is_file():
|
|
yield directory
|
|
if depth == _FULL_DEPTH_MAX_DEPTH:
|
|
return
|
|
for child in sorted(directory.iterdir()):
|
|
if child.is_dir() and not _absent_from_a_clone_or_skipped_by_the_cli(child.name):
|
|
yield from _full_depth_skill_directories(child, depth + 1)
|
|
|
|
|
|
_NON_END_USER_SKILL_DIRECTORIES: Final = tuple(
|
|
directory for directory in _full_depth_skill_directories(_REPO_ROOT, 0) if directory.parent != _SKILLS_DIR
|
|
)
|
|
|
|
|
|
def _frontmatter(skill_directory: Path) -> object:
|
|
opening, frontmatter, _body = (skill_directory / "SKILL.md").read_text(encoding="utf-8").split("---\n", 2)
|
|
assert opening == "", f"{skill_directory.name}/SKILL.md must open with a --- frontmatter block"
|
|
return yaml.safe_load(frontmatter)
|
|
|
|
|
|
@pytest.mark.parametrize("skill_directory", _SKILL_DIRECTORIES, ids=lambda path: path.name)
|
|
def test_skill_frontmatter_has_string_name_matching_its_directory_and_a_description(skill_directory: Path) -> None:
|
|
frontmatter: Final = _frontmatter(skill_directory)
|
|
assert isinstance(frontmatter, dict), frontmatter
|
|
assert frontmatter.get("name") == skill_directory.name, frontmatter
|
|
description: Final = frontmatter.get("description")
|
|
assert isinstance(description, str) and description.strip(), frontmatter
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"skill_directory",
|
|
_NON_END_USER_SKILL_DIRECTORIES,
|
|
ids=lambda path: str(path.relative_to(_REPO_ROOT)),
|
|
)
|
|
def test_skill_outside_skills_folder_is_hidden_from_end_user_skill_installs(skill_directory: Path) -> None:
|
|
frontmatter: Final = _frontmatter(skill_directory)
|
|
assert isinstance(frontmatter, dict), frontmatter
|
|
metadata: Final = frontmatter.get("metadata")
|
|
assert isinstance(metadata, dict) and metadata.get("internal") is True, (
|
|
f"{skill_directory} needs `metadata: {{internal: true}}`, or `npx skills add BerriAI/litellm` installs it "
|
|
"for end users, from a root dot-folder always and from anywhere with --full-depth (skills CLI 1.7.4, "
|
|
"checked 2026-10-28)"
|
|
)
|