Building a Smart Travel Agent with LangGraph and OpenAI
LangGraph, a powerful library for creating stateful, multi-actor applications with language models, can be leveraged to build sophisticated AI-driven tools. This article will explore how to make a smart travel agent using LangGraph, OpenAI’s GPT models, and the Tavily search API.
The Travel Agent Architecture
Our travel agent consists of three main components:
- Plan Node: Generates a high-level itinerary outline
- Research Node: Gathers information on local events
- Generation Node: Creates a detailed itinerary
These components work together in a graph structure, allowing for a flexible and extensible travel planning process.
Setting Up Environment Variables
- Create a .env file:
In the root directory of your project, create a new file named.env
. This file will store your sensitive API keys. - Add API keys to .env:
OPENAI_API_KEY=your_openai_api_key_here
TAVILY_API_KEY=your_tavily_api_key_here
3. Load environment variables:
from dotenv import load_dotenv
import os
load_dotenv()
Building the Travel Agent
Now that we have our API keys securely stored, let’s proceed with building the travel agent using LangGraph and OpenAI:
Setting Up the Graph
First, we define our graph structure and state:
from typing import TypedDict, List
from langgraph.graph import StateGraph, START, END
class State(TypedDict):
destination: str
dates: str
plan: str
content: List[str]
itinerary: str
graph_builder = StateGraph(State)
The State
class defines the structure of the application's state. Each field represents a different aspect of the travel planning process:
destination
: The user's travel destinationdates
: The travel datesplan
: The high-level itinerary outlinecontent
: A list of research findingsitinerary
: The final detailed itinerary
Creating the Plan Node
The plan node generates a high-level itinerary outline:
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model='gpt-3.5-turbo', temperature=0)
PLAN_PROMPT= """You are an expert travel planner tasked with creating a high \
level itinerary outline.Write such an outline for the user provided \
destination and dates. Give an outline of the itinerary."""
from langchain_core.messages import SystemMessage, HumanMessage
def plan_node(state: State):
messages = [
SystemMessage(content=PLAN_PROMPT),
HumanMessage(content=f"{state['destination']}\n\nHere is my \
dates:\n\n{state['dates']}")
]
response = model.invoke(messages)
return {"plan": response.content}
graph_builder.add_node('plan_node', plan_node)
graph_builder.add_edge(START, 'plan_node')
The PLAN_PROMPT
is a system message that sets the context for the AI, instructing it to act as an expert travel planner.
The plan_node
function is where the magic happens:
- It takes the current
state
as input. - It constructs a list of messages for the AI, including the system prompt and a human message containing the destination and dates.
- The AI’s response is then invoked using
model.invoke(messages)
. - The function returns a dictionary with the ‘plan’ key, containing the AI-generated content.
This node sets the foundation for the entire travel planning process by creating a high-level outline that subsequent nodes will build upon and refine.
Implementing the Research Node
The research node uses the Tavily API to gather information on local events:
from langchain_core.pydantic_v1 import BaseModel
class Queries(BaseModel):
queries: List[str]
from tavily import TavilyClient
tavily = TavilyClient(api_key=os.getenv('TAVILY_API_KEY'))
RESEARCH_PLAN_PROMPT = """You are a travel planner tasked with finding events \
for a user visiting. Generate a list of search queries that will gather \
information on local events during this period such as music festivals, \
food fairs, technical conferences, or any other noteworthy events. \
Only generate 3 queries max."""
def research_plan_node(state: State):
queries = model.with_structured_output(Queries).invoke([
SystemMessage(content=RESEARCH_PLAN_PROMPT),
HumanMessage(content=f"{state['destination']}\n\nHere is my \
dates:\n\n{state['dates']}")
])
content = []
for q in queries.queries:
response = tavily.search(query=q, max_results=2)
for r in response['results']:
content.append(r['content'])
return {'content': content}
graph_builder.add_node('research_plan', research_plan_node)
graph_builder.add_edge('plan_node', 'research_plan')
We use the Tavily API, a powerful search tool, to perform web searches based on AI-generated queries.
The research_plan_node
function is the core of this node:
- It first uses the AI model to generate structured search queries based on the destination and dates. This ensures that our searches are relevant and focused.
- The
model.with_structured_output(Queries)
call indicates that we're expecting the AI to return a specific structure (likely a list of queries). - We then iterate through these queries, performing a Tavily search for each one.
- For each search, we limit the results to 2 (
max_results=2
) to keep the information manageable. - The search results are appended to the
content
list in the state.
This node enriches our travel plan with current, relevant information about events, attractions, or other points of interest at the destination, making the final itinerary more comprehensive and up-to-date.
The Generation Node
The generation node creates a detailed itinerary based on the gathered information:
WRITER_PROMPT = """You are a travel agent tasked with creating the best \
possible travel itinerary for the user's trip. Generate a detailed itinerary \
based on the provided destination, dates, and any relevant information. \
If the user provides feedback or changes, respond with a revised version \
of your previous itinerary.\
Utilize all the information below as needed:
{content}
"""
def generation_node(state: State):
content = "\n\n".join(state['content'] or [])
user_message = HumanMessage(
content=f"{state['destination']}\n\nHere is my \
dates:\n\n{state['dates']}\n\nHere is my \
plan:\n\n{state['plan']}")
messages = [
SystemMessage(content=WRITER_PROMPT.format(content=content)),
user_message
]
response = model.invoke(messages)
return {'itinerary': response.content}
graph_builder.add_node('generation_node', generation_node)
graph_builder.add_edge('research_plan', 'generation_node')
graph_builder.add_edge('generation_node', END)
The WRITER_PROMPT
sets the context for the AI, instructing it to act as a travel agent creating a comprehensive itinerary.
The generation_node
function is the heart of this node:
- It combines all the research content into a single string, separating each piece with new lines for clarity.
- A user message is constructed, containing the destination, dates, and the initial high-level plan.
- The AI is then provided with the system prompt (including all the research content) and the user message.
- The AI’s response, which should be a detailed itinerary, is captured and returned as the itinerary in the state.
This node represents the culmination of our travel planning process, taking all the gathered information and turning it into a cohesive, detailed itinerary tailored to the user’s destination and dates.
Compiling and Running the Graph
Finally, we compile the graph and run it:
from langgraph.checkpoint.memory import MemorySaver
memory = MemorySaver()
graph = graph_builder.compile(checkpointer=memory)
thread = {'configurable': {'thread_id': '1'}}
for s in graph.stream({
'destination': 'New York, USA',
'dates': '2024-10-21 to 2024-10-25'
}, thread):
print(s)
MemorySaver()
is used for checkpointing. This allows the graph to save its state at various points during execution, which can be useful for debugging or resuming interrupted processes.graph_builder.compile(checkpointer=memory)
compiles our graph, turning our node definitions and connections into an executable structure.- The
thread
dictionary is used to configure the execution. Thethread_id
could be used to identify different user sessions or execution instances. - The
graph.stream()
method is called with initial state values (destination and dates). This starts the execution of our graph.
This structure allows for a modular, extensible travel planning system. It combines AI-generated content with real-time web information to create comprehensive travel itineraries. The use of a graph structure makes it easy to add new steps or modify the process flow in the future.
Result
Let’s get the final itinerary
snapshot = graph.get_state(thread)
def pretty_print_itinerary(itinerary):
lines = itinerary.split('\n')
for line in lines:
if line.startswith('**'):
print(f"\n{line.strip('*')}")
elif line.startswith('-'):
print(f" {line}")
else:
print(line)
pretty_print_itinerary(snapshot.values['itinerary'])
Revised New York City Itinerary:
Day 1: 2024-10-21
- Morning: Arrival in New York City
- Afternoon: Check into hotel and explore the neighborhood
- Evening: Dinner at a local restaurant
Day 2: 2024-10-22
- Morning: Visit Central Park
- Afternoon: Explore the Museum of Modern Art (MoMA)
- Evening: Attend a Broadway show or consider the Concerts in the Catacombs series for a unique experience.
Day 3: 2024-10-23
- Morning: Statue of Liberty and Ellis Island tour
- Afternoon: Visit the 9/11 Memorial & Museum
- Evening: Enjoy a dinner in Little Italy or Chinatown, or consider the Elvis-themed pop-up bar for a fun and retro experience.
Day 4: 2024-10-24
- Morning: Explore the Metropolitan Museum of Art
- Afternoon: Shopping on Fifth Avenue
- Evening: Experience Times Square and consider attending a scary screening like "The Shining" or "American Psycho" for a Halloween-themed evening.
Day 5: 2024-10-25
- Morning: Visit the Empire State Building
- Afternoon: Explore Brooklyn Bridge and DUMBO
- Evening: Departure from New York City
Additional Notes:
- Since you are visiting in October, consider the Halloween-themed events happening in the city, such as the jack o'lantern displays in Hudson Valley or Long Island.
- Check out the fall festival at Governors Island on October 26th and 27th for a unique autumn experience.
- Don't forget to book tickets in advance for any specific events or attractions you wish to attend during your stay.
- Enjoy the vibrant fall atmosphere in New York City and have a fantastic trip!
Conclusion
This LangGraph-based travel agent demonstrates the power of combining language models with structured workflows. By breaking down the travel planning process into distinct nodes, we create a flexible and extensible system that can be easily modified or expanded.The use of OpenAI’s GPT models for natural language understanding and generation, combined with the Tavily API for real-time information gathering, results in a sophisticated travel planning tool. This approach can be adapted to various other domains, showcasing the versatility of LangGraph in building complex AI applications.