The first @tool line in an agent tutorial stops a lot of beginners cold. There’s an @, a word, and then a normal-looking function — and somehow that function is now a tool the model can call. This Python decorators primer explains that one symbol, so the @ lines scattered through agent and web code read as ordinary Python instead of syntax you skip past.
You will apply decorators constantly in agent code and write your own rarely, if ever. So the goal here is recognition: know what @something does to the function beneath it, and the framework quickstarts stop hiding a step. It’s a smaller idea than it looks.
- You can read a basic Python function — new to that? The Python for AI agents primer covers functions
- That’s the whole list — no prior decorator experience assumed
- A decorator wraps a function to add behaviour, applied with an
@line directly above the definition. - The
@is pure shorthand:@logabovegreetmeans exactlygreet = log(greet). - In agent code you mostly apply, not author, decorators —
@tool,@agent.tool,@app.get. - Same syntax, different registrar:
@toolregisters a tool;@app.getregisters a web route.
What a decorator actually is
A decorator is a function that takes another function, wraps it with some extra behaviour, and gives you back a function to use in its place. You attach one with an @ line sitting directly above a definition.
@logdef greet(name):return f"Hi, {name}"
That @log line says: “before you bind the name greet, pass this function through log first.” The function still looks normal; the decorator adds something around it — logging, timing, registration — without you editing the body (Python glossary).
The @ is just shorthand
Here’s the demystifying part. The @ syntax is sugar for one plain assignment. These two snippets are identical:
@logdef greet(name):return f"Hi, {name}"# ...is exactly the same as:def greet(name):return f"Hi, {name}"greet = log(greet) # pass greet to log, rebind the name to the result
Read @log as ”greet = log(greet)” and every decorator stops being mysterious. The decorator receives your function, and whatever it returns becomes the new greet. That’s the entire mechanism — there is nothing hidden underneath (PEP 318).
@thing above a function means func = thing(func). Whenever a decorator confuses you, rewrite it as that assignment and it becomes obvious what's being passed where.What a decorator looks like inside
You’ll rarely write one, but seeing the shape once removes the mystery for good. A decorator takes a function and returns a new function that calls the original with extra behaviour around it.
import functoolsdef log(func):@functools.wraps(func) # keeps func's name and docstringdef wrapper(*args, **kwargs):print(f"calling {func.__name__}")return func(*args, **kwargs) # the original still runsreturn wrapper
The *args, **kwargs just means “accept whatever arguments the original took and pass them straight through.” The @functools.wraps(func) line is a small courtesy that keeps the original function’s name and docstring intact — which matters more than you’d think for agents, since frameworks read the docstring to describe your tool to the model.
Why agent code is full of Python decorators
Now the payoff. In agent code, a decorator is how you register a plain function with a framework. The function stays ordinary; the @ line tells the framework what role it plays.
@tooldef get_weather(city: str) -> str:"""Return the current weather for a city."""return f"It's 31C in {city}."
@tool doesn’t change what get_weather does — it registers it as a tool the model is allowed to call, building the schema from the type hints and docstring. You’ll see the same pattern everywhere: @agent.tool in Pydantic AI, and @app.get("/") in FastAPI to run a function when a URL is hit. Same @ syntax, different registrar. Recognise that, and the tool-calling step and the FastAPI deploy both read cleanly.
@tool as a label you stick on a function, not a thing you need to build. Authoring your own is a later, optional skill.Quick recap
The whole primer, in five lines:
- A decorator wraps a function to add behaviour, applied with an
@line above it. @thingmeansfunc = thing(func)— pure shorthand for one assignment.- Inside, a decorator returns a new function that calls the original with extras around it.
@functools.wrapspreserves the name and docstring frameworks read.- In practice you apply framework decorators (
@tool,@app.get), rarely write your own.
Frequently Asked Questions
What is a decorator? A function that wraps another to add behaviour, applied with an @ line above the definition (e.g. @tool registers a tool).
What does @ do? It’s shorthand: @log above greet equals greet = log(greet).
Do I need to write my own? Rarely. You mostly apply the ones a framework gives you.
@tool vs @app.get? Both are decorators that register your function — one with an agent framework, one with a web framework.
Conclusion
Python decorators are just a function wrapping a function, and the @ is a tidy way to write func = thing(func). Once you read it that way, the @tool and @app.get lines that open every agent and API tutorial turn from strange punctuation into a plain label: “register this function, leave its body alone.” That’s all you need to follow the code — and almost all you’ll ever need to write.
Which @ line first made you pause — @tool, @app.get, or something else? Tell me in the comments.
- Need the basics first? The Python for AI agents primer covers functions and syntax.
- See
@toolin action: tool calling in Python registers real functions for the model. - Decorators in a web app: FastAPI, Docker & deploy uses
@app.getto expose your agent.