Extension Development: Classroom Bootcamp
An eight-hour classroom bootcamp on building tools for LLMs as Smarter Extensions: Python
packages that a platform administrator installs at run time, and that every LLMClient on the
platform can then use. Students learn from the official weather Extension, which implements the get_weather
tool of OpenAI’s function calling guide for real, and from the open source framework that runs it,
apps/llmtool/extension/.
During the day they trace a tool call through the platform, write an Extension of their own with a
Pydantic arguments model, test it without the platform, and package and install it. See
Course Formats and Grading for the standard bootcamp day, the grading scale and the course policies,
and Extension Development for an overview of the subject.
At a Glance
Students |
Python developers who want to give LLMs new abilities. |
Deployment |
Each student runs Smarter from source ( |
Models |
Any OpenAI-compatible model that supports tool calling, through the instructor’s provider key. |
Cost |
Under $5 per student in model charges, capped with a Budget. The weather Extension needs a Google Maps Geocoding API key, whose free monthly allowance covers the course; the calculator Extensions need none. |
Learning Objectives
By the end of the bootcamp, students can:
Explain how Smarter offers a tool to an LLM, runs its call and returns the result, and choose between a Plugin, an Extension and an MCP server for a given need.
Write an Extension’s
pyproject.tomland a tool whose Pydantic arguments model is its definition.Design a tool’s result and error messages for an LLM to read.
Test a tool without the platform, the database or an LLM.
Package, install, configure and use an Extension, and explain the risks of installing someone else’s.
Technical Prerequisites
Required: Python: classes and inheritance, type hints, exceptions, packages and relative imports.
Required: Git and a GitHub account.
Required: Docker, and Smarter installed from source, proved working with a screenshot of the web console before the bootcamp. See the Smarter Instructor Guide.
Required: a superuser login on that installation, which
make initcreates.Helpful: Pydantic models, Python’s
unittestandunittest.mock, and having called an LLM API.Not needed: Django or JavaScript. The framework hides both.
Schedule
The files that the lectures refer to are listed in full under Smarter in the Source Code.
Block |
Min |
Content |
|---|---|---|
1 |
45 |
Lecture 1: Tools and extensibility. The tool call loop, and where Smarter runs it
( |
2 |
45 |
Lab 1: A tool call, traced. In the web console’s Extensions page, configure the
weather Extension’s |
3 |
45 |
Lecture 2: Anatomy of an Extension. The weather Extension’s files. The
|
4 |
45 |
Lab 2: A first tool. Starting from the date calculator, students write an Extension of
their own, with one tool, a described Pydantic arguments model and a module docstring, and
run |
5 |
45 |
Lecture 3: The tool protocol. |
6 |
45 |
Lecture 4: Results and errors for an LLM. A result says everything the LLM needs: the
weather 0.2.0 commit (51ec052a5)
added timezones and units. |
7 |
45 |
Lab 3: Tests. Students test their tool with |
8 |
45 |
Lecture 5: Settings, secrets and the lifecycle. Typed settings and |
9 |
45 |
Lab 4: Ship it to yourself. Students add a setting to their Extension, build its archive
with |
10 |
45 |
Lecture 6: Publishing and trust. The archive’s limits and reproducible builds
( |
11 |
30 |
Capstone kickoff. Students choose a capstone, and write down the tool or the comparison they will build, with its arguments and its result. |
Smarter in the Source Code
Every lecture, lab and assignment in this course rests on the files and commits below. Every
reference is to the main branch of github.com/smarter-sh/smarter, unless another repository is named, so that students
can open each one during class. Python paths are under smarter/smarter/.
Tools and Extensibility
Topic |
What Smarter does |
Where to look |
|---|---|---|
The tool call loop |
Smarter offers the LLM the tools that its LLMClient lists, the LLM asks to call one,
Smarter runs it and returns the result, and the LLM answers from it. Extension tools are
loaded by |
|
Plugins and Extensions |
Two ways to add a tool, for two kinds of people. A Plugin is a SAM manifest that configures one of six built-in tool patterns; an Extension is a Python package that a platform superuser installs at run time. Extensibility compares them line by line. |
|
The framework’s map |
The package’s module docstring lists each module and what it is for: the manifest, the archive, the protocol, settings, the loader, lifecycle, the installer, the toolkit and the registry. |
|
The models |
|
|
Listing an Extension |
An LLMClient lists Extensions by slug in |
The Official Extensions
Topic |
What it shows |
Where to look |
|---|---|---|
The weather Extension |
The developer guide’s worked example: the |
smarter-extension-weather: pyproject.toml, weather/tools.py, weather/models.py, weather/services.py |
The documentation |
The package’s module docstring, in reStructuredText, is the Extension’s documentation, written for the administrator who decides whether to install it. The platform renders it with raw HTML and file inclusion disabled. |
|
Untrusted input |
The calculator never calls |
|
The smallest Extension |
One tool and no settings, for date arithmetic: a template to start from. |
|
The test fixtures |
The framework’s own test Extensions: |
Writing an Extension
Topic |
What Smarter does |
Where to look |
|---|---|---|
The pyproject.toml |
A standard Python project file, not a SAM manifest. |
|
The tool protocol |
A tool is a class with a |
|
Running a tool |
|
|
Settings and secrets |
Settings are declared in |
|
Lifecycle hooks |
Eight hooks, from |
|
Switches and resources |
Waffle switches declared in |
|
Testing helpers |
|
extension/testing.py, tests/test_tools.py (weather) |
Management commands |
|
Installing, Loading and Publishing
Topic |
What Smarter does |
Where to look |
|---|---|---|
The archive |
A zip file, validated without importing it: at most 10 MB, 50 MB expanded and 2,000
files, with no links or paths that leave its directory. |
|
The installer |
The one place where an Extension’s state changes: install, update, roll back, configure, enable, disable and uninstall. The web console, the management commands and the registry client all go through it. |
|
Loading code at run time |
Each process extracts a version’s archive once, atomically by rename, and imports it under a name unique to its version and sha256, so an update loads beside the old version instead of reloading modules. This is why an Extension’s modules use relative imports. |
|
The registry |
A public GitHub repository whose CI validates each submission, builds an index, and signs it with an Ed25519 key. Platforms verify the signature on every read, through the hardened HTTP client, and check every archive against the signed sha256. Publishing an Extension. |
extension/registry.py, extensions-registry: scripts/validate.py, scripts/build_index.py, publishers.yaml |
Releasing a version |
semantic-release on each push to |
release.yml, test.yml, scripts/package.py (weather) |
The web console |
Superusers browse the registry, install, upload, configure, enable, update, roll back and uninstall on two React pages, whose api is typed by Pydantic models exported as a JSON Schema. Managing Extensions. |
extension/views.py, extension/contract.py, smarter-extension-console/ |
Observability |
Eight signals, from |
Extensions in the History
Extensions arrived in one large commit, with two follow-ups, and the official Extensions have histories of their own.
Commit |
Why it is worth studying |
Where to look |
|---|---|---|
Extensions arrive |
Adding a Python tool used to mean changing Smarter itself: three functions hardcoded in the
chat provider, a choices enum, and a release. The commit body explains every module of the
new framework, the migration from |
|
Drift after a big change |
Built-in system prompts still named tools that the LLM was no longer offered. The fix
renames them to |
|
From admin forms to the console |
Extension management moves from the Django admin to two React pages, with a typed contract, and the admin becomes read-only. |
|
A result the LLM can read |
|
commit 51ec052a5 (weather) |
Automating releases |
Hand-made tags replaced by semantic-release, so that a commit’s type decides the version. |
commit 45f3690c2 (weather) |
A registry release |
The commit that publishes two Extensions’ 0.2.0: one YAML file per version. |
commit a42a93ae9 (extensions-registry) |
Graded Assignments
Assignment 1: A Tool Call, Traced
Students follow two weather questions, one that succeeds and one that fails, from the prompt to
the answer, naming the code that handles each step. They compare the tool definition that
validate_extension prints with the source that produced it.
Turn in: a two-page Markdown report with the conversation, the tool calls, their arguments and results, and the code that handled each step.
Criterion |
Pts |
Exemplary |
Developing |
|---|---|---|---|
Evidence |
6 |
Real tool calls, arguments and results, from the chat and the logs. |
Paraphrased, or from the docs. |
Code located |
6 |
Each step tied to the file and method that handles it. |
Steps named, not located. |
Definition explained |
5 |
Each part of the definition traced to the docstring, model or slug. |
Pasted without comment. |
Failure path |
3 |
Explains how the error reached the LLM, and what it did with it. |
Only the success. |
Assignment 2: A First Tool
Students write an Extension with one tool that does something an LLM can’t do reliably on its own,
such as a unit conversion, a checksum or a lookup in a bundled table. validate_extension must
pass, and print the definition they intended.
Turn in: a Git repository with the Extension, and the output of validate_extension.
Criterion |
Pts |
Exemplary |
Developing |
|---|---|---|---|
Valid Extension |
5 |
|
Validates only after the instructor’s help. |
Arguments model |
6 |
Every field typed, constrained and described for the LLM. |
Bare |
Description and docs |
5 |
The docstring says what the tool does and when to use it; the module docstring serves an administrator. |
“Does the thing.” |
Worth a tool |
4 |
Does something the LLM gets wrong without it. |
Something the LLM does well already. |
Assignment 3: Tests
Students write unittest tests for their tool with call_tool() and tool_context(),
covering a success, invalid arguments and an ExtensionToolError, with every network call
mocked, and run them with manage.py test_extension.
Turn in: the tests in the Extension’s repository, and their output from test_extension.
Criterion |
Pts |
Exemplary |
Developing |
|---|---|---|---|
Cases covered |
7 |
Success, invalid arguments and a tool error, each asserted precisely. |
Only the success. |
Isolation |
5 |
No network, no API keys, no LLM. |
Calls the real service. |
Errors for the LLM |
4 |
Tests check that error messages say what the LLM should do next. |
Asserts only that an error happened. |
Runs as shown |
4 |
Output from |
Took the tests’ passing on trust; output not included. |
Capstone Projects
Students choose one. Capstones are due one week after the bootcamp.
Capstone A: An Extension for a Real Need
Learning objective. Build, test and ship an Extension that gives an LLM an ability it lacks, with settings an administrator configures.
Students build an Extension around a real service or computation, such as a public transit API, a currency converter with a configurable source, or a domain calculator for their own field. It has at least two tools or one tool and a secret setting, a result the LLM can interpret without guessing, tests with every service mocked, and a module docstring for administrators. They install its archive on their platform and show an LLMClient using it.
What students turn in:
The Extension’s Git repository, with tests and a
README.md.The archive and its sha256, from
package_extension.A conversation in which an LLMClient uses each tool, including one failure it recovers from.
A one-page design note on the tools’ arguments, results and errors.
How it is graded (100 points):
Criterion |
Pts |
Exemplary |
Developing |
|---|---|---|---|
Tool design |
25 |
Arguments and results modeled and described; the LLM calls each tool when it should. |
The LLM ignores the tool, or guesses at its result. |
Errors and input |
20 |
The LLM’s input is validated as untrusted, and every failure returns a useful error. |
Exceptions escape, or messages say only “error”. |
Tests |
20 |
Success and failure paths, with no network. |
Tests that need an API key. |
Settings and docs |
15 |
Typed settings, a secret never logged, documentation an administrator can act on. |
A key in the code, or no documentation. |
Working install |
20 |
Installed from its archive; the conversation shows every tool working. |
Shown only in tests. |
Capstone B: Plugin or Extension?
Learning objective. Decide, with evidence, when a tool should be configuration and when it should be code.
Students choose an integration with a public REST API, such as a weather, books or public data service, and build it twice: as an API Plugin with a Connection, and as an Extension. They run the same ten prompts against an LLMClient with each, and compare the two on effort, behavior, error handling, security and who can maintain them.
What students turn in:
The Plugin manifests and the Extension’s repository.
A table of the ten prompts, with each version’s tool calls, answers and tokens.
A three-page recommendation, citing
llmtool/plugin/api.pyand the Extension framework, with the cases where each choice is right.
How it is graded (100 points):
Criterion |
Pts |
Exemplary |
Developing |
|---|---|---|---|
Both versions work |
20 |
The same integration, working both ways. |
One version is a stub. |
Fair experiment |
25 |
Same prompts, model and LLMClient settings, with results recorded as they came. |
Different prompts, or results from memory. |
Analysis |
30 |
Effort, errors, selection, security and maintenance, each with evidence from the code. |
Opinions without evidence. |
Recommendation |
15 |
Clear rules for choosing, with their exceptions. |
“It depends.” |
Writing |
10 |
Concise and organized. |
Long or disorganized. |
Recommended Texts
Pre-reading: Extensibility and OpenAI’s function calling guide.
Reference: Luciano Ramalho, Fluent Python (2nd edition, O’Reilly, 2022), on classes, type hints and packages.
Reference: Chip Huyen, AI Engineering (O’Reilly, 2025), on how tools and agents fit into AI applications.
In the repository: Building an Extension, protocol.py and the weather Extension.
Further Study
Official documentation: OpenAI’s function calling guide, whose
get_weatherexample the weather Extension implements; the Pydantic documentation, on models, validation and JSON Schema; and the Python Packaging User Guide, including thepyproject.tomlspecification.Standards: PEP 440 (version identifiers), PEP 508 (dependency specifiers), Semantic Versioning, Conventional Commits, and JSON Schema.
Other plugin ecosystems: the WordPress Plugin Handbook, the model for Smarter’s run-time install, and the VS Code Extension API, for how another platform describes, packages and publishes extensions.
Software supply chain: The Update Framework, the design behind signed package indexes, Sigstore and SLSA, to compare with the registry’s Ed25519 signature and sha256 checks.
Other tool protocols: the Model Context Protocol specification, the third way to give a Smarter LLMClient tools.
YouTube: the PyCon US channel, for talks on packaging, Pydantic and testing, and the AI Engineer channel, for talks on tool use and agents.
Podcasts: Talk Python To Me and Python Bytes, which cover packaging, Pydantic and the Python ecosystem regularly, and Latent Space, on building with LLMs.
Writing: blog.smarter.sh, on how Smarter was built.