mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-05 08:07:05 +00:00
`import litellm` reaches litellm/integrations/otel/model/config.py via litellm_core_utils/litellm_logging.py, so pydantic-settings is needed at import time. It was declared only in the `proxy` extra, which left a plain `pip install litellm` unimportable on every platform. Adds tests/base_sdk_tests/check_base_sdk_install.py and a base_sdk_install CircleCI job that builds the wheel, installs it into a clean venv with no extras, and smoke-checks the import, a mock completion, a mock embedding, the bundled pricing metadata and the token counter. The check is stdlib-only on purpose; installing pytest into that venv would add packaging, pluggy and iniconfig and could mask the class of undeclared dependency it exists to catch. Previously the Windows job was the only one installing without extras, so this class of break was caught by accident rather than by design.
123 lines
3.9 KiB
Python
123 lines
3.9 KiB
Python
"""Smoke-check that a base ``pip install litellm`` (no extras) is importable and usable.
|
|
|
|
Run against a virtualenv that has the built wheel installed with no extras, using
|
|
that venv's own interpreter and nothing else. Deliberately stdlib-only: pytest would
|
|
pull ``packaging``, ``pluggy`` and ``iniconfig`` into the environment and could mask
|
|
the very class of undeclared-dependency bug this guards against.
|
|
"""
|
|
|
|
import importlib.util
|
|
import sys
|
|
import traceback
|
|
from collections.abc import Callable
|
|
|
|
EXTRAS_ONLY_MODULES = ("fastapi", "boto3", "uvicorn")
|
|
|
|
|
|
def _require(condition: bool, message: str) -> None:
|
|
if not condition:
|
|
raise AssertionError(message)
|
|
|
|
|
|
def check_environment_is_base_only() -> str:
|
|
present = tuple(name for name in EXTRAS_ONLY_MODULES if importlib.util.find_spec(name) is not None)
|
|
_require(
|
|
not present,
|
|
f"{', '.join(present)} installed, so this environment is not base-only and the run proves nothing",
|
|
)
|
|
return f"no extras-only packages present ({', '.join(EXTRAS_ONLY_MODULES)})"
|
|
|
|
|
|
def check_import() -> str:
|
|
from importlib.metadata import version
|
|
|
|
import litellm
|
|
|
|
_require(bool(litellm.__file__), "litellm has no __file__")
|
|
return f"imported litellm {version('litellm')}"
|
|
|
|
|
|
def check_completion() -> str:
|
|
import litellm
|
|
|
|
response = litellm.completion(
|
|
model="gpt-4o",
|
|
messages=[{"role": "user", "content": "ping"}],
|
|
mock_response="pong",
|
|
)
|
|
content = response.choices[0].message.content
|
|
_require(content == "pong", f"mock completion returned {content!r}")
|
|
return "mock completion round-trips"
|
|
|
|
|
|
def check_embedding() -> str:
|
|
import litellm
|
|
|
|
response = litellm.embedding(
|
|
model="text-embedding-3-small",
|
|
input=["ping"],
|
|
mock_response=[[0.1, 0.2]],
|
|
)
|
|
_require(len(response.data) == 1, f"mock embedding returned {len(response.data)} rows")
|
|
return "mock embedding round-trips"
|
|
|
|
|
|
def check_bundled_model_metadata() -> str:
|
|
import litellm
|
|
|
|
max_input_tokens = litellm.get_model_info("gpt-4o")["max_input_tokens"]
|
|
_require(
|
|
isinstance(max_input_tokens, int) and max_input_tokens > 0,
|
|
f"get_model_info returned max_input_tokens={max_input_tokens!r}",
|
|
)
|
|
prompt_cost, completion_cost = litellm.cost_per_token(model="gpt-4o", prompt_tokens=1000, completion_tokens=1000)
|
|
_require(
|
|
prompt_cost > 0 and completion_cost > 0,
|
|
f"cost_per_token returned ({prompt_cost}, {completion_cost})",
|
|
)
|
|
return f"bundled pricing metadata readable (gpt-4o max_input_tokens={max_input_tokens})"
|
|
|
|
|
|
def check_token_counter() -> str:
|
|
import litellm
|
|
|
|
count = litellm.token_counter(model="gpt-4o", text="hello world")
|
|
_require(count > 0, f"token_counter returned {count!r}")
|
|
return f"token_counter returned {count}"
|
|
|
|
|
|
CHECKS: tuple[tuple[str, Callable[[], str]], ...] = (
|
|
("environment is base-only", check_environment_is_base_only),
|
|
("import litellm", check_import),
|
|
("chat completion", check_completion),
|
|
("embedding", check_embedding),
|
|
("bundled model metadata", check_bundled_model_metadata),
|
|
("token counter", check_token_counter),
|
|
)
|
|
|
|
|
|
def _run(check: Callable[[], str]) -> tuple[bool, str]:
|
|
try:
|
|
return True, check()
|
|
except Exception:
|
|
return False, traceback.format_exc()
|
|
|
|
|
|
def main() -> int:
|
|
print(f"base SDK smoke check on {sys.executable}")
|
|
for label, check in CHECKS:
|
|
passed, detail = _run(check)
|
|
if not passed:
|
|
print(f"FAIL {label}:\n{detail}")
|
|
print(f"A base `pip install litellm` is broken at: {label}")
|
|
print("Something needed at import or call time is missing from [project].dependencies")
|
|
print("in pyproject.toml. Declaring it only in an extra is what causes this.")
|
|
return 1
|
|
print(f"PASS {label}: {detail}")
|
|
|
|
print(f"\nall {len(CHECKS)} checks passed")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|