Building AI Agents with LLMs
Artificial Intelligence has evolved beyond simple chatbots. Today, we can build AI agents — intelligent systems that can reason, plan, and take actions in the real world. These agents combine the natural language understanding of Large Language Models (LLMs) with the ability to call functions, query APIs, and interact with external systems.
In this tutorial, we’ll build a practical AI agent that monitors global disaster events using the GDACS (Global Disaster Alert and Coordination System) RSS feed. By the end, you’ll understand how to:
- Create AI agents using LangChain
- Extend LLMs with custom tools
- Parse and process real-time data feeds
- Build agents that can answer complex, data-driven questions
What Are AI Agents?
An AI agent is an autonomous system that can:
- Understand natural language queries
- Reason about what actions to take
- Execute tools or functions to gather information
- Synthesize results into coherent responses
Unlike traditional chatbots that only respond from their training data, agents can:
- Query live APIs
- Search databases
- Perform calculations
- Interact with external services
Think of an agent as a smart assistant that can not only answer questions but also do things—like looking up real-time data, making API calls, or processing information.
The Building Blocks: LLMs and Tools
Large Language Models (LLMs)
LLMs like GPT-4, Claude, or open-source models like Qwen2 are the “brain” of our agent. They excel at:
- Understanding natural language
- Generating coherent text
- Following instructions
- Reasoning about problems
However, LLMs have limitations:
- They can’t access real-time data
- They can’t call APIs
- They can’t perform calculations beyond what’s in their training
Tools: Extending LLM Capabilities
Tools are functions that agents can call to extend their capabilities. A tool might:
- Query a database
- Call an external API
- Perform calculations
- Parse data files
- Search the web
When you give an agent access to tools, it can:
- Analyze the user’s query
- Decide which tools to use
- Call those tools with appropriate parameters
- Use the results to formulate a response
This is called function calling or tool use—one of the most powerful features of modern LLM frameworks.
Our Project: A Disaster Monitoring Agent
Let’s build an agent that can answer questions about global disasters by:
- Fetching real-time disaster data from GDACS (Global Disaster Alert and Coordination System)
- Searching for disasters by location or country
- Providing detailed information about disaster events
Architecture Overview
User Query → Agent → LLM → Tool Selection → Execute Tools → Synthesize Response
The agent will have three tools:
get_lat_long: Get coordinates for a town and stateget_disaster_data: Find disasters near specific coordinatesget_disaster_data_from_country: Find disasters in a specific country
Step-by-Step Implementation
1. Setting Up the Environment
First, we’ll use LangChain for agent orchestration and Ollama for running a local LLM:
from langchain.agents import create_agent
from langchain_ollama import ChatOllama
from langchain.tools import tool
import requests
from bs4 import BeautifulSoup
model = ChatOllama(model="qwen2:7b")
Key Components:
create_agent: LangChain’s function to create an agentChatOllama: Interface to run local LLMs via Ollama@tool: Decorator that converts Python functions into agent tools
2. Data Fetching: Parsing the RSS Feed
Before we can build tools, we need to fetch and parse disaster data. GDACS provides an RSS feed with disaster information:
RSS_FEED_URL = "https://www.gdacs.org/xml/rss.xml"
def extract_text(element, default: str = "") -> str:
"""Extract text from an element, returning default if element is None or empty."""
if element is None:
return default
text = element.get_text(strip=True)
return text if text else default
def parse_item(item) -> dict:
"""Parse a single RSS item and extract relevant fields."""
data = {}
data['title'] = extract_text(item.find('title'))
data['description'] = extract_text(item.find('description'))
data['georss_point'] = extract_text(item.find('georss:point'))
# Population field
population = item.find('gdacs:population')
if population:
data['gdacs_population_text'] = extract_text(population)
else:
data['gdacs_population_text'] = ""
return data
def fetch_rss_feed():
"""Fetch and parse the RSS feed from GDACS."""
response = requests.get(RSS_FEED_URL)
response.raise_for_status()
soup = BeautifulSoup(response.content, 'xml')
items = soup.find_all('item')
return [parse_item(item) for item in items]
What’s happening here:
- We fetch the RSS feed using
requests - Parse XML with
beautifulsouplibrary - Extract relevant fields (title, description, location, population)
- Return a list of parsed disaster events
3. Building Our First Tool: Location Lookup
Our agent needs to convert place names to coordinates. We’ll use the Open-Meteo geocoding API:
@tool
def get_lat_long(town: str, state: str) -> tuple[float, float]:
"""
Get the latitude and longitude of the given town and full name of the state.
Args:
town: The town to get the latitude and longitude of
state: The full name of the state of the town
Returns:
A tuple containing the latitude and longitude of the town
"""
url = "https://geocoding-api.open-meteo.com/v1/search"
params = {"name": town}
response = requests.get(url, params=params)
response.raise_for_status()
data = response.json()
# Filter results to match the state with admin1 field
results = data.get("results", [])
matching_result = None
for result in results:
if result.get("admin1") == state:
matching_result = result
break
if matching_result is None:
raise ValueError(f"No location found for town '{town}' in state '{state}'")
latitude = matching_result.get("latitude")
longitude = matching_result.get("longitude")
if latitude is None or longitude is None:
raise ValueError(f"Latitude or longitude not found in result for '{town}', '{state}'")
return (latitude, longitude)
Key Points:
- The
@tooldecorator tells LangChain this function is a tool - The docstring is crucial—the LLM uses it to understand when and how to call the tool
- We filter results by state to ensure accuracy
- Error handling helps the agent understand when something goes wrong
4. Building Tool #2: Location-Based Disaster Search
Now we’ll create a tool that finds disasters near specific coordinates:
@tool
def get_disaster_data(lat: float, long: float) -> dict:
"""
Get the disaster data for the given latitude and longitude.
Matches events from the RSS feed where the georss_point is within +/- 4 degrees
of the given latitude and longitude.
Args:
lat: The latitude of the location
long: The longitude of the location
Returns:
A dictionary containing the matching events and the count of matching events
"""
events = fetch_rss_feed()
# Filter events where lat and long are within +/- 4 degrees
matching_events = []
for event in events:
georss_point = event.get("georss_point", "")
if georss_point:
try:
# Parse georss_point (format: "latitude longitude")
parts = georss_point.strip().split()
if len(parts) == 2:
georss_lat = float(parts[0])
georss_long = float(parts[1])
# Check if within +/- 4 degrees
if (georss_lat - 4 <= lat <= georss_lat + 4 and
georss_long - 4 <= long <= georss_long + 4):
matching_events.append(event)
except (ValueError, IndexError):
# Skip invalid georss_point values
continue
return {"events": matching_events, "count": len(matching_events)}
What this does:
- Fetches the latest disaster data from the RSS feed
- Parses geographic coordinates from each event
- Filters events within ±4 degrees of the target location
- Returns matching events with metadata
5. Building Tool #3: Country-Based Search
For simpler queries, we’ll add a tool that searches by country name:
@tool
def get_disaster_data_from_country(country: str) -> list:
"""
Get the disaster data for the given country.
Args:
country: The full name of the country
Returns:
A list of strings containing matching events
"""
events = fetch_rss_feed()
matching_events = []
country_lower = country.lower()
for event in events:
title = event.get('title', '').lower()
if country_lower in title:
matching_events.append(
event['title'] + ": " + event['description'] + " " + event['gdacs_population_text']
)
return matching_events
6. Creating the Agent
Now we bring everything together:
agent = create_agent(
tools=[get_lat_long, get_disaster_data, get_disaster_data_from_country],
model=model,
system_prompt="You are a helpful assistant that can get the latitude and longitude of a town and state, get the disaster data for a given latitude and longitude, and get the disaster data for a given country."
)
result = agent.invoke(
{"messages": [{"role": "user", "content": "Are there any disasters in India? Briefly describe the disasters."}]}
)
print(result['messages'][-1].content)
In India, there have been several forest fires that occurred between December 3rd and December 19th, 2025. Here are brief descriptions of some: 1. A forest fire started on 09/12/2025 and was still active until 16/12/2025, affecting 22,002 people. 2. Another fire occurred on 07/12/2025 that lasted until 19/12/2025; it affected 54,311 people in the area. 3. A similar incident took place on the same date range (07/12 to 19/12/2025), impacting another 36,280 individuals. 4. There was a fire on 06/12/2025 that lasted until 19/12/2025, resulting in the displacement of 10,384 people. 5. Lastly, an earlier fire broke out on 03/12/2025 and was under control by 17/12/2025, with an estimated population impact of 50,181.
What happens when you run this (Answers may be different for you depending on time):
- User asks: “Are there any disasters in India?”
- Agent analyzes the query and decides to use
get_disaster_data_from_country - Agent calls the tool with
country=”India” - Tool fetches RSS feed, filters for India-related events
- Agent receives the results and synthesizes a natural language response
- User gets a comprehensive answer about disasters in India
How Agents Make Decisions
The magic happens in the agent’s decision-making process:
- Query Analysis: The LLM reads the user’s question
- Tool Selection: Based on the query and available tools, it decides which tool(s) to use
- Parameter Extraction: It extracts the right parameters from the query
- Tool Execution: The tool runs and returns data
- Response Synthesis: The LLM combines tool results into a natural response
For example, if you ask: ”What disasters are near Seattle, Washington?”
The agent might:
- Recognize it needs coordinates → calls
get_lat_long(”Seattle”, “Washington”) - Gets coordinates:
(47.9150338, -122.0876997) - Calls
get_disaster_data(47.9150338, -122.0876997)to find nearby disasters - Synthesizes the results into a natural response
result = agent.invoke(
{"messages": [{"role": "user", "content": "Are there any disasters near Seattle, Washington?"}]}
)
print(result['messages'][-1].content)
Near Seattle, Washington, there was a green flood alert in the United States which started on September 12, 2025 and lasted until December 17, 2025. This event caused 1 death and displaced 294 people. The exact location of this event is approximately at coordinates 47.9150338, -122.0876997.
Best Practices for Building Agents
1. Write Clear Tool Descriptions
The LLM relies on docstrings to understand tools. Be specific:
@tool
def my_tool(param: str) -> str:
"""
Clear description of what this tool does.
Args:
param: What this parameter means
Returns:
What the return value represents
"""
2. Handle Errors Gracefully
Tools should raise meaningful errors that help the agent understand what went wrong:
if matching_result is None:
raise ValueError(f"No location found for town '{town}' in state '{state}'")
3. Keep Tools Focused
Each tool should do one thing well. This makes it easier for the agent to understand when to use it.
4. Provide Good System Prompts
Your system prompt should explain the agent’s role and capabilities:
system_prompt="You are a helpful assistant that can locate with town and state, get the disaster data for a given location."
5. Test with Real Queries
Try various phrasings to ensure your agent handles different question styles:
- “Are there disasters in Sudan?”
- “What’s happening with disasters in India?”
- “Show me disaster alerts for India”
Advanced Concepts
Tool Chaining
Agents can chain multiple tools together. For example:
- User: “What disasters are near Boston?”
- Agent calls
get_lat_long(”Boston”, “Massachusetts”) - Agent uses those coordinates to call
get_disaster_data(lat, long) - Agent synthesizes the final response
The agent automatically figures out this chain based on the query!
Error Recovery
Good agents can recover from errors:
try:
result = tool_call()
except ValueError as e:
# Agent can try alternative approaches
pass
Streaming Responses
For better UX, you can stream agent responses as they’re generated, giving users real-time feedback.
Real-World Applications
AI agents with tooling are being used for:
- Customer Support: Agents that can query order databases, check shipping status, process returns
- Data Analysis: Agents that can query databases, generate reports, create visualizations
- Content Creation: Agents that can search the web, fact-check, and cite sources
- Software Development: Agents that can read code, run tests, make changes
Conclusion
AI agents represent a powerful paradigm shift—from static chatbots to dynamic systems that can interact with the real world. By combining LLMs with tools, we can build assistants that:
- Access real-time data
- Perform complex operations
- Answer questions that require external information
- Adapt to new situations
The GDACS disaster monitoring agent we built demonstrates these principles in action. You can extend this pattern to build agents for any domain—whether it’s monitoring stock prices, analyzing weather data, or managing your calendar.
Next Steps
- Experiment: Try modifying the tools or adding new ones
- Improve: Add error handling, caching, or rate limiting
- Deploy: Wrap your agent in a web interface or API
- Scale: Add more sophisticated tools and capabilities
The future of AI is not just in better models, but in better ways to connect them to the world. Agents with tooling are how we make that connection.
Want to see the full code? Check out the GitHub repository https://github.com/antosara-dev/disaster-agent and try running it yourself following the README.md