Spaces:
Running
Running
First commit
Browse files- README.md +2 -2
- ai_config.py +69 -0
- app.py +232 -0
- knowledge_retrieval.py +91 -0
- prompt_instructions.py +162 -0
- requirements.txt +20 -0
- 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.
|
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
|