nurasaki commited on
Commit
d519be4
·
1 Parent(s): cb3dcae

Adapted from Hotel Tools

Browse files
Files changed (4) hide show
  1. .gitignore +0 -1
  2. app.py +18 -4
  3. src/vectorstore.py +40 -0
  4. tools.py +22 -359
.gitignore CHANGED
@@ -4,4 +4,3 @@ __pycache__
4
  .qodo
5
  data/
6
  .DS_Store
7
-
 
4
  .qodo
5
  data/
6
  .DS_Store
 
app.py CHANGED
@@ -1,17 +1,30 @@
 
 
 
1
  import gradio as gr
2
  from gradio import ChatMessage
3
 
4
  import json
5
  from openai import OpenAI
6
- from tools import tools, oitools
7
- from dotenv import load_dotenv
8
  from datetime import datetime
9
  import os
10
  import re
 
 
 
 
 
11
 
12
  load_dotenv(".env")
13
  HF_TOKEN = os.environ.get("HF_TOKEN")
14
- BASE_URL = os.environ.get("BASE_URL")
 
 
 
 
 
 
 
15
 
16
  SYSTEM_PROMPT_TEMPLATE = """You are an AI assistant designed to assist users with a **hotel booking and information system**. Your primary role is to provide detailed and accurate information about the hotel, including available accommodations, facilities, dining options, and reservation services. You can assist with **hotel room bookings**, modify or cancel reservations, and answer general inquiries about the hotel.
17
 
@@ -28,11 +41,11 @@ Do **not** ask for further details about restaurant or flight bookings. Simply c
28
  Today’s date is **{date}**."""
29
 
30
 
31
- # print(json.dumps(oitools, indent=2))
32
  client = OpenAI(
33
  base_url=f"{BASE_URL}/v1",
34
  api_key=HF_TOKEN
35
  )
 
36
 
37
  def today_date():
38
  return datetime.today().strftime('%A, %B %d, %Y, %I:%M %p')
@@ -41,6 +54,7 @@ def today_date():
41
  def clean_json_string(json_str):
42
  return re.sub(r'[ ,}\s]+$', '', json_str) + '}'
43
 
 
44
  def completion(history, model, system_prompt: str, tools=None):
45
  messages = [{"role": "system", "content": system_prompt.format(date=today_date())}]
46
  for msg in history:
 
1
+ from dotenv import load_dotenv
2
+
3
+
4
  import gradio as gr
5
  from gradio import ChatMessage
6
 
7
  import json
8
  from openai import OpenAI
 
 
9
  from datetime import datetime
10
  import os
11
  import re
12
+ import logging
13
+
14
+ logging.basicConfig(level=logging.INFO, format='[%(asctime)s][%(levelname)s] - %(message)s')
15
+ # logging.getLogger().setLevel(logging.INFO)
16
+
17
 
18
  load_dotenv(".env")
19
  HF_TOKEN = os.environ.get("HF_TOKEN")
20
+ BASE_URL = os.environ.get("BASE_URL")
21
+ EMBEDDINGS = os.environ.get("EMBEDDINGS")
22
+
23
+ print("HF_TOKEN: ", HF_TOKEN)
24
+ print("BASE_URL: ", BASE_URL)
25
+
26
+
27
+ from tools import tools, oitools
28
 
29
  SYSTEM_PROMPT_TEMPLATE = """You are an AI assistant designed to assist users with a **hotel booking and information system**. Your primary role is to provide detailed and accurate information about the hotel, including available accommodations, facilities, dining options, and reservation services. You can assist with **hotel room bookings**, modify or cancel reservations, and answer general inquiries about the hotel.
30
 
 
41
  Today’s date is **{date}**."""
42
 
43
 
 
44
  client = OpenAI(
45
  base_url=f"{BASE_URL}/v1",
46
  api_key=HF_TOKEN
47
  )
48
+ logging.info(f"Client initialized: {client}")
49
 
50
  def today_date():
51
  return datetime.today().strftime('%A, %B %d, %Y, %I:%M %p')
 
54
  def clean_json_string(json_str):
55
  return re.sub(r'[ ,}\s]+$', '', json_str) + '}'
56
 
