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:

ActorResponsibility
LLM (Gemini)Decides whether a tool is needed and supplies arguments
Your Python applicationExecutes the requested tool
Tool/functionPerforms 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

Popular posts from this blog

Aggregate function with spring data

Java Persistence API with Spring Data

Thread , Runnable and ExecutorService