Add Tool Docstrings

Su Suriya Ravichandran Updated 10 Nov 2026
8 min read ·Lesson 93 of 200

Docstrings Are Now Prompts

With @beta_tool (Lesson 91), a function's docstring becomes the tool description that Claude reads, and the docstring's Args: section becomes the parameter descriptions. A docstring is no longer just documentation for developers. It is part of the prompt, and the most important factor in whether Claude uses the tool correctly (Lesson 82).

@beta_tool
def get_course_details(slug: str) -> str:
    """Get details for one course by its slug.        ← tool description (what)

    Use it after search_courses when ...               ← when to use it
    Do not use it to find courses; ...                 ← when not to use it
    Returns the course as JSON with ...                ← what it returns

    Args:
        slug: Course slug from search_courses, ...     ← parameter description
    """

The Google Docstring Format

The decorator reads Google-style docstrings:

"""Summary line.

Longer description: one or more paragraphs.

Args:
    first_param: Description of the first parameter.
    second_param: Description of the second parameter. Descriptions
        can continue on an indented line.
"""
PartBecomes
Summary line and longer descriptionThe tool's description
Each Args: entryThe description of the matching parameter in input_schema

The parameter name in Args: must match the function parameter exactly. A typo, such as slugs: for a parameter named slug, leaves the real parameter without a description.


What a Good Tool Docstring Contains

Apply the guidance from Lesson 82 to the docstring: aim for at least 3–4 sentences that cover:

ContentExample
What the tool does"Get details for one course by its slug."
When to use it"Use it after search_courses when the student wants more information about a specific course."
When not to use it"Do not use it to find courses; use search_courses instead."
What it returns"Returns the course as JSON with title, level, duration in weeks, and a short summary."
Limits"It does not return prices or schedules."

And for each parameter:

ContentExample
Meaning"Course slug from search_courses"
Format and example"lowercase with hyphens, e.g. 'django-101'"
Allowed values"one of 'beginner', 'intermediate', 'advanced'" (or use Literal in the type hint)
Behavior when omitted"defaults to 'any', which includes all levels"

A Template

"""<What the tool does, in one sentence>.

Use it when <situations>. Do not use it for <situations>; use <other tool> instead.
Returns <format and fields>. <Limits: what it does not return or do>.

Args:
    <param>: <meaning>, <format>, e.g. <example>. <behavior when omitted, if optional>.
"""

What Not to Put in Tool Docstrings

Because docstrings are sent to Claude, everything in them becomes part of the prompt:

Do not includeWhyPut it here instead
Internal notes ("TODO: fix pagination", "hack for old DB")Confuses the model, and wastes tokens# comments in the code
Secrets, internal hostnames, credentialsCan be revealed in conversationConfiguration and environment variables (Lesson 8)
Implementation details ("queries table courses_v2 with a JOIN")Irrelevant to how Claude should use the toolCode comments or developer docs
Instructions that contradict your system promptCreates conflicting guidanceOne consistent policy in the system prompt

Use code comments for developer-facing notes, and the docstring for model-facing guidance.


Docstrings for Tools Without Parameters

A tool with no parameters still needs a full description. Explain what it returns and why no input is needed:

@beta_tool
def get_enrollments() -> str:
    """Return the current student's enrolled courses with progress percentages, as JSON.

    Use it for questions about the student's own courses, progress, or what to study next.
    It takes no input: the student is identified automatically by the application.
    """

The last sentence prevents Claude from looking for a way to pass a student ID, and makes clear that identity comes from the application (Lesson 86).


Practical Example: A Docstring Checker for Decorated Tools

This checker reads the definition that @beta_tool generates, through to_dict(), and reports common docstring problems. Running it in tests catches weak or broken docstrings before they reach Claude.

"""docstring_check.py: check the descriptions that @beta_tool generates from docstrings."""

import json
import re

from anthropic import beta_tool


def check_tool(tool) -> list[str]:
    definition = tool.to_dict()
    problems = []

    description = definition.get("description", "")
    sentences = [s for s in re.split(r"[.!?](?:\s|$)", description) if s.strip()]
    if len(sentences) < 3:
        problems.append(f"description has {len(sentences)} sentence(s); aim for at least 3")

    properties = definition.get("input_schema", {}).get("properties", {})
    for name, schema in properties.items():
        if not schema.get("description"):
            problems.append(f"parameter {name!r} has no description")
    return problems


@beta_tool
def get_course(slug: str) -> str:
    """Get course."""
    return json.dumps({"slug": slug})