57
+
58
  def completion(history, model, system_prompt: str, tools=None):
59
  messages = [{"role": "system", "content": system_prompt.format(date=today_date())}]
60
  for msg in history:
src/vectorstore.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_community.vectorstores import FAISS
2
+ from langchain_community.embeddings import HuggingFaceEmbeddings
3
+ from huggingface_hub import snapshot_download
4
+ import logging
5
+
6
+
7
+ class VectorStore:
8
+ def __init__(self, embeddings_model, vs_local_path=None, vs_hf_path=None, number_of_contexts=2):
9
+
10
+ self.number_of_contexts = number_of_contexts
11
+
12
+ logging.info("Loading vectorstore...")
13
+
14
+ embeddings = HuggingFaceEmbeddings(model_name=embeddings_model)
15
+ logging.info(f"Loaded embeddings model: {embeddings_model}")
16
+
17
+
18
+ if vs_hf_path:
19
+ hf_vectorstore = snapshot_download(repo_id=vs_hf_path)
20
+ self.vectore_store = FAISS.load_local(hf_vectorstore, embeddings, allow_dangerous_deserialization=True)
21
+ logging.info(f"Loaded vectorstore from {vs_hf_path}")
22
+ else:
23
+ self.vectore_store = FAISS.load_local(vs_local_path, embeddings, allow_dangerous_deserialization=True)
24
+ logging.info(f"Loaded vectorstore from {vs_local_path}")
25
+
26
+
27
+ def get_context(self, instruction, number_of_contexts=2):
28
+
29
+ logging.info(f"Getting context for instruction: {instruction}")
30
+ documentos = self.vectore_store.similarity_search_with_score(instruction, k=self.number_of_contexts)
31
+ return self._beautiful_context(documentos)
32
+
33
+ def _beautiful_context(self, docs):
34
+ context = ""
35
+ for doc in docs:
36
+ context += doc[0].page_content + "\n"
37
+
38
+ print("Context: ", context)
39
+ return context
40
+
tools.py CHANGED
@@ -1,44 +1,14 @@
1
- import random
2
  from abc import ABC, abstractmethod
3
- from typing import get_origin, get_args
4
- import os
5
- # from langchain.tools import tool
6
- import json
7
  from pydantic import BaseModel, Field
8
- from typing import Dict, Union
9
- import random
10
- import copy
11
  from types import UnionType
 
 
12
 
13
- from langchain_community.vectorstores import FAISS
14
- from langchain_community.embeddings import HuggingFaceEmbeddings
15
-
16
- class VectorStore:
17
- def __init__(self, embeddings_model, vectorstore):
18
- embeddings = HuggingFaceEmbeddings(model_name=embeddings_model)
19
- self.vectore_store = FAISS.load_local(vectorstore, embeddings, allow_dangerous_deserialization=True)
20
-
21
- def get_context(self, instruction, number_of_contexts=2):
22
- documentos = self.vectore_store.similarity_search_with_score(instruction, k=number_of_contexts)
23
- return self._beautiful_context(documentos)
24
-
25
- def _beautiful_context(self, docs):
26
- context = ""
27
- for doc in docs:
28
- context += doc[0].page_content + "\n"
29
- return context
30
-
31
- def read_json(data_path: str) -> tuple[list, dict]:
32
- try:
33
- with open(data_path, 'r', encoding="utf-8") as f:
34
- data = [json.loads(line) for line in f.readlines()]
35
- except:
36
- with open(data_path, 'r', encoding="utf-8") as f:
37
- data = json.loads(f.read())
38
- return data
39
 
40
- json_data = read_json("data/val_de_nuria.json")
41
- reservations = {}
42
 
43
  class ToolBase(BaseModel, ABC):
44
  @abstractmethod
@@ -109,7 +79,14 @@ class ToolBase(BaseModel, ABC):
109
 
110
  tools: Dict[str, ToolBase] = {}
111
  oitools = []
112
- vector_store = VectorStore(embeddings_model="BAAI/bge-m3", vectorstore="data/vs")
 
 
 
 
 
 
 
113
 
114
  def tool_register(cls: BaseModel):
115
  oaitool = cls.to_openai_tool()
