Thanks for your interest in contributing! This guide will help you get started.
git clone https://github.com/fabriziosalmi/llmproxy.git
cd llmproxy
make setup # Creates venv, installs deps, copies .env.example
# Edit .env with at least one provider key
make test # Run the test suite- Fork the repository and create a feature branch from
main - Write code following the conventions below
- Add tests for any new functionality
- Run checks:
make test && make lint && make syntax - Submit a PR with a clear description of what and why
- Version: Python 3.12+
- Linter:
ruff(config inpyproject.toml) - Type hints: Required on all public function signatures
- Logging: Use
logging.getLogger(__name__), neverprint() - Async: All I/O operations must be async. Protect shared state with
asyncio.Lock() - Exceptions: Never use bare
except:orexcept Exception: passin new code. Log or re-raise.
- 5-Ring Plugin System: Ingress → Pre-Flight → Routing → Post-Flight → Background
- Adapters: One per LLM provider in
proxy/adapters/. Must implementBaseModelAdapter. - Plugins: Marketplace plugins in
plugins/marketplace/. Must extendBasePlugin. - Config: All secrets via environment variables, never hardcoded.
- Use conventional commits:
feat:,fix:,docs:,refactor:,test:,chore: - Keep the first line under 72 characters
- Reference issues:
fix: resolve race condition in budget guard (#123)
make test # Core test suite (fast, ~5s)
make test-all # All tests including optional deps
make bench # Performance benchmarks- Tests live in
tests/and mirror the source structure - Use
pytestwith@pytest.mark.asynciofor async tests - Aim for: unit tests on business logic, integration tests on pipelines
- Security tests: add adversarial inputs to
tests/test_security.py
See docs/ui/contributing.md for the full UI guide — the architecture (primitives + views + strangler-fig), how to add a primitive, how to migrate a tab, test patterns, conventions. The TL;DR:
make build-ui # Install + Vite production build
make dev-ui # Vite dev server with HMR on :5173
make test-ui # Vitest (~2s)
make e2e-ui # Playwright (auto-starts backend)
make lint-ui # ESLint + Prettier (zero-warning)New code lives in ui/src/. Legacy .js in ui/components/ is the source-tree fallback shell — never accumulate new features there.
See Plugin SDK docs for the full guide. Quick version:
from core.plugin_sdk import BasePlugin, PluginResponse, PluginHook
class MyPlugin(BasePlugin):
name = "my_plugin"
hook = PluginHook.PRE_FLIGHT
async def execute(self, ctx):
# Your logic here
return PluginResponse.passthrough()- Tests pass (
make test) - Linter passes (
make lint) - New public functions have type hints and docstrings
- No hardcoded secrets or API keys
- No bare
except:blocks - CHANGELOG.md updated (for user-facing changes)
- Docs updated if API surface changed
Open a GitHub issue with:
- LLMProxy version (
cat VERSION) - Python version (
python --version) - Steps to reproduce
- Expected vs actual behavior
- Relevant logs (redact any API keys)
See SECURITY.md — please do NOT open public issues for security bugs.
By contributing, you agree that your contributions will be licensed under the MIT License.