@beta_tool
def get_course_details(slug: str) -> str:
    """Get details for one course by its slug.

    Use it after search_courses when the student wants more information about a specific course.
    Do not use it to find courses; use search_courses instead.
    Returns the course as JSON with title, level, and duration in weeks.

    Args:
        slug: Course slug from search_courses, lowercase with hyphens, e.g. 'django-101'
    """
    # Internal note (not sent to Claude): data comes from the catalog cache.
    return json.dumps({"slug": slug, "title": "Django for Beginners", "level": "beginner", "weeks": 6})


if __name__ == "__main__":
    for tool, label in [(get_course, "get_course"), (get_course_details, "get_course_details")]:
        print(f"{label}: {check_tool(tool) or 'OK'}")

Output:

get_course: ['description has 1 sentence(s); aim for at least 3', "parameter 'slug' has no description"]
get_course_details: OK

How It Works

  1. tool.to_dict() returns the definition generated from the function: name, description, and input schema (Lesson 91). The checker tests what Claude will actually see, not the source code.
  2. Sentence counting splits the description on ., !, or ? followed by whitespace or the end of the text. It is a rough measure, but it reliably flags one-line descriptions.
  3. Parameter checks look at each property in input_schema. A property without a description means the docstring is missing an Args: entry, or has a typo in a parameter name.
  4. get_course fails both checks: its docstring is a two-word summary, with no Args: section.
  5. get_course_details passes. Its internal note is a # comment inside the function body, which is not part of the docstring and is never sent to Claude.

Add the checker to your test suite: assert check_tool(tool) == [] for every tool.


Testing Whether Descriptions Work

A complete docstring is necessary, but you should also check that it works: that Claude chooses the right tool with the right inputs. Use the evaluation approach from Lesson 39:

  1. Write test questions that should, and should not, trigger each tool.
  2. Run them, and record which tools Claude calls and with what inputs.
  3. When Claude chooses wrongly, improve the relevant "when to use" or "when not to use" sentences, and run the tests again.

Common Mistakes

One-Line Docstrings

What happens: Claude has too little information to choose and use the tool correctly.

Fix: write 3–4 sentences or more: what, when, when not, returns, limits.

Missing or Misspelled Args: Entries

What happens: parameters lose their descriptions without any error.

Fix: match every parameter name exactly, and check the generated definition.

Internal Notes in Docstrings

What happens: TODOs, implementation details, or secrets are sent to Claude.

Fix: keep developer notes in # comments.

Docstrings That Conflict with the System Prompt

What happens: Claude receives contradictory instructions about when to use tools.

Fix: keep tool-specific guidance in docstrings, and overall policy in the system prompt.

Never Checking the Generated Definition

What happens: formatting mistakes go unnoticed.

Fix: inspect to_dict() output, and check it in tests.


Best Practices

  • Treat tool docstrings as prompts.
  • Use Google-style docstrings with an Args: entry for every parameter.
  • Describe what, when, when not, returns, and limits.
  • Include formats and examples in parameter descriptions.
  • Keep internal notes in comments, never in docstrings.
  • Check generated definitions automatically, and evaluate tool choice with test questions.

Exercise

  1. Rewrite the docstring of get_course so that it passes the checker.
  2. Add a check to check_tool() that warns when the description is longer than 1,200 characters.
  3. Write a docstring for a cancel_enrollment(slug: str, reason: str) tool that makes clear when it should not be used.
  4. Explain why the internal note in get_course_details is a comment and not part of the docstring.
Show answers for steps 3 and 4

Step 3:

@beta_tool
def cancel_enrollment(slug: str, reason: str) -> str:
    """Cancel the current student's enrollment in one course.

    Use it only when the student explicitly asks to cancel or leave a specific course and has
    confirmed the course. Do not use it when a student is only unhappy, asking about refunds,
    or asking what would happen if they cancelled; answer those questions instead.
    Returns a confirmation with the cancellation date, or an error if the student is not enrolled.

    Args:
        slug: Slug of the course to cancel, e.g. 'django-101'
        reason: The student's reason for cancelling, in their own words, in one sentence
    """

Actions that change data deserve especially precise "when not to use" guidance. In production, such tools should also require explicit confirmation before running (Lesson 177).

Step 4: everything in a docstring becomes part of the tool description sent to Claude. The note is useful only to developers. Sending it would waste tokens, could confuse the model, and could reveal implementation details. A # comment inside the function body is ignored by the decorator, so it stays private to the code.

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 Add Tool Docstrings and get answers drawn from it.

Signed-in readers only.