#langchain#tool-calling#function-calling#langchain-core#duckduckgo#wikipedia#pubmed#tavily#qwen3#ollama#python

Tool Calling and Function Calling with LangChain

Define custom tools, bind them to an LLM, call multiple tools in parallel, and generate grounded final answers using LangChain's tool-calling API with Ollama.

Jun 4, 2026 at 10:30 AM9 min readFollowFollow (Hindi)

Topics You Will Master

Understanding tool calling (function calling) and why it is a prerequisite for agents
Creating custom tools with the @tool decorator and writing correct docstrings for LLM dispatch
Inspecting tool metadata: name, description, args, args_schema
Binding tools to an LLM with bind_tools() and reading .tool_calls from the response
Calling multiple tools in a single LLM response (parallel tool calling)
Using built-in LangChain tools: DuckDuckGoSearchRun, TavilySearchResults, WikipediaQueryRun, PubmedQueryRun
Wrapping built-in tools as custom @tool functions for unified dispatch
Executing a multi-step tool calling loop: query → parse tool calls → execute → build message history → generate final answer
Best For

Developers who want to understand how LLM agents work internally — specifically how an LLM decides which function to call and how the result flows back into the conversation to produce a final answer.

Expected Outcome

A complete tool calling pipeline that dispatches to Wikipedia, PubMed, or a math multiply function based on the query, executes the selected tool, and produces a grounded final response from the accumulated message history.

Tool calling (also called function calling) is the mechanism that makes agents possible. Instead of generating a plain text answer, an LLM can emit a structured tool call — specifying which function to run and with what arguments. Your code executes the function, appends the result to the conversation, and the LLM synthesizes a final answer grounded in the tool's output.

Not all LLMs support tool calling. Models that do implement a .bind_tools() method in LangChain that registers tool schemas with the model. When the model receives a query, it decides whether to call a tool or answer directly. LangChain v1 uses this pattern as the foundation of its create_agent abstraction covered in the next lesson.

Prerequisites: langchain-core, langchain-community, langchain-ollama, ddgs, wikipedia, xmltodict, python-dotenv installed. Ollama running with qwen3. A Tavily API key in .env (optional — shown as an example even when unauthorized).

LangChain & Ollama — Local AI Development

Build production-ready LLM apps entirely on your own hardware. No API keys, no cloud costs.

Enroll on Udemy →

Setup

PYTHON
from dotenv import load_dotenv
load_dotenv()
OUTPUT
True
PYTHON
from langchain_ollama import ChatOllama
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_core.prompts import ChatPromptTemplate

llm = ChatOllama(model='qwen3', base_url='http://localhost:11434')
llm.invoke('hi')
PYTHON
AIMessage(content='Hello! How can I assist you today? 😊', ...)

Custom Tools

Defining Tools with @tool

The @tool decorator from langchain_core.tools transforms a regular Python function into a LangChain StructuredTool. The docstring is critical — the LLM reads it to understand when and how to call the tool. The Args: section in the docstring tells the model the name and type of each parameter:

PYTHON
from langchain_core.tools import tool

@tool
def add(a, b):
    """
    Add two integer numbers together

    Args:
    a: First Integer
    b: Second Integer
    """
    return a + b

@tool
def multiply(a, b):
    """
    Multiply two integer numbers together

    Args:
    a: First Integer
    b: Second Integer
    """
    return a * b

Inspecting Tool Metadata

Every @tool function exposes its schema automatically. The LLM uses this schema when deciding whether to call the tool:

PLAINTEXT
add.name, add.description, add.args, add.args_schema.schema()
PLAINTEXT
('add', 'Add two integer numbers together\n\nArgs:\na: First Integer\nb: Second Integer', {'a': {'title': 'A'}, 'b': {'title': 'B'}}, {'description': 'Add two integer numbers together\n\nArgs:\na: First Integer\nb: Second Integer', 'properties': {'a': {'title': 'A'}, 'b': {'title': 'B'}}, 'required': ['a', 'b'], 'title': 'add', 'type': 'object'})

Invoking Tools Directly

Tools can be called directly using .invoke() with a dict of arguments — useful for testing:

PYTHON
add.invoke({'a': 1, 'b': 2})
OUTPUT
3
PYTHON
multiply.invoke({'a': 67, 'b': 2})
OUTPUT
134

Binding Tools to an LLM

bind_tools() registers the tool schemas with the model. The model will then emit structured tool call objects instead of plain text when it decides a tool is needed:

PYTHON
tools = [add, multiply]
llm_with_tools = llm.bind_tools(tools)

Single Tool Call

PYTHON
question = "what is 1 plus 2?"
llm_with_tools.invoke(question).tool_calls
JSON
[
  {
    "name": "add",
    "args": {"a": 1, "b": 2},
    "id": "849b452a-bc95-483d-ab8f-64731dfc27b5",
    "type": "tool_call"
  }
]

