Spaces:
Running
Running
import random | |
from abc import ABC, abstractmethod | |
from typing import get_origin, get_args | |
import os | |
# from langchain.tools import tool | |
import json | |
from pydantic import BaseModel, Field | |
from typing import Dict, Union | |
import random | |
import copy | |
from types import UnionType | |
from langchain_community.vectorstores import FAISS | |
from langchain_community.embeddings import HuggingFaceEmbeddings | |
class VectorStore: | |
def __init__(self, embeddings_model, vectorstore): | |
embeddings = HuggingFaceEmbeddings(model_name=embeddings_model) | |
self.vectore_store = FAISS.load_local(vectorstore, embeddings, allow_dangerous_deserialization=True) | |
def get_context(self, instruction, number_of_contexts=2): | |
documentos = self.vectore_store.similarity_search_with_score(instruction, k=number_of_contexts) | |
return self._beautiful_context(documentos) | |
def _beautiful_context(self, docs): | |
context = "" | |
for doc in docs: | |
context += doc[0].page_content + "\n" | |
return context | |
def read_json(data_path: str) -> tuple[list, dict]: | |
try: | |
with open(data_path, 'r', encoding="utf-8") as f: | |
data = [json.loads(line) for line in f.readlines()] | |
except: | |
with open(data_path, 'r', encoding="utf-8") as f: | |
data = json.loads(f.read()) | |
return data | |
json_data = read_json("data/val_de_nuria.json") | |
reservations = {} | |
class ToolBase(BaseModel, ABC): | |
def invoke(cls, input: Dict): | |
pass | |
def to_openai_tool(cls): | |
""" | |
Extracts function metadata from a Pydantic class, including function name, parameters, and descriptions. | |
Formats it into a structure similar to OpenAI's function metadata. | |
""" | |
function_metadata = { | |
"type": "function", | |
"function": { | |
"name": cls.__name__, # Function name is same as the class name, in lowercase | |
"description": cls.__doc__.strip(), | |
"parameters": { | |
"type": "object", | |
"properties": {}, | |
"required": [], | |
}, | |
}, | |
} | |
# Iterate over the fields to add them to the parameters | |
for field_name, field_info in cls.model_fields.items(): | |
# Field properties | |
field_type = "string" # Default to string, will adjust if it's a different type | |
annotation = field_info.annotation.__args__[0] if getattr(field_info.annotation, "__origin__", None) is Union else field_info.annotation | |
has_none = False | |
if get_origin(annotation) is UnionType: # Check if it's a Union type | |
args = get_args(annotation) | |
if type(None) in args: | |
has_none = True | |
args = [arg for arg in args if type(None) != arg] | |
if len(args) > 1: | |
raise TypeError("It can be union of only a valid type (str, int, bool, etc) and None") | |
elif len(args) == 0: | |
raise TypeError("There must be a valid type (str, int, bool, etc) not only None") | |
else: | |
annotation = args[0] | |
if annotation == int: | |
field_type = "integer" | |
elif annotation == bool: | |
field_type = "boolean" | |
# Add the field's description and type to the properties | |
function_metadata["function"]["parameters"]["properties"][field_name] = { | |
"type": field_type, | |
"description": field_info.description, | |
} | |
# Determine if the field is required (not Optional or None) | |
if field_info.is_required(): | |
function_metadata["function"]["parameters"]["required"].append(field_name) | |
has_none = True | |
# If there's an enum (like for `unit`), add it to the properties | |
if hasattr(field_info, 'default') and field_info.default is not None and isinstance(field_info.default, list): | |
function_metadata["function"]["parameters"]["properties"][field_name]["enum"] = field_info.default | |
if not has_none: | |
function_metadata["function"]["parameters"]["required"].append(field_name) | |
return function_metadata | |
tools: Dict[str, ToolBase] = {} | |
oitools = [] | |
vector_store = VectorStore(embeddings_model="BAAI/bge-m3", vectorstore="data/vs") | |
def tool_register(cls: BaseModel): | |
oaitool = cls.to_openai_tool() | |
oitools.append(oaitool) | |
tools[oaitool["function"]["name"]] = cls | |
# @tool_register | |
class hotel_description(ToolBase): | |
"""Retrieves basic information about the hotel, such as its name, address, contact details, and overall description.""" | |
def invoke(cls, input: Dict) -> str: | |
return """### **Nou Vall de Núria – Brief Description** | |
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. | |
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. | |
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. | |
Whether for relaxation or adventure, **Nou Vall de Núria** promises a unique and memorable experience.""" | |
class get_hotal_general_information(ToolBase): | |
""" Retrieves general information about a hotel, including its background, amenities, location, and nearby attractions based on the query. """ | |
query: str = Field(description="User's question or query for retrieving the information.") | |
def invoke(cls, input: Dict) -> str: | |
query = input.get("query", None) | |
if not query: | |
return "Missing required argument: query." | |
# return "We are currently working on it. You can't use this tool right now—please try again later. Thank you for your patience!" | |
return vector_store.get_context(query) | |
class packs(ToolBase): | |
"""Provides a list of available activity pack at the hotel.""" | |
def invoke(cls, input: Dict) -> str: | |
return json_data["packs"] | |
class hotel_facilities(ToolBase): | |
"""Provides a list of available general facilities at the hotel, which could include amenities like a spa, pool, gym, conference rooms, etc.""" | |
def invoke(cls, input: Dict) -> str: | |
return json_data["general_facilities"] | |
class restaurants_details(ToolBase): | |
"""Provides a list of available restaurants with their details.""" | |
def invoke(cls, input: Dict) -> str: | |
""" | |
Play a playlist by its name, starting with the first or a random song. | |
""" | |
return json_data["restaurants"] | |
class restaurant_details(ToolBase): | |
"""Retrieves detailed information about a specific restaurant in the hotel, including its menu, ambiance, operating hours, and special features.""" | |
name: str = Field(default=[res["name"] for res in json_data["restaurants"]], description="Name of the resaturant") | |
def invoke(cls, input: Dict) -> str: | |
""" | |
Play a playlist by its name, starting with the first or a random song. | |
""" | |
instance = cls(**input) | |
name = instance.name | |
restaurante = [res for res in json_data["restaurants"] if res["name"] == name] | |
if restaurante: | |
return restaurante | |
else: | |
return f"We don't have any restaurante with the name: {name}" | |
class rooms_information(ToolBase): | |
""" | |
Provides a list of available hotel rooms, including brief descriptions of each room type. | |
""" | |
def invoke(cls, input: Dict) -> str: | |
return json_data["room_types"] | |
class check_room_availability(ToolBase): | |
""" | |
Checks if a specified room type is available between the provided check-in and check-out dates for a given number of guests. | |
""" | |
room_type: str = Field(default=list(json_data["room_types"].keys()), description="The type of room to check for availability.") | |
reservation_start_date: str = Field(description="The check-in date for the reservation, formatted as 'YYYY-MM-DD' (e.g., '2025-04-01')") | |
reservation_end_date: str = Field(description="The check-out date for the reservation, formatted as 'YYYY-MM-DD' (e.g., '2025-04-05')") | |
guests: int = Field(description="The number of guests that will occupy the room.") | |
def invoke(cls, input: Dict) -> str: | |
input["room_type"] = input.get("room_type", None) | |
room_type = input.get("room_type", None) | |
reservation_start_date = input.get("reservation_start_date", None) | |
reservation_end_date = input.get("reservation_end_date", None) | |
guests = input.get("guests", None) | |
missing = [] | |
if not room_type: | |
missing.append("room_type") | |
if not reservation_start_date: | |
missing.append("reservation_start_date") | |
if not reservation_end_date: | |
missing.append("reservation_end_date") | |
if not guests: | |
missing.append("guests") | |
if len(missing): | |
value = ", ".join(missing) | |
return f"Unable to check the room availability. The following required arguments are missing:{value}." | |
instance = cls(**input) | |
room_type = instance.room_type | |
reservation_start_date = instance.reservation_start_date | |
reservation_end_date = instance.reservation_end_date | |
guests = instance.guests | |
rooms = [room for room in json_data["accomodations"]["rooms"] if room_type in room["type"]] | |
if len(rooms) == 0: | |
return f"There is no room exists with room type {room_type}" | |
rooms2 = [room for room in rooms if guests <= room["number_of_guests"]] | |
if len(rooms2) == 0: | |
max_guests = json_data["room_types"][room_type]["number_of_guests"] | |
return f"The number of guest is superior then the availibilty, maximum is {max_guests}" | |
return rooms2 | |
class make_reservation(ToolBase): | |
""" | |
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. | |
""" | |
user_name: str = Field(description="The name of user who is doing the reservation.") | |
room_type: str = Field(default=list(json_data["room_types"].keys()), description="The type of room being reserved.") | |
reservation_start_date: str = Field(description="The check-in date for the reservation, formatted as 'YYYY-MM-DD' (e.g., '2025-04-01')") | |
reservation_end_date: str = Field(description="The check-out date for the reservation, formatted as 'YYYY-MM-DD' (e.g., '2025-04-05')") | |
guests: int = Field(description="The total number of guests for the reservation. Must be a positive integer.") | |
def invoke(cls, input: Dict) -> str: | |
input["room_type"] = input.get("room_type", None) | |
room_type = input.get("room_type", None) | |
reservation_start_date = input.get("reservation_start_date", None) | |
reservation_end_date = input.get("reservation_end_date", None) | |
guests = input.get("guests", None) | |
user_name = input.get("user_name", None) | |
missing = [] | |
if not room_type: | |
missing.append("room_type") | |
if not reservation_start_date: | |
missing.append("reservation_start_date") | |
if not reservation_end_date: | |
missing.append("reservation_end_date") | |
if not guests: | |
missing.append("guests") | |
if not user_name: | |
missing.append("user_name") | |
if len(missing): | |
value = ", ".join(missing) | |
return f"Unable to complete the reservation. The following required arguments are missing:{value}." | |
instance = cls(**input) | |
room_type = instance.room_type | |
reservation_start_date = instance.reservation_start_date | |
reservation_end_date = instance.reservation_end_date | |
guests = instance.guests | |
user_name = instance.user_name.lower() | |
rooms = [room for room in json_data["accomodations"]["rooms"] if room_type in room["type"]] | |
if len(rooms) == 0: | |
return f"There is no room exists with room type {room_type}" | |
rooms2 = [room for room in rooms if guests <= room["number_of_guests"]] | |
if len(rooms2) == 0: | |
max_guests = json_data["room_types"][room_type]["number_of_guests"] | |
return f"The number of guest is superior then the availibilty, maximum is {max_guests}" | |
room = rooms2[random.randint(0, len(rooms2) - 1)] | |
rand = int(random.randint(0,10000000)) | |
while rand in reservations: | |
rand = int(random.randint(0,10000000)) | |
tmp_data = { | |
"status": "Reserved", | |
"room_number": room["room_number"], | |
"room_type": room_type, | |
"reservation_start_date": reservation_start_date, | |
"reservation_end_date": reservation_end_date, | |
"guests": guests, | |
"reservation_id": rand, | |
"user_name": user_name, | |
} | |
reservations[rand] = tmp_data | |
return json.dumps(tmp_data) | |
class cancel_reservation(ToolBase): | |
"""Playing a specific playlist by its name.""" | |
reservation_id: int = Field(description="The unique identifier of the reservation to be canceled.") | |
def invoke(cls, input: Dict) -> str: | |
reservation_id = input.get("reservation_id", None) | |
missing = [] | |
if not reservation_id: | |
missing.append("reservation_id") | |
if len(missing): | |
value = ", ".join(missing) | |
return f"Unable to cancel the reservation. The following required arguments are missing:{value}." | |
instance = cls(**input) | |
reservation_id = instance.reservation_id | |
if reservation_id not in reservations: | |
return f"There is no reservations with the id: {reservation_id}" | |
reservations.pop(reservation_id) | |
return f"The reservation {reservation_id} is cancled correctly" | |
class modify_reservation(ToolBase): | |
""" | |
Allows a user to modify an existing reservation by updating the check-in/check-out dates or changing the room type, subject to availability. | |
One of these parameter is mandatory: new_room_type, new_reservation_start_date, new_reservation_end_date, guests. | |
""" | |
reservation_id: int = Field(description="The unique identifier of the reservation to be modified.") | |
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.") | |
new_reservation_start_date: str = Field(default=None, description="New check out date in format DD/MM/YYYY") | |
new_reservation_end_date: str = Field(default=None, description="New check out date in format DD/MM/YYYY") | |
new_guests: int = Field(default=None, description="New number of guests for the reservation.") | |
def invoke(cls, input: Dict) -> str: | |
input["new_room_type"] = input.get("new_room_type", None) | |
reservation_id = input.get("reservation_id", None) | |
missing = [] | |
if not reservation_id: | |
missing.append("reservation_id") | |
instance = cls(**input) | |
new_room_type = instance.new_room_type | |
new_reservation_start_date = instance.new_reservation_start_date | |
new_reservation_end_date = instance.new_reservation_end_date | |
new_guests = instance.new_guests | |
reservation_id = instance.reservation_id | |
if len(missing): | |
value = ", ".join(missing) | |
return f"Unable to modify the reservation. The following required arguments are missing:{value}." | |
if not (new_room_type or new_reservation_start_date or new_reservation_end_date or new_guests): | |
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, new_guests." | |
if reservation_id not in reservations: | |
return f"There is no reservations with the id: {reservation_id}" | |
if new_room_type or new_guests: | |
rooms = [room for room in json_data["accomodations"]["rooms"] if new_room_type in room["type"]] | |
if len(rooms) == 0: | |
return f"There is no room exists with room type {new_room_type}" | |
rooms = [room for room in rooms if new_guests <= room["number_of_guests"]] | |
if len(rooms) == 0: | |
max_guests = json_data["room_types"][new_room_type]["number_of_guests"] | |
return f"The number of guest is superior then the availibilty, maximum is {max_guests}" | |
room = rooms[random.randint(0, len(rooms) - 1)] | |
room_number = room["room_number"] | |
else: | |
room_number = reservations[reservation_id]["room_number"] | |
reservations[reservation_id]["guests"] = new_guests if new_guests else reservations[reservation_id]["guests"] | |
reservations[reservation_id]["reservation_start_date"] = new_reservation_start_date if new_reservation_start_date else reservations[reservation_id]["reservation_start_date"] | |
reservations[reservation_id]["reservation_end_date"] = new_reservation_end_date if new_reservation_end_date else reservations[reservation_id]["reservation_end_date"] | |
reservations[reservation_id]["room_type"] = new_room_type if new_room_type else reservations[reservation_id]["room_type"] | |
reservations[reservation_id]["room_number"] = room_number | |
tmp_data = reservations[reservation_id] | |
return f"The reservation {reservation_id} is modified correctly: {json.dumps(tmp_data)}" | |
class get_reservation_detail(ToolBase): | |
"""Retrieve comprehensive details of a specific room reservation by providing its unique reservation ID.""" | |
reservation_id: int = Field(description="Id of the reservation") | |
def invoke(cls, input: Dict) -> str: | |
reservation_id = input.get("reservation_id", None) | |
missing = [] | |
if not reservation_id: | |
missing.append("reservation_id") | |
if len(missing): | |
value = ", ".join(missing) | |
return f"Unable to get the details. The following required arguments are missing:{value}." | |
instance = cls(**input) | |
reservation_id = instance.reservation_id | |
if reservation_id not in reservations: | |
return f"There is no reservations with the id: {reservation_id}" | |
tmp_data = copy.deepcopy(reservations[reservation_id]) | |
return json.dumps(tmp_data) | |