@@ -117,28 +94,24 @@ def tool_register(cls: BaseModel):
117
  oitools.append(oaitool)
118
  tools[oaitool["function"]["name"]] = cls
119
 
120
- # @tool_register
121
- class hotel_description(ToolBase):
122
- """Retrieves basic information about the hotel, such as its name, address, contact details, and overall description."""
123
-
124
- @classmethod
125
- def invoke(cls, input: Dict) -> str:
126
- return """### **Nou Vall de Núria – Brief Description**
127
- Nestled in the stunning **Vall de Núria** in the Pyrenees, **Nou Vall de Núria** offers a perfect blend of comfort and adventure. Guests can enjoy breathtaking mountain views, premium accommodations, and excellent facilities, including an outdoor pool, gym, and sauna.
128
- The hotel features **two dining options**, serving traditional Catalan cuisine and refreshing drinks. Accommodations range from **cozy standard rooms to luxurious suites and fully equipped apartments**, catering to couples, families, and groups.
129
- For an unforgettable stay, guests can choose from **special packages**, including family-friendly stays, romantic getaways, ski adventures, and relaxation retreats. Outdoor enthusiasts can explore **hiking trails, ski slopes, and fishing spots** in the surrounding natural beauty.
130
- Whether for relaxation or adventure, **Nou Vall de Núria** promises a unique and memorable experience."""
131
-
132
 
133
  @tool_register
134
  class get_documents(ToolBase):
135
  """
136
  Retrieves general information about a region, its cities, activities, tourism, or surrounding areas based on query.
137
  """
 
 
 
138
  query: str = Field(description="An enhanced user query optimized for retrieving information")
 
139
 
140
  @classmethod
141
  def invoke(cls, input: Dict) -> str:
 
 
 
 
142
  query = input.get("query", None)
143
  if not query:
144
  return "Missing required argument: query."
@@ -146,313 +119,3 @@ class get_documents(ToolBase):
146
  # return "We are currently working on it. You can't use this tool right now—please try again later. Thank you for your patience!"
147
  return vector_store.get_context(query)
148
 