The model correctly identified the add tool and extracted the arguments a=1, b=2 from the natural language query.

PYTHON
question = "what is 1 multiplied by 2?"
llm_with_tools.invoke(question).tool_calls
JSON
[
  {
    "name": "multiply",
    "args": {"a": 1, "b": 2},
    "id": "a9b0f000-74c6-4f8f-a4bc-e03af39eb114",
    "type": "tool_call"
  }
]

Parallel Tool Calls

When a query requires multiple operations, the model emits multiple tool calls in a single response:

PYTHON
question = "what is 1 multiplied by 2, also what is 11 plus 22?"
llm_with_tools.invoke(question).tool_calls
JSON
[
  {
    "name": "multiply",
    "args": {"a": 1, "b": 2},
    "id": "e06e4516-6c6e-4e45-af21-ca2ba73700e2",
    "type": "tool_call"
  },
  {
    "name": "add",
    "args": {"a": 11, "b": 22},
    "id": "bbe9dabc-8824-4760-a031-d7a10d1c0b05",
    "type": "tool_call"
  }
]

Both tools are called in a single LLM response — the model plans both operations simultaneously.


Built-in LangChain Tools

LangChain ships with many ready-made tools in langchain-community. Install extras with:

BASH
pip install -qU ddgs wikipedia xmltodict tavily-python

Reference: https://python.langchain.com/docs/integrations/tools/

DuckDuckGoSearchRun is a free, no-API-key web search tool:

PYTHON
from langchain_community.tools import DuckDuckGoSearchRun

search = DuckDuckGoSearchRun()
search.invoke("What is today's stock market news?")
OUTPUT
'tradingwire.com has been visited by 10K+ users in the past month... Find the latest stock market news from every corner of the globe at Reuters.com... Get the latest stock market news and breaking stories from the world\'s best finance and investing websites.'

TavilySearchResults is a paid API that provides structured search results with raw content. Requires a TAVILY_API_KEY in .env:

PYTHON
from langchain_community.tools import TavilySearchResults

search = TavilySearchResults(
    max_results=5,
    search_depth="advanced",
    include_answer=True,
    include_raw_content=True,
)

Without a valid API key, the call returns a 401 Unauthorized error. Set TAVILY_API_KEY in your .env file to use it.

WikipediaQueryRun queries Wikipedia for free without an API key:

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

wikipedia = WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper())

question = "What is LLM?"
print(wikipedia.invoke(question))
OUTPUT
Page: Large language model
Summary: A large language model (LLM) is a language model trained with self-supervised machine learning on a vast amount of text, designed for natural language processing tasks, especially language generation. The largest and most capable LLMs are generative pre-trained transformers (GPTs) and provide the core capabilities of chatbots such as ChatGPT, Gemini and Claude...

Page: Retrieval-augmented generation
Summary: Retrieval-augmented generation (RAG) is a technique that enables large language models (LLMs) to retrieve and incorporate new information...

PubmedQueryRun queries PubMed's database of 35+ million biomedical citations:

PYTHON
from langchain_community.tools.pubmed.tool import PubmedQueryRun

search = PubmedQueryRun()
print(search.invoke("What is the latest research on COVID-19?"))
OUTPUT
No good PubMed Result was found

Note

PubMed results depend on network availability and query specificity. Specific disease or drug names yield better results than broad questions.


Wrapping Built-in Tools as Custom Tools

To use built-in tools in a unified tool dispatch loop, wrap them as @tool functions. This gives them consistent name, description, and args schemas that the LLM can reason about:

PYTHON
@tool
def wikipedia_search(query):
    """
    Search wikipedia for general information.

    Args:
    query: The search query
    """
    wikipedia = WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper())
    response = wikipedia.invoke(query)
    return response

@tool
def pubmed_search(query):
    """
    Search pubmed for medical and life sciences queries.

    Args:
    query: The search query
    """
    search = PubmedQueryRun()
    response = search.invoke(query)
    return response

@tool
def tavily_search(query):
    """
    Search the web for realtime and latest information.
    for examples, news, stock market, weather updates etc.

    Args:
    query: The search query
    """
    search = TavilySearchResults(
        max_results=5,
        search_depth="advanced",
        include_answer=True,
        include_raw_content=True,
    )
    response = search.invoke(query)
    return response

@tool
def multiply(a: int, b: int) -> int:
    """
    Multiply two integer numbers together

    Args:
    a: First Integer
    b: Second Integer
    """
    return int(a) * int(b)

Build a lookup dict for dispatch by tool name:

PYTHON
tools = [wikipedia_search, pubmed_search, tavily_search, multiply]
list_of_tools = {tool.name: tool for tool in tools}
PYTHON
list_of_tools
OUTPUT
{'wikipedia_search': StructuredTool(name='wikipedia_search', description='Search wikipedia for general information...'), 'pubmed_search': StructuredTool(name='pubmed_search', description='Search pubmed for medical and life sciences queries...'), 'tavily_search': StructuredTool(name='tavily_search', description='Search the web for realtime and latest information...'), 'multiply': StructuredTool(name='multiply', description='Multiply two integer numbers together...')}

