This example builds a ReAct-style agent equipped with two weather tools — a current-conditions tool and a forecast tool — to demonstrate something a single-tool agent can’t: real tool selection.
With only one tool available, an agent never actually has to choose anything — it just calls the tool it has. Here, the agent is given two tools with different purposes (“what’s the weather right now” vs. “what will it be like tomorrow”), and has to figure out from the wording of a question which one actually applies. Ask it about going outside right now, and it checks current conditions. Ask whether you’ll need an umbrella tomorrow, and it switches to the forecast tool instead — all without any hardcoded routing logic telling it which to use.
The key idea worth taking away: this routing decision comes entirely from well-written tool docstrings, not from custom logic in the code. That’s the reasoning step that separates a true agent from a simple API wrapper — and it’s a good example of the kind of decision-making agents are trusted to make on their own.
Two-Tool Weather Agent: Current Conditions vs. Forecast¶
Goal: We want to answer two different kinds of questions:
- “I am living in New Delhi. I am planning to go outside now. Do I need cold or warm clothes?” -> needs current weather
- “Should I carry an umbrella in New Delhi tomorrow?” -> needs a forecast, not current conditions
0. Setup¶
# Uncomment to install
# !pip install -q "langchain>=1.0" langchain-openai requests
import importlib.metadata
# List the distribution package names
packages = ["langchain", "langchain-community", "langchain-openai", ]
for package in packages:
try:
version = importlib.metadata.version(package)
print(f"{package} version: {version}")
except importlib.metadata.PackageNotFoundError:
print(f"{package} is not installed in this environment.")
langchain version: 1.3.6 langchain-community version: 0.4.2 langchain-openai version: 1.2.2
import getpass
import os
if "OPENAI_API_KEY" not in os.environ:
os.environ["OPENAI_API_KEY"] = getpass.getpass("Enter your OpenAI API key: ")
1) Tool 1 — Current Weather¶
Same tool as before: current temperature, “feels like,” and conditions for a city.
import requests
from langchain_core.tools import tool
@tool
def get_weather(city: str) -> str:
"""
Get the CURRENT weather right now for a given city.
Use this for questions about what to wear or do right now / today.
Input should be a city name, e.g. 'New Delhi'.
"""
url = f"https://wttr.in/{city}?format=j1"
response = requests.get(url, timeout=10)
response.raise_for_status()
data = response.json()
current = data["current_condition"][0]
temp_c = current["temp_C"]
feels_like_c = current["FeelsLikeC"]
description = current["weatherDesc"][0]["value"]
return (
f"Current weather in {city}: {temp_c}°C, "
f"feels like {feels_like_c}°C, conditions: {description}."
)
# Quick manual tests of tool
print(get_weather.invoke("New Delhi"))
Current weather in New Delhi: 25°C, feels like 29°C, conditions: Mist.
2) Tool 2 — Weather Forecast (new)¶
This tool answers a different kind of question: not “what’s it like right now” but
“what will it be like in N days.” response already includes a 3-day forecast
in its weather array, so we reuse the same API — just a different part of the response.
Note the docstring: it explicitly says this is for future days, so the model has a clear
signal on when to prefer this tool over get_weather.
@tool
def get_forecast(city: str, days_ahead: int = 1) -> str:
"""
Get the weather FORECAST for a given city, for a future day.
Use this for questions about tomorrow, this weekend, or any future day —
NOT for questions about right now (use get_weather for that).
Args:
city: city name, e.g. 'New Delhi'.
days_ahead: how many days from today (0 = today, 1 = tomorrow, 2 = day after). Max 2.
"""
days_ahead = max(0, min(days_ahead, 2)) # wttr.in's free tier gives today + 2 days
url = f"https://wttr.in/{city}?format=j1"
response = requests.get(url, timeout=10)
response.raise_for_status()
data = response.json()
day = data["weather"][days_ahead]
date = day["date"]
max_temp = day["maxtempC"]
min_temp = day["mintempC"]
# Average the hourly chance-of-rain readings for a simple daily estimate
rain_chances = [int(hour["chanceofrain"]) for hour in day["hourly"]]
avg_rain_chance = sum(rain_chances) // len(rain_chances)
return (
f"Forecast for {city} on {date} ({days_ahead} day(s) ahead): "
f"high {max_temp}°C, low {min_temp}°C, "
f"average chance of rain: {avg_rain_chance}%."
)
# Quick manual tests of 2nd tool
print(get_forecast.invoke({"city": "New Delhi", "days_ahead": 1}))
Forecast for New Delhi on 2026-07-10 (1 day(s) ahead): high 35°C, low 26°C, average chance of rain: 20%.
3) Build the Agent with Both Tools¶
Now the agent has two tools with clearly different purposes. The system prompt doesn’t need to tell it which one to use for which question — that’s exactly the reasoning we want the model to do on its own, guided only by each tool’s docstring.
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
tools = [get_weather, get_forecast]
SYSTEM_PROMPT = (
"You are a helpful weather assistant. Always check real weather data before "
"answering questions about clothing, umbrellas, or outdoor plans. Choose "
"between the current-weather tool and the forecast tool based on whether "
"the question is about right now or a future day. Clearly explain your "
"recommendation using the actual data you observed."
)
agent = create_agent(
model=llm,
tools=tools,
system_prompt=SYSTEM_PROMPT # OPTIONAL: Agent can figure the tools to use based on docstring
)
# Question about today
prompt = "I am living in New Delhi. I am planning to go outside right now. \
Do I need cold clothes or warm clothes?"
result = agent.invoke({"messages": [{"role": "user", "content": prompt}]})
print(result['messages'][-1].content)
The current temperature in New Delhi is 26°C, but it feels like 30°C due to mist. Given this weather, I recommend wearing light and comfortable clothing, as it is relatively warm. You may not need heavy or cold clothes. Enjoy your time outside!
# Question about tomorrow
prompt = "I am living in New Delhi. Should I carry an umbrella tomorrow ? "
result = agent.invoke({"messages": [{"role": "user", "content": prompt}]})
print(result['messages'][-1].content)
Tomorrow in New Delhi, the forecast indicates a high of 33°C and a low of 26°C, with an average chance of rain at 41%. Given this possibility of rain, it would be wise to carry an umbrella with you.
# Print the entire result: This print the detailed JSON response
result
{'messages': [HumanMessage(content='I am living in New Delhi. Should I carry an umbrella tomorrow ? ', additional_kwargs={}, response_metadata={}, id='759e0145-a0e5-4c18-ad5c-75f7f351bf3c'),
AIMessage(content='', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 22, 'prompt_tokens': 268, 'total_tokens': 290, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_provider': 'openai', 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_7b8553d7fb', 'id': 'chatcmpl-DzfwXVqAmdTbR5KeR3nd0sc2NVUzE', 'service_tier': 'default', 'finish_reason': 'tool_calls', 'logprobs': None}, id='lc_run--019f4658-21c0-76a3-bc2c-060b5785cdea-0', tool_calls=[{'name': 'get_forecast', 'args': {'city': 'New Delhi', 'days_ahead': 1}, 'id': 'call_5Jm1jhp89eajFN42PAMg65e2', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 268, 'output_tokens': 22, 'total_tokens': 290, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}),
ToolMessage(content='Forecast for New Delhi on 2026-07-10 (1 day(s) ahead): high 33°C, low 26°C, average chance of rain: 41%.', name='get_forecast', id='0fa733ab-5009-4d2a-bdd5-203871ee3e9a', tool_call_id='call_5Jm1jhp89eajFN42PAMg65e2'),
AIMessage(content='Tomorrow in New Delhi, the forecast indicates a high of 33°C and a low of 26°C, with an average chance of rain at 41%. Given this possibility of rain, it would be wise to carry an umbrella with you.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 50, 'prompt_tokens': 336, 'total_tokens': 386, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_provider': 'openai', 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_7b8553d7fb', 'id': 'chatcmpl-Dzfwab5Hk26jErV7zrKPU77uXA4o8', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='lc_run--019f4658-2dc4-7a70-9f24-78d220a664e3-0', tool_calls=[], invalid_tool_calls=[], usage_metadata={'input_tokens': 336, 'output_tokens': 50, 'total_tokens': 386, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}
4) A Helper to Run a Question and Show Which Tool Was Chosen¶
Since we now have two tools, the interesting thing to observe is which tool the agent picks for each question — that’s the reasoning step we’re demonstrating.
def ask(question: str):
result = agent.invoke({"messages": [{"role": "user", "content": question}]})
print(f"QUESTION: {question}\n")
for msg in result["messages"]:
if type(msg).__name__ == "AIMessage" and getattr(msg, "tool_calls", None):
for call in msg.tool_calls:
print(f" -> Agent chose tool: {call['name']} args={call['args']}")
elif type(msg).__name__ == "ToolMessage":
print(f" -> Observation: {msg.content}")
print(f"\nFINAL ANSWER:\n{result['messages'][-1].content}")
print("\n" + "=" * 70 + "\n")
return result
5) Test Both Question Types¶
Watch which tool gets picked for each — this is the agent reasoning about which data it actually needs, not just calling whatever tool exists.
_ = ask(
"I am living in New Delhi. I am planning to go outside right now. "
"Do I need cold clothes or warm clothes?"
)
QUESTION: I am living in New Delhi. I am planning to go outside right now. Do I need cold clothes or warm clothes?
-> Agent chose tool: get_weather args={'city': 'New Delhi'}
-> Observation: Current weather in New Delhi: 26°C, feels like 30°C, conditions: Mist.
FINAL ANSWER:
The current temperature in New Delhi is 26°C, but it feels like 30°C due to mist. Given this weather, I recommend wearing light and comfortable clothing, as it is relatively warm. You might not need heavy or cold clothes right now. Enjoy your time outside!
======================================================================
_ = ask(
"I am living in New Delhi. Should I carry an umbrella tomorrow?"
)
QUESTION: I am living in New Delhi. Should I carry an umbrella tomorrow?
-> Agent chose tool: get_forecast args={'city': 'New Delhi', 'days_ahead': 1}
-> Observation: Forecast for New Delhi on 2026-07-10 (1 day(s) ahead): high 33°C, low 26°C, average chance of rain: 41%.
FINAL ANSWER:
Tomorrow in New Delhi, the forecast indicates a high of 33°C and a low of 26°C, with an average chance of rain at 41%. Given this possibility of rain, it would be wise to carry an umbrella just in case.
======================================================================
# Or Let user ask question: I am living in New Delhi. Should I carry an umbrella tomorrow?
user_prompt = input("\nYou: ")
_ = ask(user_prompt)
QUESTION: I am thinking of wearing jacket tomorrow. Is this a good idea ?
-> Agent chose tool: get_forecast args={'city': 'New Delhi', 'days_ahead': 1}
-> Observation: Forecast for New Delhi on 2026-07-10 (1 day(s) ahead): high 33°C, low 26°C, average chance of rain: 41%.
FINAL ANSWER:
Tomorrow in New Delhi, the forecast indicates a high of 33°C and a low of 26°C, with an average chance of rain at 41%. Given these temperatures, wearing a jacket might be too warm for most of the day, especially during the afternoon. However, if you plan to be out in the evening when it cools down, a light jacket could be a good idea. Just be prepared for the possibility of rain.
======================================================================
Memory¶
- Previously, agent did not possess memory. So, it would not remember previous chats
- Now we give it memory: this way, it remembers previous chat conversations
from langgraph.checkpoint.memory import InMemorySaver
# A checkpointer gives the agent memory: it stores the message history
# per "thread_id", so the same conversation can span multiple .invoke() calls.
checkpointer = InMemorySaver()
agent_with_memory = create_agent(
model=llm,
tools=tools,
system_prompt=SYSTEM_PROMPT,
checkpointer=checkpointer,
)
# Every conversation needs a thread_id — this is how the agent knows
# which conversation's memory to load. Different thread_ids = separate,
# isolated conversations (e.g. different users).
config = {"configurable": {"thread_id": "demo-conversation-1"}}
# Turn 1: mention the city explicitly
chat1 = "I live in New Delhi. Do I need warm or cold clothes right now?"
result1 = agent_with_memory.invoke(
{"messages": [{"role": "user", "content": chat1}]},
config=config,
)
print("Turn 1:", result1["messages"][-1].content)
# Turn 2: a follow-up that does NOT repeat the city.
# Without memory, the agent wouldn't know which city we're asking about.
# With the checkpointer, it recalls "New Delhi" from Turn 1 automatically.
chat2 = "What about tomorrow — should I carry an umbrella?"
result2 = agent_with_memory.invoke(
{"messages": [{"role": "user", "content": chat2}]},
config=config,
)
print("\nTurn 2:", result2["messages"][-1].content)
Turn 1: Right now in New Delhi, the temperature is 26°C, but it feels like 30°C due to mist. This indicates a warm and humid environment. I recommend wearing light and breathable clothing, such as cotton or linen, to stay comfortable. There's no need for warm clothes at this temperature. Turn 2: Tomorrow in New Delhi, the forecast indicates a high of 33°C and a low of 26°C, with an average chance of rain at 41%. Given this possibility of rain, it would be wise to carry an umbrella just in case.
6. What This Demonstrates¶
- Tool selection as reasoning: with two tools available, the agent has to infer from the question’s wording (“right now” vs. “tomorrow”) which tool actually answers it — this is a more convincing demonstration of reasoning than a single-tool agent, where there’s no real choice to make.
- Docstrings are the interface: notice we never wrote “if the question contains the word ‘tomorrow’, use get_forecast.” The tool docstrings alone (mentioning “right now” vs. “future day”) were enough for the model to route correctly. This is worth calling out explicitly — good tool docstrings do a lot of the reasoning work for you.
- Same underlying loop, more branching: it’s still think -> act -> observe -> think -> answer, just with a real decision point now (which tool?) instead of only one option.
Try it yourself¶
- Ask an ambiguous question like “What’s the weather going to be like?” (no “today” or “tomorrow”) and see which tool the agent defaults to — a good discussion prompt on prompt/docstring ambiguity.
- Ask a question needing both tools in one turn, e.g. “Compare today’s weather to tomorrow’s in New Delhi” — you should see two tool calls in the trace.
