AI Learning Notes #01 — How LLM Tool Calling Works
Without
@tool, this is simply a Python function. The important thing is that @tool turns it into a LangChain tool.@tool
def get_stock_price(ticker: str) -> str:
"""Fetches the mock current stock price for a given ticker symbol (e.g., AAPL, GOOGL)."""
mock_prices = {"AAPL": 220.50, "GOOGL": 175.30, "MSFT": 415.00}
price = mock_prices.get(ticker.upper(), 100.00)
return json.dumps({"ticker": ticker.upper(), "price_usd": price})
This is loading environment vairables
# Load environment variables (GOOGLE_API_KEY / GEMINI_API_KEY)
load_dotenv()
After loading the environment variable you can call ChatGoogleGenerativeAI ( model, temperature)
it will by default get the value environment of "GOOGLE_API_KEY" variable. You need not to care too much.
llm = ChatGoogleGenerativeAI(
model="gemini-2.5-flash",
temperature=0.0, # 0.0 is best for accurate tool argument extraction
)
We are giving access of tool list to Gemini. you will not find below line in a sequence in code but first we are creating the tool list then binding that tool list with llm.
tools_list = [calculate_compound_interest, get_stock_price]
llm_with_tools = llm.bind_tools(tools_list)
The user asks a question
If I invest $5,000 at a 7% interest rate for 10 years, how much will I earn?
Pass the user messge to the LLM
# Step A: Pass the user message to the LLM
messages = [HumanMessage(content=user_query)]
ai_response = llm_with_tools.invoke(messages)
messages.append(ai_response)
Now Gemini looks at the question. It recognizes:
Question requires compound-interest calculation → calculate_compound_interest
So instead of necessarily answering directly, Gemini can return something conceptually like:
Tool call:
name = calculate_compound_interest
arguments:
principal = 5000
annual_rate = 7
years = 10
Complete example
import json
from dotenv import load_dotenv
from langchain_core.messages import HumanMessage, ToolMessage
from langchain_core.tools import tool
from langchain_google_genai import ChatGoogleGenerativeAI
# Load environment variables (GOOGLE_API_KEY / GEMINI_API_KEY)
load_dotenv()
# -------------------------------------------------------------
# 1. Define Custom Python Tools
# -------------------------------------------------------------
@tool
def calculate_compound_interest(
principal: float, annual_rate: float, years: int
) -> str:
"""Calculates compound interest for an investment given the principal amount,
annual interest rate (as a percentage, e.g. 5 for 5%), and time in years.
"""
amount = principal * ((1 + (annual_rate / 100)) ** years)
interest = amount - principal
return json.dumps({
"principal": principal,
"interest_earned": round(interest, 2),
"total_amount": round(amount, 2),
"years": years,
})
@tool
def get_stock_price(ticker: str) -> str:
"""Fetches the mock current stock price for a given ticker symbol (e.g., AAPL, GOOGL)."""
mock_prices = {"AAPL": 220.50, "GOOGL": 175.30, "MSFT": 415.00}
price = mock_prices.get(ticker.upper(), 100.00)
return json.dumps({"ticker": ticker.upper(), "price_usd": price})
# Create a mapping of tool names to functions for easy execution
tools_list = [calculate_compound_interest, get_stock_price]
tools_by_name = {t.name: t for t in tools_list}
# -------------------------------------------------------------
# 2. Initialize Gemini and Bind the Tools
# -------------------------------------------------------------
llm = ChatGoogleGenerativeAI(
model="gemini-2.5-flash",
temperature=0.0, # 0.0 is best for accurate tool argument extraction
)
# Attach tools to the LLM
llm_with_tools = llm.bind_tools(tools_list)
# -------------------------------------------------------------
# 3. Execution Flow: User Query -> Tool Call -> Tool Execution -> Final Response
# -------------------------------------------------------------
user_query = "If I invest $5,000 at a 7% interest rate for 10 years, how much will I earn?"
print(f"User Query: {user_query}\n")
# Step A: Pass the user message to the LLM
messages = [HumanMessage(content=user_query)]
ai_response = llm_with_tools.invoke(messages)
messages.append(ai_response)
# Step B: Check if Gemini decided to invoke a tool
if ai_response.tool_calls:
for tool_call in ai_response.tool_calls:
tool_name = tool_call["name"]
tool_args = tool_call["args"]
tool_id = tool_call["id"]
print(f"[LLM Decision] Calling tool '{tool_name}' with args: {tool_args}")
# Step C: Execute the actual Python function
selected_tool = tools_by_name[tool_name]
tool_result = selected_tool.invoke(tool_args)
print(f"[Tool Output] {tool_result}\n")
# Step D: Append the tool execution result back to the message history
messages.append(ToolMessage(content=str(tool_result), tool_call_id=tool_id))
# Step E: Let Gemini generate the final natural language answer using the tool result
final_response = llm_with_tools.invoke(messages)
print(f"Final Answer:\n{final_response.content}")
else:
# If no tool was needed, just print the direct answer
print(f"Direct Response:\n{ai_response.content}")
There are three different actors here:
| Actor | Responsibility |
|---|---|
| LLM (Gemini) | Decides whether a tool is needed and supplies arguments |
| Your Python application | Executes the requested tool |
| Tool/function | Performs the actual operation and returns data |
This is the foundation upon which agents, LangChain, and eventually LangGraph are built.
And honestly, I would spend a little time making this flow completely clear before moving on. Once you understand LLM → tool call → application → tool result → LLM, LangGraph becomes much easier to understand rather than looking like another mysterious framework.

Comments
Post a Comment