Bind all four tools to the LLM:

PYTHON
llm_with_tools = llm.bind_tools(tools)

Test tool selection:

PYTHON
query = "what is 2 * 3?"
response = llm_with_tools.invoke(query)
print(response.tool_calls)
JSON
[
  {
    "name": "multiply",
    "args": {"a": 2, "b": 3},
    "id": "d8204180-be87-45ac-a52f-4727b4c7c1b4",
    "type": "tool_call"
  }
]

Generating a Final Answer with Tool Calling

The full tool calling loop builds a message history, executes tools, and produces a final grounded answer. The message list grows as: [HumanMessage] → [AIMessage with tool_calls] → [ToolMessage] → [AIMessage final answer].

PYTHON
from langchain_core.messages import HumanMessage, AIMessage

query = "What is medicine for lung cancer?"

# Step 1: Send query to LLM with tools
messages = [HumanMessage(query)]
ai_msg = llm_with_tools.invoke(messages)
messages.append(ai_msg)

The ai_msg contains the tool call the model decided to make. Inspect it:

PYTHON
for tool_call in ai_msg.tool_calls:
    print(tool_call)
JSON
{
  "name": "pubmed_search",
  "args": {"query": "medicine for lung cancer"},
  "id": "890f5649-891a-4b16-ba96-f0bbd2e21901",
  "type": "tool_call"
}

The model correctly chose pubmed_search for a medical query.

PYTHON
# Step 2: Execute each tool call and append ToolMessages
for tool_call in ai_msg.tool_calls:
    name = tool_call['name'].lower()
    selected_tool = list_of_tools[name]
    tool_msg = selected_tool.invoke(tool_call)
    messages.append(tool_msg)

The full message history now contains the human query, the AI's tool call decision, and the tool's result:

PLAINTEXT
messages
PYTHON
[HumanMessage(content='What is medicine for lung cancer?', ...),
 AIMessage(content='', ..., tool_calls=[{'name': 'pubmed_search', 'args': {'query': 'medicine for lung cancer'}, ...}], ...),
 ToolMessage(content="Published: 2025-10-21\nTitle: Personalized Surgical Decision-Making Model for Clinical Stage IA Pure-Solid Non-small Cell Lung Cancer...\nBACKGROUND: Making an optimal surgical procedure decision... remains challenging for some early-stage NSCLC...\nRESULTS: After matching, 369 patients...", name='pubmed_search', tool_call_id='890f5649-891a-4b16-ba96-f0bbd2e21901')]
PYTHON
# Step 3: Send full message history back to LLM for final synthesis
response = llm_with_tools.invoke(messages)
print(response.content)
OUTPUT
The article focuses on surgical decision-making for early-stage non-small cell lung cancer (NSCLC):

### Key Points from the Study:
1. **Surgical Options Compared**: Lobar resection vs. sublobar resection
2. **Personalized Model**: A predictive model was developed to help decide which surgery is better using clinical and radiomic data.
3. **Outcomes**:
   - **Positive-score group**: Lobar resection was better (lower risk of recurrence).
   - **Negative-score group**: Sublobar resection was better (improved recurrence-free survival).

### Regarding "Medicine" for Lung Cancer:
Common treatments include:
- **Chemotherapy** (e.g., platinum-based regimens)
- **Targeted therapies** (e.g., EGFR inhibitors for specific mutations)
- **Immunotherapy** (e.g., PD-1/PD-L1 inhibitors like pembrolizumab)
- **Radiation therapy** (for localized treatment)

The final response synthesizes information from the PubMed result and extends it with the LLM's general medical knowledge about lung cancer treatments.


Tool Calling vs. Direct LLM Response

Scenario LLM Behaviour
Query matches a tool's docstring Emits a tool_call with structured args
Query requires multiple tools Emits multiple tool_calls in one response
Query has no matching tool Answers directly from training knowledge
Medical query + pubmed_search tool Routes to PubMed even if general knowledge exists
General query + wikipedia_search tool Routes to Wikipedia for factual grounding

What You Built

In this lesson you built the complete tool calling foundation that powers LangChain agents:

  • Custom tools@tool decorator turns Python functions into LLM-dispatchable tools with typed schemas
  • Built-in toolsDuckDuckGoSearchRun, WikipediaQueryRun, PubmedQueryRun (free); TavilySearchResults (API key)
  • Tool bindingllm.bind_tools(tools) registers all schemas with the model
  • Parallel calling — a single LLM response can trigger multiple tool calls simultaneously
  • Message loop — human message → AI tool call decision → tool execution → tool message → final synthesis

In the next lesson this entire loop is automated by create_agent from LangChain's agents module.

Find this tutorial useful?

Subscribe to our YouTube channels for more practical production walk-throughs.

Discussion & Comments