Files changed (1) hide show
  1. app.py +143 -212
app.py CHANGED
@@ -1,102 +1,97 @@
 
 
 
 
1
  import os
2
- os.environ["OPENAI_API_KEY"]
3
-
4
- from llama_index.llms.openai import OpenAI
5
- from llama_index.core.schema import MetadataMode
6
- import openai
7
- from openai import OpenAI as OpenAIOG
8
  import logging
9
  import sys
10
- llm = OpenAI(temperature=0.0, model="gpt-3.5-turbo")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  client = OpenAIOG()
12
 
13
- from langdetect import detect
14
- from langdetect import DetectorFactory
15
  DetectorFactory.seed = 0
16
- from deep_translator import GoogleTranslator
17
- from lingua import Language, LanguageDetectorBuilder
18
 
19
- # Load index
20
- from llama_index.core import VectorStoreIndex
21
- from llama_index.core import StorageContext
22
- from llama_index.core import load_index_from_storage
23
  storage_context = StorageContext.from_defaults(persist_dir="arv_metadata")
24
  index = load_index_from_storage(storage_context)
25
  query_engine = index.as_query_engine(similarity_top_k=3, llm=llm)
26
- retriever = index.as_retriever(similarity_top_k = 3)
27
-
28
- import gradio as gr
29
- import re
30
- import json
31
- from datetime import datetime
32
 
33
- acknowledgment_keywords_sw = ["sawa", "ndiyo", "naam", "hakika", "asante", "nimeelewa", "nimekupata", "ni kweli",
34
- "kwa hakika", "nimesikia", "ahsante"]
35
  acknowledgment_keywords_en = ["thanks", "thank you", "thx", "ok", "okay", "great", "got it", "appreciate", "good", "makes sense"]
36
- follow_up_keywords = ["but", "also", "and", "what", "how", "why", "when", "is", "?",
37
- "lakini", "pia", "na", "nini", "vipi", "kwanini", "wakati"]
38
  greeting_keywords_sw = ["sasa", "niaje", "habari", "mambo", "jambo", "shikamoo", "marahaba", "hujambo", "hamjambo", "salama", "vipi"]
39
  greeting_keywords_en = ["hi", "hello", "hey", "how's it", "what's up", "yo", "howdy"]
 
 
40
 
41
  def contains_exact_word_or_phrase(text, keywords):
 
42
  text = text.lower()
43
- for keyword in keywords:
44
- if re.search(r'\b' + re.escape(keyword) + r'\b', text):
45
- return True
46
- return False
47
 
48
- def contains_greeting_sw(question):
49
- # Check if the question contains acknowledgment keywords
50
- return contains_exact_word_or_phrase(question, greeting_keywords_sw)
51
 
52
- def contains_greeting_en(question):
53
- # Check if the question contains acknowledgment keywords
54
- return contains_exact_word_or_phrase(question, greeting_keywords_en)
55
 
56
- def contains_acknowledgment_sw(question):
57
- # Check if the question contains acknowledgment keywords
58
- return contains_exact_word_or_phrase(question, acknowledgment_keywords_sw)
59
 
60
- def contains_acknowledgment_en(question):
61
- # Check if the question contains acknowledgment keywords
62
- return contains_exact_word_or_phrase(question, acknowledgment_keywords_en)
63
 
64
- def contains_follow_up(question):
65
- # Check if the question contains follow-up indicators
66
- return contains_exact_word_or_phrase(question, follow_up_keywords)
67
 
68
  def convert_to_date(date_str):
69
- date_obj = datetime.strptime(date_str, "%Y%m%d")
70
- return date_obj.strftime("%Y-%m-%d")
71
-
72
- def detect_language(question):
73
- # Check if the text has less than 5 words
74
- if len(question.split()) < 5:
75
- languages = [Language.ENGLISH, Language.SWAHILI] # Add more languages as needed
 
 
 
76
  detector = LanguageDetectorBuilder.from_languages(*languages).build()
77
- detected_language = detector.detect_language_of(question)
78
- # Return language code for consistency
79
- if detected_language == Language.SWAHILI:
80
- return "sw"
81
- elif detected_language == Language.ENGLISH:
82
- return "en"
83
- else:
84
- try:
85
- lang_detect = detect(question)
86
- return lang_detect
87
- except Exception as e:
88
- print(f"Error with langdetect: {e}")
89
- return "unknown"
90
-
91
  def nishauri(user_params: str, conversation_history: list[str]):
92
 
93
- # Get conversation history
94
  context = " ".join([item["user"] + " " + item["chatbot"] for item in conversation_history])
