DS DevShelfHub Projects · AI tools
Articles / Building AI Agents for Production — Day 2: Tool Calling, Built-In Tools, and Custom Tools

AI Engineering

Building AI Agents Day 2: Tool Calling, Custom Tools, and LangChain

By DevShelfHub

Day 2 of the AI Agents crash course — wiring real tool calling into an LLM with LangChain. Built-in tools (Wikipedia, Tavily, DuckDuckGo, YouTube), three ways to define a custom tool, why descriptions and type hints decide everything, and a live Yahoo Finance example.

Building AI Agents Day 2: Tool Calling, Custom Tools, and LangChain

Introduction

Day 2 of the “Building AI Agents for Production” crash course is the day the LLM stops being a chatbot and starts being an agent. The conceptual map and the development environment from Day 1 are in place. Day 2 fills in the single most important primitive on top of them: tool calling.

The flow is deliberate — first the practical, then the theory, then more practical. Built-in LangChain tools come first (Wikipedia, Tavily, DuckDuckGo, YouTube search) so you can feel what tool calling does. Then the precise definition of a tool, why it exists, and the three ways to build a custom one. By the end, your notebook can reach out to the live internet, query a finance API, and decide for itself which tool to call.

📌 Part of a 4-day crash course. Day 1 framed the mental model and the dev environment. Day 2 (this article) covers tool calling. Day 3 turns the notebook into a production-shaped Python project. Day 4 ships it.

📚 Table of contents

  • Why tool calling is the heart of an agent
  • A first taste: the Wikipedia tool
  • The precise definition of a tool
  • The built-in tools tour: Tavily, DuckDuckGo, YouTube
  • Building your first custom tool with @tool
  • The three ways to define a custom tool
  • Why descriptions and type hints matter
  • A real-time example: finance data via Yahoo Finance
  • How the LLM decides which tool to use
  • Best practices for tool design
  • Common mistakes to avoid
  • Conclusion
  • Frequently asked questions

🪛 Why tool calling is the heart of an agent

An LLM by itself can only answer from its training data. Ask it “what’s the weather in Bangalore right now?” and you get a confident hallucination. The training cut-off doesn’t include this morning’s temperature.

Tools fix this by giving the LLM access to services that know things the model doesn’t. The agent loop looks like this:

🔁 With tools, the LLM can branch

  1. Input comes in.
  2. If the LLM can answer directly — small talk, definitions — it does.
  3. If it needs fresh data, it picks a tool: Wikipedia, an open weather API, a SQL query, your CRM.
  4. The tool returns a result. The LLM reads it and decides whether to call another tool or write the final answer.

Take this away and an agent is just a fancy prompt. Layer it in and the LLM becomes a decision-making system that can act on real state.

📖 A first taste: the Wikipedia tool

LangChain ships dozens of built-in tools. The Wikipedia one is the friendliest to start with — no API key, no rate-limit headaches, and the output is structured prose the LLM can summarize cleanly.

Python
from langchain_community.tools import WikipediaQueryRun
from langchain_community.utilities import WikipediaAPIWrapper

api_wrapper = WikipediaAPIWrapper(top_k_results=5, doc_content_chars_max=500)
wiki_tool = WikipediaQueryRun(api_wrapper=api_wrapper)

result = wiki_tool.run({"query": "Generative AI"})
print(result)

The two knobs that matter are top_k_results (how many pages to consult) and doc_content_chars_max (how much of each page to pull). Keep both small until you know your context budget. Wikipedia is famously verbose, and a careless tool will burn your token budget on filler.

🧩 The precise definition of a tool

A tool, in the agentic sense, is any service from which the LLM can collect context. That definition is intentionally wide, because the universe of things that qualify is wide:

🌐 External services

  • Search APIs — Tavily, DuckDuckGo, Bing, Google Serper
  • Wikipedia, YouTube, ArXiv
  • SaaS APIs — Gmail, Slack, Notion, GitHub
  • Weather, finance, news, maps

🏠 Your own stuff

  • Custom Python functions
  • Internal REST APIs
  • SQL queries against your DB
  • Vector store retrievers
  • A SharePoint client, a CRM lookup, a payment processor

The mental model: an agent is an LLM plus tools plus memory plus planning. Strip out tools and you’ve crippled it. The MCP protocol — covered later in the course — is just a standard way to expose those tools over HTTP so anyone’s agent can use them.

🧰 The built-in tools tour

Before you write a custom tool, see what’s already on the shelf. Four are worth knowing on Day 2.

🔎 Tavily search

Tavily is purpose-built for LLM agents — it returns clean title, URL, and content triples, which is much easier for an LLM to summarize than raw HTML. Free tier covers enough calls to learn on.

