Send Tools to Claude
The tools Parameter
Tools are sent with a request through the tools parameter: a list of tool definitions (Lessons 82 and 83).
from anthropic import Anthropic
client = Anthropic()
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=2048,
tools=[weather_tool, search_courses_tool],
messages=[{"role": "user", "content": "Is it raining in Chennai right now?"}],
)
When tools is present:
- Claude may call one or more of the tools, or answer directly without them. By default (
tool_choiceauto), Claude decides (Lesson 89). - If Claude calls a tool, the response has
stop_reason == "tool_use"and containstool_useblocks (Lesson 85). - The API adds a tool-use system prompt that explains how to call tools, combined with your own
systemprompt.
What Claude Sees
When tools are included, the API builds a special system prompt from:
- instructions on how to use tools,
- the tool definitions: names, descriptions, and schemas,
- your system prompt, if you provided one, and
- the tool configuration, such as
tool_choice.
Claude chooses tools based on the user's request, the tool descriptions, and the instructions in your system prompt. Your Python code is never visible to it.
Guiding Tool Use with the System Prompt
Tool descriptions say what each tool does. The system prompt says how your application wants tools used overall:
SYSTEM = """You are the GOCOURSE study assistant.
Use the tools to answer questions about courses, enrollments, and weather; never guess
information that a tool can provide. If no tool fits a question, answer from general
knowledge and say so. Before calling a tool, briefly tell the student what you are checking."""
| Instruction | Effect |
|---|---|
| "Never guess information that a tool can provide" | Claude prefers tool calls over assumptions |
| "If no tool fits, answer from general knowledge and say so" | Claude does not force irrelevant tool calls |
| "Briefly tell the student what you are checking" | Claude writes a short preamble before tool calls |
Sending Several Tools
Most applications offer several tools. Claude chooses among them, and can call more than one in a single response when a request needs several independent pieces of information. Parallel tool calls are on by default (Lesson 81).
tools = [tool.definition for tool in REGISTRY.values()] # From a registry (Lesson 82)
How Many Tools?
Every tool definition is sent with every request that includes it, which adds input tokens and gives Claude more options to choose from.
| Approach | When it fits |
|---|---|
| Send all tools | A small set of tools (up to a handful) that all fit the application |
| Send only relevant tools | Different features of your app use different tools |
| Tool search (Lesson 154) | Very large tool libraries, where Claude loads tool definitions on demand |
Only include tools that make sense for the current request or feature. A course recommendation feature does not need a billing tool.
Keep Tools Consistent Within a Conversation
In a multi-turn tool conversation, send the same tools in every request:
- Claude's earlier
tool_useblocks refer to tools by name. If a later request omits a tool that appears in the history, the conversation becomes inconsistent. - Changing tools between turns also invalidates prompt caching for the parts of the request that follow the change (Lessons 121–130).
If tools must change, for example because a feature unlocks a new tool, add tools rather than removing ones that appear in the history.
The Cost of Tools
Tools add input tokens in two ways: the tool definitions themselves, and the tool-use system prompt that the API adds when tools are present. Measure the overhead with count_tokens() (Lesson 44):
base = client.messages.count_tokens(model=MODEL, messages=messages).input_tokens
with_tools = client.messages.count_tokens(model=MODEL, messages=messages, tools=tools).input_tokens
print(f"Tool overhead: {with_tools - base} tokens per request")
Because tool definitions are identical across requests, they are an excellent candidate for prompt caching (Lessons 121–130), which makes the repeated part much cheaper.
What the Response Can Look Like
When tools are available, a response can take several shapes:
| Shape | stop_reason | Content |
|---|---|---|
| Direct answer | end_turn | Text only (possibly after thinking) |
| Tool call | tool_use | Optional preamble text, then one tool_use block |
| Parallel tool calls | tool_use | Optional preamble text, then several tool_use blocks |
Claude often writes a short explanation before calling tools, such as "Let me check the weather in Chennai." Treat this text like any other assistant text, and do not depend on its exact wording.
Practical Example: Inspecting How Claude Uses Tools
This script sends three different questions with the same three tools, and prints how Claude responded to each: a direct answer, one tool call, or parallel tool calls. It also measures the token overhead of the tools.
"""send_tools.py: send several tools and inspect Claude's decisions."""
from anthropic import Anthropic
client = Anthropic()
MODEL = "claude-opus-5-5"
SYSTEM = (
"You are the GOCOURSE study assistant. Use the tools for course, enrollment, and weather "
"questions; never guess what a tool can tell you. If no tool fits, answer directly."
)
TOOLS = [
{
"name": "get_weather",
"description": (
"Gets the current weather for a city. Use it for questions about current conditions "
"or whether the weather suits an activity. Returns temperature in Celsius and a short "
"description. It does not provide multi-day forecasts."
),
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string", "description": "City name, e.g. Chennai"}},
"required": ["city"],
},
},
{
"name": "search_courses",
"description": (
"Searches the GOCOURSE catalog by topic. Use it when a student asks which courses exist "
"or wants a recommendation. Do not use it for the student's own enrollments. "
"Returns up to 5 courses with title, level, and duration."
),
"input_schema": {
"type": "object",
"properties": {
"topic": {"type": "string", "description": "Subject, e.g. 'Django'"},
"level": {"type": "string", "enum": ["beginner", "intermediate", "advanced"],
"description": "Optional level filter; omit for all levels"},
},
"required": ["topic"],
},
},
{
"name": "get_enrollments",
"description": (
"Returns the courses the current student is enrolled in, with progress percentages. "
"Use it for questions about the student's own courses or progress. "
"Do not use it to find new courses. It takes no input."
),
"input_schema": {"type": "object", "properties": {}, "required": []},
},
]
QUESTIONS = [
"What is the difference between a list and a tuple in Python?",
"Which beginner Django courses do you have?",
"How far am I in my courses, and is there an advanced SQL course I could take next?",
]
def describe(response) -> str:
calls = [f"{b.name}({b.input})" for b in response.content if b.type == "tool_use"]
text = "".join(b.text for b in response.content if b.type == "text").strip()
if not calls:
return f"direct answer: {text[:70]}..."
preamble = f' preamble: "{text}"' if text else ""
return f"{len(calls)} tool call(s): {', '.join(calls)}{preamble}"
if __name__ == "__main__":
for question in QUESTIONS:
response = client.messages.create(
model=MODEL, max_tokens=2048, system=SYSTEM, tools=TOOLS,
messages=[{"role": "user", "content": question}],
)
print(f"Q: {question}\n stop_reason={response.stop_reason} -> {describe(response)}\n")
sample = [{"role": "user", "content": QUESTIONS[0]}]
base = client.messages.count_tokens(model=MODEL, system=SYSTEM, messages=sample).input_tokens
with_tools = client.messages.count_tokens(model=MODEL, system=SYSTEM, messages=sample,
tools=TOOLS).input_tokens
print(f"Tool overhead: {with_tools - base} input tokens per request")
Example output (wording and counts vary):
Q: What is the difference between a list and a tuple in Python?
stop_reason=end_turn -> direct answer: Lists are mutable, so you can change them after creation, while tuples...
Q: Which beginner Django courses do you have?
stop_reason=tool_use -> 1 tool call(s): search_courses({'topic': 'Django', 'level': 'beginner'}) preamble: "Let me search the catalog for beginner Django courses."
Q: How far am I in my courses, and is there an advanced SQL course I could take next?
stop_reason=tool_use -> 2 tool call(s): get_enrollments({}), search_courses({'topic': 'SQL', 'level': 'advanced'})
Tool overhead: 942 input tokens per request
How It Works
TOOLScontains three definitions with detailed descriptions, including when not to use each tool, so Claude can tellsearch_coursesandget_enrollmentsapart.get_enrollmentstakes no input: its schema is an object with no properties. The current student's identity comes from your application when the tool runs, not from Claude (Lesson 82).- The system prompt sets the overall policy: use tools for what they cover, and answer directly otherwise.
- The three questions produce the three response shapes: a general Python question gets a direct answer; a catalog question gets one tool call with a preamble; a two-part question gets two parallel tool calls, because the parts are independent.
describe()summarizes each response: tool calls with their inputs, or the start of the direct answer.- The overhead measurement compares
count_tokens()with and withouttools. The difference includes the definitions and the tool-use system prompt that the API adds. Overhead like this is paid on every request that includes the tools, which is why sending only relevant tools, and caching them, matters.
This script only inspects Claude's decisions. Lessons 85–88 read the calls, run the tools, return the results, and continue the conversation.
Common Mistakes
Sending Every Tool Everywhere
What happens: extra tokens on every request, and more ways for Claude to choose the wrong tool.
Fix: send the tools each feature needs.
Changing the Tool Set Mid-Conversation
What happens: history refers to tools that are no longer defined, and caching benefits are lost.
Fix: keep tools consistent within a conversation; add, do not remove.
No Guidance in the System Prompt
What happens: Claude guesses answers that a tool could provide, or calls tools for questions that do not need them.
Fix: state the tool policy in the system prompt.
Assuming Every Response Has a Tool Call
What happens: code that expects tool_use blocks fails on direct answers.
Fix: check stop_reason and the block types.
Parsing the Preamble Text
What happens: code breaks when Claude phrases the preamble differently.
Fix: read tool calls from tool_use blocks only (Lesson 85).
Best Practices
- Send a focused set of well-described tools.
- State a tool policy in the system prompt.
- Keep tools identical across a tool-use conversation.
- Expect three response shapes: direct answer, one call, several calls.
- Measure tool overhead, and cache tool definitions for repeated use.
Exercise
- Remove the "when not to use" sentence from
get_enrollmentsandsearch_courses, and run the third question several times. Does Claude still choose the right tools? - Add a system-prompt instruction that Claude must never write a preamble before tool calls. How do the responses change?
- Measure the tool overhead with only one tool, with two, and with all three. Is the growth roughly proportional to the size of the definitions?
- Ask "Should I study Django or go for a walk in Bengaluru today?". Which tools does Claude call?
Step 2: the responses with tool calls usually contain only tool_use blocks, without preamble text. Your code is not affected, because it reads tool calls from the tool_use blocks, not from the text.
Step 4: Claude typically calls get_weather with {"city": "Bengaluru"}, and may also call search_courses with the topic Django, in parallel, because the two pieces of information are independent. It then combines both results to answer the question after you return them (Lesson 88).