Skip to content

Chapter 5: Implementing Tools and Function Calling #5

@yagop

Description

@yagop

Part of the Build Your Own Coding Agent tutorial. One issue = one chapter (chapters/05-tools.md) plus its examples/05-tools/ samples.

Tools are how your agent reaches outside the model: reading files, calling APIs, running queries. This chapter covers the core tool-use protocol of the Messages API - you declare tools, the model decides when to call one and returns a tool_use block, your code executes it, and you feed a tool_result block back so the model can continue. The model never runs your code; it only emits structured requests and reads structured results, and you own the loop in between. Getting this single round-trip right is the foundation for every multi-tool workflow in the chapters that follow.

Goal (1 sentence): Master the core tool-use round-trip: declare a tool, detect a tool_use block, execute it, and return a tool_result.

After this chapter you can

  • Define a tool with name, description, and a JSON Schema input_schema, then detect stop_reason: "tool_use", extract the block, and dispatch to a typed handler.
  • Close the loop by returning a tool_result block that echoes the tool_use_id back to the model, including error results with is_error: true so the model can recover.
  • Write descriptions and schemas that make the model pick the right tool ("call this when..." phrasing, per-property descriptions, enum for fixed value sets), and steer tool selection with tool_choice (auto, any, tool, none) and disable_parallel_tool_use.

What to cover (ONE paragraph, not a list)

Introduce the tool-use contract: a tool has name, description, and an input_schema (JSON Schema with type, properties, required, and enum for fixed value sets). When the model decides to call a tool it returns a tool_use content block with id, name, and input (already a parsed object via the SDK - never raw-string-match the serialized JSON); your code reads response.content for text and tool_use blocks, checks stop_reason: "tool_use", appends the assistant turn verbatim, dispatches block.name to a handler, and sends back a user turn whose content is one or more tool_result blocks, each echoing tool_use_id exactly - every tool_use in a turn needs a matching tool_result. Cover argument validation and error reporting via is_error: true and an informative message so the model can recover or retry rather than throwing, then close with writing effective tool descriptions ("call this when..." phrasing, per-property descriptions, enum for disambiguation) and the tool_choice modes that steer or force a specific tool. Use Anthropic.Tool, Anthropic.ToolUseBlock, and Anthropic.ToolResultBlockParam SDK types throughout - not hand-rolled interfaces.

Going deeper (optional asides - keep OFF the main line)

  • None

Out of scope (defer - do NOT preview)

  • Multi-tool workflows and parallel tool calls beyond a single round-trip (later chapters).

Code samples - examples/05-tools/

  • define-tool.ts - declare a get_weather tool and inspect the returned tool_use block.
  • single-tool-loop.ts - full create -> tool_use -> execute -> tool_result -> answer.
  • dispatch-handlers.ts - map block.name to typed handlers, parse block.input.
  • tool-errors.ts - validate args and return is_error: true results.
  • tool-choice.ts - compare auto, any, { type: "tool", name }, disable_parallel_tool_use.

Must-keep for a beginner (floor - never cut for brevity)

  • The run command for the first sample.
  • "Never hardcode your key; it comes from the environment" (once, in prose).
  • Anything a beginner cannot infer from the code (e.g. Bun auto-loads .env, no loader).
  • The one genuinely non-obvious gotcha: block.input arrives as a parsed object from the SDK - never raw-string-match the serialized JSON.

Friendliness floor (never cut - terse is not friendly)

  • The chapter addresses the reader as "you", never "the user" or "one".
  • The intro AND at least one section open with a warm, second-person sentence.

Key APIs (flat list, reference only - NOT a coverage checklist)

client.messages.create, tools, name, description, input_schema, tool_use (id, name, input), stop_reason: "tool_use", tool_result (tool_use_id, content, is_error), tool_choice, disable_parallel_tool_use, Anthropic.Tool, Anthropic.ToolUseBlock, Anthropic.ToolResultBlockParam

Prerequisites

Chapters 1-2 (and Chapter 4 helps). Standalone; needs ANTHROPIC_API_KEY.

Definition of done

  • Chapter at chapters/05-tools.md, <=120 lines, <=4 main-line H2s plus an optional "What's next" closer (paste wc -l AND grep -c '^## ' in the PR).
  • Every sample runnable with bun run, imported via <<< @/examples/05-tools/file.ts, <=35 lines, comment:code <=0.30.
  • One-home rule held: no prose sentence restates an inline code comment.
  • Friendliness floor held: reader addressed as "you"; intro + >=1 section open warm.
  • Samples use only real @anthropic-ai/sdk surface, verified against current docs; ASCII punctuation only.
  • Optional material lives in Going-deeper asides, not main-line H2s.
  • Linked from README.md and the .vitepress/config.ts sidebar; bun x vitepress build passes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentation

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions