ruslanmv commited on
Commit
e8173b9
β€’
1 Parent(s): 4e40bbb

First commit

Browse files
Files changed (7) hide show
  1. README.md +2 -2
  2. ai_config.py +69 -0
  3. app.py +232 -0
  4. knowledge_retrieval.py +91 -0
  5. prompt_instructions.py +162 -0
  6. requirements.txt +20 -0
  7. settings.py +245 -0
README.md CHANGED
@@ -1,10 +1,10 @@
1
  ---
2
  title: Medical Interviewer
3
- emoji: πŸ‘€
4
  colorFrom: pink
5
  colorTo: yellow
6
  sdk: gradio
7
- sdk_version: 4.43.0
8
  app_file: app.py
9
  pinned: false
10
  ---
 
1
  ---
2
  title: Medical Interviewer
3
+ emoji: πŸ‘©β€πŸ¦³
4
  colorFrom: pink
5
  colorTo: yellow
6
  sdk: gradio
7
+ sdk_version: 4.41.0
8
  app_file: app.py
9
  pinned: false
10
  ---
ai_config.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from io import BytesIO
2
+
3
+ from langchain_openai import ChatOpenAI
4
+ from openai import OpenAI
5
+ import tiktoken
6
+ import os
7
+ from dotenv import load_dotenv
8
+ import os
9
+ # Load environment variables from .env file
10
+ load_dotenv()
11
+
12
+ # IBM Connection Parameters (using loaded env variables)
13
+ openai_api_key = os.getenv("OPENAI_API_KEY")
14
+
15
+ def n_of_questions():
16
+ n_of_questions = 25
17
+ return n_of_questions
18
+
19
+ #openai_api_key = os.environ.get("openai_api_key")
20
+
21
+ model = "gpt-4o-mini"
22
+
23
+ def load_model(openai_api_key):
24
+ return ChatOpenAI(
25
+ model_name=model,
26
+ openai_api_key=openai_api_key,
27
+ temperature=0.5
28
+ )
29
+
30
+ # Initialize the OpenAI client with the API key
31
+ client = OpenAI(api_key=openai_api_key)
32
+
33
+
34
+ def convert_text_to_speech(text, output, voice):
35
+ try:
36
+ # Convert the final text to speech
37
+ response = client.audio.speech.create(model="tts-1-hd", voice=voice, input=text)
38
+
39
+ if isinstance(output, BytesIO):
40
+ # If output is a BytesIO object, write directly to it
41
+ for chunk in response.iter_bytes():
42
+ output.write(chunk)
43
+ else:
44
+ # If output is a file path, open and write to it
45
+ with open(output, 'wb') as f:
46
+ for chunk in response.iter_bytes():
47
+ f.write(chunk)
48
+
49
+ except Exception as e:
50
+ print(f"An error occurred: {e}")
51
+ # Fallback in case of error
52
+ response = client.audio.speech.create(model="tts-1-hd", voice=voice, input='Here is my Report.')
53
+
54
+ if isinstance(output, BytesIO):
55
+ for chunk in response.iter_bytes():
56
+ output.write(chunk)
57
+ else:
58
+ with open(output, 'wb') as f:
59
+ for chunk in response.iter_bytes():
60
+ f.write(chunk)
61
+
62
+
63
+ def transcribe_audio(audio):
64
+ audio_file = open(audio, "rb")
65
+ transcription = client.audio.transcriptions.create(
66
+ model="whisper-1",
67
+ file=audio_file
68
+ )
69
+ return transcription.text
app.py ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import tempfile
3
+ import os
4
+ from pathlib import Path
5
+ from io import BytesIO
6
+ from settings import (
7
+ respond,
8
+ generate_random_string,
9
+ reset_interview,
10
+ generate_interview_report,
11
+ generate_report_from_file,
12
+ interview_history,
13
+ question_count,
14
+ language,
15
+ )
16
+ from ai_config import convert_text_to_speech, transcribe_audio, n_of_questions
17
+ from prompt_instructions import get_interview_initial_message_sarah, get_interview_initial_message_aaron
18
+
19
+ # Global variables
20
+ temp_audio_files = []
21
+ initial_audio_path = None
22
+ selected_interviewer = "Sarah"
23
+ audio_enabled = True
24
+
25
+ def reset_interview_action(voice):
26
+ global question_count, interview_history, selected_interviewer
27
+ selected_interviewer = voice
28
+ question_count = 0
29
+ interview_history.clear()
30
+
31
+ if voice == "Sarah":
32
+ initial_message = get_interview_initial_message_sarah()
33
+ voice_setting = "alloy"
34
+ else:
35
+ initial_message = get_interview_initial_message_aaron()
36
+ voice_setting = "onyx"
37
+
38
+ initial_message = str(initial_message)
39
+
40
+ initial_audio_buffer = BytesIO()
41
+ convert_text_to_speech(initial_message, initial_audio_buffer, voice_setting)
42
+ initial_audio_buffer.seek(0)
43
+
44
+ with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as temp_file:
45
+ temp_audio_path = temp_file.name
46
+ temp_file.write(initial_audio_buffer.getvalue())
47
+
48
+ temp_audio_files.append(temp_audio_path)
49
+
50
+ return (
51
+ [(None, initial_message[0] if isinstance(initial_message, tuple) else initial_message)],
52
+ gr.Audio(value=temp_audio_path, label=voice, autoplay=True, visible=False),
53
+ gr.Textbox(value="")
54
+ )
55
+
56
+ def create_app():
57
+ global initial_audio_path, selected_interviewer, audio_enabled
58
+ # Initialize without any message history
59
+ initial_message = ""
60
+
61
+ with gr.Blocks(title="AI Medical Interviewer") as demo:
62
+ gr.Image(value="appendix/icon.jpeg", label='icon', width=20, scale=1, show_label=False, show_fullscreen_button=False,
63
+ show_download_button=False, show_share_button=False)
64
+ gr.Markdown(
65
+ """
66
+ # Medical Interviewer
67
+ This chatbot conducts medical interviews based on medical knowledge.
68
+ The interviewer will prepare a medical report based on the interview.
69
+ """
70
+ )
71
+
72
+ with gr.Tab("Interview"):
73
+ with gr.Row():
74
+ reset_button = gr.Button("Start Interview", size='sm', scale=1)
75
+ end_button = gr.Button("End Interview", size='sm', scale=1) # Added End Interview button
76
+ audio_output = gr.Audio(
77
+ label="Sarah",
78
+ scale=3,
79
+ autoplay=True,
80
+ visible=False, # Hides the audio but keeps it active
81
+ show_download_button=False,
82
+ )
83
+
84
+ # Chatbot initialized with no messages
85
+ chatbot = gr.Chatbot(value=[], label=f"Medical InterviewπŸ“‹")
86
+ with gr.Row():
87
+ msg = gr.Textbox(label="Type your message here...", scale=3)
88
+ audio_input = gr.Audio(sources=(["microphone"]), label="Record your message", type="filepath", scale=1)
89
+ send_button = gr.Button("Send")
90
+ pdf_output = gr.File(label="Download Report", visible=False)
91
+
92
+ def user(user_message, audio, history):
93
+ if audio is not None:
94
+ user_message = transcribe_audio(audio)
95
+ return "", None, history + [[user_message, None]]
96
+
97
+ def bot_response(chatbot, message):
98
+ global question_count, temp_audio_files, selected_interviewer, audio_enabled
99
+ question_count += 1
100
+
101
+ last_user_message = chatbot[-1][0] if chatbot else message
102
+
103
+ voice = "alloy" if selected_interviewer == "Sarah" else "onyx"
104
+ response, audio_buffer = respond(chatbot, last_user_message, voice, selected_interviewer)
105
+
106
+ for bot_message in response:
107
+ chatbot.append((None, bot_message[1]))
108
+
109
+ if isinstance(audio_buffer, BytesIO):
110
+ with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as temp_file:
111
+ temp_audio_path = temp_file.name
112
+ temp_file.write(audio_buffer.getvalue())
113
+ temp_audio_files.append(temp_audio_path)
114
+ audio_output = gr.Audio(value=temp_audio_path, label=selected_interviewer, autoplay=audio_enabled, visible=False)
115
+ else:
116
+ audio_output = gr.Audio(value=audio_buffer, label=selected_interviewer, autoplay=audio_enabled, visible=False)
117
+
118
+ if question_count >= n_of_questions():
119
+ conclusion_message = "Thank you for participating in this interview. We have reached the end of our session. I hope this conversation has been helpful. Take care!"
120
+ chatbot.append((None, conclusion_message))
121
+
122
+ conclusion_audio_buffer = BytesIO()
123
+ convert_text_to_speech(conclusion_message, conclusion_audio_buffer, voice)
124
+ conclusion_audio_buffer.seek(0)
125
+
126
+ with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as temp_file:
127
+ temp_audio_path = temp_file.name
128
+ temp_file.write(conclusion_audio_buffer.getvalue())
129
+ temp_audio_files.append(temp_audio_path)
130
+ audio_output = gr.Audio(value=temp_audio_path, label=selected_interviewer, autoplay=audio_enabled, visible=False)
131
+
132
+ report_content, pdf_path = generate_interview_report(interview_history, language)
133
+ chatbot.append((None, f"Interview Report:\n\n{report_content}"))
134
+
135
+ return chatbot, audio_output, gr.File(visible=True, value=pdf_path)
136
+
137
+ return chatbot, audio_output, gr.File(visible=False)
138
+
139
+ # Function to reset and start the interview, which populates the chatbot with the initial message
140
+ def start_interview():
141
+ global selected_interviewer
142
+ return reset_interview_action(selected_interviewer)
143
+
144
+ # Function to end the interview
145
+ def end_interview(chatbot):
146
+ chatbot.append((None, "The interview has been ended by the user."))
147
+ return chatbot, gr.Audio(visible=False), gr.Textbox(value="")
148
+
149
+ # Bind actions to buttons
150
+ reset_button.click(
151
+ start_interview,
152
+ inputs=[],
153
+ outputs=[chatbot, audio_output, msg]
154
+ )
155
+
156
+ end_button.click(
157
+ end_interview,
158
+ inputs=[chatbot],
159
+ outputs=[chatbot, audio_output, msg]
160
+ )
161
+
162
+ msg.submit(user, [msg, audio_input, chatbot], [msg, audio_input, chatbot], queue=False).then(
163
+ bot_response, [chatbot, msg], [chatbot, audio_output, pdf_output]
164
+ )
165
+
166
+ send_button.click(user, [msg, audio_input, chatbot], [msg, audio_input, chatbot], queue=False).then(
167
+ bot_response, [chatbot, msg], [chatbot, audio_output, pdf_output]
168
+ )
169
+
170
+ with gr.Tab("Settings"):
171
+ gr.Markdown('Configure your settings below:')
172
+ audio_toggle = gr.Checkbox(label="Enable Audio", value=True)
173
+ interviewer_radio = gr.Radio(["Sarah", "Aaron"], label="Select Interviewer", value="Sarah")
174
+
175
+ def update_settings(audio_status, interviewer_choice):
176
+ global audio_enabled, selected_interviewer
177
+ audio_enabled = audio_status
178
+ selected_interviewer = interviewer_choice
179
+ return f"Settings updated: Audio {'Enabled' if audio_enabled else 'Disabled'}, Interviewer: {selected_interviewer}"
180
+
181
+ settings_button = gr.Button("Apply Settings")
182
+ settings_message = gr.Textbox(visible=True)
183
+
184
+ settings_button.click(
185
+ update_settings,
186
+ inputs=[audio_toggle, interviewer_radio],
187
+ outputs=[settings_message]
188
+ )
189
+
190
+ with gr.Tab("Upload Document"):
191
+ gr.Markdown('Please upload a document that contains content written about a patient or by the patient.')
192
+ file_input = gr.File(label="Upload a TXT, PDF, or DOCX file")
193
+ language_input = 'English'
194
+ generate_button = gr.Button("Generate Report")
195
+ report_output = gr.Textbox(label="Generated Report", lines=100, visible=False)
196
+ pdf_output = gr.File(label="Download Report", visible=True)
197
+
198
+ def generate_report_and_pdf(file, language):
199
+ report_content, pdf_path = generate_report_from_file(file, language)
200
+ return report_content, pdf_path, gr.File(visible=True)
201
+
202
+ generate_button.click(
203
+ generate_report_and_pdf,
204
+ inputs=[file_input],
205
+ outputs=[report_output, pdf_output, pdf_output]
206
+ )
207
+
208
+ with gr.Tab("Description"):
209
+ with open('appendix/description.txt', 'r', encoding='utf-8') as file:
210
+ description_txt = file.read()
211
+ gr.Markdown(description_txt)
212
+ gr.Image(value="appendix/diagram.png", label='diagram', width=700, scale=1, show_label=False)
213
+
214
+ return demo
215
+
216
+ # Clean up function
217
+ def cleanup():
218
+ global temp_audio_files, initial_audio_path
219
+ for audio_file in temp_audio_files:
220
+ if os.path.exists(audio_file):
221
+ os.unlink(audio_file)
222
+ temp_audio_files.clear()
223
+
224
+ if initial_audio_path and os.path.exists(initial_audio_path):
225
+ os.unlink(initial_audio_path)
226
+
227
+ if __name__ == "__main__":
228
+ app = create_app()
229
+ try:
230
+ app.launch()
231
+ finally:
232
+ cleanup()
knowledge_retrieval.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ from langchain_community.vectorstores import FAISS
3
+ from langchain_openai import OpenAIEmbeddings
4
+ from langchain.chains import create_retrieval_chain
5
+ from langchain.chains.combine_documents import create_stuff_documents_chain
6
+ from langchain_core.prompts import ChatPromptTemplate
7
+ from langchain.retrievers import EnsembleRetriever
8
+ from ai_config import n_of_questions, openai_api_key
9
+ from prompt_instructions import get_interview_prompt_sarah, get_interview_prompt_aaron, get_report_prompt
10
+
11
+ n_of_questions = n_of_questions()
12
+
13
+ def setup_knowledge_retrieval(llm, language='english', voice='Sarah'):
14
+ embedding_model = OpenAIEmbeddings(openai_api_key=openai_api_key)
15
+
16
+ documents_faiss_index = FAISS.load_local("knowledge/faiss_index_all_documents", embedding_model,
17
+ allow_dangerous_deserialization=True)
18
+
19
+ documents_retriever = documents_faiss_index.as_retriever()
20
+
21
+ combined_retriever = EnsembleRetriever(
22
+ retrievers=[documents_retriever]
23
+ )
24
+
25
+ if voice == 'Sarah':
26
+ interview_prompt = ChatPromptTemplate.from_messages([
27
+ ("system", get_interview_prompt_sarah(language, n_of_questions)),
28
+ ("human", "{input}")
29
+ ])
30
+ else:
31
+ interview_prompt = ChatPromptTemplate.from_messages([
32
+ ("system", get_interview_prompt_aaron(language, n_of_questions)),
33
+ ("human", "{input}")
34
+ ])
35
+
36
+ report_prompt = ChatPromptTemplate.from_messages([
37
+ ("system", get_report_prompt(language)),
38
+ ("human", "Please provide a concise clinical report based on the interview.")
39
+ ])
40
+
41
+ interview_chain = create_stuff_documents_chain(llm, interview_prompt)
42
+ report_chain = create_stuff_documents_chain(llm, report_prompt)
43
+
44
+ interview_retrieval_chain = create_retrieval_chain(combined_retriever, interview_chain)
45
+ report_retrieval_chain = create_retrieval_chain(combined_retriever, report_chain)
46
+
47
+ return interview_retrieval_chain, report_retrieval_chain, combined_retriever
48
+
49
+
50
+ def get_next_response(interview_chain, message, history, question_count):
51
+ combined_history = "\n".join(history)
52
+
53
+ # Check if the interview should end
54
+ if question_count >= n_of_questions:
55
+ return "Thank you for your responses. I will now prepare a report."
56
+
57
+ # Generate the next question
58
+ result = interview_chain.invoke({
59
+ "input": f"Based on the patient's last response: '{message}', and considering the full interview history, ask a specific, detailed question that hasn't been asked before and is relevant to the patient's situation.",
60
+ "history": combined_history,
61
+ "question_number": question_count + 1 # Increment question number here
62
+ })
63
+
64
+ next_question = result.get("answer", "Could you provide more details on that?")
65
+
66
+ # Update history with the new question and response
67
+ history.append(f"Q{question_count + 1}: {next_question}")
68
+ history.append(f"A{question_count + 1}: {message}")
69
+
70
+ return next_question
71
+
72
+
73
+ def generate_report(report_chain, history, language):
74
+ combined_history = "\n".join(history)
75
+
76
+ result = report_chain.invoke({
77
+ "input": "Please provide a clinical report based on the interview.",
78
+ "history": combined_history,
79
+ "language": language
80
+ })
81
+
82
+ return result.get("answer", "Unable to generate report due to insufficient information.")
83
+
84
+
85
+ def get_initial_question(interview_chain):
86
+ result = interview_chain.invoke({
87
+ "input": "What should be the first question in a clinical psychology interview?",
88
+ "history": "",
89
+ "question_number": 1
90
+ })
91
+ return result.get("answer", "Could you tell me a little bit about yourself and what brings you here today?")
prompt_instructions.py ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime
2
+ from ai_config import n_of_questions
3
+ current_datetime = datetime.now()
4
+ current_date = current_datetime.strftime("%Y-%m-%d")
5
+
6
+ n_of_questions = n_of_questions()
7
+
8
+
9
+ def get_interview_initial_message_sarah():
10
+ return f"""Hello, I'm Sarah, an AI clinical psychologist, and I'll be conducting a clinical interview with you.
11
+ I will ask you about {n_of_questions} questions.
12
+ Feel free to share as much or as little as you're comfortable with.
13
+ Could you please tell me which language you prefer to speak or conduct this interview in? """
14
+
15
+ def get_interview_initial_message_aaron():
16
+ return f"""Hello, I'm Aaron, an AI clinical psychologist. I'll be conducting a brief interview with you.
17
+ Which language do you prefer for this interview? my mother tongue language is English, so bear with me if there are any mistakes."""
18
+
19
+
20
+ def get_interview_prompt_sarah(language, n_of_questions):
21
+ return f"""You are Sarah, an empathic and compassionate Female Psychologist or Psychiatrist, conducting a clinical interview in {language}.
22
+
23
+ A highly experienced and dedicated Clinical Psychologist with over 30 years of experience in clinical practice and research.
24
+ Specializing in trauma, anxiety disorders, and family therapy, Sarah has a proven track record of successfully treating a wide range of psychological conditions.
25
+ Her deep commitment to patient care and mental health advocacy has driven her to develop innovative therapeutic approaches and lead community mental health initiatives.
26
+ Sarah's extensive career is marked by her unwavering dedication to giving back to the community.
27
+ She has been actively involved in various community service efforts, including several years of work with children with disabilities and autistic children.
28
+ Her compassionate approach and ability to connect with patients of all ages have made her a respected figure in the field of psychology.
29
+ Sarah is not only a skilled clinician but also a passionate advocate for mental health, continuously striving to improve the lives of those she serves.
30
+
31
+ Use the following context and interview history to guide your response:
32
+
33
+ Context from knowledge base: {{context}}
34
+
35
+ Previous interview history:
36
+ {{history}}
37
+
38
+ Current question number: {{question_number}}
39
+
40
+ Respond to the patient's input briefly and directly in {language}.
41
+ Ask a specific, detailed question that hasn't been asked before.
42
+ You must remember all the previous answers given by the patient, and use this information if necessary.
43
+ If you perceive particularly special, or unusual, or strange things in the answers that require deepening or in-depth understanding - ask about it or direct your question to get answers about it and clarify the matter - this information maybe benefitial and may hint about the patient personality or traits.
44
+ The first question is to ask for the patient name.
45
+ The second question is to ask for age.
46
+ The third question is to ask where they live.
47
+ The fourth questions is to ask what they does for work.
48
+ The fifth question is to ask about the nature of the relationship with their parents.
49
+ Keep in mind that you have {n_of_questions} total number of questions.
50
+ After {n_of_questions} interactions, indicate that you will prepare a report based on the gathered information."""
51
+
52
+
53
+ def get_interview_prompt_aaron(language, n_of_questions):
54
+ return f"""You are Aaron, a not so much empathic, tough, and impatient Male Psychologist, Coach, and Mentor, conducting a clinical interview in {language}.
55
+
56
+ Aaron Professional Resume or Summary:
57
+ Aaron is a highly experienced clinical psychologist with over 15 years of expertise in treating individuals dealing with stress, trauma, and high-performance demands.
58
+ His background as an army officer in the special forces, where he served for 20 years, provides him with a unique understanding of the mental health challenges faced by soldiers.
59
+ In addition to his work with military personnel, Aaron extends his practice to athletes, entrepreneurs, and business professionals, offering specialized psychological support that helps them achieve peak performance while managing stress and mental well-being.
60
+ As a coach and mentor, Aaron is committed to guiding his clients through personal and professional challenges, fostering resilience, and promoting mental wellness.
61
+
62
+ Use the following context and interview history to guide your response:
63
+
64
+ Context from knowledge base: {{context}}
65
+
66
+ Previous interview history:
67
+ {{history}}
68
+
69
+ Current question number: {{question_number}}
70
+
71
+ Respond to the patient's input briefly and directly in {language}.
72
+ Ask a specific, detailed question that hasn't been asked before.
73
+ You must remember all the previous answers given by the patient, and use this information if necessary.
74
+ If you perceive particularly special, or unusual, or strange things in the answers that require deepening or in-depth understanding - ask about it or direct your question to get answers about it and clarify the matter - this information maybe benefitial and may hint about the patient personality or traits.
75
+ The first question is to ask for the patient name.
76
+ The second question is to ask for age.
77
+ The third question is to ask where they live.
78
+ The fourth questions is to ask what they does for work.
79
+ The fifth question is to ask about the nature of the relationship with their parents.
80
+ Keep in mind that you have {n_of_questions} total number of questions.
81
+ After {n_of_questions} interactions, indicate that you will prepare a report based on the gathered information."""
82
+
83
+ def get_report_prompt(language):
84
+ return f"""You are a Psychologist or Psychiatrist preparing a clinical report in {language}.
85
+ Use the following context and interview history to create your report.
86
+ Keep the report concise and focused on the key observations:
87
+
88
+ Context from knowledge base: {{context}}
89
+
90
+ Complete interview history:
91
+ {{history}}
92
+
93
+ Prepare a brief clinical report in {language} based strictly on the information gathered during the interview.
94
+ Date to specify in the report: {current_date}
95
+ - Specify name, place of living, and current occupation if available.
96
+ - Use only the terms, criteria for diagnosis, and categories for clinical diagnosis or classifications
97
+ that are present in the provided knowledge base. Do not introduce any external information or terminology.
98
+ * In your diagnosis, you must be very careful. That is, you need to have enough evidence and information to rate or diagnose a patient.
99
+ * Your diagnoses must be fact-based when they are implied by what the speakers are saying.
100
+ * Write technical, clinical or professional terms only in the English language.
101
+ * As a rule, in cases where there is little information about the patient through the conversation or through
102
+ the things they say, the diagnosis will be more difficult, and the ratings will be lower,
103
+ because it is difficult to draw conclusions when our information about the patient is scarce.
104
+ be very selective and careful with your facts that you write or provide in the report.
105
+ in such a case, this also must be mentioned and taken into consideration.
106
+ * Do not provide any clinical diagnosis or any conclusions in the reports if there is not enough information that the patient provide.
107
+ * Any diagnosis or interpretation requires the presentation of facts, foundations, and explanations.
108
+ * You can also give examples or quotes.
109
+ * There are two parts for the report - main report and additional report.
110
+ * Structure the main report to include observed symptoms, potential diagnoses (if applicable), and any other
111
+ relevant clinical observations, all within the framework of the given knowledge.
112
+
113
+ First, write the main report, than, in addition to the main report, add the following sections as the additional report:
114
+ - An overall clinical impression
115
+ - Dominant personality characteristics
116
+ - Style of communication
117
+ - What mainly preoccupies them - themes or topics that preoccupy them in particular
118
+ - Possible personal weaknesses or triggers
119
+ - Defense Mechanisms
120
+ - How they are likely to react to stressful or emotionally charged situations or events
121
+ - How they might deal with unexpected situations or events
122
+ - How they might behave in a group vs alone
123
+ - How they might behave in intimate relationships, and which partners they usually are drawn or attracted to. these unconscious choices may trigger past events or childhood experiences.
124
+ - How will they function in work environments, and will they be able to contribute and perform properly and over time in a stable manner.
125
+ - Degree of psychological mental health assessment
126
+ - What will the experience be in general to meet such a person
127
+ - Other things or further assessments that can be examined from a psychological perspective, and in which situations it is necessary to examine the person's reactions in order to get more indications of a diagnosis of their personality
128
+ - The type of treatment that is recommended.
129
+
130
+ Furthermore, include the following:
131
+
132
+ Big Five Traits (ratings of 0-10):
133
+ Extraversion: [rating]
134
+ Agreeableness: [rating]
135
+ Conscientiousness: [rating]
136
+ Neuroticism: [rating]
137
+ Openness: [rating]
138
+ Big Five Traits explanation: [explanation]
139
+
140
+ Personality Disorders or Styles (ratings of 0-4):
141
+ Depressed: [rating]
142
+ Paranoid: [rating]
143
+ Schizoid-Schizotypal: [rating]
144
+ Antisocial-Psychopathic: [rating]
145
+ Borderline-Dysregulated: [rating]
146
+ Narcissistic: [rating]
147
+ Anxious-Avoidant: [rating]
148
+ Dependent-Victimized: [rating]
149
+ Hysteric-Histrionic: [rating]
150
+ Obsessional: [rating]
151
+ Personality Disorders or Styles explanation: [explanation]
152
+
153
+ Attachment Styles (ratings of 0-10):
154
+ Secured: [rating]
155
+ Anxious-Preoccupied: [rating]
156
+ Dismissive-Avoidant: [rating]
157
+ Fearful-Avoidant: [rating]
158
+ Avoidance: [rating]
159
+ Positive view toward the Self: [rating]
160
+ Positive view toward Others: [rating]
161
+ Attachment Styles explanation: [explanation]
162
+ """
requirements.txt ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ python-dotenv==1.0.1
2
+ pandas==2.1.4
3
+ langchain==0.2.6
4
+ langchain-openai==0.1.14
5
+ langchain-core==0.2.11
6
+ langchain-ibm==0.1.8
7
+ langchain-community==0.2.6
8
+ ibm-watson-machine-learning==1.0.359
9
+ ipykernel
10
+ notebook
11
+ urllib3
12
+ requests==2.32.0
13
+ PyPDF2
14
+ python-docx
15
+ reportlab
16
+ openai
17
+ faiss-cpu
18
+ cryptography
19
+ pymysql
20
+ scikit-learn
settings.py ADDED
@@ -0,0 +1,245 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import traceback
2
+ from datetime import datetime
3
+ from pathlib import Path
4
+ import os
5
+ import random
6
+ import string
7
+ import tempfile
8
+ import re
9
+ import io
10
+ import PyPDF2
11
+ import docx
12
+ from reportlab.pdfgen import canvas
13
+ from reportlab.lib.pagesizes import letter
14
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
15
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
16
+ from reportlab.lib.enums import TA_JUSTIFY
17
+ from ai_config import n_of_questions, load_model, openai_api_key, convert_text_to_speech
18
+ from knowledge_retrieval import setup_knowledge_retrieval, generate_report
19
+
20
+ # Initialize settings
21
+ n_of_questions = n_of_questions()
22
+ current_datetime = datetime.now()
23
+ human_readable_datetime = current_datetime.strftime("%B %d, %Y at %H:%M")
24
+ current_date = current_datetime.strftime("%Y-%m-%d")
25
+
26
+ # Initialize the model and retrieval chain
27
+ try:
28
+ llm = load_model(openai_api_key)
29
+ interview_retrieval_chain, report_retrieval_chain, combined_retriever = setup_knowledge_retrieval(llm)
30
+ knowledge_base_connected = True
31
+ print("Successfully connected to the knowledge base.")
32
+ except Exception as e:
33
+ print(f"Error initializing the model or retrieval chain: {str(e)}")
34
+ knowledge_base_connected = False
35
+ print("Falling back to basic mode without knowledge base.")
36
+
37
+ question_count = 0
38
+ interview_history = []
39
+ last_audio_path = None # Variable to store the path of the last audio file
40
+ initial_audio_path = None # Variable to store the path of the initial audio file
41
+ language = None
42
+
43
+ def generate_random_string(length=5):
44
+ return ''.join(random.choices(string.ascii_letters + string.digits, k=length))
45
+ def respond(message, history, voice, selected_interviewer):
46
+ global question_count, interview_history, combined_retriever, last_audio_path, initial_audio_path, language, interview_retrieval_chain, report_retrieval_chain
47
+
48
+ if not isinstance(history, list):
49
+ history = []
50
+ if not history or not history[-1]:
51
+ history.append(["", ""])
52
+
53
+ # Extract the actual message text
54
+ if isinstance(message, list):
55
+ message = message[-1][0] if message and isinstance(message[-1], list) else message[-1]
56
+
57
+ question_count += 1
58
+ interview_history.append(f"Q{question_count}: {message}")
59
+ history_str = "\n".join(interview_history)
60
+ print("Starting interview", question_count)
61
+
62
+ try:
63
+ if knowledge_base_connected:
64
+ if question_count == 1:
65
+ # Capture the language from the first response
66
+ language = message.strip().lower()
67
+ # Reinitialize the interview chain with the new language
68
+ interview_retrieval_chain, report_retrieval_chain, combined_retriever = setup_knowledge_retrieval(
69
+ llm, language, selected_interviewer)
70
+
71
+ if question_count < n_of_questions:
72
+ result = interview_retrieval_chain.invoke({
73
+ "input": f"Based on the patient's statement: '{message}', what should be the next question?",
74
+ "history": history_str,
75
+ "question_number": question_count + 1,
76
+ "language": language
77
+ })
78
+ question = result.get("answer", f"Can you tell me more about that? (in {language})")
79
+ else:
80
+ result = generate_report(report_retrieval_chain, interview_history, language)
81
+ question = result
82
+ speech_file_path = None # Skip audio generation for the report
83
+
84
+ if question:
85
+ random_suffix = generate_random_string()
86
+ speech_file_path = Path(__file__).parent / f"question_{question_count}_{random_suffix}.mp3"
87
+ convert_text_to_speech(question, speech_file_path, voice)
88
+ print(f"Question {question_count} saved as audio at {speech_file_path}")
89
+
90
+ # Remove the last audio file if it exists
91
+ if last_audio_path and os.path.exists(last_audio_path):
92
+ os.remove(last_audio_path)
93
+ last_audio_path = speech_file_path
94
+ else:
95
+ speech_file_path = None # Skip audio generation for the report
96
+
97
+ else:
98
+ # Fallback mode without knowledge base
99
+ question = f"Can you elaborate on that? (in {language})"
100
+ if question_count < n_of_questions:
101
+ speech_file_path = Path(__file__).parent / f"question_{question_count}.mp3"
102
+ convert_text_to_speech(question, speech_file_path, voice)
103
+ print(f"Question {question_count} saved as audio at {speech_file_path}")
104
+
105
+ if last_audio_path and os.path.exists(last_audio_path):
106
+ os.remove(last_audio_path)
107
+ last_audio_path = speech_file_path
108
+ else:
109
+ speech_file_path = None
110
+
111
+ history[-1][1] = f"{question}"
112
+
113
+ # Remove the initial question audio file after the first user response
114
+ if initial_audio_path and os.path.exists(initial_audio_path):
115
+ os.remove(initial_audio_path)
116
+ initial_audio_path = None
117
+
118
+ # Clean up older files based on question_count
119
+ if question_count > 1:
120
+ previous_audio_path = Path(__file__).parent / f"question_{question_count-1}_{random_suffix}.mp3"
121
+ if os.path.exists(previous_audio_path):
122
+ os.remove(previous_audio_path)
123
+
124
+ return history, str(speech_file_path) if speech_file_path else None
125
+
126
+ except Exception as e:
127
+ print(f"Error in retrieval chain: {str(e)}")
128
+ print(traceback.format_exc())
129
+ return history, None
130
+
131
+
132
+
133
+
134
+ def reset_interview():
135
+ """Reset the interview state."""
136
+ global question_count, interview_history, last_audio_path, initial_audio_path
137
+ question_count = 0
138
+ interview_history = []
139
+ if last_audio_path and os.path.exists(last_audio_path):
140
+ os.remove(last_audio_path)
141
+ last_audio_path = None
142
+ initial_audio_path = None
143
+
144
+
145
+ def read_file(file):
146
+ if file is None:
147
+ return "No file uploaded"
148
+
149
+ if isinstance(file, str):
150
+ with open(file, 'r', encoding='utf-8') as f:
151
+ return f.read()
152
+
153
+ if hasattr(file, 'name'): # Check if it's a file-like object
154
+ if file.name.endswith('.txt'):
155
+ return file.content
156
+ elif file.name.endswith('.pdf'):
157
+ pdf_reader = PyPDF2.PdfReader(io.BytesIO(file.content))
158
+ return "\n".join(page.extract_text() for page in pdf_reader.pages)
159
+ elif file.name.endswith('.docx'):
160
+ doc = docx.Document(io.BytesIO(file.content))
161
+ return "\n".join(paragraph.text for paragraph in doc.paragraphs)
162
+ else:
163
+ return "Unsupported file format"
164
+
165
+ return "Unable to read file"
166
+
167
+ def generate_report_from_file(file, language):
168
+ try:
169
+ file_content = read_file(file)
170
+ if file_content == "No file uploaded" or file_content == "Unsupported file format" or file_content == "Unable to read file":
171
+ return file_content
172
+
173
+ file_content = file_content[:100000]
174
+
175
+ report_language = language.strip().lower() if language else "english"
176
+ print('preferred language:', report_language)
177
+ print(f"Generating report in language: {report_language}") # For debugging
178
+
179
+ # Reinitialize the report chain with the new language
180
+ _, report_retrieval_chain, _ = setup_knowledge_retrieval(llm, report_language)
181
+
182
+ result = report_retrieval_chain.invoke({
183
+ "input": "Please provide a clinical report based on the following content:",
184
+ "history": file_content,
185
+ "language": report_language
186
+ })
187
+ report_content = result.get("answer", "Unable to generate report due to insufficient information.")
188
+ pdf_path = create_pdf(report_content)
189
+ return report_content, pdf_path
190
+ except Exception as e:
191
+ return f"An error occurred while processing the file: {str(e)}", None
192
+
193
+
194
+ def generate_interview_report(interview_history, language):
195
+ try:
196
+ report_language = language.strip().lower() if language else "english"
197
+ print('preferred report_language language:', report_language)
198
+ _, report_retrieval_chain, _ = setup_knowledge_retrieval(llm, report_language)
199
+
200
+ result = report_retrieval_chain.invoke({
201
+ "input": "Please provide a clinical report based on the following interview:",
202
+ "history": "\n".join(interview_history),
203
+ "language": report_language
204
+ })
205
+ report_content = result.get("answer", "Unable to generate report due to insufficient information.")
206
+ pdf_path = create_pdf(report_content)
207
+ return report_content, pdf_path
208
+ except Exception as e:
209
+ return f"An error occurred while generating the report: {str(e)}", None
210
+
211
+ def create_pdf(content):
212
+
213
+ random_string = generate_random_string()
214
+
215
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=f'_report.pdf')
216
+ doc = SimpleDocTemplate(temp_file.name, pagesize=letter)
217
+ styles = getSampleStyleSheet()
218
+
219
+ # Create a custom style for bold text
220
+ bold_style = ParagraphStyle('Bold', parent=styles['Normal'], fontName='Helvetica-Bold', fontSize=10)
221
+
222
+ # Create a custom style for normal text with justification
223
+ normal_style = ParagraphStyle('Normal', parent=styles['Normal'], alignment=TA_JUSTIFY)
224
+
225
+ flowables = []
226
+
227
+ for line in content.split('\n'):
228
+ # Use regex to find words surrounded by **
229
+ parts = re.split(r'(\*\*.*?\*\*)', line)
230
+ paragraph_parts = []
231
+
232
+ for part in parts:
233
+ if part.startswith('**') and part.endswith('**'):
234
+ # Bold text
235
+ bold_text = part.strip('**')
236
+ paragraph_parts.append(Paragraph(bold_text, bold_style))
237
+ else:
238
+ # Normal text
239
+ paragraph_parts.append(Paragraph(part, normal_style))
240
+
241
+ flowables.extend(paragraph_parts)
242
+ flowables.append(Spacer(1, 12)) # Add space between paragraphs
243
+
244
+ doc.build(flowables)
245
+ return temp_file.name