95
-
96
- # Convert the user_params_str to a dictionary
97
  user_params = json.loads(user_params)
98
 
99
- ## Parse user params
100
  consent = user_params.get("CONSENT")
101
  person_info = user_params.get("PERSON_INFO", {})
102
  gender = person_info.get("GENDER", "")
@@ -105,160 +100,94 @@ def nishauri(user_params: str, conversation_history: list[str]):
105
  vl_date = convert_to_date(person_info.get("VIRAL_LOAD_DATETIME", ""))
106
  next_appt_date = convert_to_date(person_info.get("APPOINTMENT_DATETIME", ""))
107
  regimen = person_info.get("REGIMEN", "")
108
- question = user_params.get("QUESTION")
109
-
110
- # Keep those user params that are present
111
- info_pieces = []
112
-
113
- if gender:
114
- info_pieces.append(f"The person is {gender}.")
115
-
116
- if age:
117
- info_pieces.append(f"The person is age {age}.")
118
-
119
- if next_appt_date:
120
- info_pieces.append(f"The person's next clinical check-in is scheduled for {next_appt_date}.")
121
-
122
- if regimen:
123
- info_pieces.append(f"The person is on the following regimen for HIV: {regimen}.")
124
-
125
- if vl_result:
126
- info_pieces.append(f"The person's most recent viral load result was {vl_result}.")
127
-
128
- if vl_date:
129
- info_pieces.append(f"The person's most recent viral load was taken on {vl_date}.")
130
-
131
- full_text = " ".join(info_pieces)
132
-
133
- ## Process greeting
134
- # greet_response = process_greeting_response(question)
135
- if contains_greeting_en(question) and not contains_follow_up(question):
136
- greeting = (
137
- f" The user previously asked and answered the following: {context}. "
138
- f" The user just provided the following greeting: {question}. "
139
- "Please respond accordingly in English."
140
- )
141
- completion = client.chat.completions.create(
142
- model="gpt-4o",
143
- messages=[
144
- {"role": "user", "content": greeting}
145
- ]
146
- )
147
- reply_to_user = completion.choices[0].message.content
148
- conversation_history.append({"user": question, "chatbot": reply_to_user})
149
- return reply_to_user, conversation_history
150
-
151
- if contains_greeting_sw(question) and not contains_follow_up(question):
152
- greeting = (
153
- f" The user previously asked and answered the following: {context}. "
154
- f" The user just provided the following greeting: {question}. "
155
- "Please respond accordingly in Swahili."
156
- )
157
- completion = client.chat.completions.create(
158
- model="gpt-4o",
159
- messages=[
160
- {"role": "user", "content": greeting}
161
- ]
162
- )
163
- reply_to_user = completion.choices[0].message.content
164
- conversation_history.append({"user": question, "chatbot": reply_to_user})
165
- return reply_to_user, conversation_history
166
-
167
- ## Process acknowledgment
168
- if contains_acknowledgment_en(question) and not contains_follow_up(question):
169
- acknowledgment = (
170
- f" The user previously asked and answered the following: {context}. "
171
- f" The user just provided the following acknowledgement: {question}. "
172
- "Please respond accordingly in English."
173
- )
174
- completion = client.chat.completions.create(
175
- model="gpt-4o",
176
- messages=[
177
- {"role": "user", "content": acknowledgment}
178
- ]
179
- )
180
- reply_to_user = completion.choices[0].message.content
181
- conversation_history.append({"user": question, "chatbot": reply_to_user})
182
- return reply_to_user, conversation_history
183
-
184
- if contains_acknowledgment_sw(question) and not contains_follow_up(question):
185
- acknowledgment = (
186
- f" The user previously asked and answered the following: {context}. "
187
- f" The user just provided the following acknowledgment: {question}. "
188
- "Please respond accordingly in Swahili."
189
- )
190
- completion = client.chat.completions.create(
191
- model="gpt-4o",
192
- messages=[
193
- {"role": "user", "content": acknowledgment}
194
- ]
195
- )
196
- reply_to_user = completion.choices[0].message.content
197
- conversation_history.append({"user": question, "chatbot": reply_to_user})
198
- return reply_to_user, conversation_history
199
-
200
- # context = " ".join([item["user"] + " " + item["chatbot"] for item in conversation_history])
201
-
202
- ## If not greeting or acknowledgement, then proceed with RAG
203
 
204
- ## Detect language of question - if Swahili, translate to English
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  lang_question = detect_language(question)
206
- if lang_question=="sw":
207
  question = GoogleTranslator(source='sw', target='en').translate(question)
208
 
209
- # Retrieve sources
210
  sources = retriever.retrieve(question)
