Add a custom tool¶
Extend a running stack with your own tool: add it to the project's Python package, wire it into a room, exercise it, and cover it with a test. This continues from First steps.
Prerequisites¶
- A generated stack, brought up (see First steps).
gitanduv.- A stack generated by soliplex-template v0.16 or later. Earlier stacks put
the package directly at
src/<package>/with its tests at the stack root, so none of the paths below will match; convert one with Migrating to thesrc/project layout.
1. Add a tool¶
Your project is an installable Python package living in its own project
directory under src/; tools live in src/<package>/src/<package>/tools.py
(the greeting tool from First steps is there). Add a
sibling:
# src/<package>/src/<package>/tools.py
def farewell(name: str) -> str:
"""Return a friendly farewell for ``name``."""
return f"Goodbye, {name}! Come back soon."
The docstring matters: the LLM uses it as the tool's description.
2. Wire it into the custom room¶
Add the new tool to backend/environment/rooms/custom/room_config.yaml:
The backend watches both its config (--reload=config) and your package (a
--reload-dirs flag on its serve command), so saving either file restarts it,
picking up farewell. (If it doesn't appear, docker compose restart backend.
A stack generated before the backend watched its package needs
uv run <skill>/scripts/src_projects.py add <package> once to start.)
3. Test it in the room¶
Open the Custom Tool Demo room and ask:
Please say farewell to Ada.
The agent calls your new tool and replies:
Goodbye, Ada! Come back soon.
4. Add a unit test¶
The generated project is also a normal Python package with a test suite already
wired up: src/<package>/tests/unit/test_tools.py covers greeting. Add a
case for farewell beside it (tools is already imported at the top of that
file):
# src/<package>/tests/unit/test_tools.py
def test_farewell_includes_name():
result = tools.farewell("Ada")
assert "Ada" in result
Create the dev environment once, then run the suite:
No setup is needed: the project's own pyproject.toml already declares
pytest, puts its src/ on the path, and points testpaths at tests/unit/,
so your new test is collected and passes. (Run these from the project
directory, not the stack root — the root pyproject.toml is for stack
tooling.)
That run uses the project's own lock file, not the versions the backend has.
To run the same tests against what the backend actually imports your code
with, run them in the dev-mode soliplex-dev service, which carries your
project's dev dependencies on top of the backend's image:
docker compose run --rm --no-deps -w /app/src/<package> soliplex-dev \
/app/.venv/bin/python -m pytest
(The first run builds soliplex-dev.) See
Running its tests there.
5. Commit it¶
From the stack root again:
cd ../.. # back to the stack root from src/<package>
git add src/<package>/src/<package>/tools.py \
src/<package>/tests/unit/test_tools.py \
backend/environment/rooms/custom/room_config.yaml
git commit -m "Add a farewell tool (with a test) to the custom room"
Where next¶
That completes the core series: Next steps collects the optional walkthroughs you can take from here.
Related: Work with Gitea ships a change like the one you just made through a pull request hosted by the stack's own Gitea.