Extension Development: Two-Week Online Course
A two-week online course 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/. They
trace a tool call through the platform, write and test an Extension of their own, and package and
install it. There are six 30-minute recorded lectures and a 90-minute live session each week. See
Course Formats and Grading for the format, 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. |
Workload |
3 hours of instruction and 4 to 6 hours of assignments each week. |
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. |
Learning Objectives
By the end of the course, 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 by the end of week 1: Docker, and Smarter installed from source with a superuser login. Install it before the course starts: two weeks leave little time for setup. See the Smarter Instructor Guide.
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.
Week |
Item |
Content |
|---|---|---|
1 |
Lecture 1 (30 min) |
Tools and extensibility. The tool call loop, and where Smarter runs it
( |
1 |
Lecture 2 (30 min) |
Anatomy of an Extension. The weather Extension’s files, its |
1 |
Lecture 3 (30 min) |
The tool protocol, results and errors. |
1 |
Live session 1 (90 min) |
Enabling the weather Extension in the web console and tracing a tool call live. Then each
student starts an Extension of their own, from the date calculator, and runs
|
2 |
Lecture 4 (30 min) |
Settings, secrets and the lifecycle. Typed settings and |
2 |
Lecture 5 (30 min) |
Testing. |
2 |
Lecture 6 (30 min) |
Publishing and trust. The archive ( |
2 |
Live session 2 (90 min) |
Capstone presentations (about 5 minutes each, with 2 minutes of questions). |
Assignment 1 is due at the end of week 1, Assignment 2 in the middle of week 2, and Assignment 3 and the capstone at the start of Live session 2.
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, and 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, with a setting an
administrator configures. 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: Tested and Installed
Students test their tool with call_tool() and tool_context(), covering a success, invalid
arguments and an ExtensionToolError with every network call mocked. They build its archive with
package_extension, install and configure it on their platform, and show an LLMClient using it.
Turn in: the tests in the Extension’s repository, their test_extension output, the
archive’s sha256, and the conversation.
Criterion |
Pts |
Exemplary |
Developing |
|---|---|---|---|
Cases covered |
6 |
Success, invalid arguments and a tool error, each asserted precisely. |
Only the success. |
Isolation |
4 |
No network, no API keys, no LLM. |
Calls the real service. |
Installed and used |
6 |
Installed from its archive, configured, and called by an LLMClient. |
Shown only in tests. |
Runs as shown |
4 |
The output and sha256 match the repository. |
Took the tests’ passing on trust; output not included. |
Capstone Projects
Students choose one, and present it in Live session 2.
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 five-minute presentation.
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.A five-minute presentation.
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.” |
Presentation |
10 |
Concise and organized. |
Long or disorganized. |
Recommended Texts
Required: Extensibility, Building an Extension and OpenAI’s function calling guide, in week 1.
Required: Testing an Extension and Publishing an Extension, in week 2.
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: protocol.py, toolkit.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.