Contributing¶
Thanks for considering a contribution.
Dev setup¶
Clone and install with all extras:
Or with uv (faster):
For gated HuggingFace models, log in once:
Run the tests¶
The default suite is mock-based and needs no backend:
To run against a real backend:
pytest tests/test_models.py --client=ollama --model=QWEN_3_5_9B
pytest tests/test_models.py --client=anthropic
pytest tests/test_models.py --client=llamacpp --model-path=/path/to/model.gguf
See CLAUDE.md for the full list of --client options.
Linting¶
Ruff is configured in pyproject.toml (line length 120):
Build the docs locally¶
The site rebuilds on file changes. Browse at http://127.0.0.1:8000.
To check for broken links and missing pages:
Work on the notebooks¶
The notebooks/ collection is authored as plain-text Quarto .qmd
files (markdown with executable python cells), not .ipynb JSON, so they diff cleanly
and are easy to edit or hand to an AI assistant. Install the Quarto CLI
(a standalone binary, e.g. brew install quarto) plus the Python toolchain:
pip install -e '.[notebooks]' # or: uv sync --extra notebooks — installs jupyter + jupytext
quarto preview notebooks/ # browsable local render of the whole collection
quarto preview/render produce the rendered site; they are not how you iterate
cell-by-cell. For interactive execution, pick one:
- Convert to
.ipynb(best inline experience).python notebooks/convert.py to-ipynbgenerates local, git-ignored.ipynbyou open natively in VS Code or Jupyter with inline cell execution..qmdstays the source of truth, so after editing a notebook runpython notebooks/convert.py to-qmd <stem>to sync it back (the helper defends against the string-source newline mangling noted below). - VS Code + Quarto extension. Runs
.qmdcells in the Jupyter Interactive Window (a side panel, not inline). A.vscode/settings.jsonpins the.venvinterpreter; rebind "Quarto: Run Cell" (Ctrl+Shift+Enter) toShift+Enter(when: editorLangId == 'quarto') for Jupyter muscle memory. - JupyterLab via jupytext (in the
[notebooks]extra). Runjupyter laband open a.qmddirectly as a native notebook; edits save back to the.qmdplain text.
Conventions:
- Naming. Kebab-case with a zero-padded ordinal, e.g.
07-agents.qmd. The number sets the reading order (and the sidebar order innotebooks/_quarto.yml). - Not executed at render time.
_quarto.ymlsetsexecute: eval: falsebecause most notebooks need a live backend (Ollama, a cloud API key, or a GPU) and gracefully skip. Run cells yourself against your own backend. To bake real outputs for a cheap (e.g. Ollama-only) notebook, seteval: true+freeze: autoin that notebook's own front matter. - Convert an existing
.ipynb.quarto convertmangles string-typed cell sources, so normalize them to line lists first:python -c "import json,sys; nb=json.load(open(sys.argv[1])); [c.__setitem__('source', c['source'].splitlines(keepends=True)) for c in nb['cells'] if isinstance(c['source'],str)]; json.dump(nb, open(sys.argv[1],'w'))" nb.ipynbthenquarto convert nb.ipynb.
Coding conventions¶
- Plain Python over framework primitives. No
Runnableprotocol, noBaseTool, no LCEL|, no Pydantic for tool args. See design principles for the full list of what's deliberately out of scope. - OpenAI message dicts as the only data model. No
Messageclass. Provider-specific formats are adapted at request time, never persisted. - Loud failures. Bad input raises with an actionable message. Silent skips and
try/except: passare reviewed carefully.
Adding a new provider¶
Write a client module under aimu/models/providers/ (a flat providers/<name>.py, or a providers/<name>/<modality>.py subpackage only if the provider ships several standalone modality clients), subclass BaseModelClient (or OpenAICompatClient for OpenAI-compatible endpoints), wire it into the ModelClient factory and _provider_registry(), export it from aimu/models/__init__.py under a HAS_* flag, and mirror it on the async surface.
See how-to: add or update a provider for the full step-by-step, and an existing provider (e.g. providers/anthropic.py) for the pattern.
Adding a new model to an existing provider¶
Add a member to the provider's Model enum with a ModelSpec(id, tools=..., thinking=..., vision=...) value. The TOOL_MODELS / THINKING_MODELS / VISION_MODELS lists derive automatically. See how-to: add a new model.
Pull requests¶
- One concern per PR. Splitting a refactor across multiple PRs makes review tractable.
- Include or update tests. Each module has a dedicated test file (
tests/test_models_api.py,tests/test_tool_decorator.py,tests/test_workflow_chain.py, etc.). Add new behavioural tests next to the existing ones for the surface you're touching. - Update docs when adding a public API. New
@tool-decorated function? Add it tobuiltin.<group>. New workflow? Add a tutorial or how-to. - Run
ruff check .andpytestbefore pushing.
Reporting bugs¶
File an issue with:
- A minimal reproducer (under 20 lines if possible).
- The full traceback.
- AIMU version, Python version, and which provider you were using.
Questions¶
Open a GitHub discussion. For Claude Code agents working in this repo, CLAUDE.md is the canonical engineering reference.