Unfolding the universe of possibilities..

Every load time is a step closer to discovery.

Building a Smart Travel Itinerary Suggester with LangChain, Google Maps API, and Gradio (Part 3)

Learn how to build an application that might inspire your next road trip

This article is the final one in a three part series where we build a travel itinerary suggester application using OpenAI and Google APIs and display it in a simple UI generated with gradio. In this part, we discuss how to build that UI, putting together the Agent and RouteFinder modules that we built in parts 1 and 2. Just want see the code? Find it here.

1. Recap of part 2

In the second part of this three part series, we built a system that takes a parsed list of waypoints from a set of LLM calls (part 1) and uses the Google Maps API and Folium to generate a route between them and plot it on an interactive map. Recall that our goal in this project is to build an application that allows someone to easily enter a travel request like “A four day trip from Berlin to Zurich where I try lots of local beer and food” and returns a detailed itinerary along with a map for them to explore. Thanks to parts 1 and 2 we have all the components assembled, we now just need to put them together in a UI that makes using them easy.

2. Connecting maps to gradio

gradio is an excellent library for quickly building interactive apps that can showcase machine learning models. It has a gradio.Plot component that is designed to work with Matplotlib, Bohkeh and Plotly (details here). However, the maps we were generating in Part 2 are made with folium. It would certainly be possilbe to re-make them using one of these other libraries, but thankfully we don’t need to. Instead, we can use the leafmap package, which allows us to reuse the folium code we already have and force it to output a html that can be understood by gradio . Details can be found here.

Let’s look at a simple example to see how this works. First we will make a function that outputs a html from in the desired format

import leafmap.foliumap as leafmap
import folium
import gradio as gr

def generate_map(center_coordinates, zoom_level):

coords = center_coordinates.split(“,”)
lat, lon = float(coords[0]), float(coords[1])
map = leafmap.Map(location=(lat,lon), tiles=”Stamen Terrain”, zoom_start=zoom_level)

return map.to_gradio()

Here, the function generate_map takes in a string of coordinates in the format “lat,lon” and a zoom level for the folium map. It generates the map and converts it to a format that can be readable by gradio.

Next, lets build a really simple gradio interface to display our map

demo = gr.Blocks()

