You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

396 lines
14 KiB

import re
import json
import os
import openai
from dotenv import load_dotenv
import requests
from bot_strings import botstrings
# Call the load_dotenv function
load_dotenv()
from pprint import pprint
class Report:
def __init__(self):
self.description = None
self.incident = None
self.name = None
self.role = None
self.location = None
self.image = None
self.size = None
self.shape = None
self.color = None
self.organization = None
self.type = None
self.marked: bool = None
self.blockage: bool = None
self.area_description = None
self.contact_person_phone_number = False
self.contact_person_name = False
self.info_groups = [
["description", "color", "size", "shape"],
["image"],
["location", "area_description"],
["incident"],
["blockage", "marked"],
["contact_person_name", "contact_person_phone_number"],
["organization", "name", "role"],
]
self.descriptions = {
"description": "A general description of the object.",
"incident": "Has there been an incident related to the object?",
"name": "The name of the person reporting.",
"role": "The role or occupation of the person reporting.",
"location": "The location of the object.", # TODO Add more details
"image": "A picture of the object.",
"size": "The size of the object.",
"shape": "The shape of the object.",
"color": "The color of the object.",
"organization": "The organization of the person reporting.",
"type": "The type of the object.",
"marked": "Is the object marked?",
"blockage": "Is the object blocking anything?",
"area_description": "A description of the area where the object is located.",
"contact_person_phone_number": "The phone number of the contact person.",
"contact_person_name": "The name of the contact person.",
}
self.looking_for: list = self.info_groups[0]
class BaseBot:
def __init__(self) -> None:
# Fetch the OpenAI key from the environment variables
self.OPEN_AI_KEY = os.getenv("OPEN_AI_KEY")
# # Set the OpenAI key
# openai.api_key = self.OPEN_AI_KEY
self.url = "https://api.openai.com/v1/chat/completions"
self.headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {self.OPEN_AI_KEY}",
}
self.memory: list[dict] = None
def generate(self, message):
message = re.sub(" +", " ", message)
if self.memory:
messages = self.memory
if messages[-1]["content"] != message:
messages.append({"role": "user", "content": message})
else:
messages = [
{"role": "system", "content": self.system_prompt},
{"role": "user", "content": message},
]
data = {"model": "gpt-3.5-turbo", "messages": messages}
# Print the last message in yellow
last_message = messages[-1]["content"]
print("\n\033[94m" + last_message + "\033[0m\n")
response = requests.post(self.url, headers=self.headers, json=data).json()
answer = response["choices"][0]["message"]
print("\033[95m" + answer["content"] + "\033[0m")
return answer
class Chatbot(BaseBot):
def __init__(self) -> None:
system_prompt = botstrings.chatbot_system_prompt
super().__init__() # Inherit from BaseBot
# TODO Check if there is an older conversation with the same number.
# Set the system prompt and the first user message.
self.first_instructions_sent = False
self.informations_requested = False
self.memory = [{"role": "system", "content": system_prompt}]
def ask_for_info(self, report: Report, chatbot: "Chatbot"):
# Ask for information
looking_for = []
group_found = False
for group in report.info_groups:
if not group_found:
for info in group:
if getattr(report, info) is None:
group_found = True
looking_for.append(info)
# Check so that the set of info is not already asked for without an answer
if looking_for == report.looking_for:
for info in looking_for:
setattr(report, info, "Unanswered")
looking_for = []
group_found = False
if looking_for != []:
report.looking_for = looking_for
# Ask for the information
general_bot.memory = [
{"role": "user", "content": 'Formulate a question asking for the following information: ["name", "age"]'},
{"role": "assistant", "content": "Can you tell me about the name and age of what you've found?"},
]
if looking_for == ["image"]:
question = 'image'
else:
prompt = f"Formulate a question asking for the following information: {looking_for}"
general_bot.memory.append({"role": "user", "content": prompt})
question = general_bot.generate(prompt)["content"]
return question
else:
return False, False
class CheckerBot(BaseBot):
def __init__(self) -> None:
self.system_prompt = botstrings.checker_bot_system_prompt
super().__init__() # Inherit from BaseBot
self.history = []
def check_message_type(self, message):
prompt = f"""What kind of message is this?
'''{message}'''
Don't care about the content, only the type of message. \
Answer with any of the following: ["greeting", "question", "information", "statement"]\
"""
result = self.generate(prompt)["content"]
if "greeting" in result.lower():
message_type = "greeting"
elif "question" in result.lower():
message_type = "question"
elif "information" in result.lower() or "statement" in result.lower(): #TODO should "statement" be here?
message_type = "information"
return message_type
def check_answer(self, answer, question, chatbot: Chatbot):
return True # TODO We need to fins a good way to check if the answer is an answer to the question.
question = question.replace("\n", " ")
answer = answer.replace("\n", " ")
if question == botstrings.first_instructions:
question = "What have you found?"
prompt = f'''
Have a look at this conversation:\n
"""
{chat2string(chatbot.memory[-4:]).strip()}
"""\n
Is the last message a resonable answer to the last question ({question})?
Answer ONLY with any of the following: "True", "False", "Unclear"
'''.strip()
result = self.generate(prompt)["content"]
if "unclear" in result.lower():
prompt = f"""
A user is having a conversation with an assistant. This is the conversation:\n'''{chatbot.memory}'''\
Is the last message ('''{answer}''') an answer to the question ('{question}')?
Answer ONLY with any of the following: "True", "False", "Unclear"\
"""
self.memory.append({"role": "user", "content": prompt})
result = self.generate(prompt)
if "true" in result.lower():
answered = True
elif "false" in result.lower() or "unclear" in result.lower():
answered = False
return answered
def check_for_info(self, user_message, report: Report, looking_for: list, n_try=0):
info_dict = {}
for info in looking_for:
if getattr(report, info) is None:
info_dict[info] = report.descriptions[info]
prompt = f""""
This is a message from a user: '''{user_message}'''\n
This is a dict describing what info I want:\n\n{info_dict} \n\n\
Take a look at the message and create a dictionary with the information that is requested.\
There might not be information available in the mesasge for all fields. \
If you can't find information for a certain field, fill that with a python null value ("None" or "null").
Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation.\
"""
n_try += 1
result = self.generate(prompt)
try:
json_content = json.loads(
result["content"]
) # Parse the string back into a dictionary
for key, value in json_content.items():
if value not in ["null", "None", "", "Unknown"]:
setattr(report, key, value)
except:
try:
result_content = result_content[
result_content.find("{") : result_content.rfind("}") + 1
]
json_content = json.loads(
result["content"]
) # Parse the string back into a dictionary
for key, value in json_content.items():
if value not in ["null", "None", "", "Unknown"]:
setattr(report, key, value)
except:
if n_try > 3:
return False
self.check_for_info(user_message, report, looking_for, n_try=n_try)
return json_content
def check_for_tips(self, bot_message):
# Check if the message is a tip
prompt = f"""
An assistant wants to send this message to a user: '''{bot_message}'''\
Is the message a tip or recommendation on how to handle a suspicious object?
Answer ONLY with any of the following: "True", "False"\
"""
result = self.generate(prompt)["content"]
print(result)
if "true" in result.lower():
is_tip = True
elif "false" in result.lower():
is_tip = False
return is_tip
class GeneralBot(BaseBot):
def __init__(self) -> None:
self.system_prompt = botstrings.general_bot_system_prompt
super().__init__()
self.memory = []
def send(message, check=True):
# Check if the message is a tip.
if check:
tip = checker_bot.check_for_tips(message)
else:
tip = False
if tip:
return False
else:
print(message) # TODO Send the message to the user
return True
def chat2string(chat: list):
chat_string = ""
for message in chat:
chat_string += f"{message['role']}: {message['content']}\n"
return chat_string
if __name__ == "__main__":
#! For testing
user_input = "I have found a strange looking thing on my lawn."
# Initiate the chatbot.
chatbot = Chatbot(
first_message=user_input,
system_prompt="You are an assistant chatting with a user.",
) # TODO ? Add phone number, etc.
checker_bot = CheckerBot()
general_bot = GeneralBot()
report = Report()
while True:
# Check the message type
message_type = checker_bot.check_message_type(message=user_input)
print("Message type:", message_type)
if "greeting" in message_type.lower():
# Answer the greeting
bot_answer = chatbot.generate()
if not chatbot.first_instructions_sent:
# Give instructions for how to use the bot
chatbot.instructions_sent = True
chatbot.informations_requested = True
chatbot.memory.append(
{"role": "system", "content": botstrings.first_instructions}
)
else:
chatbot.memory.append(bot_answer)
elif "question" in message_type.lower():
if not chatbot.first_instructions_sent:
# Give instructions for how to use the bot
chatbot.first_instructions_sent = True
chatbot.memory.append(
{"role": "system", "content": botstrings.first_instructions}
)
bot_answer = chatbot.generate() # TODO How to handle questions?
# Check if the answer is an answer to the question.
answered = checker_bot.check_answer(
bot_answer["content"], user_input, chatbot
)
if not answered:
bot_answer = "I could not understand your question. Please ask again."
send(bot_answer)
chatbot.memory.append({"role": "system", "content": bot_answer})
elif "information" in message_type.lower():
if not chatbot.first_instructions_sent:
# Give instructions for how to use the bot
chatbot.first_instructions_sent = True
chatbot.informations_requested = True
chatbot.memory.append(
{"role": "system", "content": botstrings.first_instructions}
)
send(botstrings.first_instructions, check=False)
else:
if chatbot.informations_requested:
answered = checker_bot.check_answer(
user_input, chatbot.memory[-1]["content"], chatbot
)
if answered:
# Ask for information and send that message to the report
result = checker_bot.check_for_info(
user_input, report, report.looking_for
)
if result:
pprint(result)
else:
send(
"I could not understand your message. Please try again.",
check=False,
)
looking_for = chatbot.ask_for_info(report)
else:
send(
"I could not understand your message. Please try again.",
check=False,
)
else:
print("Unknown message type")
user_input = input(">>> ")
if os.path.isfile(user_input):
report.image = user_input
else:
print("User input is not a valid image file.")
general_bot = GeneralBot()