Python
import os
from langchain_community.tools.tavily_search import TavilySearchResults

os.environ["TAVILY_API_KEY"] = os.getenv("TAVILY_API_KEY")
tavily = TavilySearchResults()

results = tavily.invoke("How is the job market for fresh AI graduates in 2026?")
for r in results:
    print(r["title"], "—", r["url"])

🦆 DuckDuckGo search

DuckDuckGo doesn’t need an API key, which makes it the right default for quick experiments. The output is rougher than Tavily’s, but the price is right.

Python
from langchain_community.tools import DuckDuckGoSearchRun

ddg = DuckDuckGoSearchRun()
print(ddg.invoke("Latest update on the iPhone 17 release"))

📺 YouTube search

Useful when you want the agent to recommend videos or pull metadata about a creator’s catalog.

Python
from langchain_community.tools import YouTubeSearchTool

yt = YouTubeSearchTool()
print(yt.name)
print(yt.description)
print(yt.run("Sunny Savita"))

🧭 Where to find the full list

The LangChain docs index of integrations changes layout often, so don’t bookmark a sub-page — bookmark the integrations root and search from there for “tools”. Expect well over 500 built-in tools across search, SaaS APIs, databases, file systems, and cloud providers. Most of what you need on day one is already wrapped.

🛠️ Building your first custom tool with @tool

A plain Python function is not a tool. It can’t be invoked through LangChain’s tool interface, the agent can’t reason about its name or schema, and the LLM can’t decide when to call it. Turning a function into a tool is one decorator and one docstring away.

Python
from langchain.tools import tool

@tool
def multiply(a: int, b: int) -> int:
    """Multiply two integers and return the product."""
    return a * b

# Now multiply behaves like a tool, not a function:
multiply.name           # 'multiply'
multiply.description    # 'Multiply two integers and return the product.'
multiply.args           # the JSON schema for inputs
multiply.invoke({"a": 10, "b": 20})   # 200

Two non-obvious requirements:

  • A docstring is mandatory. The LLM reads the docstring to decide whether this tool fits the current task. No docstring → LangChain raises an error → even if it didn’t, the LLM would be guessing.
  • Type hints are mandatory. They generate the input schema the LLM uses to format its call. Without them, the agent can’t reliably pass arguments.

🧱 The three ways to define a custom tool

@tool decorator

The fastest path. A function plus a docstring plus a decorator. Use this 90% of the time. Type hints become the schema; docstring becomes the description.

StructuredTool

Build the tool from an existing function plus an explicit Pydantic schema. Best when the function lives elsewhere (third-party lib, existing service) and you want strict input validation.

BaseTool subclass

The most flexible. Subclass BaseTool, override _run and optionally _arun. Use this when the tool needs custom lifecycle hooks, async paths, or stateful behaviour.

Python
# StructuredTool example
from langchain.tools import StructuredTool
from pydantic import BaseModel, Field

class MultiplyInput(BaseModel):
    a: int = Field(description="First operand")
    b: int = Field(description="Second operand")

def _multiply(a: int, b: int) -> int:
    return a * b

multiply_tool = StructuredTool.from_function(
    func=_multiply,
    name="multiply",
    description="Multiply two integers and return the product.",
    args_schema=MultiplyInput,
)

📝 Why descriptions and type hints matter

The LLM never sees your tool’s implementation. It only sees the name, the docstring, and the input schema. That metadata is the entire interface.

⚠️ A bad description hides the tool

A tool named lookup with a docstring “does the lookup” is functionally invisible to the LLM. It can’t tell when to call it, what to pass in, or what comes back. The agent will silently route around it. A precise name (customer_order_lookup), a precise docstring (“Fetch the most recent order for a customer by email. Returns order_id, status, and total_usd. Use when the user asks about their order status.”), and tight types are what make a tool reliably callable.

💹 A real-time example: finance data via Yahoo Finance

To close the loop, wrap a real third-party package as a tool. yfinance pulls live equity quotes from Yahoo, and it’s a clean example of why type hints + docstrings carry the whole interface.

Python
import yfinance as yf
from langchain.tools import tool

@tool
def get_stock_price(ticker: str) -> dict:
    """
    Get the latest market price and key metadata for a stock ticker.
    Use when the user asks for a current share price, market cap, or
    52-week range. Returns symbol, currentPrice, marketCap, currency.
    """
    info = yf.Ticker(ticker).info
    return {
        "symbol": info.get("symbol"),
        "currentPrice": info.get("currentPrice"),
        "marketCap": info.get("marketCap"),
        "currency": info.get("currency"),
    }

