Spaces:
Sleeping
Sleeping
enricorampazzo
commited on
Commit
•
54af9e3
1
Parent(s):
7faadb5
code refactoring, added logic to allow users to save their details in the browser local storage
Browse files- app.py +170 -30
- llm_manager/llm_parser.py +7 -12
- local_storage/__init__.py +0 -0
- local_storage/entities.py +53 -0
- prompts/prompts_manager.py +35 -9
- prompts/verification_prompt2.txt +1 -1
- repository/intel_npu.py +17 -7
- repository/repository.py +14 -1
- requirements-base.txt +2 -1
- ui_manager.py +153 -58
- utils/env_utils.py +0 -17
app.py
CHANGED
@@ -1,41 +1,181 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
from repository.repository import get_repository
|
4 |
-
from repository.repository_abc import ModelRoles, Model
|
5 |
|
6 |
import streamlit as st
|
|
|
|
|
7 |
|
8 |
-
from
|
9 |
-
|
10 |
-
from
|
|
|
|
|
|
|
|
|
11 |
|
12 |
user_msg = "Please describe what you need to do. To get the best results try to answer all the following questions:"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
13 |
|
14 |
|
15 |
def use_streamlit():
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
if
|
30 |
-
|
31 |
-
|
32 |
-
if st.session_state.get("step") == "ask_again":
|
33 |
-
build_ui_for_ask_again(pm)
|
34 |
-
if st.session_state.get("step") == "check_category":
|
35 |
-
build_ui_for_check_category(repository, pm)
|
36 |
-
|
37 |
-
if st.session_state.get("step") == "form_created":
|
38 |
-
build_ui_for_form_created()
|
39 |
|
40 |
|
41 |
use_streamlit()
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
|
|
|
|
|
|
2 |
|
3 |
import streamlit as st
|
4 |
+
from streamlit import session_state as ss
|
5 |
+
from streamlit_local_storage import LocalStorage
|
6 |
|
7 |
+
from form.form import build_form_data_from_answers, write_pdf_form
|
8 |
+
from llm_manager.llm_parser import LlmParser
|
9 |
+
from local_storage.entities import PersonalDetails, LocationDetails, ContractorDetails
|
10 |
+
from prompts.prompts_manager import PromptsManager, Questions as Q
|
11 |
+
from repository.repository import build_repo_from_environment, get_repository
|
12 |
+
from repository.repository_abc import Model, ModelRoles
|
13 |
+
from utils.parsing_utils import check_for_missing_answers
|
14 |
|
15 |
user_msg = "Please describe what you need to do. To get the best results try to answer all the following questions:"
|
16 |
+
ls: LocalStorage = LocalStorage()
|
17 |
+
class UIManager:
|
18 |
+
def __init__(self):
|
19 |
+
self.pm: PromptsManager = PromptsManager()
|
20 |
+
self.repository = (build_repo_from_environment(self.pm.system_prompt) or
|
21 |
+
get_repository("testing",
|
22 |
+
Model("fakeModel", ModelRoles("a", "b", "c"))))
|
23 |
+
|
24 |
+
@staticmethod
|
25 |
+
def get_current_step():
|
26 |
+
return ss.get("step")
|
27 |
+
|
28 |
+
@staticmethod
|
29 |
+
def _build_base_ui():
|
30 |
+
st.markdown("## Dubai Asset Management red tape cutter")
|
31 |
+
|
32 |
+
def build_ui_for_initial_state(self, user_message):
|
33 |
+
help_ = user_message
|
34 |
+
self._build_base_ui()
|
35 |
+
with st.form("Please describe your request"):
|
36 |
+
user_input = st.text_area("Your input", height=700, label_visibility="hidden", placeholder=help_,
|
37 |
+
help=help_)
|
38 |
+
signature = st.file_uploader("Your signature", key="file_upload")
|
39 |
+
ss["signature"] = signature
|
40 |
+
submit_button = st.form_submit_button()
|
41 |
+
if submit_button:
|
42 |
+
ss["user_input"] = user_input
|
43 |
+
ss["step"] = "parsing_answers"
|
44 |
+
st.rerun()
|
45 |
+
|
46 |
+
def build_ui_for_parsing_answers(self):
|
47 |
+
self._build_base_ui()
|
48 |
+
with st.status("initialising LLM"):
|
49 |
+
self.repository.init()
|
50 |
+
with st.status("waiting for LLM"):
|
51 |
+
answer = self.repository.send_prompt(self.pm.verify_user_input_prompt(ss["user_input"]))
|
52 |
+
st.write(f"answers from LLM: {answer['content']}")
|
53 |
+
with st.status("Checking for missing answers"):
|
54 |
+
answers = LlmParser.parse_verification_prompt_answers(answer['content'])
|
55 |
+
ss["answers"] = answers
|
56 |
+
if len(answers) != len(Q):
|
57 |
+
ss["step"] = "parsing_error"
|
58 |
+
st.rerun()
|
59 |
+
ss["missing_answers"] = check_for_missing_answers(ss["answers"])
|
60 |
+
if not ss.get("missing_answers"):
|
61 |
+
ss["step"] = "check_category"
|
62 |
+
else:
|
63 |
+
ss["step"] = "ask_again"
|
64 |
+
st.rerun()
|
65 |
+
|
66 |
+
def build_ui_for_ask_again(self):
|
67 |
+
self._build_base_ui()
|
68 |
+
with st.form("form1"):
|
69 |
+
for ma in ss["missing_answers"]:
|
70 |
+
st.text_input(self.pm.questions[ma].lower(), key=ma)
|
71 |
+
submitted = st.form_submit_button("Submit answers")
|
72 |
+
if submitted:
|
73 |
+
for ma in ss["missing_answers"]:
|
74 |
+
ss["answers"][ma] = ss[ma]
|
75 |
+
ss["step"] = "check_category"
|
76 |
+
st.rerun()
|
77 |
+
|
78 |
+
def build_ui_for_check_category(self):
|
79 |
+
self._build_base_ui()
|
80 |
+
with st.status("finding the work categories applicable to your work"):
|
81 |
+
answer = self.repository.send_prompt(self.pm.get_work_category(ss["answers"][1]))
|
82 |
+
categories = LlmParser.parse_get_categories_answer(answer['content'])
|
83 |
+
|
84 |
+
with st.status("categories found, creating PDF form"):
|
85 |
+
form_data, filename = build_form_data_from_answers(ss["answers"], categories,
|
86 |
+
ss.get("signature"))
|
87 |
+
pdf_form = write_pdf_form(form_data)
|
88 |
+
pdf_form_filename = filename
|
89 |
+
ss["pdf_form"] = pdf_form
|
90 |
+
ss["pdf_form_filename"] = pdf_form_filename
|
91 |
+
ss["step"] = "form_created"
|
92 |
+
st.rerun()
|
93 |
+
|
94 |
+
def build_ui_for_form_created(self):
|
95 |
+
self._build_base_ui()
|
96 |
+
st.download_button("download form", ss["pdf_form"],
|
97 |
+
file_name=ss["pdf_form_filename"], mime="application/pdf")
|
98 |
+
start_over_button = st.button("Start over")
|
99 |
+
if start_over_button:
|
100 |
+
del ss["step"]
|
101 |
+
del ss["pdf_form"]
|
102 |
+
del ss["pdf_form_filename"]
|
103 |
+
if "signature" in ss:
|
104 |
+
del ss["signature"]
|
105 |
+
st.rerun()
|
106 |
+
|
107 |
+
def build_ui_for_parsing_error(self):
|
108 |
+
def build_form_fragment(form_, col, title, *questions):
|
109 |
+
form_.text(title)
|
110 |
+
for user_data in questions:
|
111 |
+
with col:
|
112 |
+
form_.text_input(self.pm.questions_to_field_labels()[user_data], value=ss.get("answers", {})
|
113 |
+
.get(user_data), key=f"fq_{user_data.name}")
|
114 |
+
with col:
|
115 |
+
form_.text_input("Save as", key=title.replace(" ", "_"))
|
116 |
+
|
117 |
+
self._build_base_ui()
|
118 |
+
f = st.form("Please check the following information and correct fix any inaccuracies")
|
119 |
+
col1, col2 = f.columns(2)
|
120 |
+
build_form_fragment(f, col1, "your details", Q.FULL_NAME, Q.CONTACT_NUMBER, Q.YOUR_EMAIL)
|
121 |
+
build_form_fragment(f, col2, "work details", Q.WORK_TO_DO, Q.START_DATE, Q.END_DATE)
|
122 |
+
build_form_fragment(f, col1, "location details", Q.COMMUNITY, Q.BUILDING, Q.UNIT_APT_NUMBER,
|
123 |
+
Q.OWNER_OR_TENANT)
|
124 |
+
build_form_fragment(f, col2, "contractor details", Q.COMPANY_NAME, Q.COMPANY_NUMBER, Q.COMPANY_EMAIL)
|
125 |
+
submit_data = f.form_submit_button()
|
126 |
+
if submit_data:
|
127 |
+
for i in range(len(Q)):
|
128 |
+
ss["answers"][Q(i).name] = ss[f"fq_{Q(i).name}"]
|
129 |
+
|
130 |
+
for details_key, func in [("your_details", self._get_personal_details),
|
131 |
+
("location_details", self._get_location_details),
|
132 |
+
("contractor_details", self._get_contractor_details)]:
|
133 |
+
details = func(details_key)
|
134 |
+
if details:
|
135 |
+
key = ss[details_key] # get the name under which this data should be saved
|
136 |
+
ls.setItem(key, json.dumps(details.__dict__))
|
137 |
+
|
138 |
+
@staticmethod
|
139 |
+
def _get_personal_details(personal_details_key) -> PersonalDetails | None:
|
140 |
+
if ss.get(personal_details_key):
|
141 |
+
return PersonalDetails(ss[f"fq_{Q.FULL_NAME.name}"], ss[f"fq_{Q.YOUR_EMAIL.name}"], ss[f"fq_{Q.CONTACT_NUMBER.name}"])
|
142 |
+
return None
|
143 |
+
|
144 |
+
@staticmethod
|
145 |
+
def _get_location_details(location_details_key) -> LocationDetails | None:
|
146 |
+
if ss.get(location_details_key):
|
147 |
+
return LocationDetails(ss[f"fq_{Q.OWNER_OR_TENANT.name}"], ss[f"fq_{Q.COMMUNITY.name}"],
|
148 |
+
ss[f"fq_{Q.BUILDING.name}"], ss[f"fq_{Q.UNIT_APT_NUMBER.name}"])
|
149 |
+
return None
|
150 |
+
|
151 |
+
@staticmethod
|
152 |
+
def _get_contractor_details(contractor_details_key) -> ContractorDetails | None:
|
153 |
+
if ss.get(contractor_details_key):
|
154 |
+
return ContractorDetails(ss[f"fq_{Q.COMPANY_NAME}"], ss[f"fq_{Q.COMPANY_NUMBER}"],
|
155 |
+
ss[f"fq_{Q.COMPANY_EMAIL}"])
|
156 |
+
return None
|
157 |
|
158 |
|
159 |
def use_streamlit():
|
160 |
+
|
161 |
+
um = UIManager()
|
162 |
+
|
163 |
+
if not um.get_current_step():
|
164 |
+
um.build_ui_for_initial_state(user_msg)
|
165 |
+
if um.get_current_step() == "parsing_answers":
|
166 |
+
um.build_ui_for_parsing_answers()
|
167 |
+
if um.get_current_step() == "parsing_error":
|
168 |
+
um.build_ui_for_parsing_error()
|
169 |
+
if um.get_current_step() == "ask_again":
|
170 |
+
um.build_ui_for_ask_again()
|
171 |
+
if um.get_current_step() == "check_category":
|
172 |
+
um.build_ui_for_check_category()
|
173 |
+
if um.get_current_step() == "form_created":
|
174 |
+
um.build_ui_for_form_created()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
175 |
|
176 |
|
177 |
use_streamlit()
|
178 |
+
|
179 |
+
|
180 |
+
|
181 |
+
|
llm_manager/llm_parser.py
CHANGED
@@ -1,39 +1,34 @@
|
|
1 |
from form.form import work_categories
|
|
|
2 |
|
3 |
|
4 |
class LlmParser:
|
5 |
|
6 |
@classmethod
|
7 |
-
def parse_verification_prompt_answers(cls, llm_answer) -> dict[
|
8 |
print(f"llm answer: {llm_answer}")
|
9 |
-
expected_answers_count = 13
|
10 |
answers = {}
|
11 |
i = 0
|
12 |
question_id = 0
|
13 |
-
lines = [l for l in llm_answer.split("\n") if len(l.strip()) > 0]
|
14 |
while i < len(lines):
|
15 |
line = lines[i].strip()
|
16 |
if len(line) == 0:
|
17 |
i += 1
|
18 |
-
elif line.endswith("?") and i+1<len(lines):
|
19 |
-
i+=1
|
20 |
elif "null" in lines[i]:
|
21 |
-
answers[question_id] = None
|
22 |
i += 1
|
23 |
question_id += 1
|
24 |
elif ":" in lines[i]:
|
25 |
-
answers[question_id] = line.split(":")[1]
|
26 |
i += 1
|
27 |
question_id += 1
|
28 |
else:
|
29 |
-
answers[question_id] = line
|
30 |
-
i+=1
|
31 |
question_id += 1
|
32 |
return answers
|
33 |
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
@classmethod
|
38 |
def parse_get_categories_answer(cls, category_answer) -> list[str]:
|
39 |
categories = []
|
|
|
1 |
from form.form import work_categories
|
2 |
+
from prompts.prompts_manager import Questions
|
3 |
|
4 |
|
5 |
class LlmParser:
|
6 |
|
7 |
@classmethod
|
8 |
+
def parse_verification_prompt_answers(cls, llm_answer) -> dict[Questions, str | None]:
|
9 |
print(f"llm answer: {llm_answer}")
|
|
|
10 |
answers = {}
|
11 |
i = 0
|
12 |
question_id = 0
|
13 |
+
lines = [l for l in llm_answer.split("\n") if len(l.strip()) > 0 and not l.strip().endswith("?")]
|
14 |
while i < len(lines):
|
15 |
line = lines[i].strip()
|
16 |
if len(line) == 0:
|
17 |
i += 1
|
|
|
|
|
18 |
elif "null" in lines[i]:
|
19 |
+
answers[Questions(question_id)] = None
|
20 |
i += 1
|
21 |
question_id += 1
|
22 |
elif ":" in lines[i]:
|
23 |
+
answers[Questions(question_id)] = line.split(":")[1]
|
24 |
i += 1
|
25 |
question_id += 1
|
26 |
else:
|
27 |
+
answers[Questions(question_id)] = line
|
28 |
+
i += 1
|
29 |
question_id += 1
|
30 |
return answers
|
31 |
|
|
|
|
|
|
|
32 |
@classmethod
|
33 |
def parse_get_categories_answer(cls, category_answer) -> list[str]:
|
34 |
categories = []
|
local_storage/__init__.py
ADDED
File without changes
|
local_storage/entities.py
ADDED
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import abc
|
2 |
+
import json
|
3 |
+
|
4 |
+
from streamlit_local_storage import LocalStorage
|
5 |
+
|
6 |
+
ls: LocalStorage = LocalStorage()
|
7 |
+
|
8 |
+
|
9 |
+
class SavedDetails(abc.ABC):
|
10 |
+
def __init__(self, type_: str):
|
11 |
+
self.type_ = type_
|
12 |
+
|
13 |
+
def save_to_local_storage(self, key: str):
|
14 |
+
ls.setItem(key, json.dumps(self.__dict__))
|
15 |
+
|
16 |
+
@classmethod
|
17 |
+
def load(cls, json_data: str):
|
18 |
+
data = json.loads(json_data)
|
19 |
+
type_ = data.get("type_")
|
20 |
+
if not type_ or type_ != cls.type_:
|
21 |
+
raise ValueError(f"the expected type is {cls.type_} but is actually {type_}")
|
22 |
+
return cls.__init__(**{k: v for k, v in data if k != "type"})
|
23 |
+
|
24 |
+
|
25 |
+
class PersonalDetails(SavedDetails):
|
26 |
+
type_ = "personal_details"
|
27 |
+
|
28 |
+
def __init__(self, full_name, email, contact_number):
|
29 |
+
super().__init__(self.type_)
|
30 |
+
self.full_name = full_name
|
31 |
+
self.email = email
|
32 |
+
self.contact_number = contact_number
|
33 |
+
|
34 |
+
|
35 |
+
class LocationDetails(SavedDetails):
|
36 |
+
type_ = "location_details"
|
37 |
+
|
38 |
+
def __init__(self, owner_or_tenant: str, community: str, building: str, unit_number: str):
|
39 |
+
super().__init__(self.type_)
|
40 |
+
self.owner_or_tenant = owner_or_tenant
|
41 |
+
self.community = community
|
42 |
+
self.building = building
|
43 |
+
self.unit_number = unit_number
|
44 |
+
|
45 |
+
|
46 |
+
class ContractorDetails(SavedDetails):
|
47 |
+
type_ = "contractor_details"
|
48 |
+
|
49 |
+
def __init__(self, contractor_name:str, contractor_contact_number:str, contractor_email:str):
|
50 |
+
super().__init__(self.type_)
|
51 |
+
self.contractor_name = contractor_name
|
52 |
+
self.contractor_contact_number = contractor_contact_number
|
53 |
+
self.contractor_email = contractor_email
|
prompts/prompts_manager.py
CHANGED
@@ -1,13 +1,28 @@
|
|
1 |
import datetime
|
|
|
2 |
from pathlib import Path
|
3 |
|
4 |
from utils.date_utils import get_today_date_as_dd_mm_yyyy
|
5 |
from form.form import work_categories as wc
|
6 |
|
7 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
class PromptsManager:
|
9 |
def __init__(self, work_categories: dict[str, str] = None):
|
10 |
-
|
11 |
if not work_categories:
|
12 |
self.work_categories = wc
|
13 |
base_path = Path(__file__).parent
|
@@ -22,14 +37,25 @@ class PromptsManager:
|
|
22 |
self.verification_prompt: str = verification_prompt
|
23 |
|
24 |
def verify_user_input_prompt(self, user_prompt) -> str:
|
25 |
-
return (
|
26 |
-
|
27 |
-
|
28 |
-
|
|
|
29 |
|
30 |
def get_work_category(self, work_description: str) -> str:
|
31 |
-
return (
|
32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
|
34 |
-
def ingest_user_answers(self, user_prompt: str) -> str:
|
35 |
-
return f"Ingest the following information: {user_prompt}"
|
|
|
1 |
import datetime
|
2 |
+
from enum import Enum
|
3 |
from pathlib import Path
|
4 |
|
5 |
from utils.date_utils import get_today_date_as_dd_mm_yyyy
|
6 |
from form.form import work_categories as wc
|
7 |
|
8 |
|
9 |
+
class Questions(Enum):
|
10 |
+
FULL_NAME = 0
|
11 |
+
WORK_TO_DO = 1
|
12 |
+
COMMUNITY = 2
|
13 |
+
BUILDING = 3
|
14 |
+
UNIT_APT_NUMBER = 4
|
15 |
+
OWNER_OR_TENANT = 5
|
16 |
+
START_DATE = 6
|
17 |
+
END_DATE = 7
|
18 |
+
CONTACT_NUMBER = 8
|
19 |
+
COMPANY_NAME = 9
|
20 |
+
COMPANY_EMAIL = 10
|
21 |
+
COMPANY_NUMBER = 11
|
22 |
+
YOUR_EMAIL = 12
|
23 |
+
|
24 |
class PromptsManager:
|
25 |
def __init__(self, work_categories: dict[str, str] = None):
|
|
|
26 |
if not work_categories:
|
27 |
self.work_categories = wc
|
28 |
base_path = Path(__file__).parent
|
|
|
37 |
self.verification_prompt: str = verification_prompt
|
38 |
|
39 |
def verify_user_input_prompt(self, user_prompt) -> str:
|
40 |
+
return (
|
41 |
+
f"Using only this information \n {user_prompt} \n answer the following questions, for each question that you cannot answer just answer 'null'. "
|
42 |
+
f"Put each answer in a new line, keep the answer brief "
|
43 |
+
f"and maintain the order in which the questions are asked. Do not add any preamble: "
|
44 |
+
f"{self.verification_prompt}")
|
45 |
|
46 |
def get_work_category(self, work_description: str) -> str:
|
47 |
+
return (
|
48 |
+
f"The work to do is {work_description}: choose the most accurate categories among the following {', '.join(self.work_categories.values())} "
|
49 |
+
f"Only return the categories, separated by a semicolon")
|
50 |
+
|
51 |
+
@staticmethod
|
52 |
+
def questions_to_field_labels():
|
53 |
+
return {
|
54 |
+
Questions.FULL_NAME: "Full name", Questions.WORK_TO_DO: "Work to do", Questions.COMMUNITY: "Community",
|
55 |
+
Questions.BUILDING:"Building name", Questions.UNIT_APT_NUMBER: "Unit/apartment number",
|
56 |
+
Questions.OWNER_OR_TENANT: "Owner/Tenant", Questions.START_DATE: "Start date",
|
57 |
+
Questions.END_DATE: "End date", Questions.CONTACT_NUMBER: "Your contact number",
|
58 |
+
Questions.COMPANY_NAME: "Contractor company name", Questions.COMPANY_EMAIL: "Contracting company email",
|
59 |
+
Questions.COMPANY_NUMBER: "Contracting company contact number", Questions.YOUR_EMAIL: "Your email"
|
60 |
+
}
|
61 |
|
|
|
|
prompts/verification_prompt2.txt
CHANGED
@@ -8,6 +8,6 @@ In which date is the work taking place? Please answer with just a date formatted
|
|
8 |
In which date will the work finish? Please answer with just a date formatted as dd/mm/yyyy. In case I used expressions like today, tomorrow, in two days, ecc, know that today it is {today}. If no date is provided, consider that it will finish on the same day as the start date
|
9 |
What is my contact number?
|
10 |
What is the name of the contracting company?
|
11 |
-
What is the contact number of the contracting company?
|
12 |
What is the email of the contracting company?
|
13 |
What is my email?
|
|
|
8 |
In which date will the work finish? Please answer with just a date formatted as dd/mm/yyyy. In case I used expressions like today, tomorrow, in two days, ecc, know that today it is {today}. If no date is provided, consider that it will finish on the same day as the start date
|
9 |
What is my contact number?
|
10 |
What is the name of the contracting company?
|
11 |
+
What is the contact number of the contracting company?
|
12 |
What is the email of the contracting company?
|
13 |
What is my email?
|
repository/intel_npu.py
CHANGED
@@ -1,8 +1,9 @@
|
|
1 |
import json
|
2 |
from pathlib import Path
|
3 |
|
4 |
-
from intel_npu_acceleration_library import NPUModelForCausalLM, int4
|
5 |
from intel_npu_acceleration_library.compiler import CompilerConfig
|
|
|
6 |
from transformers import AutoTokenizer
|
7 |
|
8 |
from repository.repository_abc import Repository, Model
|
@@ -25,14 +26,10 @@ class IntelNpuRepository(Repository):
|
|
25 |
return self.message_history
|
26 |
|
27 |
def init(self):
|
28 |
-
|
29 |
-
self.
|
30 |
-
export=True, temperature=0).eval()
|
31 |
-
self.tokenizer = AutoTokenizer.from_pretrained(self.model_info.name)
|
32 |
-
self.terminators = [self.tokenizer.eos_token_id, self.tokenizer.convert_tokens_to_ids("<|eot_id|>")]
|
33 |
|
34 |
def send_prompt(self, prompt: str, add_to_history: bool = True) -> dict[str, str]:
|
35 |
-
pass
|
36 |
print("prompt to be sent: " + prompt)
|
37 |
user_prompt = {"role": self.model_info.roles.user_role, "content": prompt}
|
38 |
if self.log_to_file:
|
@@ -40,6 +37,10 @@ class IntelNpuRepository(Repository):
|
|
40 |
log_file.write(json.dumps(user_prompt, indent=2))
|
41 |
log_file.write("\n")
|
42 |
self.get_message_history().append(user_prompt)
|
|
|
|
|
|
|
|
|
43 |
input_ids = (self.tokenizer.apply_chat_template(self.get_message_history(), add_generation_prompt=True,
|
44 |
return_tensors="pt")
|
45 |
.to(self.model.device))
|
@@ -57,3 +58,12 @@ class IntelNpuRepository(Repository):
|
|
57 |
else:
|
58 |
self.message_history.pop()
|
59 |
return answer
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import json
|
2 |
from pathlib import Path
|
3 |
|
4 |
+
from intel_npu_acceleration_library import NPUModelForCausalLM, int4, float16
|
5 |
from intel_npu_acceleration_library.compiler import CompilerConfig
|
6 |
+
from intel_npu_acceleration_library.dtypes import float32, float64, bfloat16
|
7 |
from transformers import AutoTokenizer
|
8 |
|
9 |
from repository.repository_abc import Repository, Model
|
|
|
26 |
return self.message_history
|
27 |
|
28 |
def init(self):
|
29 |
+
self._init_model()
|
30 |
+
self._init_tokenizer()
|
|
|
|
|
|
|
31 |
|
32 |
def send_prompt(self, prompt: str, add_to_history: bool = True) -> dict[str, str]:
|
|
|
33 |
print("prompt to be sent: " + prompt)
|
34 |
user_prompt = {"role": self.model_info.roles.user_role, "content": prompt}
|
35 |
if self.log_to_file:
|
|
|
37 |
log_file.write(json.dumps(user_prompt, indent=2))
|
38 |
log_file.write("\n")
|
39 |
self.get_message_history().append(user_prompt)
|
40 |
+
if self.model is None:
|
41 |
+
self._init_model()
|
42 |
+
if self.tokenizer is None:
|
43 |
+
self._init_tokenizer()
|
44 |
input_ids = (self.tokenizer.apply_chat_template(self.get_message_history(), add_generation_prompt=True,
|
45 |
return_tensors="pt")
|
46 |
.to(self.model.device))
|
|
|
58 |
else:
|
59 |
self.message_history.pop()
|
60 |
return answer
|
61 |
+
|
62 |
+
def _init_tokenizer(self):
|
63 |
+
self.tokenizer = AutoTokenizer.from_pretrained(self.model_info.name)
|
64 |
+
self.terminators = [self.tokenizer.eos_token_id, self.tokenizer.convert_tokens_to_ids("<|eot_id|>")]
|
65 |
+
|
66 |
+
def _init_model(self):
|
67 |
+
compiler_conf = CompilerConfig(dtype=float16)
|
68 |
+
self.model = NPUModelForCausalLM.from_pretrained(self.model_info.name, use_cache=True, config=compiler_conf,
|
69 |
+
export=True, temperature=0.1).eval()
|
repository/repository.py
CHANGED
@@ -1,4 +1,6 @@
|
|
|
|
1 |
from pathlib import Path
|
|
|
2 |
from utils.env_utils import in_hf
|
3 |
|
4 |
if not in_hf():
|
@@ -8,6 +10,17 @@ from repository.ondemand import OndemandRepository
|
|
8 |
from repository.repository_abc import Model
|
9 |
from repository.testing_repo import TestingRepository
|
10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
|
12 |
def get_repository(implementation: str, model: Model, system_msg: str = None, log_to_file: Path = None):
|
13 |
known_implementations = ["ollama", "intel_npu", "testing", "ondemand"]
|
@@ -24,7 +37,7 @@ def get_repository(implementation: str, model: Model, system_msg: str = None, lo
|
|
24 |
return TestingRepository(prompts_answers=[
|
25 |
{
|
26 |
"role": "assistant",
|
27 |
-
"content": "What is my full name?\n\nnull\n\nWhat is the nature of the work I need to do?\n\nPest control\n\nIn which community is the work taking place?\n\nJBR\n\nIn which building?\n\nnull\n\nIn which unit/apartment number?\n\nnull\n\nAm I the owner or the tenant?\n\nTenant\n\nIn which date is the work taking place?\n\n12/09/2024\n\nIn which date will the work finish?\n\n12/09/2024\n\nWhat is my contact number?\n\nnull\n\nWhat is the name of the contracting company?\n\nnull\n\nWhat is the contact number of the contracting company?\n\nnull\n\nWhat is the email of the contracting company?\n\nnull
|
28 |
},
|
29 |
{
|
30 |
"role":"assistant",
|
|
|
1 |
+
import os
|
2 |
from pathlib import Path
|
3 |
+
|
4 |
from utils.env_utils import in_hf
|
5 |
|
6 |
if not in_hf():
|
|
|
10 |
from repository.repository_abc import Model
|
11 |
from repository.testing_repo import TestingRepository
|
12 |
|
13 |
+
def build_repo_from_environment(system_prompt: str):
|
14 |
+
implementation = os.getenv("implementation")
|
15 |
+
model_name = os.getenv("model_name")
|
16 |
+
|
17 |
+
if implementation:
|
18 |
+
return get_repository(implementation, Model(model_name, ModelRoles("system",
|
19 |
+
"user",
|
20 |
+
"assistant")),
|
21 |
+
system_prompt)
|
22 |
+
else:
|
23 |
+
return None
|
24 |
|
25 |
def get_repository(implementation: str, model: Model, system_msg: str = None, log_to_file: Path = None):
|
26 |
known_implementations = ["ollama", "intel_npu", "testing", "ondemand"]
|
|
|
37 |
return TestingRepository(prompts_answers=[
|
38 |
{
|
39 |
"role": "assistant",
|
40 |
+
"content": "What is my full name?\n\nnull\n\nWhat is the nature of the work I need to do?\n\nPest control\n\nIn which community is the work taking place?\n\nJBR\n\nIn which building?\n\nnull\n\nIn which unit/apartment number?\n\nnull\n\nAm I the owner or the tenant?\n\nTenant\n\nIn which date is the work taking place?\n\n12/09/2024\n\nIn which date will the work finish?\n\n12/09/2024\n\nWhat is my contact number?\n\nnull\n\nWhat is the name of the contracting company?\n\nnull\n\nWhat is the contact number of the contracting company?\n\nnull\n\nWhat is the email of the contracting company?\n\nnull"
|
41 |
},
|
42 |
{
|
43 |
"role":"assistant",
|
requirements-base.txt
CHANGED
@@ -1,3 +1,4 @@
|
|
1 |
transformers
|
2 |
streamlit
|
3 |
-
PyPDFForm
|
|
|
|
1 |
transformers
|
2 |
streamlit
|
3 |
+
PyPDFForm
|
4 |
+
streamlit-local-storage
|
ui_manager.py
CHANGED
@@ -1,67 +1,162 @@
|
|
|
|
|
|
1 |
import streamlit as st
|
|
|
|
|
2 |
|
3 |
from form.form import build_form_data_from_answers, write_pdf_form
|
4 |
from llm_manager.llm_parser import LlmParser
|
|
|
|
|
|
|
|
|
5 |
from utils.parsing_utils import check_for_missing_answers
|
6 |
|
|
|
7 |
|
8 |
-
def
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
st.rerun()
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
st.
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
st.session_state["step"] = "ask_again"
|
31 |
-
st.rerun()
|
32 |
-
|
33 |
-
def build_ui_for_ask_again(pm):
|
34 |
-
with st.form("form1"):
|
35 |
-
for ma in st.session_state["missing_answers"]:
|
36 |
-
st.text_input(pm.questions[ma].lower(), key=ma)
|
37 |
-
submitted = st.form_submit_button("Submit answers")
|
38 |
-
if submitted:
|
39 |
-
for ma in st.session_state["missing_answers"]:
|
40 |
-
st.session_state["answers"][ma] = st.session_state[ma]
|
41 |
-
st.session_state["step"] = "check_category"
|
42 |
st.rerun()
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
st.
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
import os
|
3 |
import streamlit as st
|
4 |
+
from streamlit import session_state as ss
|
5 |
+
from streamlit_local_storage import LocalStorage
|
6 |
|
7 |
from form.form import build_form_data_from_answers, write_pdf_form
|
8 |
from llm_manager.llm_parser import LlmParser
|
9 |
+
from local_storage.entities import PersonalDetails, LocationDetails, ContractorDetails
|
10 |
+
from prompts.prompts_manager import PromptsManager, Questions as Q
|
11 |
+
from repository.repository import get_repository
|
12 |
+
from repository.repository_abc import Model, ModelRoles
|
13 |
from utils.parsing_utils import check_for_missing_answers
|
14 |
|
15 |
+
ls: LocalStorage = LocalStorage()
|
16 |
|
17 |
+
def in_hf() -> bool:
|
18 |
+
return os.getenv("env") == "hf"
|
19 |
+
|
20 |
+
|
21 |
+
|
22 |
+
|
23 |
+
class UIManager:
|
24 |
+
def __init__(self):
|
25 |
+
self.pm: PromptsManager = PromptsManager()
|
26 |
+
self.repository = (build_repo_from_environment(self.pm.system_prompt) or
|
27 |
+
get_repository("testing",
|
28 |
+
Model("fakeModel", ModelRoles("a", "b", "c"))))
|
29 |
+
|
30 |
+
@staticmethod
|
31 |
+
def get_current_step():
|
32 |
+
return ss.get("step")
|
33 |
+
|
34 |
+
@staticmethod
|
35 |
+
def _build_base_ui():
|
36 |
+
st.markdown("## Dubai Asset Management red tape cutter")
|
37 |
+
|
38 |
+
def build_ui_for_initial_state(self, user_message):
|
39 |
+
help_ = user_message
|
40 |
+
self._build_base_ui()
|
41 |
+
with st.form("Please describe your request"):
|
42 |
+
user_input = st.text_area("Your input", height=700, label_visibility="hidden", placeholder=help_,
|
43 |
+
help=help_)
|
44 |
+
signature = st.file_uploader("Your signature", key="file_upload")
|
45 |
+
ss["signature"] = signature
|
46 |
+
submit_button = st.form_submit_button()
|
47 |
+
if submit_button:
|
48 |
+
ss["user_input"] = user_input
|
49 |
+
ss["step"] = "parsing_answers"
|
50 |
+
st.rerun()
|
51 |
+
|
52 |
+
def build_ui_for_parsing_answers(self):
|
53 |
+
self._build_base_ui()
|
54 |
+
with st.status("initialising LLM"):
|
55 |
+
self.repository.init()
|
56 |
+
with st.status("waiting for LLM"):
|
57 |
+
answer = self.repository.send_prompt(self.pm.verify_user_input_prompt(ss["user_input"]))
|
58 |
+
st.write(f"answers from LLM: {answer['content']}")
|
59 |
+
with st.status("Checking for missing answers"):
|
60 |
+
answers = LlmParser.parse_verification_prompt_answers(answer['content'])
|
61 |
+
ss["answers"] = answers
|
62 |
+
if len(answers) != len(Q):
|
63 |
+
ss["step"] = "parsing_error"
|
64 |
+
st.rerun()
|
65 |
+
ss["missing_answers"] = check_for_missing_answers(ss["answers"])
|
66 |
+
if not ss.get("missing_answers"):
|
67 |
+
ss["step"] = "check_category"
|
68 |
+
else:
|
69 |
+
ss["step"] = "ask_again"
|
70 |
+
st.rerun()
|
71 |
+
|
72 |
+
def build_ui_for_ask_again(self):
|
73 |
+
self._build_base_ui()
|
74 |
+
with st.form("form1"):
|
75 |
+
for ma in ss["missing_answers"]:
|
76 |
+
st.text_input(self.pm.questions[ma].lower(), key=ma)
|
77 |
+
submitted = st.form_submit_button("Submit answers")
|
78 |
+
if submitted:
|
79 |
+
for ma in ss["missing_answers"]:
|
80 |
+
ss["answers"][ma] = ss[ma]
|
81 |
+
ss["step"] = "check_category"
|
82 |
+
st.rerun()
|
83 |
+
|
84 |
+
def build_ui_for_check_category(self):
|
85 |
+
self._build_base_ui()
|
86 |
+
with st.status("finding the work categories applicable to your work"):
|
87 |
+
answer = self.repository.send_prompt(self.pm.get_work_category(ss["answers"][1]))
|
88 |
+
categories = LlmParser.parse_get_categories_answer(answer['content'])
|
89 |
+
|
90 |
+
with st.status("categories found, creating PDF form"):
|
91 |
+
form_data, filename = build_form_data_from_answers(ss["answers"], categories,
|
92 |
+
ss.get("signature"))
|
93 |
+
pdf_form = write_pdf_form(form_data)
|
94 |
+
pdf_form_filename = filename
|
95 |
+
ss["pdf_form"] = pdf_form
|
96 |
+
ss["pdf_form_filename"] = pdf_form_filename
|
97 |
+
ss["step"] = "form_created"
|
98 |
st.rerun()
|
99 |
+
|
100 |
+
def build_ui_for_form_created(self):
|
101 |
+
self._build_base_ui()
|
102 |
+
st.download_button("download form", ss["pdf_form"],
|
103 |
+
file_name=ss["pdf_form_filename"], mime="application/pdf")
|
104 |
+
start_over_button = st.button("Start over")
|
105 |
+
if start_over_button:
|
106 |
+
del ss["step"]
|
107 |
+
del ss["pdf_form"]
|
108 |
+
del ss["pdf_form_filename"]
|
109 |
+
if "signature" in ss:
|
110 |
+
del ss["signature"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
111 |
st.rerun()
|
112 |
+
|
113 |
+
def build_ui_for_parsing_error(self):
|
114 |
+
def build_form_fragment(form_, col, title, *questions):
|
115 |
+
form_.text(title)
|
116 |
+
for user_data in questions:
|
117 |
+
with col:
|
118 |
+
form_.text_input(self.pm.questions_to_field_labels()[user_data], value=ss.get("answers", {})
|
119 |
+
.get(user_data), key=f"fq_{user_data.value}")
|
120 |
+
with col:
|
121 |
+
form_.text_input("Save as", key=title.replace(" ", "_"))
|
122 |
+
|
123 |
+
self._build_base_ui()
|
124 |
+
f = st.form("Please check the following information and correct fix any inaccuracies")
|
125 |
+
col1, col2 = f.columns(2)
|
126 |
+
build_form_fragment(f, col1, "your details", Q.FULL_NAME, Q.CONTACT_NUMBER, Q.YOUR_EMAIL)
|
127 |
+
build_form_fragment(f, col2, "work details", Q.WORK_TO_DO, Q.START_DATE, Q.END_DATE)
|
128 |
+
build_form_fragment(f, col1, "location details", Q.COMMUNITY, Q.BUILDING, Q.UNIT_APT_NUMBER,
|
129 |
+
Q.OWNER_OR_TENANT)
|
130 |
+
build_form_fragment(f, col2, "contractor details", Q.COMPANY_NAME, Q.COMPANY_NUMBER, Q.COMPANY_EMAIL)
|
131 |
+
submit_data = f.form_submit_button()
|
132 |
+
if submit_data:
|
133 |
+
for i in range(len(Q)):
|
134 |
+
ss["answers"][Q(i)] = ss[f"fq_{i}"]
|
135 |
+
|
136 |
+
for details_key, func in [("your_details", self._get_personal_details),
|
137 |
+
("location_details", self._get_location_details),
|
138 |
+
("contractor_details", self._get_contractor_details)]:
|
139 |
+
details = func(details_key)
|
140 |
+
if details:
|
141 |
+
key = ss[details_key] # get the name under which this data should be saved
|
142 |
+
ls.setItem(key, json.dumps(details))
|
143 |
+
|
144 |
+
@staticmethod
|
145 |
+
def _get_personal_details(personal_details_key) -> PersonalDetails | None:
|
146 |
+
if ss.get(personal_details_key):
|
147 |
+
return PersonalDetails(ss[f"fq_{Q.FULL_NAME}"], ss[f"fq_{Q.FULL_NAME}"], ss[f"fq_{Q.CONTACT_NUMBER}"])
|
148 |
+
return None
|
149 |
+
|
150 |
+
@staticmethod
|
151 |
+
def _get_location_details(location_details_key) -> LocationDetails | None:
|
152 |
+
if ss.get(location_details_key):
|
153 |
+
return LocationDetails(ss[f"fq_{Q.OWNER_OR_TENANT}"], ss[f"fq_{Q.COMMUNITY}"], ss[f"fq_{Q.BUILDING}"],
|
154 |
+
ss[f"fq_{Q.UNIT_APT_NUMBER}"])
|
155 |
+
return None
|
156 |
+
|
157 |
+
@staticmethod
|
158 |
+
def _get_contractor_details(contractor_details_key) -> ContractorDetails | None:
|
159 |
+
if ss.get(contractor_details_key):
|
160 |
+
return ContractorDetails(ss[f"fq_{Q.COMPANY_NAME}"], ss[f"fq_{Q.COMPANY_NUMBER}"],
|
161 |
+
ss[f"fq_{Q.COMPANY_EMAIL}"])
|
162 |
+
return None
|
utils/env_utils.py
CHANGED
@@ -1,22 +1,5 @@
|
|
1 |
import os
|
2 |
|
3 |
-
import repository.repository
|
4 |
-
from repository.repository_abc import Model, ModelRoles
|
5 |
-
|
6 |
|
7 |
def in_hf() -> bool:
|
8 |
return os.getenv("env") == "hf"
|
9 |
-
|
10 |
-
|
11 |
-
def build_repo_from_environment(system_prompt: str):
|
12 |
-
implementation = os.getenv("implementation")
|
13 |
-
model_name = os.getenv("model_name")
|
14 |
-
|
15 |
-
if implementation:
|
16 |
-
return repository.repository.get_repository(implementation, Model(model_name, ModelRoles("system",
|
17 |
-
"user",
|
18 |
-
"assistant")),
|
19 |
-
system_prompt)
|
20 |
-
else:
|
21 |
-
return None
|
22 |
-
|
|
|
1 |
import os
|
2 |
|
|
|
|
|
|
|
3 |
|
4 |
def in_hf() -> bool:
|
5 |
return os.getenv("env") == "hf"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|