211
- source0 = sources[0].text
212
- source1 = sources[1].text
213
- source2 = sources[2].text
214
-
215
- # If user consented, add user parameters, otherwise proceed with out
216
- if consent == "YES":
217
- background = ("The person who asked the question is a person living with HIV."
218
- f" The person is {info_pieces} "
219
- " They are asking questions about HIV. Do not talk about anything that is not related to HIV. "
220
- " Recognize that they already have HIV and do not suggest that they have to get tested"
221
- " for HIV or take post-exposure prophylaxis, as that is not relevant, though their partners perhaps should."
222
- " Do not suggest anything that is not relevant to someone who already has HIV."
223
- " Do not mention in the response that the person is living with HIV."
224
- " The following information about viral loads is authoritative for any question about viral loads:"
225
- " A high viral load or non-suppressed viral load is any viral load above 200 copies/ml."
226
- " A viral load above 1000 copies/ml suggests treatment failure."
227
- " A suppressed viral load is one below 200 copies / ml.")
228
- else:
229
- background = ("The person who asked the question is a person living with HIV."
230
- " They are asking questions about HIV. Do not talk about anything that is not related to HIV. "
231
- " Recognize that they already have HIV and do not suggest that they have to get tested"
232
- " for HIV or take post-exposure prophylaxis, as that is not relevant, though their partners perhaps should."
233
- " Do not suggest anything that is not relevant to someone who already has HIV."
234
- " Do not mention in the response that the person is living with HIV."
235
- " The following information about viral loads is authoritative for any question about viral loads:"
236
- " A high viral load or non-suppressed viral load is any viral load above 200 copies/ml."
237
- " A viral load above 1000 copies/ml suggests treatment failure."
238
- " A suppressed viral load is one below 200 copies / ml.")
239
 
240
- # Combine into final prompt - user background, conversation history, new question, retrieved sources
241
  question_final = (
242
- f" The user previously asked and answered the following: {context}. "
243
- f" The user just asked the following question: {question}."
244
- f" Please use the following content to generate a response: {source0} {source1} {source2}."
245
- f" Please consider the following background information when generating a response: {background}."
246
- " Keep answers brief and limited to the question that was asked."
247
- " If they share a greeting, just greet them in return and ask if they have a question."
248
- " Do not change the subject or address anything the user didn't directly ask about."
249
- " If they respond with an acknowledgement, simply thank them."
250
- " Do not discuss anything other than HIV. If they ask a question that is not about HIV, respond that"
251
- " you are only able to discuss HIV."
252
- " Keep the response to under 50 words and use simple language. The person asking the question does not know technical terms."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  )
254
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
  # Generate response
256
  completion = client.chat.completions.create(
257
- model="gpt-4o",
258
- messages=[
259
- {"role": "user", "content": question_final}
260
- ]
261
  )
 
262
  # Collect response
263
  reply_to_user = completion.choices[0].message.content
264
 
@@ -269,8 +198,10 @@ def nishauri(user_params: str, conversation_history: list[str]):
269
  if lang_question=="sw":
270
  reply_to_user = GoogleTranslator(source='auto', target='sw').translate(reply_to_user)
271
 
272
- return reply_to_user, conversation_history
 