print(get_stock_price.invoke({"ticker": "AAPL"}))

Notice how the docstring tells the LLM when to call this tool, not just what it does. “Use when the user asks for a current share price…” is the line that drives correct tool selection inside the agent loop.

🔗 How the LLM decides which tool to use

Defining a tool isn’t enough — you have to bind it to the LLM so the model knows it’s available. With LangChain’s chat models this is one call:

Python
from langchain_openai import ChatOpenAI

chat_llm = ChatOpenAI(model="gpt-4o-mini")
llm_with_tools = chat_llm.bind_tools([multiply, get_stock_price, wiki_tool])

response = llm_with_tools.invoke("What's the current Apple share price?")
# response.tool_calls now lists the tool the LLM chose plus the args
print(response.tool_calls)

Under the hood, the model gets a JSON description of every bound tool. It picks one (or several) by reading the descriptions and matching them against the user’s intent. Then your code is responsible for actually calling the chosen tool with the chosen args and feeding the result back to the LLM for the next turn — that’s exactly the Think → Act → Observe loop from Day 1, made concrete.

🏗️ Best practices for tool design

✅ Do this

  • Write the docstring like a tool-selection prompt — include when to use it
  • Keep return values small and structured — dicts beat free-form strings
  • Validate inputs with type hints or a Pydantic schema
  • Cap tool latency and add a sensible timeout — a hung tool stalls the whole agent
  • Log every tool call: name, args, latency, error — you’ll need it for debugging on Day 4

❌ Avoid this

  • One mega-tool that does ten unrelated things — the LLM will pick it for everything
  • Returning huge HTML pages or PDF dumps — you’ll blow the context window
  • Hiding side effects (sending email, writing DB rows) behind innocent-looking names
  • Skipping the docstring or the type hints — the tool is effectively invisible
  • Exposing the same secret in every tool — centralize key loading once

🚫 Common mistakes to avoid

  • Confusing built-in tools with custom ones. Built-ins are fast to wire up but rarely give you exactly the shape your business needs. Wrap your own logic in a custom tool whenever the data lives in your systems.
  • Forgetting to bind the tool. Defining @tool functions does nothing on its own — the LLM only knows about tools you explicitly pass to bind_tools.
  • Treating every API as one tool. A REST endpoint with five params and three modes is usually three small tools, not one. Smaller tools are easier for the LLM to pick correctly.
  • Ignoring failure modes. Tools fail — rate limits, timeouts, 500s. Return a structured error from the tool so the agent can decide to retry, try another tool, or surface the error to the user. Letting the exception bubble crashes the loop.
  • Hardcoding API keys inside the tool. Load secrets once at startup, share them through environment variables or a secrets manager. Day 4 production deployment will not forgive baked-in keys.

Conclusion

By the end of Day 2 the notebook has a working Wikipedia tool, a Tavily search, a YouTube lookup, two custom tools, and a live finance tool — all bound to a chat model that knows when to call each one. The agent loop is no longer theoretical. Day 3 takes this notebook and rebuilds it as a real Python project: a logger, a config layer, exception classes, and a provider-agnostic model loader. Tool calling stays, but the scaffolding gets serious.

Related reading: Day 1: AI agent foundations and LangChain setupMCP explained: build your own serverLangChain review

Building AI Agents for Production — Day 2: Tool Calling, Built-In Tools, and Custom Tools FAQ

When should I use a built-in tool vs writing my own?

Use a built-in whenever the service is generic—web search, Wikipedia, a public API LangChain already wraps. Write a custom tool the moment the data lives in your systems (your DB, your CRM, your internal APIs).

Do all tools require an API key?

No. Wikipedia, DuckDuckGo, and most local-function tools need no key. Tavily, Bing, Serper, and most SaaS-API tools need a key. Always check the docs before you wire one in.

How does the LLM actually pick a tool?

When you call bind_tools, LangChain serialises each tool's name, description, and input schema into the function-calling format the chat model understands. The model receives that list with every prompt and decides which tool to call based on the task.

Can a tool call another tool?

Not directly. Tools should be small and side-effect-aware. If you want chained behaviour, that's the agent's job—the LLM reads the first tool's output and decides to call the second tool.

How does MCP fit into this?

MCP (Model Context Protocol) is a standardised way to expose your tools over HTTP, so a different agent—possibly written in a different framework—can discover and call them. The mental model of a tool is identical; MCP just standardises the transport.

How many tools should an agent have?

Few enough that the LLM can keep them straight. For most production agents that's between 3 and 15 tools. Past 20, selection quality drops and you should refactor—either group tools behind a sub-agent or use on-demand tool discovery.