149
-
150
- @tool_register
151
- class packs(ToolBase):
152
- """Provides a list of available activity pack at the hotel."""
153
-
154
- @classmethod
155
- def invoke(cls, input: Dict) -> str:
156
- return json_data["packs"]
157
-
158
- @tool_register
159
- class hotel_facilities(ToolBase):
160
- """Provides a list of available general facilities at the hotel, which could include amenities like a spa, pool, gym, conference rooms, etc."""
161
-
162
- @classmethod
163
- def invoke(cls, input: Dict) -> str:
164
- return json_data["general_facilities"]
165
-
166
- @tool_register
167
- class restaurants_details(ToolBase):
168
- """Provides a list of available restaurants with their details."""
169
-
170
- @classmethod
171
- def invoke(cls, input: Dict) -> str:
172
- """
173
- Play a playlist by its name, starting with the first or a random song.
174
- """
175
-
176
- return json_data["restaurants"]
177
-
178
-
179
- @tool_register
180
- class restaurant_details(ToolBase):
181
- """Retrieves detailed information about a specific restaurant in the hotel, including its menu, ambiance, operating hours, and special features."""
182
-
183
- name: str = Field(default=[res["name"] for res in json_data["restaurants"]], description="Name of the resaturant")
184
-
185
- @classmethod
186
- def invoke(cls, input: Dict) -> str:
187
- """
188
- Play a playlist by its name, starting with the first or a random song.
189
- """
190
-
191
- instance = cls(**input)
192
- name = instance.name
193
-
194
- restaurante = [res for res in json_data["restaurants"] if res["name"] == name]
195
- if restaurante:
196
- return restaurante
197
- else:
198
- return f"We don't have any restaurante with the name: {name}"
199
-
200
-
201
- @tool_register
202
- class rooms_information(ToolBase):
203
- """
204
- Provides a list of available hotel rooms, including brief descriptions of each room type.
205
- """
206
- @classmethod
207
- def invoke(cls, input: Dict) -> str:
208
- return json_data["room_types"]
209
-
210
-
211
- @tool_register
212
- class check_room_availability(ToolBase):
213
- """
214
- Checks if a specified room type is available between the provided check-in and check-out dates for a given number of guests.
215
- """
216
- room_type: str = Field(default=list(json_data["room_types"].keys()), description="The type of room to check for availability.")
217
- reservation_start_date: str = Field(description="The check-in date for the reservation, formatted as 'YYYY-MM-DD' (e.g., '2025-04-01')")
218
- reservation_end_date: str = Field(description="The check-out date for the reservation, formatted as 'YYYY-MM-DD' (e.g., '2025-04-05')")
219
- guests: int = Field(description="The number of guests that will occupy the room.")
220
-
221
- @classmethod
222
- def invoke(cls, input: Dict) -> str:
223
-
224
- room_type = input.get("room_type", None)
225
- reservation_start_date = input.get("reservation_start_date", None)
226
- reservation_end_date = input.get("reservation_end_date", None)
227
- guests = input.get("guests", None)
228
-
229
- missing = []
230
- if not room_type:
231
- missing.append("room_type")
232
- if not reservation_start_date:
233
- missing.append("reservation_start_date")
234
- if not reservation_end_date:
235
- missing.append("reservation_end_date")
236
- if not guests:
237
- missing.append("guests")
238
-
239
- if len(missing):
240
- value = ", ".join(missing)
241
- return f"Unable to check the room availability. The following required arguments are missing:{value}."
242
-
243
- instance = cls(**input)
244
- room_type = instance.room_type
245
- reservation_start_date = instance.reservation_start_date
246
- reservation_end_date = instance.reservation_end_date
247
- guests = instance.guests
248
-
249
- rooms = [room for room in json_data["accomodations"]["rooms"] if room_type in room["type"]]
250
- if len(rooms) == 0:
251
- return f"There is no room exists with room type {room_type}"
252
-
253
- rooms2 = [room for room in rooms if guests <= room["number_of_guests"]]
254
- if len(rooms2) == 0:
255
- max_guests = json_data["room_types"][room_type]["number_of_guests"]
256
- return f"The number of guest is superior then the availibilty, maximum is {max_guests}"
257
-
258
- return rooms2
259
-
260
-
261
-
262
-
263
- @tool_register
264
- class make_reservation(ToolBase):
265
- """
266
- Creates a new reservation for the hotel by booking a room of the specified type for the desired dates, and associating the booking with a user.
267
- """
268
-
269
- user_name: str = Field(description="The name of user who is doing the reservation.")
270
- room_type: str = Field(default=list(json_data["room_types"].keys()), description="The type of room being reserved.")
271
- reservation_start_date: str = Field(description="The check-in date for the reservation, formatted as 'YYYY-MM-DD' (e.g., '2025-04-01')")
272
- reservation_end_date: str = Field(description="The check-out date for the reservation, formatted as 'YYYY-MM-DD' (e.g., '2025-04-05')")
273
- guests: int = Field(description="The total number of guests for the reservation. Must be a positive integer.")
274
-
275
- @classmethod
276
- def invoke(cls, input: Dict) -> str:
277
-
278
- room_type = input.get("room_type", None)
279
- reservation_start_date = input.get("reservation_start_date", None)
280
- reservation_end_date = input.get("reservation_end_date", None)
281
- guests = input.get("guests", None)
282
- user_name = input.get("user_name", None)
283
-
284
- missing = []
285
- if not room_type:
286
- missing.append("room_type")
287
- if not reservation_start_date:
288
- missing.append("reservation_start_date")
289
- if not reservation_end_date:
290
- missing.append("reservation_end_date")
291
- if not guests:
292
- missing.append("guests")
293
- if not user_name:
294
- missing.append("user_name")
295
-
296
- if len(missing):
297
- value = ", ".join(missing)
298
- return f"Unable to complete the reservation. The following required arguments are missing:{value}."
299
-
300
-
301
- instance = cls(**input)
302
- room_type = instance.room_type
303
- reservation_start_date = instance.reservation_start_date
304
- reservation_end_date = instance.reservation_end_date
305
- guests = instance.guests
306
- user_name = instance.user_name.lower()
307
-
308
- rooms = [room for room in json_data["accomodations"]["rooms"] if room_type in room["type"]]
309
- if len(rooms) == 0:
310
- return f"There is no room exists with room type {room_type}"
311
-
312
- rooms2 = [room for room in rooms if guests <= room["number_of_guests"]]
313
- if len(rooms2) == 0:
314
- max_guests = json_data["room_types"][room_type]["number_of_guests"]
315
- return f"The number of guest is superior then the availibilty, maximum is {max_guests}"
316
-
317
- room = rooms2[random.randint(0, len(rooms2) - 1)]
318
-
319
- rand = int(random.randint(0,10000000))
320
- while rand in reservations:
321
- rand = int(random.randint(0,10000000))
322
-
323
- tmp_data = {
324
- "status": "Reserved",
325
- "room_number": room["room_number"],
326
- "room_type": room_type,
327
- "reservation_start_date": reservation_start_date,
328
- "reservation_end_date": reservation_end_date,
329
- "guests": guests,
330
- "reservation_id": rand,
331
- "user_name": user_name,
332
- }
333
-
334
- reservations[rand] = tmp_data
335
-
336
- return json.dumps(tmp_data)
337
-
338
- @tool_register
339
- class cancel_reservation(ToolBase):
340
- """Playing a specific playlist by its name."""
341
-
342
- reservation_id: int = Field(description="The unique identifier of the reservation to be canceled.")
343
-
344
- @classmethod
345
- def invoke(cls, input: Dict) -> str:
346
-
347
- reservation_id = input.get("reservation_id", None)
348
-
349
- missing = []
350
- if not reservation_id:
351
- missing.append("reservation_id")
352
-
353
- if len(missing):
354
- value = ", ".join(missing)
355
- return f"Unable to cancel the reservation. The following required arguments are missing:{value}."
356
-
357
- instance = cls(**input)
358
- reservation_id = instance.reservation_id
359
-
360
-
361
-
362
- if reservation_id not in reservations:
363
- return f"There is no reservations with the id: {reservation_id}"
364
-
365
- reservations.pop(reservation_id)
366
- return f"The reservation {reservation_id} is cancled correctly"
367
-
368
- @tool_register
369
- class modify_reservation(ToolBase):
370
- """
371
- Allows a user to modify an existing reservation by updating the check-in/check-out dates or changing the room type, subject to availability.
372
- """
373
-
374
-
375
- new_room_type: str | None = Field(default=list(json_data["room_types"].keys()), description=f"The type of new room to be modified, if {None} same room will be modified.")
376
- new_reservation_start_date: str = Field(default=None, description="New check out date in format DD/MM/YYYY")
377
- new_reservation_end_date: str = Field(default=None, description="New check out date in format DD/MM/YYYY")
378
- guests: int = Field(default=None, description="New number of guests for the reservation.")
379
- reservation_id: int = Field(description="The unique identifier of the reservation to be modified.")
380
-
381
- @classmethod
382
- def invoke(cls, input: Dict) -> str:
383
-
384
- reservation_id = input.get("reservation_id", None)
385
-
386
- missing = []
387
- if not reservation_id:
388
- missing.append("reservation_id")
389
-
390
- instance = cls(**input)
391
- new_room_type = instance.new_room_type
392
- new_reservation_start_date = instance.new_reservation_start_date
393
- new_reservation_end_date = instance.new_reservation_end_date
394
- guests = instance.guests
395
- reservation_id = instance.reservation_id
396
-
397
- if len(missing):
398
- value = ", ".join(missing)
399
- return f"Unable to modify the reservation. The following required arguments are missing:{value}."
400
-
401
- if not (new_room_type or new_reservation_start_date or new_reservation_end_date or guests):
402
- return "Unable to modify the reservation. One of the following arguments must be passed: new_room_type, new_reservation_start_date, new_reservation_end_date, guests."
403
-
404
-
405
- if reservation_id not in reservations:
406
- return f"There is no reservations with the id: {reservation_id}"
407
-
408
- if new_room_type or guests:
409
- rooms = [room for room in json_data["room_types"] if new_room_type in room["type"]]
410
- if len(rooms) == 0:
411
- return f"There is no room exists with room type {new_room_type}"
412
-
413
- rooms = [room for room in rooms if guests <= room["number_of_guests"]]
414
- if len(rooms) == 0:
415
- max_guests = json_data["room_types"][new_room_type]["number_of_guests"]
416
- return f"The number of guest is superior then the availibilty, maximum is {max_guests}"
417
-
418
- room = rooms[random.randint(0, len(rooms) - 1)]
419
- room_number = room["room_number"]
420
- else:
421
- room_number = reservations[reservation_id]["room_number"]
422
-
423
-
424
- reservations[reservation_id]["guests"] = guests if guests else reservations[reservation_id]["guests"]
425
- reservations[reservation_id]["reservation_start_date"] = new_reservation_start_date if new_reservation_start_date else reservations[reservation_id]["reservation_start_date"]
426
- reservations[reservation_id]["reservation_end_date"] = new_reservation_end_date if new_reservation_end_date else reservations[reservation_id]["reservation_end_date"]
427
- reservations[reservation_id]["room_type"] = new_room_type if new_room_type else reservations[reservation_id]["room_type"]
428
- reservations[reservation_id]["room_number"] = room_number
429
- tmp_data = reservations[reservation_id]
430
- return f"The reservation {reservation_id} is modified correctly: {json.dumps(tmp_data)}"
431
-
432
- @tool_register
433
- class reservation_details(ToolBase):
434
- """Playing a specific playlist by its name."""
435
-
436
- reservation_id: int = Field(description="Id of the reservation")
437
-
438
- @classmethod
439
- def invoke(cls, input: Dict) -> str:
440
- reservation_id = input.get("reservation_id", None)
441
-
442
- missing = []
443
- if not reservation_id:
444
- missing.append("reservation_id")
445
-
446
- if len(missing):
447
- value = ", ".join(missing)
448
- return f"Unable to get the details. The following required arguments are missing:{value}."
449
-
450
- instance = cls(**input)
451
- reservation_id = instance.reservation_id
452
-
453
- if reservation_id not in reservations:
454
- return f"There is no reservations with the id: {reservation_id}"
455
-
456
-
457
- tmp_data = copy.deepcopy(reservations[reservation_id])
458
- return json.dumps(tmp_data)
 
