Functions
Define a function with def, return a value, write a docstring, pass keyword arguments, and store tools in a dict.
A function is a block of code with a name. You define it once. You call it when you need that work done. The definition is the recipe. The call is cooking one meal. You can call the same function many times with different inputs.
In an agent, a tool is a function. A tool is an action the program can run, such as search or add. The model picks a tool name and inputs. Your Python looks up the function and calls it. If you can write a small function that returns a value, you can write a tool.
A function takes inputs and returns a value. print only shows text. return is for other code.
In, then outFunctions also keep you from copying the same five lines into every branch. Name the work. Call the name. Test the name.
What you will learn
defstarts a functionreturnsends a value back;printonly shows text- A docstring is a note inside the function
- Keyword arguments and
in a call** - A dict of functions, plus
call_tool(name, **kwargs)
The parts of a function
| Word | Meaning | Example |
|---|---|---|
| Name | What you call | search |
| Parameters | Input names in the definition | q, k |
| Arguments | Values you pass in a call | "rain", 2 |
| Return | The value sent back | a list of hits |
| Body | The indented lines | the work |
The name should be a verb or a job: search, add, call_tool. data is a bad function name. snake_case, like variables.
def and return
def means “define this function.” Names in parentheses are parameters (the inputs). The body is indented. return hands a value back and stops the function. Lines after return in that path do not run.
def search(q, k=3):
hits = []
for i in range(k):
hits.append(class="tok-s">"doc " + str(i) + class="tok-s">" about " + q)
return hits
print(search(class="tok-s">"weather"))
print(search(class="tok-s">"weather", 1))- No
returnmeans the function returnsNone.Nonemeans “no value.” search("weather")is a positional call: values line up in order. First value fills first parameter.search(q="weather", k=1)is a keyword call: you pass inputs by name. Order can change:search(k=1, q="weather")is the same.
After you use a keyword, the rest of that call must be keywords too. search(q="weather", 1) is a syntax error. search("weather", k=1) is fine: positional first, then keyword.
Defaults like k=3 mean the caller may skip that input. Defaults have a trap when the default is a list. The next lesson covers that. Numbers and strings as defaults are safe.
Return vs print
print is for people. It writes text on the screen. return is for other code. It sends a value back to the caller. You can do both. Beginners often print and forget to return. Then hits = search("rain") stores None.
A tool must return data. Then the agent can save that data in a transcript (a list of what happened). You may also print while you debug. The useful result is still the return value.
def add(a, b):
print(class="tok-s">"adding") class="tok-c"># for you to read
return a + b class="tok-c"># for the program to use
total = add(2, 3)
print(class="tok-s">"got", total)If you only print inside add and do not return, total is None, and print("got", total) shows None. The addition happened. Nobody handed it back.
return can send any value: number, string, list, dict, tuple, None. Tools often return a dict: {"ok": True, "result": ...}. That shape is easy to store and easy to test.
A note inside the function
The first string in a function body is a docstring. A docstring is a note inside the function. It tells a person (or a model) what the function does. It does not change the result. Triple quotes let the note span lines.
def search(q, k=3):
class="tok-s">"""Return k fake documents for query q."""
return [class="tok-s">"doc 0 about " + q]Keep the note short and clear. Tool descriptions you send to a model are the same idea: they say how to call the function. A docstring that lies is worse than none. Update it when you change the return shape.
You can read it with search.__doc__. You do not need that often. Editors show it. Tests sometimes check it. Humans read it first.
pass means "do nothing yet"
Python needs at least one line in a block. pass is that line when you have not written the real body yet.
def search(q):
pass class="tok-c"># fill this in laterIf you leave the body empty, Python raises an error. pass is the empty body. Use it while you design a tool. Replace it before you ship. A function that only passes returns None. That can look like “the tool ran” if you forget to fill it in.
Keyword arguments and unpacking a dict
Models often send inputs as a dict, like {"q": "agents", "k": 2}.
In a call, two stars in front of a dict unpack it. Unpack means “turn this dict into named inputs.”
args = {class="tok-s">"q": class="tok-s">"agents", class="tok-s">"k": 2}
print(search(**args)) class="tok-c"># same as search(q="agents", k=2)That ** is in the call. If the dict has a key the function does not accept, Python raises TypeError. Check keys, or write a function that accepts extras (next lesson). If a required name is missing, you also get TypeError.
A dict of functions
Functions are values. You can store them in a dict without calling them. search is the function. search("q") is a call. The dict stores the function.
That dict is a tool registry: a map from a name to a function.
tools = {class="tok-s">"search": search, class="tok-s">"add": add}
print(tools[class="tok-s">"search"](class="tok-s">"python"))A helper like call_tool(name, **kwargs) looks up the name, then calls the function with the named inputs. If the name is missing, return an error dict. Do not crash the agent loop.
Common mistakes
- Printing instead of returning.
memory = memory.append(x)inside a function, sameNonetrap.- Calling
tools["search"]without()when you meant to run it — you get the function object. **argswith extra or missing keys.- Forgetting parentheses on
def search():or the colon.
Run to execute this in your browser. Nothing is sent to a server.
Change "shell" to "add" and pass a and b. Click Run. Unknown names should stay error dicts, not crashes.
If a Try it box looks empty, you forgot print. The function returned a value and nobody showed it.
How agents use this
When a model asks for a tool, it sends a name and a dict of inputs. Your job is registry[name](**inputs). That is the whole trick. Keep each tool small. Give it clear inputs. Return a dict, list, string, or number. Huge functions with hidden names are hard to test and hard to read in a log.
call_tool is the executor. The model never calls Python directly. It only emits a name. Your registry is the allowlist and the lookup table in one. If a name is not in the dict, it is not a tool. There is no second back door.
Docstrings (or a separate description string) are what you later send the model so it knows the arguments. The function body is what actually runs. Keep those two aligned. A tool that claims it searches the web but returns a constant string will pass a demo and fail in production. Return real data. Print only for you.
Check your understanding