with demo:
gr.Markdown(“## Generate a map”)
with gr.Row():
with gr.Column():
# first col is for buttons
coordinates_input = gr.Textbox(value=””,label=”Your center coordines”,lines=1)
zoom_level_input = gr.Dropdown(choices=[1,2,3,4,5,6,7,8,9],label=”choose zoom level”)
map_button = gr.Button(“Generate map”)
with gr.Column():
# second col is for the map
map_output = gr.HTML(label=”Travel map”)

map_button.click(generate_map, inputs=[coordinates_input,zoom_level_input], outputs=[map_output])

# run this in a notebook to display the UI
demo.queue().launch(debug=True)

Here we are making use of the Blocks API, which gives us flexibility over how our app’s UI will be set up. We make one row pf components with two columns. The first column contains three elements: A text box for the user to enter the desired center coordinates, a dropdown to select the zoom level and a button called “generate map”, which they need to click on.

In the second column, we have map_output which is agradio.HTML() component, which is going to display the map html.

Then, all we need to do is define what happens when map_button is clicked. When this happens, we will run the generate_map function, passing in the selected values from coordinates_input and zoom_input . The result will be send to the map_output variable.

Running this produces the following UI

Basic mapping UI generated with leafmap and gradio

It’s certainly not sophisticated or well laid out, but it contains the essential element for building a mapping tool with gradio.

3. A simple UI for our travel agent

Let’s first look at some of the features of the gradio app for our travel mapper before we examine the code. Bear in mind though that gradio has a large variety of components available to make sophisticated and good-looking UIs, and this travel mapper UI is still very much at the POC stage.

Description of all the components in the final gradio app for the travel mapper

Essentially our app has two columns. The first contains a text box for the user to enter their query, a set of radio buttons that allows us to switch between models and a text box that displays the output of the validation check.

The second column contains the map, generated with leafmap.folium and a text box that displays the full text itinerary output from the LLM call. The “generate map” button is at the bottom, off screen in the screenshot above.

The code for all this is remarkably concise, thanks to all the work being done in the background by gradio.

import gradio as gr
from travel_mapper.TravelMapper import TravelMapperForUI, load_secrets, assert_secrets
from travel_mapper.user_interface.utils import generate_generic_leafmap
from travel_mapper.user_interface.constants import EXAMPLE_QUERY

def main():

# load the AP keys
secrets = load_secrets()
assert_secrets(secrets)

# set up travel mapper (see part 2)
travel_mapper = TravelMapperForUI(
openai_api_key=secrets[“OPENAI_API_KEY”],
google_maps_key=secrets[“GOOGLE_MAPS_API_KEY”],
google_palm_api_key=secrets[“GOOGLE_PALM_API_KEY”],
)

# build the UI in gradio
app = gr.Blocks()

# make a generic map to display when the app first loads
generic_map = generate_generic_leafmap()

with app:
gr.Markdown(“## Generate travel suggestions”)

# make multple tabs
with gr.Tabs():
# make the first tab
with gr.TabItem(“Generate with map”):
# make rows 1 within tab 1
with gr.Row():
# make column 1 within row 1
with gr.Column():
text_input_map = gr.Textbox(
EXAMPLE_QUERY, label=”Travel query”, lines=4
)

radio_map = gr.Radio(
value=”gpt-3.5-turbo”,
choices=[“gpt-3.5-turbo”, “gpt-4”, “models/text-bison-001”],
label=”models”,
)

query_validation_text = gr.Textbox(
label=”Query validation information”, lines=2
)

# make column 2 within row 1
with gr.Column():
# place where the map will appear
map_output = gr.HTML(generic_map, label=”Travel map”)
# place where the suggested trip will appear
itinerary_output = gr.Textbox(
value=”Your itinerary will appear here”,
label=”Itinerary suggestion”,
lines=3,
)
# generate button
map_button = gr.Button(“Generate”)

# make the second tab
with gr.TabItem(“Generate without map”):
# make the first row within the second tab
with gr.Row():
# make the first column within the first row
with gr.Column():
text_input_no_map = gr.Textbox(
value=EXAMPLE_QUERY, label=”Travel query”, lines=3
)

radio_no_map = gr.Radio(
value=”gpt-3.5-turbo”,
choices=[“gpt-3.5-turbo”, “gpt-4”, “models/text-bison-001”],
label=”Model choices”,
)

query_validation_no_map = gr.Textbox(
label=”Query validation information”, lines=2
)
# make the second column within the first row
with gr.Column():
text_output_no_map = gr.Textbox(
value=”Your itinerary will appear here”,
label=”Itinerary suggestion”,
lines=3,
)
# generate button
text_button = gr.Button(“Generate”)

# instructions for what happens whrn the buttons are clicked
# note use of the “generate_with_leafmap” method here.
map_button.click(
travel_mapper.generate_with_leafmap,
inputs=[text_input_map, radio_map],
outputs=[map_output, itinerary_output, query_validation_text],
)
text_button.click(
travel_mapper.generate_without_leafmap,
inputs=[text_input_no_map, radio_no_map],
outputs=[text_output_no_map, query_validation_no_map],
)

# run the app
app.launch()

4. Creating the package

As can be seen from looking at the repository on github, the travel mapper code is structured with the help of a standard template from cookiecutter, but some important parts of the template are not yet filled in. Ideally we would include unit and integration tests, and complete the repository set up so that continuous integration/continuous delivery (CI/CD) concepts are used. If the project develops further beyond this POC stage, these aspects will be added in future.

There are a few ways that the code can be run locally. If we put the main function in the block above into a script called driver.py , we should be able to run it from the top level of the travel_mapperproject from the terminal. If the package successfully runs, a message like this should appear in the terminal

Running on local URL: http://127.0.0.1:7860

Copy-pasting this url into a web browser should display the gradio app, running locally on your machine. Of course, more steps would be needed if we actually wanted to deploy this on the web (which I do not recommend because of the costs incurred from the API calls), but this is beyond the scope of these articles.

The driver can also be run from a bash script called run.sh , which can be found in the user_interface module of the codebase.

# Run the UI
# run this from the top level directory of the travel mapper project
export PYTHONPATH=$PYTHONPATH:$(pwd)
echo “Starting travel mapper UI”
$(pwd)/travel_mapper/user_interface/driver.py

When run from the top level of the project, this also sets the PYTHONPATH correctly so that the project-specific import statements are always recognized.

Thats it for the series and thanks for making it to the end! Please feel free to explore the full codebase here https://github.com/rmartinshort/travel_mapper. Any suggestions for improvement or extensions to the functionality would be much appreciated!

Building a Smart Travel Itinerary Suggester with LangChain, Google Maps API, and Gradio (Part 3) was originally published in Towards Data Science on Medium, where people are continuing the conversation by highlighting and responding to this story.

Leave a Comment