ruslanmv commited on
Commit
b68fd83
·
1 Parent(s): eda6c5a
Files changed (2) hide show
  1. app.py +82 -62
  2. backend.py +385 -0
app.py CHANGED
@@ -1,63 +1,83 @@
1
  import gradio as gr
2
- from huggingface_hub import InferenceClient
3
-
4
- """
5
- For more information on `huggingface_hub` Inference API support, please check the docs: https://huggingface.co/docs/huggingface_hub/v0.22.2/en/guides/inference
6
- """
7
- client = InferenceClient("HuggingFaceH4/zephyr-7b-beta")
8
-
9
-
10
- def respond(
11
- message,
12
- history: list[tuple[str, str]],
13
- system_message,
14
- max_tokens,
15
- temperature,
16
- top_p,
17
- ):
18
- messages = [{"role": "system", "content": system_message}]
19
-
20
- for val in history:
21
- if val[0]:
22
- messages.append({"role": "user", "content": val[0]})
23
- if val[1]:
24
- messages.append({"role": "assistant", "content": val[1]})
25
-
26
- messages.append({"role": "user", "content": message})
27
-
28
- response = ""
29
-
30
- for message in client.chat_completion(
31
- messages,
32
- max_tokens=max_tokens,
33
- stream=True,
34
- temperature=temperature,
35
- top_p=top_p,
36
- ):
37
- token = message.choices[0].delta.content
38
-
39
- response += token
40
- yield response
41
-
42
- """
43
- For information on how to customize the ChatInterface, peruse the gradio docs: https://www.gradio.app/docs/chatinterface
44
- """
45
- demo = gr.ChatInterface(
46
- respond,
47
- additional_inputs=[
48
- gr.Textbox(value="You are a friendly Chatbot.", label="System message"),
49
- gr.Slider(minimum=1, maximum=2048, value=512, step=1, label="Max new tokens"),
50
- gr.Slider(minimum=0.1, maximum=4.0, value=0.7, step=0.1, label="Temperature"),
51
- gr.Slider(
52
- minimum=0.1,
53
- maximum=1.0,
54
- value=0.95,
55
- step=0.05,
56
- label="Top-p (nucleus sampling)",
57
- ),
58
- ],
59
- )
60
-
61
-
62
- if __name__ == "__main__":
63
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
2
+ from gradio_multimodalchatbot import MultimodalChatbot
3
+ from gradio.data_classes import FileData
4
+ from backend import *
5
+
6
+
7
+ def multimodal_results(description_df):
8
+ conversation = []
9
+ for _, row in description_df.iterrows():
10
+ hotel_name = row['hotel_name']
11
+ description = row['description']
12
+ img = row['image']
13
+
14
+ img_path = f"{hotel_name}.png"
15
+ img.save(img_path)
16
+
17
+ bot_msg = {
18
+ "text": f"Here is {hotel_name}. {description}",
19
+ "files": [{"file": FileData(path=img_path)}]
20
+ }
21
+
22
+ conversation.append([{"text": "", "files": []}, bot_msg])
23
+
24
+ return conversation
25
+
26
+ def llm_results(description_df):
27
+ result_df = grouped_description(description_df)
28
+ context_result = create_prompt_result(result_df)
29
+ recommendation_prompt = build_prompt(context_result)
30
+ result = generate_text_response(recommendation_prompt)
31
+ conversation = [[{"text": "Based on your search...", "files": []}, {"text": f"**My recommendation:** {result}", "files": []}]]
32
+ return conversation
33
+
34
+
35
+ def chatbot_response(user_input, conversation):
36
+ bot_initial_message = {
37
+ "text": f"Looking for hotels in {user_input}...",
38
+ "files": []
39
+ }
40
+ conversation.append([{"text": user_input, "files": []}, bot_initial_message])
41
+
42
+ yield conversation
43
+
44
+ description_df = search_hotel(user_input)
45
+
46
+ if description_df is None or description_df.empty:
47
+ error_message = {"text": f"Sorry, I couldn't find any hotels for {user_input}. Please try another location.", "files": []}
48
+ conversation.append([{"text": user_input, "files": []}, error_message])
49
+ yield conversation
50
+ return # Exit the function early
51
+
52
+ hotel_conversation = multimodal_results(description_df)
53
+
54
+ for message_pair in hotel_conversation:
55
+ conversation.append(message_pair)
56
+ yield conversation
57
+
58
+ final_recommendation = llm_results(description_df)
59
+ for message_pair in final_recommendation:
60
+ conversation.append(message_pair)
61
+ yield conversation
62
+
63
+
64
+ def initial_conversation():
65
+ return [[
66
+ {"text": "**Welcome to Hotel Recommendation!**", "files": []},
67
+ {"text": "Please enter the place you're interested in visiting.", "files": []}
68
+ ]]
69
+
70
+ with gr.Blocks() as demo:
71
+ gr.Markdown("# 🏨 Hotel Recommendation Chatbot")
72
+ gr.Markdown("**Provide the location to discover hotels and receive personalized recommendations!**")
73
+
74
+ initial_conv = initial_conversation()
75
+ chatbot = MultimodalChatbot(value=initial_conv, height=800)
76
+
77
+ with gr.Row():
78
+ place_input = gr.Textbox(label="Enter a place", placeholder="E.g., Paris, Tokyo, New York")
79
+ send_btn = gr.Button("Search Hotels")
80
+
81
+ send_btn.click(chatbot_response, inputs=[place_input, chatbot], outputs=chatbot)
82
+
83
+ demo.launch(debug=True)
backend.py ADDED
@@ -0,0 +1,385 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import pandas as pd
3
+ import requests
4
+ from PIL import Image, UnidentifiedImageError
5
+ from io import BytesIO
6
+ import matplotlib.pyplot as plt
7
+ import urllib3
8
+ from transformers import pipeline
9
+ from transformers import BitsAndBytesConfig
10
+ import torch
11
+ import textwrap
12
+ import pandas as pd
13
+ import numpy as np
14
+ from haversine import haversine # Install haversine library: pip install haversine
15
+ from transformers import AutoProcessor, LlavaForConditionalGeneration
16
+ from transformers import BitsAndBytesConfig
17
+ import torch
18
+
19
+ from huggingface_hub import InferenceClient
20
+ IS_SPACES_ZERO = os.environ.get("SPACES_ZERO_GPU", "0") == "1"
21
+ IS_SPACE = os.environ.get("SPACE_ID", None) is not None
22
+
23
+ device = "cuda" if torch.cuda.is_available() else "cpu"
24
+ LOW_MEMORY = os.getenv("LOW_MEMORY", "0") == "1"
25
+ print(f"Using device: {device}")
26
+ print(f"low memory: {LOW_MEMORY}")
27
+ # Define BitsAndBytesConfig
28
+
29
+ # Ensure model is on the correct device
30
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
31
+
32
+ quantization_config = BitsAndBytesConfig(
33
+ load_in_4bit=True,
34
+ bnb_4bit_compute_dtype=torch.float16
35
+ )
36
+
37
+
38
+ model_id = "llava-hf/llava-1.5-7b-hf"
39
+
40
+ processor = AutoProcessor.from_pretrained(model_id)
41
+
42
+
43
+ model = LlavaForConditionalGeneration.from_pretrained(model_id, quantization_config=quantization_config, device_map="auto")
44
+ model.to(device)
45
+
46
+
47
+ import os
48
+ import requests
49
+
50
+ url = 'https://github.com/ruslanmv/watsonx-with-multimodal-llava/raw/master/geocoded_hotels.csv'
51
+ filename = 'geocoded_hotels.csv'
52
+
53
+ # Check if the file already exists
54
+ if not os.path.isfile(filename):
55
+ response = requests.get(url)
56
+
57
+ if response.status_code == 200:
58
+ with open(filename, 'wb') as f:
59
+ f.write(response.content)
60
+ print(f"File {filename} downloaded successfully!")
61
+ else:
62
+ print(f"Error downloading file. Status code: {response.status_code}")
63
+ else:
64
+ print(f"File {filename} already exists.")
65
+
66
+ import os
67
+ import pandas as pd
68
+ from datasets import load_dataset
69
+ import pyarrow
70
+
71
+ # 1. Get the Current Directory
72
+ current_directory = os.getcwd()
73
+
74
+ # 2. Construct the Full Path to the CSV File
75
+ csv_file_path = os.path.join(current_directory, 'hotel_multimodal.csv')
76
+
77
+ # 3. Check if the file exists
78
+ if not os.path.exists(csv_file_path):
79
+ # If not, download the dataset
80
+ print("File not found, downloading from Hugging Face...")
81
+
82
+ dataset = load_dataset("ruslanmv/hotel-multimodal")
83
+
84
+ # Convert the 'train' dataset to a DataFrame using .to_pandas()
85
+ df_hotels = dataset['train'].to_pandas()
86
+
87
+ # 4.Save to CSV
88
+ df_hotels.to_csv(csv_file_path, index=False)
89
+ print("Dataset downloaded and saved as CSV.")
90
+
91
+
92
+ # 5. Read the CSV file
93
+ df_hotels = pd.read_csv(csv_file_path)
94
+
95
+ print("DataFrame loaded:")
96
+ geocoded_hotels_path = os.path.join(current_directory, 'geocoded_hotels.csv')
97
+ # Read the CSV file
98
+ geocoded_hotels = pd.read_csv(geocoded_hotels_path)
99
+
100
+ import requests
101
+
102
+ def get_current_location():
103
+ try:
104
+ response = requests.get('https://ipinfo.io/json')
105
+ data = response.json()
106
+
107
+ location = data.get('loc', '')
108
+ if location:
109
+ latitude, longitude = map(float, location.split(','))
110
+ return latitude, longitude
111
+ else:
112
+ return None, None
113
+ except Exception as e:
114
+ print(f"An error occurred: {e}")
115
+ return None, None
116
+
117
+ latitude, longitude = get_current_location()
118
+ if latitude and longitude:
119
+ print(f"Current location: Latitude = {latitude}, Longitude = {longitude}")
120
+ else:
121
+ print("Could not retrieve the current location.")
122
+
123
+
124
+ from geopy.geocoders import Nominatim
125
+
126
+ def get_coordinates(location_name):
127
+ """Fetches latitude and longitude coordinates for a given location name.
128
+
129
+ Args:
130
+ location_name (str): The name of the location (e.g., "Rome, Italy").
131
+
132
+ Returns:
133
+ tuple: A tuple containing the latitude and longitude (float values),
134
+ or None if the location is not found.
135
+ """
136
+
137
+ geolocator = Nominatim(user_agent="coordinate_finder")
138
+ location = geolocator.geocode(location_name)
139
+
140
+ if location:
141
+ return location.latitude, location.longitude
142
+ else:
143
+ return None # Location not found
144
+
145
+
146
+
147
+ def find_nearby(place=None):
148
+ if place!=None:
149
+ coordinates = get_coordinates(place)
150
+ if coordinates:
151
+ latitude, longitude = coordinates
152
+ print(f"The coordinates of {place} are: Latitude: {latitude}, Longitude: {longitude}")
153
+ else:
154
+ print(f"Location not found: {place}")
155
+ else:
156
+ latitude, longitude = get_current_location()
157
+ if latitude and longitude:
158
+ print(f"Current location: Latitude = {latitude}, Longitude = {longitude}")
159
+ # Load the geocoded_hotels DataFrame
160
+ current_directory = os.getcwd()
161
+ geocoded_hotels_path = os.path.join(current_directory, 'geocoded_hotels.csv')
162
+ geocoded_hotels = pd.read_csv(geocoded_hotels_path)
163
+
164
+ # Define input coordinates for the reference location
165
+ reference_latitude = latitude
166
+ reference_longitude = longitude
167
+
168
+ # Haversine Distance Function
169
+ def calculate_haversine_distance(lat1, lon1, lat2, lon2):
170
+ """Calculates the Haversine distance between two points on the Earth's surface."""
171
+ return haversine((lat1, lon1), (lat2, lon2))
172
+
173
+ # Calculate distances to all other points in the DataFrame
174
+ geocoded_hotels['distance_km'] = geocoded_hotels.apply(
175
+ lambda row: calculate_haversine_distance(
176
+ reference_latitude, reference_longitude, row['latitude'], row['longitude']
177
+ ),
178
+ axis=1
179
+ )
180
+
181
+ # Sort by distance and get the top 5 closest points
182
+ closest_hotels = geocoded_hotels.sort_values(by='distance_km').head(5)
183
+
184
+ # Display the results
185
+ print("The 5 closest locations are:\n")
186
+ print(closest_hotels)
187
+ return closest_hotels
188
+
189
+ @spaces.GPU
190
+ # Define the respond function
191
+ def search_hotel(place=None):
192
+ import os
193
+ import pandas as pd
194
+ import requests
195
+ from PIL import Image, UnidentifiedImageError
196
+ from io import BytesIO
197
+ import urllib3
198
+ from transformers import pipeline
199
+ from transformers import BitsAndBytesConfig
200
+ import torch
201
+
202
+ # Suppress the InsecureRequestWarning
203
+ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
204
+
205
+ # 1. Get the Current Directory
206
+ current_directory = os.getcwd()
207
+ # 2. Construct the Full Path to the CSV File
208
+ csv_file_path = os.path.join(current_directory, 'hotel_multimodal.csv')
209
+ # Read the CSV file
210
+ df_hotels = pd.read_csv(csv_file_path)
211
+ geocoded_hotels_path = os.path.join(current_directory, 'geocoded_hotels.csv')
212
+ # Read the CSV file
213
+ geocoded_hotels = pd.read_csv(geocoded_hotels_path)
214
+
215
+ # Assuming find_nearby function is defined elsewhere
216
+ df_found = find_nearby(place)
217
+
218
+ # Converting df_found[["hotel_id"]].values to a list
219
+ hotel_ids = df_found["hotel_id"].values.tolist()
220
+
221
+ # Extracting rows from df_hotels where hotel_id is in the list hotel_ids
222
+ filtered_df = df_hotels[df_hotels['hotel_id'].isin(hotel_ids)]
223
+
224
+ # Ordering filtered_df by the order of hotel_ids
225
+ filtered_df['hotel_id'] = pd.Categorical(filtered_df['hotel_id'], categories=hotel_ids, ordered=True)
226
+ filtered_df = filtered_df.sort_values('hotel_id').reset_index(drop=True)
227
+
228
+ # Define the quantization config and model ID
229
+ quantization_config = BitsAndBytesConfig(
230
+ load_in_4bit=True,
231
+ bnb_4bit_compute_dtype=torch.float16
232
+ )
233
+
234
+ model_id = "llava-hf/llava-1.5-7b-hf"
235
+
236
+ # Initialize the pipeline
237
+ pipe = pipeline("image-to-text", model=model_id, model_kwargs={"quantization_config": quantization_config})
238
+
239
+ # Group by hotel_id and take the first 2 image URLs for each hotel
240
+ grouped_df = filtered_df.groupby('hotel_id', observed=True).head(2)
241
+
242
+ # Create a new DataFrame for storing image descriptions
243
+ description_data = []
244
+
245
+ # Download and generate descriptions for the images
246
+ for index, row in grouped_df.iterrows():
247
+ hotel_id = row['hotel_id']
248
+ hotel_name = row['hotel_name']
249
+ image_url = row['image_url']
250
+
251
+ try:
252
+ response = requests.get(image_url, verify=False)
253
+ response.raise_for_status() # Check for request errors
254
+ img = Image.open(BytesIO(response.content))
255
+
256
+ # Generate description for the image
257
+ prompt = "USER: <image>\nAnalyze this image. Give me feedback on whether this hotel is worth visiting based on the picture. Provide a summary review.\nASSISTANT:"
258
+ outputs = pipe(img, prompt=prompt, generate_kwargs={"max_new_tokens": 200})
259
+ description = outputs[0]["generated_text"].split("\nASSISTANT:")[-1].strip()
260
+
261
+ # Append data to the list
262
+ description_data.append({
263
+ 'hotel_name': hotel_name,
264
+ 'hotel_id': hotel_id,
265
+ 'image': img,
266
+ 'description': description
267
+ })
268
+ except (requests.RequestException, UnidentifiedImageError):
269
+ print(f"Skipping image at URL: {image_url}")
270
+
271
+ # Create a DataFrame from the description data
272
+ description_df = pd.DataFrame(description_data)
273
+ return description_df
274
+
275
+
276
+ def show_hotels(place=None):
277
+ description_df = search_hotel(place)
278
+
279
+ # Calculate the number of rows needed
280
+ num_images = len(description_df)
281
+ num_rows = (num_images + 1) // 2 # Two images per row
282
+
283
+ fig, axs = plt.subplots(num_rows * 2, 2, figsize=(20, 10 * num_rows))
284
+
285
+ current_index = 0
286
+
287
+ for _, row in description_df.iterrows():
288
+ img = row['image']
289
+ description = row['description']
290
+
291
+ if img is None: # Skip if the image is missing
292
+ continue
293
+
294
+ row_idx = (current_index // 2) * 2
295
+ col_idx = current_index % 2
296
+
297
+ # Plot the image
298
+ axs[row_idx, col_idx].imshow(img)
299
+ axs[row_idx, col_idx].axis('off')
300
+ axs[row_idx, col_idx].set_title(f"{row['hotel_name']}\nHotel ID: {row['hotel_id']} Image {current_index + 1}", fontsize=16)
301
+
302
+ # Wrap the description text
303
+ wrapped_description = "\n".join(textwrap.wrap(description, width=50))
304
+
305
+ # Plot the description
306
+ axs[row_idx + 1, col_idx].text(0.5, 0.5, wrapped_description, ha='center', va='center', wrap=True, fontsize=14)
307
+ axs[row_idx + 1, col_idx].axis('off')
308
+
309
+ current_index += 1
310
+
311
+ # Hide any unused subplots
312
+ total_plots = (current_index + 1) // 2 * 2
313
+ for j in range(current_index, total_plots * 2):
314
+ row_idx = (j // 2) * 2
315
+ col_idx = j % 2
316
+ if row_idx < num_rows * 2:
317
+ axs[row_idx, col_idx].axis('off')
318
+ if row_idx + 1 < num_rows * 2:
319
+ axs[row_idx + 1, col_idx].axis('off')
320
+
321
+ plt.tight_layout()
322
+ plt.show()
323
+
324
+ def grouped_description(description_df):
325
+
326
+ # Group by 'hotel_id' and aggregate descriptions
327
+ grouped_descriptions = description_df.groupby('hotel_id')['description'].apply(lambda x: ' '.join(x.astype(str))).reset_index()
328
+
329
+ # Merge with original DataFrame to get hotel names
330
+ result_df = pd.merge(grouped_descriptions, description_df[['hotel_id', 'hotel_name']], on='hotel_id', how='left')
331
+
332
+ # Drop duplicates and keep only the first occurrence of each hotel_id
333
+ result_df = result_df.drop_duplicates(subset='hotel_id', keep='first')
334
+
335
+ # Reorder columns
336
+ result_df = result_df[['hotel_name', 'hotel_id', 'description']]
337
+ return result_df
338
+
339
+ # prompt: please create a new python function that given the result_df as an input create a single prompt where for given hotel_name you append the hotel_id and description , such we can use later this as context for a future llm query
340
+
341
+ def create_prompt_result(result_df):
342
+ prompt = ""
343
+ for _, row in result_df.iterrows():
344
+ hotel_name = row['hotel_name']
345
+ hotel_id = row['hotel_id']
346
+ description = row['description']
347
+ prompt += f"Hotel Name: {hotel_name}\nHotel ID: {hotel_id}\nDescription: {description}\n\n"
348
+ return prompt
349
+ from transformers import pipeline, BitsAndBytesConfig
350
+ import torch
351
+ from langchain import PromptTemplate
352
+
353
+ # Create a LangChain prompt template for the hotel recommendation
354
+ hotel_recommendation_template = """
355
+ <s>[INST] <<SYS>>
356
+ You are a helpful and informative chatbot assistant.
357
+ <</SYS>>
358
+ Based on the following hotel descriptions, recommend the best hotel:
359
+ {context_result}
360
+ [/INST]
361
+ """
362
+ @spaces.GPU
363
+ # Define the respond function
364
+ # Use LangChain to create a prompt based on the template
365
+ def build_prompt(context_result):
366
+ prompt_template = PromptTemplate(template=hotel_recommendation_template)
367
+ return prompt_template.format(context_result=context_result)
368
+
369
+ # Quantization configuration for efficient model loading
370
+ quantization_config = BitsAndBytesConfig(
371
+ load_in_4bit=True,
372
+ bnb_4bit_compute_dtype=torch.float16
373
+ )
374
+
375
+ # Initialize the text generation pipeline
376
+ pipe_text = pipeline("text-generation", model="mistralai/Mistral-7B-Instruct-v0.2",
377
+ model_kwargs={"quantization_config": quantization_config})
378
+
379
+ def generate_text_response(prompt):
380
+ outputs = pipe_text(prompt, max_new_tokens=500)
381
+ # Extract only the response after the instruction token
382
+ response = outputs[0]['generated_text'].split("[/INST]")[-1].strip()
383
+ return response
384
+ #place='Genova Italia'
385
+ #show_hotels(place)