Deep Dive into AI Agent Tool Use: From @tool Decorator Internals to Hand-Written Agent

This tutorial explains how AI agents use tools via Function Calling, demystifies the @tool decorator by showing how Python introspection generates OpenAI JSON schemas, provides a complete 60-line hand-written agent implementation, and outlines five golden rules for tool design including detailed docstrings, semantic parameter names, limiting tools to 20, single responsibility, and recoverable errors.

AI Digital Ideal
AI Digital Ideal
AI Digital Ideal
Deep Dive into AI Agent Tool Use: From @tool Decorator Internals to Hand-Written Agent

From L3 to L4: What Problems Are Solved?

Building on the L3 smolagents experience with the @tool decorator, this lesson answers four key questions: what happens behind the @tool decorator, how to build an agent without any framework using OpenAI/Anthropic native APIs, how an agent decides which tool to call first when multiple tools are needed, and how an agent handles tool execution failures such as network timeouts.

Tools as the LLM's "Swiss Army Knife"

Using the "personal butler" metaphor from L1, the article illustrates that a butler needs a clear tool catalog — not just a Swiss Army knife handed over with "figure it out." A proper tool list includes get_weather(city: str), convert_currency(amount: float, from: str, to: str), and send_email(to: str, subject: str, body: str), each with a clear description, parameters, and return format. The key insight: tool descriptions are the LLM's "user manual"; the better the manual, the more accurately the LLM uses the tool. The @tool decorator essentially auto-generates this manual from Python functions.

What Happens Behind the @tool Decorator?

The article tears open smolagents' magic by manually replicating the decorator using Python's inspect module. A simple add(a: int, b: int) -> int function with docstring "Returns the sum of two integers." is converted into an OpenAI Function Calling JSON schema:

{
  "type": "function",
  "function": {
    "name": "add",
    "description": "Returns the sum of two integers.",
    "parameters": {
      "type": "object",
      "properties": {
        "a": {"type": "integer", "description": "参数 a"},
        "b": {"type": "integer", "description": "参数 b"}
      },
      "required": ["a", "b"]
    }
  }
}

The decorator's essence: use inspect.signature to read parameter names, type annotations, and defaults; use inspect.getdoc to read the docstring; package them into OpenAI's standard JSON schema; inject that schema into the system prompt so the LLM knows which tools are available. No black magic — just Python introspection plus string concatenation.

Hand-Writing a Complete Agent (60 Lines)

With the internals understood, the article presents a full agent implementation using the OpenAI API (swappable to Anthropic). The code defines three tools — get_weather, send_email, calculate — each with detailed docstrings. A func_to_schema function generates schemas via inspect. The main loop ( run_agent) maintains a message history, calls the LLM with tools_schema and tool_choice="auto", checks for tool_calls, executes each tool via a registry, catches exceptions and returns structured error messages, appends tool results as role: "tool" messages, and repeats up to max_turns (default 10). A sample run for "Check Beijing weather and email it to [email protected] with subject 'Today's Weather'" shows three turns: first get_weather({"city": "Beijing"}) returns "Sunny 25°C", second send_email(...) returns confirmation, third the LLM summarizes the result. This demonstrates the ReAct loop (Reason + Act) combined with tool calling.

Five Golden Rules of Tool Design

Rule 1: Docstring Is the Tool's User Manual

Bad: """Get weather.""" Good: detailed docstring specifying input format ("city must be English name like 'Beijing'"), output format ("'Weather Temperature' e.g. 'Sunny 25°C'"), and edge cases ("returns 'No weather data for {city}' if not found"). The LLM cannot read minds; it only sees the docstring.

Rule 2: Parameter Names Must Be Semantic

Bad: query(d1: str, d2: int) Good: search_models(task: str, max_results: int). Self-explanatory names reduce LLM confusion.

Rule 3: Limit Tools to ≤20

OpenAI's official guidance: each request's tools list counts as input tokens (~1-2K tokens for 20 tools). Beyond 20, tool selection accuracy drops significantly. For 50+ tools, consider hierarchical architecture (LLM picks category first) or tool merging (single tool with an action parameter).

Rule 4: Tools Must Have Single Responsibility

Bad: one manage_database(action, table, data) handling create/read/update/delete. Good: separate create_user, read_user, update_user, delete_user. LLMs pick wrong actions less often when each tool has a clear, narrow purpose.

Rule 5: Errors Must Be Explicit and Recoverable

Bad: raise exceptions that leave the LLM clueless. Good: return structured error objects, e.g., {"error": "User 123 not found"} instead of raising. Error messages become part of the LLM's decision context; designing them so the LLM can understand and adjust the next step is key to agent robustness.

Advanced tool_choice : Four Control Granularities

OpenAI offers four tool_choice values: "auto" (default): LLM decides whether to call tools and how many. "none": force no tool calls (pure chat). "required": force at least one tool call. {"type": "function", "name": "get_weather"}: force a specific tool.

Practical advice: 90% of scenarios use "auto"; the others are advanced controls for beginners to avoid.

Parallel Function Calling: Letting the Agent Call Multiple Tools Simultaneously

OpenAI supports parallel tool calls by default. For "What's the weather in Beijing and Shanghai?", the LLM may emit two get_weather calls in one response. The agent code must loop over all tool_calls (as shown in the 60-line implementation). To disable parallelism, set parallel_tool_calls=False — necessary when later calls depend on earlier results (e.g., check weather then email).

Comparison with L3 smolagents

Code: 30 lines (smolagents) vs 60 lines (hand-written).

Flexibility: medium (framework constraints) vs high (full control).

Safety: sandbox execution vs manual error handling.

Use case: rapid prototyping vs production + interviews.

Learning value: tool feel vs deep principle understanding.

Conclusion: production environments should use smolagents/LangGraph (don't reinvent the wheel), but understanding the internals lets you debug framework failures quickly — a hallmark of senior engineers.

Hands-On: 30-Minute Refactoring Exercises

Add a tool-call counter to track cumulative invocations per run.

Add a max-retry mechanism (e.g., 3 retries) when a tool fails (e.g., simulated send_email failure). The reference solution places retry logic inside the tool-execution layer, keeping the main loop clean and retry logic independently testable.

Add a logger that appends all messages to agent_log.json for post-hoc decision tracing.

Summary of Key Points

@tool

decorator essence: inspect reads signature + inspect.getdoc reads docstring → assemble JSON schema.

Function Calling protocol: OpenAI standard format {type, function: {name, description, parameters}}.

Docstring = manual: LLM sees only docstring; write it clearly.

Five golden rules: detailed docstrings / semantic param names / ≤20 tools / single responsibility / recoverable errors. tool_choice: auto default / none disable / required force / object specify tool.

Parallel calls: default enabled; disable with parallel_tool_calls=False for dependent steps.

Next: L5 Building Stateful Agents with LangGraph

The next lesson introduces LangGraph, covering why state graphs are the best abstraction for complex agents, mastering Node/Edge/State core concepts, and using Checkpoints for long-term memory across conversations. References include Microsoft AI Agents for Beginners, Hugging Face Agents Course, OpenAI Function Calling docs, and LangGraph Building ReAct Agents.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

PythonAI AgentsFunction CallingTool UseOpenAI APILLM Tool IntegrationAgent Design PatternsParallel Function Calling
AI Digital Ideal
Written by

AI Digital Ideal

Express ideas with code, expand imagination with AI.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.