LangChain's @tool decorator is a schema generator, not magic — it reads your function's signature and docstring and builds what the real tools API needs.
1@tool Is a Schema Generator, Not Magic
It's tempting to treat a decorator like @tool as opaque framework magic. It isn't — it inspects a plain Python function's type-hinted parameters and docstring, and from those, builds the exact JSON schema structure (name, description, parameters) the underlying tools API requires. You could write that schema by hand; @tool just automates a mechanical, error-prone step.
2The Model Still Doesn't Execute Code
Nothing about tool calling changes the fundamental fact that an LLM only generates text — in this case, a specially structured tool_calls response instead of prose. Your own backend is still the thing that actually runs get_stock_price() and gets a real answer; the model's job stops at requesting that call with the right arguments.
3Step-by-Step Breakdown
Module 5 gives your LLM the ability to take actions, not just answer from text. LangChain's @tool decorator wraps a plain Python function, auto-generating the JSON schema an LLM API needs from the function's signature and docstring — you write normal Python, LangChain builds the schema.
Underneath the decorator, this is the exact same OpenAI tools API you may have used before — @tool just saves you from hand-writing the JSON schema for every function. This lesson calls that real API directly to make the mechanism fully visible.
Trigger a Real Tool Call. This is the exact schema @tool would generate for get_stock_price. Run it against a real model and check the response: instead of a text answer, you should see a structured tool_calls block requesting get_stock_price with ticker: "ACME" — the model choosing to act, not guessing a price.
What does LangChain's @tool decorator actually generate for you, that you'd otherwise have to write by hand?
- →The JSON schema (name, description, parameter types) the LLM API needs to understand and call the tool — inferred automatically from the function's signature and docstring.
- →It makes the LLM execute the Python function internally, inside the model itself.
The model chose to act instead of guessing. Next: the actual control loop — Thought, Action, Observation — that a real LangChain AgentExecutor runs to use a tool's result and decide what to do next.
Level Up 🚀
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Surface When an Answer Required a Tool Call
A chatbot UI that used a tool to answer (like a live stock lookup) should indicate that as real text, not just show the final number, so users understand the answer came from a live action rather than the model's own knowledge.
<span>Looked up live via get_stock_price</span>SEO Implications
- 1
Target 'LangChain @tool decorator example' as a distinct, high-intent search
This is one of the most-searched LangChain features by developers building their first tool-using agent.
Best Practices
Write Clear, Specific Docstrings for Every @tool Function
Since @tool generates the schema's description directly from your docstring, a vague or missing docstring produces a vague schema description — which directly hurts the model's ability to correctly decide when to use the tool.
Frequent Bugs
A @tool-decorated function with an unclear docstring or missing type hints, causing the model to never call it (or call it with wrong argument types) because the auto-generated schema is too vague.
Always add a specific, one-sentence docstring and explicit type hints to any @tool function — both are the actual source of the schema quality the model relies on.
Real-World Examples
Live Data Lookup Agent
A financial assistant correctly requests a get_stock_price tool call instead of guessing a plausible-sounding number, because the question genuinely requires live data the model can't have in its training set — a direct parallel to why RAG exists for private documents.
@tool
def get_stock_price(ticker: str) -> str: ...