""" Travel planner based on Agentic AI workflow. This module deploys a portal which can customize a day to day travel itinerary for a person using multiple specialized AI crews. Implemented using Sambanova Cloud, Gradio and Crew AI. A deployment is available at https://huggingface.co/spaces/sambanovasystems/trip-planner """ import datetime import json import logging from typing import List, Tuple import gradio as gr import plotly.graph_objects as go import os import openai from crew import AddressSummaryCrew, TravelCrew from db import log_query from fpdf import FPDF client = openai.OpenAI( api_key=os.environ.get("SAMBANOVA_API_KEY"), base_url="https://api.sambanova.ai/v1", ) def start_chat(context): return gr.Chatbot(visible=True), gr.Textbox(visible=True), context def respond(message, chat_history, context, model="Meta-Llama-3.1-70B-Instruct"): # Simple response incorporating context response = client.chat.completions.create( model=model, messages=[{"role":"system", "content":"You are a helpful assistant"}, {"role": "user", "content": "Here is a trip itinerary: %s. Please answer the specific question asked by the user. %s " % (message, context)}], temperature=0.1, top_p=0.1 ) result = response.choices[0].message.content bot_message = result chat_history.append((message, bot_message)) return "", chat_history def export_pdf(input_text:str, input_chat:str): """ Create a downloadable pdf for the given input text Args: input_text: The text that needs to be made a pdf input_chat: Chat messages Result: Downloadable pdf """ current_datetime = datetime.datetime.now() # Format the current date and time as YYYY-MM-DD_HH-MM-SS datetime_str = current_datetime.strftime("%Y-%m-%d_%H-%M-%S") file_name = "itinerary_%s.pdf" % datetime_str pdf = FPDF() pdf.add_page() pdf.set_font("helvetica", size=12) for line in input_text.split('\n'): clean_line = line.strip() if clean_line.startswith('**'): pdf.set_font("Arial", size=12, style='B') pdf.multi_cell(0, 5, clean_line[2:].lstrip()[:-2].rstrip()) pdf.set_font("Arial", size=12, style='') else: pdf.multi_cell(0, 5, clean_line) pdf.ln() for conversation in input_chat: counter = 0 for line in conversation: clean_line = line.strip() if clean_line: if counter == 0: pdf.ln() pdf.set_font("Arial", size=12, style='I') counter += 1 else: pdf.set_font("Arial", size=12, style='') pdf.multi_cell(0, 5, clean_line) pdf.output(file_name).encode('latin-1') return file_name def filter_map(text_list: List[str], lat: List[str], lon: List[str]) -> go.Figure: """ Create a Map showing the points specified in the inputs. Args: text_list: List of the description of all locations that will be shown on the map. lat: List of latitude coordinates of the locations. lon: List of longitude coordinates of the locations. Returns: Figure: Map with the points specified in the inputs """ # Creating a map with the provided markers using their latitude and longitude coordinates. fig = go.Figure( go.Scattermapbox(lat=lat, lon=lon, mode='markers', marker=go.scattermapbox.Marker(size=11), hovertext=text_list) ) # Update the map by centering it on of the the provided longitude and latitude coordinates fig.update_layout( mapbox_style='open-street-map', hovermode='closest', mapbox=dict(bearing=0, center=go.layout.mapbox.Center(lat=lat[1], lon=lon[1]), pitch=0, zoom=10), ) return fig def run( origin: str, destination: str, arrival_date: str, age: int, trip_duration: int, interests: List[str], cuisine_preferences: List[str], children: bool, budget: int, model_name:str='Meta-Llama-3.1-70B-Instruct' ) -> Tuple[str, go.Figure]: """ Run the specfied query using Crew AI agents. Args: origin: Origin city of the traveller. destination: Destination to which the traveller is going. arrival_date: Approximate date when the trip will begin in epoch time. age: Age profile of traveller. interests: Specific interests of the traveller. cuisine_preferences: Specific cuisine preferences of the traveller. children: Whether traveller has children travelling with them. budget: Total budget of traveller in US Dollars. Returns: Returns a tuple containing the itinerary and map """ # Gradio Datetime is currently not working on HF # See https://github.com/gradio-app/gradio/issues/10358 # Hece disabling datetime input and reverting back to string input """ if arrival_date: arrival_date_input = datetime.datetime.fromtimestamp(arrival_date).strftime("%m-%d-%Y") else: arrival_date_input = None """ if arrival_date: arrival_date_input = arrival_date.strip() else: arrival_date_input = None log_query(origin, destination, age, trip_duration, budget) logger.info( f'Origin: {origin}, Destination: {destination}, Arrival Date: {arrival_date_input},' f' Age: {age}, Duration: {trip_duration},' f' Interests: {interests}, Cuisines: {cuisine_preferences},' f' Children: {children}, Daily Budget: {budget}, Model Name: {model_name}' ) # Creating a dictionary of user provided preferences and providing these to the crew agents # to work on. user_preferences = { 'origin': origin, 'destination': destination, 'arrival_date': arrival_date_input, 'age': age, 'trip_duration': trip_duration, 'interests': interests, 'cuisine_preferences': cuisine_preferences, 'children': children, 'budget': budget, } #result = TravelCrew(model_name).crew().kickoff(inputs=user_preferences) crew = TravelCrew(model_name).crew() result = crew.kickoff(inputs=user_preferences) metrics = crew.usage_metrics logger.info("Result Metrics") logger.info(metrics) """ Now we will pass the result to a address summary crew whose job is to extract position coordinates of the addresses (latitude and longitude), so that the addresses in the result can be displayed in map coordinates """ inputs_for_address = {'text': str(result)} addresses = AddressSummaryCrew(model_name).crew().kickoff(inputs=inputs_for_address) """ We have requested the crew agent to return latitude, longitude coordinates. But the exact way the LLMs return varies. Hence we try multiple different ways of extracting addresses in JSON format from the result. """ json_addresses = None if addresses.json_dict is not None: json_addresses = addresses.json_dict if json_addresses is None: try: json_addresses = json.loads(addresses.raw) except json.JSONDecodeError as e: # Try with different format of result data generated with ```json and ending with ```. try: json_addresses = json.loads(addresses.raw[8:-4]) except json.JSONDecodeError as e: # Try with different format of result data generated with ``` and ending with ```. try: json_addresses = json.loads(addresses.raw[4:-4]) except json.JSONDecodeError as e: logger.error('Error loading Crew Output for addresses') logger.info(addresses.raw) return (result, None) fig = filter_map(json_addresses['name'], json_addresses['lat'], json_addresses['lon']) return (result, fig) logger = logging.getLogger() logger.setLevel(logging.INFO) with gr.Blocks() as demo: gr.Markdown('Use this app to create a detailed itinerary on how to explore a new place.' ' Itinerary is customized to your taste. Powered by Sambanova Cloud.') # Store context between interactions context = gr.State() with gr.Row(): with gr.Column(scale=1): inp_source = gr.Textbox(label='Where are you travelling from?') inp_dest = gr.Textbox(label='Where are you going?') inp_cal = gr.Textbox(label='Approximate arrival date in mm-dd-yyyy') inp_age = gr.Slider(label='Your age?', value=30, minimum=15, maximum=90, step=5) inp_days = gr.Slider(label='How many days are you travelling?', value=5, minimum=1, maximum=14, step=1) inp_interests =\ gr.CheckboxGroup( [ 'Museums', 'Outdoor Adventures', 'Shopping', 'Children\'s Entertainment', 'Off the beat activities', 'Night Life', ], label='Checkbox your interests.', ) inp_cuisine =\ gr.CheckboxGroup( [ 'Ethnic', 'American', 'Italian', 'Mexican', 'Chinese', 'Japanese', 'Indian', 'Thai', 'French', 'Vietnamese', 'Vegan', ], label='Checkbox your cuisine preferences.', ) inp_children = gr.Checkbox(label='Check if children are travelling with you') inp_budget =\ gr.Slider( label='Total budget of trip in USD', show_label=True, value=1000, minimum=500, maximum=10000, step=500 ) inp_model = gr.Textbox(value="Meta-Llama-3.1-70B-Instruct", label='Sambanova Model Name') plan_button = gr.Button("Plan your Trip") inputs = [inp_source, inp_dest, inp_cal, inp_age, inp_days, inp_interests, inp_cuisine, inp_children, inp_budget, inp_model] with gr.Column(scale=2): with gr.Row(): output_itinerary =\ gr.Textbox( label='Complete Personalized Itinerary of your Trip', show_label=True, show_copy_button=True, autoscroll=False, ) # Chat interface (hidden initially) with gr.Row(visible=False) as chat_interface: chatbot = gr.Chatbot(label='Chat with the itinerary') input_msg = gr.Textbox(label='Ask a question') # Chat controls start_chat_btn = gr.Button("Start Chat", visible=False) # Download button download_btn = gr.Button("Download Itinerary") output_map = gr.Plot(label='Venues on a Map. Please verify with a Navigation System before traveling.') output = [output_itinerary, output_map] plan_button.click(fn=run, inputs=inputs, outputs=output).then( lambda: gr.Button(visible=True), outputs=start_chat_btn) download_btn_hidden = gr.DownloadButton(visible=False, elem_id="download_btn_hidden") download_btn.click(fn=export_pdf, inputs=[output_itinerary, chatbot], outputs=[download_btn_hidden]).then(fn=None, inputs=None, outputs=None, js="() => document.querySelector('#download_btn_hidden').click()") start_chat_btn.click( start_chat, inputs=output_itinerary, outputs=[chatbot, input_msg, context] ).then( lambda: gr.Row(visible=True), outputs=chat_interface ).then( lambda: gr.Button(visible=False), outputs=start_chat_btn) input_msg.submit( respond, inputs=[input_msg, chatbot, context, inp_model], outputs=[input_msg, chatbot] ) demo.launch()