273
 
 
274
  demo = gr.Interface(
275
  title = "Nishauri Chatbot Demo",
276
  fn=nishauri,
 
1
+ #%% md
2
+ # ## Nuru HIV Informational Chatbot
3
+ #%%
4
+ # Import libraries
5
  import os
6
+ from dotenv import load_dotenv
 
 
 
 
 
7
  import logging
8
  import sys
9
+ import re
10
+ import json
11
+ from datetime import datetime
12
+ from langdetect import detect, DetectorFactory
13
+ from deep_translator import GoogleTranslator
14
+ from lingua import Language, LanguageDetectorBuilder
15
+ import gradio as gr
16
+ from openai import OpenAI as OpenAIOG
17
+ from llama_index.llms.openai import OpenAI
18
+ from llama_index.core import VectorStoreIndex, StorageContext, load_index_from_storage
19
+ from deep_translator import GoogleTranslator
20
+
21
+ # Set OpenAI API Key (Ensure this is set in the environment)
22
+ # load_dotenv("config.env")
23
+ os.environ.get("OPENAI_API_KEY")
24
+
25
+ # Initialize OpenAI clients
26
+ llm = OpenAI(temperature=0.0, model="gpt-4o")
27
  client = OpenAIOG()
28
 
29
+ # Set seed for language detection consistency
 
30
  DetectorFactory.seed = 0
 
 
31
 
32
+ # Load index for retrieval
 
 
 
33
  storage_context = StorageContext.from_defaults(persist_dir="arv_metadata")
34
  index = load_index_from_storage(storage_context)
35
  query_engine = index.as_query_engine(similarity_top_k=3, llm=llm)
36
+ retriever = index.as_retriever(similarity_top_k=3)
 
 
 
 
 
37
 
38
+ # Define keyword lists
39
+ acknowledgment_keywords_sw = ["sawa", "ndiyo", "naam", "hakika", "asante", "nimeelewa", "nimekupata", "ni kweli", "kwa hakika", "nimesikia", "ahsante"]
40
  acknowledgment_keywords_en = ["thanks", "thank you", "thx", "ok", "okay", "great", "got it", "appreciate", "good", "makes sense"]
41
+ follow_up_keywords = ["but", "also", "and", "what", "how", "why", "when", "is", "?", "lakini", "pia", "na", "nini", "vipi", "kwanini", "wakati"]
 
42
  greeting_keywords_sw = ["sasa", "niaje", "habari", "mambo", "jambo", "shikamoo", "marahaba", "hujambo", "hamjambo", "salama", "vipi"]
43
  greeting_keywords_en = ["hi", "hello", "hey", "how's it", "what's up", "yo", "howdy"]
44
+ #%%
45
+ # Define helper functions
46
 
47
  def contains_exact_word_or_phrase(text, keywords):
48
+ """Check if the given text contains any exact keyword from the list."""
49
  text = text.lower()
50
+ return any(re.search(r'\b' + re.escape(keyword) + r'\b', text) for keyword in keywords)
 
 
 
51
 
52
+ def contains_greeting_sw(text):
53
+ return contains_exact_word_or_phrase(text, greeting_keywords_sw)
 
54
 
55
+ def contains_greeting_en(text):
56
+ return contains_exact_word_or_phrase(text, greeting_keywords_en)
 
57
 
58
+ def contains_acknowledgment_sw(text):
59
+ return contains_exact_word_or_phrase(text, acknowledgment_keywords_sw)
 
60
 
61
+ def contains_acknowledgment_en(text):
62
+ return contains_exact_word_or_phrase(text, acknowledgment_keywords_en)
 
63
 
64
+ def contains_follow_up(text):
65
+ return contains_exact_word_or_phrase(text, follow_up_keywords)
 
66
 
67
  def convert_to_date(date_str):
68
+ """Convert date string in YYYYMMDD format to YYYY-MM-DD."""
69
+ try:
70
+ return datetime.strptime(date_str, "%Y%m%d").strftime("%Y-%m-%d")
71
+ except ValueError:
72
+ return "Unknown Date"
73
+
74
+ def detect_language(text):
75
+ """Detect language of a given text using Lingua for short texts and langdetect for longer ones."""
76
+ if len(text.split()) < 5:
77
+ languages = [Language.ENGLISH, Language.SWAHILI]
78
  detector = LanguageDetectorBuilder.from_languages(*languages).build()
79
+ detected_language = detector.detect_language_of(text)
80
+ return "sw" if detected_language == Language.SWAHILI else "en"
81
+ try:
82
+ return detect(text)
83
+ except Exception as e:
84
+ logging.warning(f"Language detection error: {e}")
85
+ return "unknown"
86
+ #%%
87
+ # Define Gradio function
 
 
 
 
 
88
  def nishauri(user_params: str, conversation_history: list[str]):
89
 
90
+ """Process user query, detect language, handle greetings, acknowledgments, and retrieve relevant information."""
91
  context = " ".join([item["user"] + " " + item["chatbot"] for item in conversation_history])
 
 
92
  user_params = json.loads(user_params)
93
 
94
+ # Extract user information
95
  consent = user_params.get("CONSENT")
96
  person_info = user_params.get("PERSON_INFO", {})
97
  gender = person_info.get("GENDER", "")
 
100
  vl_date = convert_to_date(person_info.get("VIRAL_LOAD_DATETIME", ""))
101
  next_appt_date = convert_to_date(person_info.get("APPOINTMENT_DATETIME", ""))
102
  regimen = person_info.get("REGIMEN", "")
103
+ question = user_params.get("QUESTION", "")
104
+
105
+ info_pieces = [
106
+ "Here is information about the person asking the question."
107
+ f"The person is {gender}." if gender else "",
108
+ f"The person is age {age}." if age else "",
109
+ f"The person's next clinical check-in is scheduled for {next_appt_date}." if next_appt_date else "",
110
+ f"The person is on the following regimen for HIV: {regimen}." if regimen else "",
111
+ f"The person's most recent viral load result was {vl_result}." if vl_result else "",
112
+ f"The person's most recent viral load was taken on {vl_date}." if vl_date else "",
113
+ ]
114
+ full_text = " ".join(filter(None, info_pieces))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
+ # Process greetings and acknowledgments
117
+ for lang, contains_greeting, contains_acknowledgment in [("en", contains_greeting_en, contains_acknowledgment_en), ("sw", contains_greeting_sw, contains_acknowledgment_sw)]:
118
+ if contains_greeting(question) and not contains_follow_up(question):
119
+ prompt = f"The user said: {question}. Respond accordingly in {lang}."
120
+ elif contains_acknowledgment(question) and not contains_follow_up(question):
121
+ prompt = f"The user acknowledged: {question}. Respond accordingly in {lang}."
122
+ else:
123
+ continue
124
+ completion = client.chat.completions.create(
125
+ model="gpt-4o",
126
+ messages=[{"role": "user", "content": prompt}]
127
+ )
128
+ reply_to_user = completion.choices[0].message.content
129
+ conversation_history.append({"user": question, "chatbot": reply_to_user})
130
+ return reply_to_user, conversation_history
131
+
132
+ # Detect language and translate if needed
133
  lang_question = detect_language(question)
134
+ if lang_question == "sw":
135
  question = GoogleTranslator(source='sw', target='en').translate(question)
136
 
137
+ # Retrieve relevant sources
138
  sources = retriever.retrieve(question)
139
+ retrieved_text = "\n\n".join([f"Source {i+1}: {source.text}" for i, source in enumerate(sources[:3])])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
 
141
+ # Combine into new user question - conversation history, new question, retrieved sources
142
  question_final = (
143
+ f"The user asked the following question: \"{question}\"\n\n"
144
+ f"Use only the content below to answer the question:\n\n{retrieved_text}\n\n"
145
+ "Guidelines:\n"
146
+ "- Only answer the question that was asked.\n"
147
+ "- Do not change the subject or include unrelated information.\n"
148
+ "- Only discuss HIV. If the question is not about HIV, say that you can only answer HIV-related questions.\n"
149
+ )
150
+
151
+ # Set LLM instructions. If user consented, add user parameters, otherwise proceed without
152
+ system_prompt = (
153
+ "You are a helpful assistant who only answers questions about HIV.\n"
154
+ "- Do not answer questions about other topics (e.g., malaria or tuberculosis).\n"
155
+ "- If a question is unrelated to HIV, politely respond that you can only answer HIV-related questions.\n\n"
156
+
157
+ "The person asking the question is living with HIV.\n"
158
+ "- Do not suggest they get tested for HIV or take post-exposure prophylaxis (PEP).\n"
159
+ "- You may mention that their partners might benefit from testing or PEP, if relevant.\n"
160
+ "- Do not mention in your response that the person is living with HIV.\n"
161
+ "- Only suggest things relevant to someone who already has HIV.\n\n"
162
+ "- Keep the answer under 50 words.\n"
163
+ "- Use simple, easy-to-understand language. Avoid medical jargon.\n"
164
+
165
+ "Use the following authoritative information about viral loads:\n"
166
+ "- A high or non-suppressed viral load is above 200 copies/ml.\n"
167
+ "- A viral load above 1000 copies/ml suggests treatment failure.\n"
168
+ "- A suppressed viral load is one below 200 copies/ml.\n\n"
169
  )
170
 
171
+ if consent == "YES":
172
+ system_prompt = f"{system_prompt} {full_text}."
173
+
174
+ # Start with context
175
+ messages = [{"role": "system", "content": system_prompt}]
176
+
177
+ # Add conversation history
178
+ for turn in conversation_history:
179
+ messages.append({"role": "user", "content": turn["user"]})
180
+ messages.append({"role": "assistant", "content": turn["chatbot"]})
181
+
182
+ # Finally, add the current question
183
+ messages.append({"role": "user", "content": question_final})
184
+
185
  # Generate response
186
  completion = client.chat.completions.create(
187
+ model="gpt-4o",
188
+ messages=messages
 
 
189
  )
190
+
191
  # Collect response
192
  reply_to_user = completion.choices[0].message.content
193
 
 
198
  if lang_question=="sw":
199
  reply_to_user = GoogleTranslator(source='auto', target='sw').translate(reply_to_user)
200
 
201
+ # return system_prompt, conversation_history
202
+ return reply_to_user, conversation_history
203
 
204
+ #%%
205
  demo = gr.Interface(
206
  title = "Nishauri Chatbot Demo",
207
  fn=nishauri,