Building a Smart Travel Agent with LangGraph and OpenAI

Abhinav Kumar
7 min read15 hours ago

--

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:

  1. Plan Node: Generates a high-level itinerary outline
  2. Research Node: Gathers information on local events
  3. 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

  1. 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.
  2. 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 destination
  • dates: The travel dates
  • plan: The high-level itinerary outline
  • content: A list of research findings
  • itinerary: 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.
Graph Up Until Plan Node

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.
Graph Until Research Plan Node

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.
Final Graph

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. The thread_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.

--

--

Abhinav Kumar

Data enthusiast deeply passionate about power of data. Sifting through datasets and bringing insights to life through visualization is my idea of a good time.