Your LLM agents often need to act upon or retrieve information that isn't directly available within their immediate programming environment. To truly extend their capabilities, your custom Python tools will frequently need to communicate with external services. The two most common types of external services you'll encounter are Application Programming Interfaces (APIs) and databases. This section explores how to build tools that effectively interact with these resources, enabling your agents to fetch real-time data, access specialized functionalities, and work with persisted information.
APIs serve as gateways to a vast array of functionalities and data sources on the web. Whether it's fetching the latest financial news, translating text, getting weather updates, or interacting with a proprietary business system, APIs allow your tools to programmatically access these services. The requests
library is the de facto standard in Python for making HTTP requests, which is the underlying protocol for most web APIs.
If you don't have it installed, you can add requests
to your project using pip:
pip install requests
Most API interactions involve sending an HTTP request to a specific URL (an endpoint) and then processing the response. The two most common HTTP methods you'll use are GET
(to retrieve data) and POST
(to send data or trigger an action).
GET Requests
A GET
request is used to retrieve information from an API. You might pass parameters as part of the URL's query string.
import requests
def get_current_weather(api_key, city_name):
"""Fetches current weather data for a given city using an example weather API."""
base_url = "http://api.exampleweather.com/v1/current.json" # Fictional API endpoint
params = {
"key": api_key,
"q": city_name
}
try:
response = requests.get(base_url, params=params, timeout=10) # 10-second timeout
response.raise_for_status() # Raises an HTTPError for bad responses (4XX or 5XX)
weather_data = response.json() # Assumes the API returns JSON
# Process and return relevant weather information
description = weather_data.get("current", {}).get("condition", {}).get("text", "Not available")
temp_c = weather_data.get("current", {}).get("temp_c", "N/A")
return f"Current weather in {city_name}: {description}, Temperature: {temp_c}°C"
except requests.exceptions.Timeout:
return f"Error: The request to the weather API timed out."
except requests.exceptions.HTTPError as http_err:
return f"Error: HTTP error occurred: {http_err} - Status: {response.status_code}"
except requests.exceptions.RequestException as e:
return f"Error: An error occurred while fetching weather data: {e}"
# Example usage (API key would be managed securely)
# print(get_current_weather("YOUR_API_KEY", "London"))
In this example, requests.get()
sends the request. response.raise_for_status()
is a convenient way to check if the request was successful (status codes 200-299); otherwise, it raises an exception. API responses are often in JSON format, which can be easily parsed into a Python dictionary using response.json()
.
POST Requests
A POST
request is used to send data to an API, perhaps to create a new resource or trigger an operation. Data is typically sent in the request body, often as JSON.
import requests
import json
def submit_user_feedback(api_endpoint, user_id, feedback_text):
"""Submits user feedback to an example feedback API."""
payload = {
"user_id": user_id,
"feedback": feedback_text,
"source": "LLM_Agent_Tool"
}
headers = {
"Content-Type": "application/json"
# "Authorization": "Bearer YOUR_ACCESS_TOKEN" # If authentication is needed
}
try:
response = requests.post(api_endpoint, data=json.dumps(payload), headers=headers, timeout=10)
response.raise_for_status()
# Assuming the API returns a JSON response, e.g., with a submission ID
return f"Feedback submitted successfully. Response: {response.json()}"
except requests.exceptions.RequestException as e:
return f"Error submitting feedback: {e}"
# Example usage
# feedback_api_url = "http://api.examplefeedback.com/v1/submit" # Fictional API endpoint
# print(submit_user_feedback(feedback_api_url, "user123", "The agent was very helpful!"))
Here, json.dumps(payload)
serializes the Python dictionary into a JSON string, which is sent as the request body. The Content-Type
header tells the server that we're sending JSON.
Always inspect the response.status_code
to understand the outcome of your request. Common status codes include:
200 OK
: Request succeeded.201 Created
: Request succeeded, and a new resource was created (often for POST/PUT).400 Bad Request
: The server could not understand the request due to invalid syntax.401 Unauthorized
: Authentication is required and has failed or has not yet been provided.403 Forbidden
: The server understood the request, but is refusing to fulfill it.404 Not Found
: The requested resource could not be found.429 Too Many Requests
: You've hit a rate limit.500 Internal Server Error
: The server encountered an unexpected condition.Many APIs require authentication. Common methods include API keys (sent as a header or query parameter) or OAuth tokens. Securely managing these credentials is very important; avoid hardcoding them directly in your tool. Use environment variables, configuration files, or dedicated secrets management services. Chapter 4, "Integrating External APIs as Tools," will cover authentication and other API integration aspects like rate limiting in more detail.
Databases are essential for tools that need to store, retrieve, and manage structured data persistently. Your LLM agent might need a tool to look up product information, retrieve customer history, or log interaction details.
Python has a standard API for connecting to SQL databases, known as the DB-API 2.0 (PEP 249). Most Python database drivers for relational databases like PostgreSQL, MySQL, and SQLite adhere to this specification.
sqlite3
for SimplicitySQLite is a lightweight, file-based database engine that's built into Python's standard library via the sqlite3
module, making it excellent for examples and simpler applications.
Let's imagine a tool that needs to fetch customer details from a SQLite database.
First, you'd establish a connection and create a cursor object. The cursor allows you to execute SQL queries.
import sqlite3
def get_customer_details(db_path, customer_id):
"""Retrieves customer details from an SQLite database using their ID."""
conn = None # Initialize conn to None for the finally block
try:
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Use parameterized queries to prevent SQL injection!
query = "SELECT name, email, join_date FROM customers WHERE id = ?;"
cursor.execute(query, (customer_id,)) # The tuple (customer_id,) is important
customer_data = cursor.fetchone() # Fetches one row
if customer_data:
name, email, join_date = customer_data
return f"Customer ID: {customer_id}\nName: {name}\nEmail: {email}\nJoined: {join_date}"
else:
return f"No customer found with ID {customer_id}."
except sqlite3.Error as e:
return f"Database error occurred: {e}"
finally:
if conn:
conn.close() # Always close the connection
# Example: Assume 'company.db' exists and has a 'customers' table.
# To set up for testing:
# def setup_dummy_db(db_file):
# conn_setup = sqlite3.connect(db_file)
# cursor_setup = conn_setup.cursor()
# cursor_setup.execute("DROP TABLE IF EXISTS customers")
# cursor_setup.execute("""
# CREATE TABLE customers (
# id INTEGER PRIMARY KEY,
# name TEXT NOT NULL,
# email TEXT NOT NULL UNIQUE,
# join_date TEXT
# )
# """)
# cursor_setup.execute("INSERT INTO customers (id, name, email, join_date) VALUES (?, ?, ?, ?)",
# (1, 'Alice Wonderland', '[email protected]', '2023-01-15'))
# cursor_setup.execute("INSERT INTO customers (id, name, email, join_date) VALUES (?, ?, ?, ?)",
# (2, 'Bob The Builder', '[email protected]', '2022-11-05'))
# conn_setup.commit()
# conn_setup.close()
# setup_dummy_db('company.db')
# print(get_customer_details('company.db', 1))
# print(get_customer_details('company.db', 3))
A significant aspect highlighted here is the use of parameterized queries (cursor.execute(query, (customer_id,))
). You should always use placeholders (like ?
for sqlite3
or %s
for some other drivers) and pass values as a separate tuple or dictionary. This practice is vital for preventing SQL injection vulnerabilities, where malicious input could otherwise alter your SQL queries.
After executing a SELECT
query, you can retrieve results using methods like:
fetchone()
: Fetches the next row of a query result set, returning a single sequence, or None
when no more data is available.fetchall()
: Fetches all remaining rows of a query result, returning a list of sequences.fetchmany(size)
: Fetches the next set of rows, returning a list.For operations that modify the database (like INSERT
, UPDATE
, DELETE
), you need to commit the transaction using conn.commit()
for the changes to be saved. Always ensure connections are closed (typically in a finally
block) to release resources.
While sqlite3
is convenient, for more robust applications you'll likely use other database systems like PostgreSQL or MySQL. The connection process and query execution are similar, but you'll need to install their respective Python drivers (e.g., psycopg2-binary
for PostgreSQL, mysql-connector-python
for MySQL).
For complex applications, Object-Relational Mappers (ORMs) like SQLAlchemy or Django ORM can provide a higher-level abstraction over direct SQL. ORMs allow you to interact with your database using Python objects and methods, which can simplify development and improve code maintainability, especially for tools handling intricate data models.
The following diagram illustrates a typical flow where an LLM agent uses a Python tool to interact with an external API:
This diagram shows an LLM agent requesting stock price information. The agent calls a Python tool, which in turn queries an external stock API. The tool processes the API's response and returns a structured result to the LLM.
When building tools that interact with external services, keep these points in mind:
location
and date
.try-except
blocks) to catch these issues and return informative error messages to the LLM, allowing it to potentially retry or try an alternative approach.By mastering interactions with APIs and databases, you significantly broaden the scope of tasks your LLM agents can perform. These tools become the agent's senses and effectors in the digital world, allowing them to fetch up-to-date information and trigger actions in external systems. As you progress, you'll combine these foundational skills with more advanced techniques for error handling, input validation, and output structuring to build truly powerful and reliable tools.
Was this section helpful?
© 2025 ApX Machine Learning