CLAUDE.md is a symlink to this file. Always edit AGENTS.md directly; never modify CLAUDE.md.
- README.md: high-level usage overview.
- CONTRIBUTING.md: canonical dev setup, test matrix, and contribution workflow.
- pyproject.toml and .pre-commit-config.yaml: formatting/lint/typecheck configuration.
- docs/: GitBook documentation sources (flat markdown layout). CI builds to
site/and pushes to thegitbook-docsbranch that GitBook watches.
src/any_llm/: Python SDK source (providers insrc/any_llm/providers/, shared types insrc/any_llm/types/).tests/:unit/,integration/, plus shared fixtures intests/conftest.py.docs/: Hand-authored GitBook documentation (flat markdown layout). Generated files (api/,providers.md,cookbooks/any-llm-getting-started.md) are build artifacts produced byscripts/convert_to_gitbook.pyand are not committed to the repository. The final publish artifact issite/, built by CI and pushed to thegitbook-docsbranch.
This repo uses uv for local dev (Python 3.11+). For the full, up-to-date command set, follow CONTRIBUTING.md.
- Create env + install dev deps:
uv venv && source .venv/bin/activate && uv sync --all-extras -U - Run all checks (preferred):
uv run pre-commit run --all-files --verbose - Unit tests:
uv run pytest -v tests/unit - Integration tests (often require API keys):
uv run pytest -v tests/integration -n auto - Build GitBook site locally:
uv run python scripts/convert_to_gitbook.py(output insite/)
- Python indentation: 4 spaces; formatting/linting via
ruff(line length 120) andpre-commit. - Type hints: required;
mypyruns in strict mode for library code (seepyproject.toml). - Provider code lives under
src/any_llm/providers/<provider>/(keep provider-specific behavior isolated there). - Override decorator: When overriding methods from base classes (like
AnyLLM), always use the@overridedecorator fromtyping_extensions. This is enforced by mypy'sexplicit-overrideerror code. For static methods, the order is@staticmethodfollowed by@override. - Prefer direct attribute access (e.g.,
obj.field) overgetattr(obj, "field")when the field is typed. This enablesruffandmypyto catch errors at lint time. Only usegetattr/setattrwhen working with truly dynamic attributes or when type information is unavailable. - Please add code comments if you find them helpful to accomplish your objective. However, please remove any comments you added that describe obvious behavior before finishing your task.
- Never use emdashes or -- in any comments or descriptions.
- Framework:
pytest(+pytest-asyncio,pytest-xdist). - Add/adjust tests with every change (happy path + error cases). Integration tests should
pytest.skip(...)when credentials/services aren’t available. - New code should target ~85%+ coverage (see
CONTRIBUTING.md). Write tests for every branch in new code, including error/raise paths and edge cases, so that patch coverage passes in CI. - Do not use class-based test grouping (
class TestFoo:). All tests should be standalone functions. - Do not add decorative section-separator comments (e.g.,
# -----------banners). Well-named test functions and natural file ordering are sufficient. - Place imports at the top of test files unless the import is for an optional dependency that may not be installed (e.g., provider-specific SDKs like
mistralai,cohere). In that case, inline imports inside the test function are acceptable to avoid breaking the entire file. - Integration tests run on
mainpost-merge, and on a PR only via therun-integration-testslabel (auto-removed after each run); they are not a merge gate, so failures accumulate. A red suite is usually a backlog of unrelated causes, not one regression: check a test's history (gh run view <id> --log) before assuming a recent break, then root-cause each as an any-llm bug (fix + test), a deprecated/wrong test model (updatetests/conftest.py, cite docs), missing CI infra (skip, naming it), or a provider outage (a whole provider failing at once, e.g. 429s, is transient). - The dataclass/dict structured-output path (
parse_responses_output) is separate from the Pydanticresponses.parse()path; a bug can hit one and not the other, so test both.
- Commits follow the project’s history: Conventional Commits such as
feat(scope): ...,fix: ...,chore(deps): ...,tests: .... - PRs should follow .github/pull_request_template.md: clear description, linked issues (e.g.,
Fixes #123), completed checklist, and AI-usage disclosure when applicable.
Before requesting review, every PR must clear:
uv run pre-commit run --all-filesclean (ruff lint + format,mypystrict). Don't drop# type: ignorebased on local mypy; CIrun-linteris authoritative.uv run pytest tests/unitgreen, with tests for every branch in changed code (~85% patch coverage; Codecov gates it).- Integration tests for any provider/feature you touched, run with real keys locally or via the
run-integration-testslabel on the PR. Don't claim a provider works without running it. - Every skip and fix is root-caused, with a concrete reason (HTTP status, deprecated model, missing CI infra), never "flaky" or "does not reliably support".
- Provider SDKs are optional and may not be installed locally. When missing, mypy treats their types as
Any, which makes# type: ignorecomments appear "unused" even though they suppress real errors in CI. - Do not remove
# type: ignorecomments based on local mypy output. CI (run-linter) is the authoritative environment.
- Never commit secrets. Use environment variables or a local
.env(gitignored) for provider API keys.