1
+
2
  from abc import ABC, abstractmethod
3
+ from typing import Dict, Union, get_origin, get_args
 
 
 
4
  from pydantic import BaseModel, Field
 
 
 
5
  from types import UnionType
6
+ import os
7
+ import logging
8
 
9
+ from src.vectorstore import VectorStore
10
+ # from langchain.tools import tool
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
 
 
12
 
13
  class ToolBase(BaseModel, ABC):
14
  @abstractmethod
 
79
 
80
  tools: Dict[str, ToolBase] = {}
81
  oitools = []
82
+
83
+
84
+ vector_store = VectorStore(
85
+ # embeddings_model="BAAI/bge-m3",
86
+ embeddings_model=os.environ.get("EMBEDDINGS_MODEL"),
87
+ vs_local_path=os.environ.get("VS_LOCAL_PATH"),
88
+ vs_hf_path=os.environ.get("VS_HF_PATH"))
89
+
90
 
91
  def tool_register(cls: BaseModel):
92
  oaitool = cls.to_openai_tool()
 
94
  oitools.append(oaitool)
95
  tools[oaitool["function"]["name"]] = cls
96
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
  @tool_register
99
  class get_documents(ToolBase):
100
  """
101
  Retrieves general information about a region, its cities, activities, tourism, or surrounding areas based on query.
102
  """
103
+
104
+ logging.info("@tool_register: get_documents()")
105
+
106
  query: str = Field(description="An enhanced user query optimized for retrieving information")
107
+ logging.info(f"query: {query}")
108
 
109
  @classmethod
110
  def invoke(cls, input: Dict) -> str:
111
+
112
+ logging.info(f"get_documents.invoke() input: {input}")
113
+ # Check if the input is a dictionary
114
+
115
  query = input.get("query", None)
116
  if not query:
117
  return "Missing required argument: query."
 
119
  # return "We are currently working on it. You can't use this tool right now—please try again later. Thank you for your patience!"
120
  return vector_store.get_context(query)
121