Send Tools to Claude

Su Suriya Ravichandran Updated 10 Nov 2026
9 min read ·Lesson 84 of 200

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_choice auto), Claude decides (Lesson 89).
  • If Claude calls a tool, the response has stop_reason == "tool_use" and contains tool_use blocks (Lesson 85).
  • The API adds a tool-use system prompt that explains how to call tools, combined with your own system prompt.

What Claude Sees

When tools are included, the API builds a special system prompt from:

  1. instructions on how to use tools,
  2. the tool definitions: names, descriptions, and schemas,
  3. your system prompt, if you provided one, and
  4. 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."""
InstructionEffect
"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.

ApproachWhen it fits
Send all toolsA small set of tools (up to a handful) that all fit the application
Send only relevant toolsDifferent 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_use blocks 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:

Shapestop_reasonContent
Direct answerend_turnText only (possibly after thinking)
Tool calltool_useOptional preamble text, then one tool_use block
Parallel tool callstool_useOptional 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

  1. TOOLS contains three definitions with detailed descriptions, including when not to use each tool, so Claude can tell search_courses and get_enrollments apart.
  2. get_enrollments takes 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).
  3. The system prompt sets the overall policy: use tools for what they cover, and answer directly otherwise.
  4. 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.
  5. describe() summarizes each response: tool calls with their inputs, or the start of the direct answer.
  6. The overhead measurement compares count_tokens() with and without tools. 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

  1. Remove the "when not to use" sentence from get_enrollments and search_courses, and run the third question several times. Does Claude still choose the right tools?
  2. Add a system-prompt instruction that Claude must never write a preamble before tool calls. How do the responses change?
  3. 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?
  4. Ask "Should I study Django or go for a walk in Bengaluru today?". Which tools does Claude call?
Show answers for steps 2 and 4

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).

0 Comments

Reviewed before they appear

No comments yet.

Anthropic SDK Using Python
Anthropic SDK Intro Claude API Intro SDK Requirements Install Anthropic SDK Upgrade the SDK Create an API Key Set API Keys Use Environment Variables Import Anthropic Create a Client Send Your First Message Choose a Claude Model Set Max Tokens Read a Response Print Response Text Understand Message Blocks Use System Prompts Send User Messages Set Temperature Handle Stop Reasons Messages API Intro Create Messages Use Message Roles Send Conversation History Continue a Conversation Build a Chat Loop Reset Chat History Use Multiple Turns Inspect Message IDs Read Usage Data Write Clear Prompts Set a System Prompt Add Context to Prompts Use Prompt Templates Add Examples to Prompts Request JSON Output Use XML Tags Control Response Length Improve Prompt Quality Avoid Prompt Injection Read Text Blocks Read Content Blocks Check Stop Reasons Count Input Tokens Count Output Tokens Count Tokens Before Sending Limit Output Tokens Use Stop Sequences Read Request IDs Parse API Responses Streaming Intro Stream Text Output Use Stream Events Use Stream Helpers Get Final Messages Stream Async Responses Handle Stream Errors Build a Live Chat Stream Long Responses Close Streams Safely Build a Chatbot Keep Chat History Manage Chat Sessions Add Chat Memory Build a CLI Chatbot Build a Web Chatbot Use Flask with Claude Use FastAPI with Claude Return JSON from an API Add Chat Error Messages Async SDK Intro Create an Async Client Send Async Messages Use asyncio Run Parallel Requests Stream Async Text Handle Async Errors Set Async Timeouts Close Async Clients Choose Sync or Async Tool Use Intro Define a Tool Create Tool Schemas Send Tools to Claude Read Tool Calls Run Python Tools Return Tool Results Continue After Tool Use Control Tool Choice Handle Tool Errors Use the beta_tool Decorator Create a Tool Runner Add Tool Docstrings Validate Tool Inputs Use Multiple Tools Limit Tool Calls Log Tool Activity Build a Weather Tool Build a Search Tool Build a Calculator Tool Structured Output Intro Request JSON Data Validate JSON Output Use Pydantic Models Handle Invalid JSON Extract Fields from Text Generate Typed Data Use Image URLs Send Base64 Images Read PDFs with Claude Use Output Schemas Build Data Extraction Test Structured Responses Send Image Inputs Upload Files Use File IDs Process Text Files Summarize Documents Extract Table Data Handle File Errors Read Cache Usage Reduce Repeated Input Costs Debug Cache Misses Prompt Caching Intro Enable Prompt Caching Cache System Prompts Cache Long Context Test Cache Performance Use Cache Breakpoints Cache Best Practices Message Batches Intro Create a Message Batch Set Batch IDs Check Batch Status List Message Batches Get Batch Results Process Batch Outputs Handle Failed Batch Items Use Batches for Bulk Tasks Compare Batches and Requests Handle API Errors Handle Rate Limits Retry Failed Requests Set Request Timeouts Handle Network Errors Use Exponential Backoff Build Reliable API Calls Use Extended Thinking Log API Errors Track Request IDs Paginate API Results Set Thinking Budgets Read Thinking Blocks Use Tool Search Use Web Search Tools Use Computer Tools Use Citations Use Token Counting Read Raw Responses Set Custom Headers Use Claude on AWS Bedrock Use Claude on Vertex AI Use Claude on Microsoft Foundry Configure Cloud Credentials Choose a Cloud Provider Compare API Integrations Configure Provider Clients Handle Cloud Errors Use Cloud SDK Extras Deploy Cloud Apps Protect API Keys Use a .env File Ignore Secrets in Git Validate User Input Reduce Prompt Injection Limit Tool Permissions Avoid Unsafe Tool Calls Sanitize Tool Results Set Usage Limits Review Security Logs Test SDK Functions Mock API Responses Test Error Handling Test Streaming Code Measure Token Usage Track API Costs Configure Logging Deploy with Docker Deploy a FastAPI App Monitor Production Apps Build a Claude Chatbot Build a Text Summarizer Build a Code Explainer Build a JSON Extractor Build a PDF Assistant Build a Streaming Chat App Build a Tool-Calling Agent Build a Batch Processor Build a Research Assistant Build a Claude API Dashboard
Ask about this post
AI Ask about this post

Ask questions about Send Tools to Claude and get answers drawn from it.

Signed-in readers only.