Lesson 4. Tools
For an LLM to call a tool, a contract is needed: name, description, and parameters. The model reads the list of tools in the prompt and responds with a JSON call, and the Harness from lesson 3 executes it.
What to read
- [Calling tools] — how the model turns a decision into a call
- [Five categories of tools] — what kinds of tools there are in general
- [MCP protocol] — standard for connecting tools (in our course — simple local functions; MCP is a topic for a separate guide)
In the code
Tool spec for the system prompt:
TOOLS_SPEC = """
- calc(expr: string) — calculates an arithmetic expression, e.g. "123*45"
- time() — current time and date
"""The tools themselves:
import datetime
def calc(expr):
# eval cannot import inside __builtins__ — the first safeguard
return eval(expr, {"__builtins__": {}}, {})
def time():
return datetime.datetime.now().strftime("%H:%M %d.%m.%Y")const TOOLS = {
calc: (args) => Function('"use strict"; return (' + args.expr + ')'),
time: () => new Date().toLocaleString("ru-RU"),
};JSON call that we expect from the model (exactly what parse_tool from lesson 2 parses):
{"tool": "calc", "args": {"expr": "123*45"}}Try calc("abs(-3)") — it will fall over with an error because abs is not available. This is not a bug, but a safeguard: the tool can do exactly what it was allowed to.
Practice
Add a third tool for your agent — for example, dice(sides) or flip(). Write its spec (what it does, what parameters it takes) and implementation in Python and JS. Manually check the call and JSON response.
Related: [Lesson 3. Harness and safeguards], [Lesson 5. Assembling a CLI agent]