Module 2

Tool Design & Schema Safety

This module covers building reliable, cost-efficient tool integrations, versioning web search for dynamic filtering, enforcing strict JSON schemas on tool inputs, and understanding the compliance trade-offs for each approach.

Answer key Module2_Complete.ipynb

1. Concepts: Tool Selection & Strict Mode

Before writing any code, understand how Claude decides which tool to call.

  • Tool descriptions are the primary selection mechanism. Claude reads the name and description on each tool to choose. If two tools have overlapping purposes (e.g. analyze_content vs. analyze_document), Claude may misroute the request, write descriptions that disambiguate exactly when each tool is appropriate.
  • Dynamic filtering with web_search_20260209 alongside code_execution_20260120 lets Claude write Python that post-processes search results before they enter the context window. Lower token cost, higher signal.
  • Strict mode (strict: True) uses grammar-constrained sampling to force tool inputs to match your JSON schema 100%. No trailing commas, no missing required fields, no surprise keys.
ZDR Compliance Warning

Standard web_search alone is ZDR-eligible. Adding code_execution_20260120 for dynamic filtering makes the entire request non-ZDR because execution state is held server-side. Communicate this trade-off to regulated clients before enabling it.

Architect Tip for the Exam

The web_search_20260209 tool version activates dynamic filtering, the older version does not. Always check the tool type version string, it is a direct exam signal for which capabilities are available.

2. Defining the Toolbox

Both web_search and code_execution are server-side built-ins, Anthropic runs them and feeds the results back to Claude. add_lead_to_crm is a client-side custom tool: when Claude calls it, your code is responsible for executing the action and returning the result.

Note the explicit name field on every entry. code_execution_20260120 requires name even though it's a built-in, omitting it returns a 400 BadRequestError.

Python
# DEFINE THE TOOLS
# Note: Use 'strict: True' for the CRM tool to guarantee valid JSON.
# Note: 'code_execution' must have a name even if it's a built-in server tool.

tools = [
    {
        "type": "web_search_20260209",
        "name": "web_search",
        "max_uses": 5,
        "allowed_domains": ["gartner.com", "forrester.com", "hbr.org"],
    },
    {
        "type": "code_execution_20260120",
        "name": "code_execution"   # CRITICAL: required alongside type
    },
    {
        "name": "add_lead_to_crm",
        "description": "Adds a validated prospect to the enterprise CRM. Use only after industry research is complete.",
        "strict": True,
        "input_schema": {
            "type": "object",
            "properties": {
                "company": {"type": "string"},
                "contact_email": {"type": "string", "format": "email"},
                "budget_range": {"type": ["string", "null"]},   # Nullable to prevent hallucinations
            },
            "required": ["company", "contact_email", "budget_range"],
            "additionalProperties": False,
        },
    },
]

3. The Initial Request & stop_reason

Send the user's prospecting goal with the toolbox attached. Don't print just the final text, inspect stop_reason and walk the content blocks. On the first turn you should see tool_use, not end_turn.

Python
# THE INITIAL REQUEST
# On Opus 4.7, 'thinking' is required for complex tasks like this.
response = client.messages.create(
    model="claude-opus-4-7",
    max_tokens=8192,
    thinking={"type": "adaptive"},
    tools=tools,
    messages=[{
        "role": "user",
        "content": "Find the top 5 AI consulting prospects in EMEA finance and add them to my CRM.",
    }],
)

# SHOW OUTPUT
print(f"Stop Reason: {response.stop_reason}")   # Expected: 'tool_use'
for block in response.content:
    if block.type == "thinking":
        print("Reasoning Signature Detected.")
    if block.type == "tool_use":
        print(f"Claude requested tool: {block.name}")
        print(f"Parameters: {block.input}")

4. Handling the Agentic Loop

Web search runs server-side, Anthropic executes it and feeds the results back to Claude inside the same request. Your CRM write is client-side, your code must execute it and return a tool_result block before Claude can finish its turn.

  • Turn 1: Claude calls web_search. The API runs it server-side and silently injects results into Claude's context.
  • Turn 2: Claude reasons over the results and emits a tool_use block for add_lead_to_crm.
  • Turn 3: your application performs the database write and sends a tool_result block back. Claude generates the final end_turn response.
Python (client-side leg)
def handle_tool_use(block):
    if block.name == "add_lead_to_crm":
        # crm.write(block.input)  # your client-side action
        return {
            "type": "tool_result",
            "tool_use_id": block.id,
            "content": "ok: lead written",
        }
    raise ValueError(f"unhandled client-side tool: {block.name}")

# Loop until Claude is done.
while response.stop_reason == "tool_use":
    tool_results = [handle_tool_use(b) for b in response.content if b.type == "tool_use"]
    response = client.messages.create(
        model="claude-opus-4-7",
        max_tokens=8192,
        thinking={"type": "adaptive"},
        tools=tools,
        messages=[
            {"role": "user", "content": "Find the top 5 AI consulting prospects ..."},
            {"role": "assistant", "content": response.content},
            {"role": "user", "content": tool_results},
        ],
    )

5. Final Verification & Citations

Once the loop terminates with end_turn, inspect the final text blocks. Web search automatically attaches citations to grounded claims, verify them so you can audit the sources Claude relied on.

Python
# FINAL OUTPUT VERIFICATION
# Web search automatically provides citations.
for block in response.content:
    if block.type == "text":
        print("Final Answer:", block.text)
        if hasattr(block, "citations") and block.citations:
            print("Sources:", [c.url for c in block.citations])
Architectural Summary
  • Safety: nullable types in the schema (["string", "null"]) prevent Claude from inventing a budget figure when one isn't in the search results.
  • Efficiency: code_execution_20260120 is free when bundled with web_search or web_fetch, so dynamic filtering costs nothing extra in token generation.
  • Compliance: that same bundling makes the request non-ZDR. Standard web search alone stays ZDR-eligible.
  • Selection: tool descriptions, not names alone, drive Claude's routing. Disambiguate any tools whose purposes overlap.

Lab Exercise: Tool Design & Schema Safety

Self-driven lab Module2_Self_Driven_Lab.ipynb

Objective: design a tool surface that routes correctly, validates inputs strictly, and makes compliance trade-offs explicit.

  1. Define web search, code execution, and CRM write tools with clear descriptions and versioned built-in tool types.
  2. Make the CRM schema strict and nullable where data may be absent.
  3. Trigger a tool-use turn, inspect the requested tool, and return a correctly keyed `tool_result`.
  4. Write a short ZDR note explaining what changes when dynamic filtering is enabled.
Expected Deliverable

A tool definition and loop handler that can safely add validated leads.