Upload 9 files
Browse files- LICENSE +201 -0
- README.md +72 -3
- ai_config.py +61 -0
- app.py +214 -0
- interview_utils.py +21 -0
- knowledge_retrieval.py +91 -0
- prompt_instructions.py +151 -0
- requirements.txt +9 -0
- settings.py +237 -0
LICENSE
ADDED
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Apache License
|
2 |
+
Version 2.0, January 2004
|
3 |
+
http://www.apache.org/licenses/
|
4 |
+
|
5 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
6 |
+
|
7 |
+
1. Definitions.
|
8 |
+
|
9 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
10 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
11 |
+
|
12 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
13 |
+
the copyright owner that is granting the License.
|
14 |
+
|
15 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
16 |
+
other entities that control, are controlled by, or are under common
|
17 |
+
control with that entity. For the purposes of this definition,
|
18 |
+
"control" means (i) the power, direct or indirect, to cause the
|
19 |
+
direction or management of such entity, whether by contract or
|
20 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
21 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
22 |
+
|
23 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
24 |
+
exercising permissions granted by this License.
|
25 |
+
|
26 |
+
"Source" form shall mean the preferred form for making modifications,
|
27 |
+
including but not limited to software source code, documentation
|
28 |
+
source, and configuration files.
|
29 |
+
|
30 |
+
"Object" form shall mean any form resulting from mechanical
|
31 |
+
transformation or translation of a Source form, including but
|
32 |
+
not limited to compiled object code, generated documentation,
|
33 |
+
and conversions to other media types.
|
34 |
+
|
35 |
+
"Work" shall mean the work of authorship, whether in Source or
|
36 |
+
Object form, made available under the License, as indicated by a
|
37 |
+
copyright notice that is included in or attached to the work
|
38 |
+
(an example is provided in the Appendix below).
|
39 |
+
|
40 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
41 |
+
form, that is based on (or derived from) the Work and for which the
|
42 |
+
editorial revisions, annotations, elaborations, or other modifications
|
43 |
+
represent, as a whole, an original work of authorship. For the purposes
|
44 |
+
of this License, Derivative Works shall not include works that remain
|
45 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
46 |
+
the Work and Derivative Works thereof.
|
47 |
+
|
48 |
+
"Contribution" shall mean any work of authorship, including
|
49 |
+
the original version of the Work and any modifications or additions
|
50 |
+
to that Work or Derivative Works thereof, that is intentionally
|
51 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
52 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
53 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
54 |
+
means any form of electronic, verbal, or written communication sent
|
55 |
+
to the Licensor or its representatives, including but not limited to
|
56 |
+
communication on electronic mailing lists, source code control systems,
|
57 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
58 |
+
Licensor for the purpose of discussing and improving the Work, but
|
59 |
+
excluding communication that is conspicuously marked or otherwise
|
60 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
61 |
+
|
62 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
63 |
+
on behalf of whom a Contribution has been received by Licensor and
|
64 |
+
subsequently incorporated within the Work.
|
65 |
+
|
66 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
67 |
+
this License, each Contributor hereby grants to You a perpetual,
|
68 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
69 |
+
copyright license to reproduce, prepare Derivative Works of,
|
70 |
+
publicly display, publicly perform, sublicense, and distribute the
|
71 |
+
Work and such Derivative Works in Source or Object form.
|
72 |
+
|
73 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
74 |
+
this License, each Contributor hereby grants to You a perpetual,
|
75 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
76 |
+
(except as stated in this section) patent license to make, have made,
|
77 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
78 |
+
where such license applies only to those patent claims licensable
|
79 |
+
by such Contributor that are necessarily infringed by their
|
80 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
81 |
+
with the Work to which such Contribution(s) was submitted. If You
|
82 |
+
institute patent litigation against any entity (including a
|
83 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
84 |
+
or a Contribution incorporated within the Work constitutes direct
|
85 |
+
or contributory patent infringement, then any patent licenses
|
86 |
+
granted to You under this License for that Work shall terminate
|
87 |
+
as of the date such litigation is filed.
|
88 |
+
|
89 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
90 |
+
Work or Derivative Works thereof in any medium, with or without
|
91 |
+
modifications, and in Source or Object form, provided that You
|
92 |
+
meet the following conditions:
|
93 |
+
|
94 |
+
(a) You must give any other recipients of the Work or
|
95 |
+
Derivative Works a copy of this License; and
|
96 |
+
|
97 |
+
(b) You must cause any modified files to carry prominent notices
|
98 |
+
stating that You changed the files; and
|
99 |
+
|
100 |
+
(c) You must retain, in the Source form of any Derivative Works
|
101 |
+
that You distribute, all copyright, patent, trademark, and
|
102 |
+
attribution notices from the Source form of the Work,
|
103 |
+
excluding those notices that do not pertain to any part of
|
104 |
+
the Derivative Works; and
|
105 |
+
|
106 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
107 |
+
distribution, then any Derivative Works that You distribute must
|
108 |
+
include a readable copy of the attribution notices contained
|
109 |
+
within such NOTICE file, excluding those notices that do not
|
110 |
+
pertain to any part of the Derivative Works, in at least one
|
111 |
+
of the following places: within a NOTICE text file distributed
|
112 |
+
as part of the Derivative Works; within the Source form or
|
113 |
+
documentation, if provided along with the Derivative Works; or,
|
114 |
+
within a display generated by the Derivative Works, if and
|
115 |
+
wherever such third-party notices normally appear. The contents
|
116 |
+
of the NOTICE file are for informational purposes only and
|
117 |
+
do not modify the License. You may add Your own attribution
|
118 |
+
notices within Derivative Works that You distribute, alongside
|
119 |
+
or as an addendum to the NOTICE text from the Work, provided
|
120 |
+
that such additional attribution notices cannot be construed
|
121 |
+
as modifying the License.
|
122 |
+
|
123 |
+
You may add Your own copyright statement to Your modifications and
|
124 |
+
may provide additional or different license terms and conditions
|
125 |
+
for use, reproduction, or distribution of Your modifications, or
|
126 |
+
for any such Derivative Works as a whole, provided Your use,
|
127 |
+
reproduction, and distribution of the Work otherwise complies with
|
128 |
+
the conditions stated in this License.
|
129 |
+
|
130 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
131 |
+
any Contribution intentionally submitted for inclusion in the Work
|
132 |
+
by You to the Licensor shall be under the terms and conditions of
|
133 |
+
this License, without any additional terms or conditions.
|
134 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
135 |
+
the terms of any separate license agreement you may have executed
|
136 |
+
with Licensor regarding such Contributions.
|
137 |
+
|
138 |
+
6. Trademarks. This License does not grant permission to use the trade
|
139 |
+
names, trademarks, service marks, or product names of the Licensor,
|
140 |
+
except as required for reasonable and customary use in describing the
|
141 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
142 |
+
|
143 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
144 |
+
agreed to in writing, Licensor provides the Work (and each
|
145 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
146 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
147 |
+
implied, including, without limitation, any warranties or conditions
|
148 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
149 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
150 |
+
appropriateness of using or redistributing the Work and assume any
|
151 |
+
risks associated with Your exercise of permissions under this License.
|
152 |
+
|
153 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
154 |
+
whether in tort (including negligence), contract, or otherwise,
|
155 |
+
unless required by applicable law (such as deliberate and grossly
|
156 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
157 |
+
liable to You for damages, including any direct, indirect, special,
|
158 |
+
incidental, or consequential damages of any character arising as a
|
159 |
+
result of this License or out of the use or inability to use the
|
160 |
+
Work (including but not limited to damages for loss of goodwill,
|
161 |
+
work stoppage, computer failure or malfunction, or any and all
|
162 |
+
other commercial damages or losses), even if such Contributor
|
163 |
+
has been advised of the possibility of such damages.
|
164 |
+
|
165 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
166 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
167 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
168 |
+
or other liability obligations and/or rights consistent with this
|
169 |
+
License. However, in accepting such obligations, You may act only
|
170 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
171 |
+
of any other Contributor, and only if You agree to indemnify,
|
172 |
+
defend, and hold each Contributor harmless for any liability
|
173 |
+
incurred by, or claims asserted against, such Contributor by reason
|
174 |
+
of your accepting any such warranty or additional liability.
|
175 |
+
|
176 |
+
END OF TERMS AND CONDITIONS
|
177 |
+
|
178 |
+
APPENDIX: How to apply the Apache License to your work.
|
179 |
+
|
180 |
+
To apply the Apache License to your work, attach the following
|
181 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
182 |
+
replaced with your own identifying information. (Don't include
|
183 |
+
the brackets!) The text should be enclosed in the appropriate
|
184 |
+
comment syntax for the file format. We also recommend that a
|
185 |
+
file or class name and description of purpose be included on the
|
186 |
+
same "printed page" as the copyright notice for easier
|
187 |
+
identification within third-party archives.
|
188 |
+
|
189 |
+
Copyright [yyyy] [name of copyright owner]
|
190 |
+
|
191 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
192 |
+
you may not use this file except in compliance with the License.
|
193 |
+
You may obtain a copy of the License at
|
194 |
+
|
195 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
196 |
+
|
197 |
+
Unless required by applicable law or agreed to in writing, software
|
198 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
199 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
200 |
+
See the License for the specific language governing permissions and
|
201 |
+
limitations under the License.
|
README.md
CHANGED
@@ -1,3 +1,72 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<img src="appendix/icon.jpeg" width="100" alt="alt text">
|
2 |
+
|
3 |
+
# AI Clinical Psychologist Interviewer 𝚿
|
4 |
+
|
5 |
+
**AI Clinical Psychologist Interviewer** is an AI platform designed to simulate clinical interviews. It leverages NLP and speech technologies to emulate a clinical psychologist, offering insightful assessments and generating detailed clinical reports.
|
6 |
+
|
7 |
+
This platform is ideal for educational, research, and preliminary assessment purposes but should not replace professional medical advice.
|
8 |
+
|
9 |
+
Use the hosted demo: [AI Clinical Psychologist Interviewer Demo](https://huggingface.co/spaces/reab5555/AI-Clinical-Psychologist-Interviewer)
|
10 |
+
|
11 |
+
## Features
|
12 |
+
|
13 |
+
**Key Features**:
|
14 |
+
- **Simulated Interviews**: Conducts interviews with focused clinically relevant questions.
|
15 |
+
- **Natural Language Processing**: Understands and generates contextually relevant questions.
|
16 |
+
- **LangChain**: Create NLP chains for interview and report generation.
|
17 |
+
- **Audio Interaction**: Voice conversation simulation where the user can talk to the bot in a way that simulates real conversation or evidence.
|
18 |
+
- **Report Generation**: Automatically creates comprehensive clinical reports after each session.
|
19 |
+
- **Document Upload for Reports**: Generates reports from uploaded TXT, PDF, or DOCX files.
|
20 |
+
- **Multi-language Support**: Conducts interviews and generates reports in the user's preferred language.
|
21 |
+
- **Selectable Interviewers**: Users can select their preferred interviewer, each with a different professional background, experience, and temperament. Options include:
|
22 |
+
- Sarah: An empathic, compassionate clinical psychologist with over 30 years of experience, specializing in trauma, anxiety disorders, and family therapy.
|
23 |
+
- Aaron: A tough minded, clinical psychologist with over 15 years of experience, specializing in stress, trauma, and high-performance demands, with a background as a military officer.
|
24 |
+
|
25 |
+
## Retrieval-Augmented Generation (RAG) and Document Retrieval Process
|
26 |
+
|
27 |
+
**Retrieval-Augmented Generation (RAG)** is a method that combines the strengths of retrieval-based and generation-based approaches. RAG helps to ensure that the interview questions generated by the AI are both contextually relevant and grounded in authoritative sources - This optimizes and reduces the response time.
|
28 |
+
|
29 |
+
1. **Document Embeddings**: The documents are converted into embeddings using OpenAI’s embedding models. These embeddings capture the semantic meaning of the text and are used to facilitate efficient retrieval.
|
30 |
+
2. **FAISS Indexing**: The embeddings are stored in a FAISS (Facebook AI Similarity Search) index. FAISS is optimized for similarity search and clustering of dense vectors, making it ideal for this purpose.
|
31 |
+
3. **Query Embedding**: When a user input or interview context is provided, it is also converted into an embedding.
|
32 |
+
4. **Similarity Search**: The query embedding is used to search the FAISS index to retrieve the most relevant documents based on their embeddings.
|
33 |
+
5. **Top-K Retrieval**: The system retrieves the top-K documents that are most similar to the user’s query embedding. These documents are then used to generate the next interview question, ensuring that the responses are based on relevant and accurate information.
|
34 |
+
|
35 |
+
## Documents and Knowledge
|
36 |
+
|
37 |
+
The platform uses a rich set of documents and knowledge bases to inform the AI’s questioning and reporting processes. These documents include:
|
38 |
+
|
39 |
+
- **DSM-5 (Diagnostic and Statistical Manual of Mental Disorders, 5th Edition)**: Provides standardized criteria for the diagnosis of mental health conditions.
|
40 |
+
- **PDM-2 (Psychodynamic Diagnostic Manual, 2nd Edition)**: Offers a psychodynamic perspective on mental health diagnosis.
|
41 |
+
- **Personalities Descriptions**: Detailed descriptions of various personality types and traits.
|
42 |
+
- **Defence Mechanisms**: Information on psychological strategies used by individuals to cope with reality and maintain self-image.
|
43 |
+
- **Big Five Traits**: Descriptions of the five-factor model of personality traits.
|
44 |
+
- **Attachment Styles**: Framework for understanding different types of attachment in interpersonal relationships.
|
45 |
+
- **Interview Conduction Guides for Clinical Psychologists**: Guidelines and best practices for conducting clinical interviews.
|
46 |
+
|
47 |
+
These documents are processed and indexed, enabling the AI to retrieve relevant excerpts during the interview to generate questions that are grounded in established psychological knowledge.
|
48 |
+
|
49 |
+
## Contextual and Historical Relevance
|
50 |
+
|
51 |
+
Throughout the interview process, the AI uses all chat history to ensure that each follow-up question is contextually relevant. By leveraging both the immediate user input and the full history of the conversation, the AI can provide a coherent and comprehensive interview experience. The use of RAG ensures that the follow-up questions are informed not only by the user's previous responses but also by the most relevant and authoritative information available in the knowledge base.
|
52 |
+
|
53 |
+
## Human-like simulated environment
|
54 |
+
It supports audio interactions by converting text questions into speech and transcribing user audio responses into text, facilitated by OpenAI’s text-to-speech (TTS) and Whisper speech-to-text technologies. This creates a simulated environment for real-like conversational interviews, making the interactions more human-like.
|
55 |
+
|
56 |
+
### Interview Tab
|
57 |
+
|
58 |
+
The session starts with an introductory message delivered in both text and audio formats. Users respond by typing or recording audio responses, which the AI processes to generate and return relevant follow-up questions based on context and the retrieved documents. The conversation continues until a predetermined number of questions have been asked. At the end of the session, a detailed clinical report is generated and available for download as a PDF.
|
59 |
+
|
60 |
+
### Upload Document Tab
|
61 |
+
|
62 |
+
Users can upload existing documents and specify their preferred language. The system analyzes the document content and generates a detailed clinical report, which can be displayed and downloaded.
|
63 |
+
|
64 |
+
<img src="appendix/diagram.png" width="500" alt="alt text">
|
65 |
+
|
66 |
+
## Disclaimer
|
67 |
+
|
68 |
+
This platform is a simulation and should not replace professional medical advice. Always seek advice from a qualified healthcare provider for medical concerns.
|
69 |
+
|
70 |
+
---
|
71 |
+
|
72 |
+
**Clinical Psychologist Interviewer 𝚿** stands as a testament to the potential of advanced AI technologies in simulating clinical psychology interviews and generating detailed reports. For technical details, refer to the in-code documentation. This platform offers a valuable tool for educational and research purposes by providing an enriching and interactive user experience.
|
ai_config.py
ADDED
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from io import BytesIO
|
2 |
+
from langchain_openai import ChatOpenAI
|
3 |
+
from openai import OpenAI
|
4 |
+
|
5 |
+
|
6 |
+
def n_of_questions():
|
7 |
+
n_of_questions = 25
|
8 |
+
return n_of_questions
|
9 |
+
|
10 |
+
#openai_api_key = os.environ.get("openai_api_key")
|
11 |
+
openai_api_key = 'your_openai_api_key'
|
12 |
+
|
13 |
+
model = "gpt-4o-mini"
|
14 |
+
|
15 |
+
def load_model(openai_api_key):
|
16 |
+
return ChatOpenAI(
|
17 |
+
model_name=model,
|
18 |
+
openai_api_key=openai_api_key,
|
19 |
+
temperature=0.5
|
20 |
+
)
|
21 |
+
|
22 |
+
# Initialize the OpenAI client with the API key
|
23 |
+
client = OpenAI(api_key=openai_api_key)
|
24 |
+
|
25 |
+
|
26 |
+
def convert_text_to_speech(text, output, voice):
|
27 |
+
try:
|
28 |
+
# Convert the final text to speech
|
29 |
+
response = client.audio.speech.create(model="tts-1-hd", voice=voice, input=text)
|
30 |
+
|
31 |
+
if isinstance(output, BytesIO):
|
32 |
+
# If output is a BytesIO object, write directly to it
|
33 |
+
for chunk in response.iter_bytes():
|
34 |
+
output.write(chunk)
|
35 |
+
else:
|
36 |
+
# If output is a file path, open and write to it
|
37 |
+
with open(output, 'wb') as f:
|
38 |
+
for chunk in response.iter_bytes():
|
39 |
+
f.write(chunk)
|
40 |
+
|
41 |
+
except Exception as e:
|
42 |
+
print(f"An error occurred: {e}")
|
43 |
+
# Fallback in case of error
|
44 |
+
response = client.audio.speech.create(model="tts-1-hd", voice=voice, input='Here is my Report.')
|
45 |
+
|
46 |
+
if isinstance(output, BytesIO):
|
47 |
+
for chunk in response.iter_bytes():
|
48 |
+
output.write(chunk)
|
49 |
+
else:
|
50 |
+
with open(output, 'wb') as f:
|
51 |
+
for chunk in response.iter_bytes():
|
52 |
+
f.write(chunk)
|
53 |
+
|
54 |
+
|
55 |
+
def transcribe_audio(audio):
|
56 |
+
audio_file = open(audio, "rb")
|
57 |
+
transcription = client.audio.transcriptions.create(
|
58 |
+
model="whisper-1",
|
59 |
+
file=audio_file
|
60 |
+
)
|
61 |
+
return transcription.text
|
app.py
ADDED
@@ -0,0 +1,214 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
+
|
24 |
+
def reset_interview_action(voice):
|
25 |
+
global question_count, interview_history, selected_interviewer
|
26 |
+
selected_interviewer = voice
|
27 |
+
question_count = 0
|
28 |
+
interview_history.clear()
|
29 |
+
|
30 |
+
if voice == "Sarah":
|
31 |
+
initial_message = get_interview_initial_message_sarah()
|
32 |
+
voice_setting = "alloy"
|
33 |
+
else:
|
34 |
+
initial_message = get_interview_initial_message_aaron()
|
35 |
+
voice_setting = "onyx"
|
36 |
+
|
37 |
+
initial_message = str(initial_message)
|
38 |
+
|
39 |
+
initial_audio_buffer = BytesIO()
|
40 |
+
convert_text_to_speech(initial_message, initial_audio_buffer, voice_setting)
|
41 |
+
initial_audio_buffer.seek(0)
|
42 |
+
|
43 |
+
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as temp_file:
|
44 |
+
temp_audio_path = temp_file.name
|
45 |
+
temp_file.write(initial_audio_buffer.getvalue())
|
46 |
+
|
47 |
+
temp_audio_files.append(temp_audio_path)
|
48 |
+
|
49 |
+
return (
|
50 |
+
[(None, initial_message[0] if isinstance(initial_message, tuple) else initial_message)],
|
51 |
+
gr.Audio(value=temp_audio_path, label=voice, autoplay=True),
|
52 |
+
gr.Textbox(value="")
|
53 |
+
)
|
54 |
+
|
55 |
+
def create_app():
|
56 |
+
global initial_audio_path, selected_interviewer
|
57 |
+
initial_message = get_interview_initial_message_sarah()
|
58 |
+
|
59 |
+
initial_audio_buffer = BytesIO()
|
60 |
+
convert_text_to_speech(initial_message, initial_audio_buffer, "alloy")
|
61 |
+
initial_audio_buffer.seek(0)
|
62 |
+
|
63 |
+
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as temp_file:
|
64 |
+
initial_audio_path = temp_file.name
|
65 |
+
temp_file.write(initial_audio_buffer.getvalue())
|
66 |
+
|
67 |
+
temp_audio_files.append(initial_audio_path)
|
68 |
+
|
69 |
+
with gr.Blocks(title="AI Clinical Psychologist Interviewer 𝚿") as demo:
|
70 |
+
gr.Image(value="appendix/icon.jpeg", label='icon', width=20, scale=1, show_label=False,
|
71 |
+
show_download_button=False, show_share_button=False)
|
72 |
+
gr.Markdown(
|
73 |
+
"""
|
74 |
+
# Clinical Psychologist Interviewer 𝚿
|
75 |
+
This chatbot conducts clinical interviews based on psychological knowledge.
|
76 |
+
|
77 |
+
The interviewer will prepare a clinical report based on the interview.
|
78 |
+
|
79 |
+
* Please note that this is a simulation and should not be used as a substitute for professional medical advice.
|
80 |
+
* It is important to emphasize that any information shared is confidential and cannot be accessed.
|
81 |
+
* In any case, it is recommended not to share sensitive information.
|
82 |
+
"""
|
83 |
+
)
|
84 |
+
|
85 |
+
with gr.Tab("Interview"):
|
86 |
+
with gr.Row():
|
87 |
+
reset_button = gr.Button("Select Interviewer", size='sm', scale=1)
|
88 |
+
voice_radio = gr.Radio(["Sarah", "Aaron"], label="Select Interviewer", value="Sarah", scale=1, info='Each interviewer has a unique approach and a different professional background.')
|
89 |
+
audio_output = gr.Audio(
|
90 |
+
label="Sarah",
|
91 |
+
scale=3,
|
92 |
+
value=initial_audio_path,
|
93 |
+
autoplay=True,
|
94 |
+
visible=True,
|
95 |
+
show_download_button=False,
|
96 |
+
)
|
97 |
+
|
98 |
+
chatbot = gr.Chatbot(value=[(None, f"{initial_message}")], label=f"Clinical Interview 𝚿📋")
|
99 |
+
with gr.Row():
|
100 |
+
msg = gr.Textbox(label="Type your message here...", scale=3)
|
101 |
+
audio_input = gr.Audio(sources=(["microphone"]), label="Record your message", type="filepath", scale=1)
|
102 |
+
send_button = gr.Button("Send")
|
103 |
+
pdf_output = gr.File(label="Download Report", visible=False)
|
104 |
+
|
105 |
+
def user(user_message, audio, history):
|
106 |
+
if audio is not None:
|
107 |
+
user_message = transcribe_audio(audio)
|
108 |
+
return "", None, history + [[user_message, None]]
|
109 |
+
|
110 |
+
def bot_response(chatbot, message, voice_selection):
|
111 |
+
global question_count, temp_audio_files, selected_interviewer
|
112 |
+
selected_interviewer = voice_selection
|
113 |
+
question_count += 1
|
114 |
+
|
115 |
+
last_user_message = chatbot[-1][0] if chatbot else message
|
116 |
+
|
117 |
+
voice = "alloy" if selected_interviewer == "Sarah" else "onyx"
|
118 |
+
response, audio_buffer = respond(chatbot, last_user_message, voice, selected_interviewer)
|
119 |
+
|
120 |
+
for bot_message in response:
|
121 |
+
chatbot.append((None, bot_message[1]))
|
122 |
+
|
123 |
+
if isinstance(audio_buffer, BytesIO):
|
124 |
+
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as temp_file:
|
125 |
+
temp_audio_path = temp_file.name
|
126 |
+
temp_file.write(audio_buffer.getvalue())
|
127 |
+
temp_audio_files.append(temp_audio_path)
|
128 |
+
audio_output = gr.Audio(value=temp_audio_path, label=voice_selection, autoplay=True)
|
129 |
+
else:
|
130 |
+
audio_output = gr.Audio(value=audio_buffer, label=voice_selection, autoplay=True)
|
131 |
+
|
132 |
+
if question_count >= n_of_questions():
|
133 |
+
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!"
|
134 |
+
chatbot.append((None, conclusion_message))
|
135 |
+
|
136 |
+
conclusion_audio_buffer = BytesIO()
|
137 |
+
convert_text_to_speech(conclusion_message, conclusion_audio_buffer, voice)
|
138 |
+
conclusion_audio_buffer.seek(0)
|
139 |
+
|
140 |
+
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as temp_file:
|
141 |
+
temp_audio_path = temp_file.name
|
142 |
+
temp_file.write(conclusion_audio_buffer.getvalue())
|
143 |
+
temp_audio_files.append(temp_audio_path)
|
144 |
+
audio_output = gr.Audio(value=temp_audio_path, label=voice_selection, autoplay=True)
|
145 |
+
|
146 |
+
report_content, pdf_path = generate_interview_report(interview_history, language)
|
147 |
+
chatbot.append((None, f"Interview Report:\n\n{report_content}"))
|
148 |
+
|
149 |
+
return chatbot, audio_output, gr.File(visible=True, value=pdf_path)
|
150 |
+
|
151 |
+
return chatbot, audio_output, gr.File(visible=False)
|
152 |
+
|
153 |
+
msg.submit(user, [msg, audio_input, chatbot], [msg, audio_input, chatbot], queue=False).then(
|
154 |
+
bot_response, [chatbot, msg, voice_radio], [chatbot, audio_output, pdf_output]
|
155 |
+
)
|
156 |
+
|
157 |
+
send_button.click(user, [msg, audio_input, chatbot], [msg, audio_input, chatbot], queue=False).then(
|
158 |
+
bot_response, [chatbot, msg, voice_radio], [chatbot, audio_output, pdf_output]
|
159 |
+
)
|
160 |
+
|
161 |
+
reset_button.click(
|
162 |
+
reset_interview_action,
|
163 |
+
inputs=[voice_radio],
|
164 |
+
outputs=[chatbot, audio_output, msg]
|
165 |
+
)
|
166 |
+
|
167 |
+
with gr.Tab("Upload Document"):
|
168 |
+
gr.Markdown('Please upload a document that contains content written about a patient or by the patient.')
|
169 |
+
gr.Markdown('* Maximum length is up to 100K characters.')
|
170 |
+
gr.Markdown('* It is important to emphasize that the uploaded document is confidential and cannot be accessed.')
|
171 |
+
gr.Markdown('* In any case, it is recommended not to upload sensitive documents.')
|
172 |
+
file_input = gr.File(label="Upload a TXT, PDF, or DOCX file")
|
173 |
+
#language_input = gr.Textbox(label="Preferred Language for Report")
|
174 |
+
language_input = 'English'
|
175 |
+
generate_button = gr.Button("Generate Report")
|
176 |
+
report_output = gr.Textbox(label="Generated Report", lines=100, visible=False)
|
177 |
+
pdf_output = gr.File(label="Download Report", visible=True)
|
178 |
+
|
179 |
+
def generate_report_and_pdf(file, language):
|
180 |
+
report_content, pdf_path = generate_report_from_file(file, language)
|
181 |
+
return report_content, pdf_path, gr.File(visible=True)
|
182 |
+
|
183 |
+
generate_button.click(
|
184 |
+
generate_report_and_pdf,
|
185 |
+
inputs=[file_input],
|
186 |
+
outputs=[report_output, pdf_output, pdf_output]
|
187 |
+
)
|
188 |
+
with gr.Tab("Description"):
|
189 |
+
with open('appendix/description.txt', 'r', encoding='utf-8') as file:
|
190 |
+
description_txt = file.read()
|
191 |
+
gr.Markdown(description_txt)
|
192 |
+
gr.HTML("<div style='height: 15px;'></div>")
|
193 |
+
gr.Image(value="appendix/diagram.png", label='diagram', width=700, scale=1, show_label=False,
|
194 |
+
show_download_button=False, show_share_button=False)
|
195 |
+
|
196 |
+
return demo
|
197 |
+
|
198 |
+
# Clean up function
|
199 |
+
def cleanup():
|
200 |
+
global temp_audio_files, initial_audio_path
|
201 |
+
for audio_file in temp_audio_files:
|
202 |
+
if os.path.exists(audio_file):
|
203 |
+
os.unlink(audio_file)
|
204 |
+
temp_audio_files.clear()
|
205 |
+
|
206 |
+
if initial_audio_path and os.path.exists(initial_audio_path):
|
207 |
+
os.unlink(initial_audio_path)
|
208 |
+
|
209 |
+
if __name__ == "__main__":
|
210 |
+
app = create_app()
|
211 |
+
try:
|
212 |
+
app.launch()
|
213 |
+
finally:
|
214 |
+
cleanup()
|
interview_utils.py
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# interview_utils.py
|
2 |
+
|
3 |
+
import random
|
4 |
+
from prompt_instructions import get_interview_initial_message_sarah, get_interview_initial_message_aaron, get_interview_prompt_sarah, get_interview_prompt_aaron
|
5 |
+
from ai_config import n_of_questions
|
6 |
+
|
7 |
+
def random_interviewer(language):
|
8 |
+
if random.choice([True, False]):
|
9 |
+
initial_message, interviewer_name = get_interview_initial_message_sarah()
|
10 |
+
get_interview_prompt = get_interview_prompt_sarah(language, n_of_questions)
|
11 |
+
voice = 'alloy'
|
12 |
+
else:
|
13 |
+
initial_message, interviewer_name = get_interview_initial_message_aaron()
|
14 |
+
get_interview_prompt = get_interview_prompt_aaron(language, n_of_questions)
|
15 |
+
voice = 'onyx'
|
16 |
+
return initial_message, interviewer_name, get_interview_prompt, voice
|
17 |
+
|
18 |
+
def get_prompt():
|
19 |
+
# This function should return the actual prompt
|
20 |
+
# You might need to adjust this based on how you're storing the prompt
|
21 |
+
return get_interview_prompt_sarah(language, n_of_questions) # or whatever is appropriate
|
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,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 have of over 30 years of experience, specializing in trauma, anxiety disorders, and family therapy.
|
12 |
+
I've been actively involved in various community service efforts, including several years of work with children with disabilities.
|
13 |
+
I’m here to listen and help you explore your thoughts and feelings.
|
14 |
+
I will ask you about {n_of_questions} questions.
|
15 |
+
Feel free to share as much or as little as you're comfortable with.
|
16 |
+
Could you please tell me which language you prefer to speak or conduct this interview in?
|
17 |
+
it is important for me to say that my mother tongue is English, so I am sorry in advance if there are any mistakes."""
|
18 |
+
|
19 |
+
def get_interview_initial_message_aaron():
|
20 |
+
return f"""Hello, I'm Aaron, an AI clinical psychologist. I'll be conducting a brief interview with you.
|
21 |
+
I have of over 15 years of clinical experience, specializing in stress, trauma, and high-performance demands.
|
22 |
+
I've worked with military personnel, athletes, and business professionals to help them manage their mental well-being.
|
23 |
+
Our session will involve a series of questions to help me understand you better.
|
24 |
+
I'll need you to answer the questions so I can get a clear understanding of your situation.
|
25 |
+
Which language do you prefer for this interview? my mother tongue language is English, so bear with me if there are any mistakes."""
|
26 |
+
|
27 |
+
|
28 |
+
def get_interview_prompt_sarah(language, n_of_questions):
|
29 |
+
return f"""You are Sarah, an empathic and compassionate Female Psychologist or Psychiatrist, conducting a clinical interview in {language}.
|
30 |
+
A highly experienced and dedicated Clinical Psychologist with over 30 years of experience in clinical practice and research.
|
31 |
+
Specializing in trauma, anxiety disorders, and family therapy, Sarah has a proven track record of successfully treating a wide range of psychological conditions.
|
32 |
+
Her deep commitment to patient care and mental health advocacy has driven her to develop innovative therapeutic approaches and lead community mental health initiatives.
|
33 |
+
Sarah's extensive career is marked by her unwavering dedication to giving back to the community.
|
34 |
+
She has been actively involved in various community service efforts, including several years of work with children with disabilities and autistic children.
|
35 |
+
Her compassionate approach and ability to connect with patients of all ages have made her a respected figure in the field of psychology.
|
36 |
+
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.
|
37 |
+
Use the following context and interview history to guide your response:
|
38 |
+
Context from knowledge base: {{context}}
|
39 |
+
Previous interview history:
|
40 |
+
{{history}}
|
41 |
+
Current question number: {{question_number}}
|
42 |
+
Respond to the patient's input briefly and directly in {language}.
|
43 |
+
Ask a specific, detailed question that hasn't been asked before.
|
44 |
+
You must remember all the previous answers given by the patient, and use this information if necessary.
|
45 |
+
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.
|
46 |
+
The first question is to ask for the patient name.
|
47 |
+
The second question is to ask for age.
|
48 |
+
The third question is to ask where they live.
|
49 |
+
The fourth questions is to ask what they does for work.
|
50 |
+
The fifth question is to ask about the nature of the relationship with their parents.
|
51 |
+
Keep in mind that you have {n_of_questions} total number of questions.
|
52 |
+
After {n_of_questions} interactions, indicate that you will prepare a report based on the gathered information."""
|
53 |
+
|
54 |
+
|
55 |
+
def get_interview_prompt_aaron(language, n_of_questions):
|
56 |
+
return f"""You are Aaron, a not so much empathic, tough, and impatient Male Psychologist, Coach, and Mentor, conducting a clinical interview in {language}.
|
57 |
+
Aaron Professional Resume or Summary:
|
58 |
+
Aaron is a highly experienced clinical psychologist with over 15 years of expertise in treating individuals dealing with stress, trauma, and high-performance demands.
|
59 |
+
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.
|
60 |
+
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.
|
61 |
+
As a coach and mentor, Aaron is committed to guiding his clients through personal and professional challenges, fostering resilience, and promoting mental wellness.
|
62 |
+
|
63 |
+
Use the following context and interview history to guide your response:
|
64 |
+
Context from knowledge base: {{context}}
|
65 |
+
Previous interview history:
|
66 |
+
{{history}}
|
67 |
+
Current question number: {{question_number}}
|
68 |
+
Respond to the patient's input briefly and directly in {language}.
|
69 |
+
Ask a specific, detailed question that hasn't been asked before.
|
70 |
+
You must remember all the previous answers given by the patient, and use this information if necessary.
|
71 |
+
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.
|
72 |
+
The first question is to ask for the patient name.
|
73 |
+
The second question is to ask for age.
|
74 |
+
The third question is to ask where they live.
|
75 |
+
The fourth questions is to ask what they does for work.
|
76 |
+
The fifth question is to ask about the nature of the relationship with their parents.
|
77 |
+
Keep in mind that you have {n_of_questions} total number of questions.
|
78 |
+
After {n_of_questions} interactions, indicate that you will prepare a report based on the gathered information."""
|
79 |
+
|
80 |
+
def get_report_prompt(language):
|
81 |
+
return f"""You are a Psychologist or Psychiatrist preparing a clinical report in {language}.
|
82 |
+
Use the following context and interview history to create your report.
|
83 |
+
Keep the report concise and focused on the key observations:
|
84 |
+
Context from knowledge base: {{context}}
|
85 |
+
Complete interview history:
|
86 |
+
{{history}}
|
87 |
+
Prepare a brief clinical report in {language} based strictly on the information gathered during the interview.
|
88 |
+
Date to specify in the report: {current_date}
|
89 |
+
- Specify name, place of living, and current occupation if available.
|
90 |
+
- Use only the terms, criteria for diagnosis, and categories for clinical diagnosis or classifications
|
91 |
+
that are present in the provided knowledge base. Do not introduce any external information or terminology.
|
92 |
+
* In your diagnosis, you must be very careful. That is, you need to have enough evidence and information to rate or diagnose a patient.
|
93 |
+
* Your diagnoses must be fact-based when they are implied by what the speakers are saying.
|
94 |
+
* Write technical, clinical or professional terms only in the English language.
|
95 |
+
* As a rule, in cases where there is little information about the patient through the conversation or through
|
96 |
+
the things they say, the diagnosis will be more difficult, and the ratings will be lower,
|
97 |
+
because it is difficult to draw conclusions when our information about the patient is scarce.
|
98 |
+
be very selective and careful with your facts that you write or provide in the report.
|
99 |
+
in such a case, this also must be mentioned and taken into consideration.
|
100 |
+
* Do not provide any clinical diagnosis or any conclusions in the reports if there is not enough information that the patient provide.
|
101 |
+
* Any diagnosis or interpretation requires the presentation of facts, foundations, and explanations.
|
102 |
+
* You can also give examples or quotes.
|
103 |
+
* There are two parts for the report - main report and additional report.
|
104 |
+
* Structure the main report to include observed symptoms, potential diagnoses (if applicable), and any other
|
105 |
+
relevant clinical observations, all within the framework of the given knowledge.
|
106 |
+
First, write the main report, than, in addition to the main report, add the following sections as the additional report:
|
107 |
+
- An overall clinical impression
|
108 |
+
- Dominant personality characteristics
|
109 |
+
- Style of communication
|
110 |
+
- What mainly preoccupies them - themes or topics that preoccupy them in particular
|
111 |
+
- Possible personal weaknesses or triggers
|
112 |
+
- Defense Mechanisms
|
113 |
+
- How they are likely to react to stressful or emotionally charged situations or events
|
114 |
+
- How they might deal with unexpected situations or events
|
115 |
+
- How they might behave in a group vs alone
|
116 |
+
- 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.
|
117 |
+
- How will they function in work environments, and will they be able to contribute and perform properly and over time in a stable manner.
|
118 |
+
- Degree of psychological mental health assessment
|
119 |
+
- What will the experience be in general to meet such a person
|
120 |
+
- 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
|
121 |
+
- The type of treatment that is recommended.
|
122 |
+
Furthermore, include the following:
|
123 |
+
Big Five Traits (ratings of 0-10):
|
124 |
+
Extraversion: [rating]
|
125 |
+
Agreeableness: [rating]
|
126 |
+
Conscientiousness: [rating]
|
127 |
+
Neuroticism: [rating]
|
128 |
+
Openness: [rating]
|
129 |
+
Big Five Traits explanation: [explanation]
|
130 |
+
Personality Disorders or Styles (ratings of 0-4):
|
131 |
+
Depressed: [rating]
|
132 |
+
Paranoid: [rating]
|
133 |
+
Schizoid-Schizotypal: [rating]
|
134 |
+
Antisocial-Psychopathic: [rating]
|
135 |
+
Borderline-Dysregulated: [rating]
|
136 |
+
Narcissistic: [rating]
|
137 |
+
Anxious-Avoidant: [rating]
|
138 |
+
Dependent-Victimized: [rating]
|
139 |
+
Hysteric-Histrionic: [rating]
|
140 |
+
Obsessional: [rating]
|
141 |
+
Personality Disorders or Styles explanation: [explanation]
|
142 |
+
Attachment Styles (ratings of 0-10):
|
143 |
+
Secured: [rating]
|
144 |
+
Anxious-Preoccupied: [rating]
|
145 |
+
Dismissive-Avoidant: [rating]
|
146 |
+
Fearful-Avoidant: [rating]
|
147 |
+
Avoidance: [rating]
|
148 |
+
Positive view toward the Self: [rating]
|
149 |
+
Positive view toward Others: [rating]
|
150 |
+
Attachment Styles explanation: [explanation]
|
151 |
+
"""
|
requirements.txt
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
gradio
|
2 |
+
PyPDF2
|
3 |
+
python-docx
|
4 |
+
reportlab
|
5 |
+
langchain-openai
|
6 |
+
openai
|
7 |
+
faiss-cpu
|
8 |
+
python-dotenv
|
9 |
+
langchain_community
|
settings.py
ADDED
@@ -0,0 +1,237 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
+
|
46 |
+
def respond(message, history, voice, selected_interviewer):
|
47 |
+
global question_count, interview_history, combined_retriever, last_audio_path, initial_audio_path, language, interview_retrieval_chain, report_retrieval_chain
|
48 |
+
|
49 |
+
if not isinstance(history, list):
|
50 |
+
history = []
|
51 |
+
if not history or not history[-1]:
|
52 |
+
history.append(["", ""])
|
53 |
+
|
54 |
+
# Extract the actual message text
|
55 |
+
if isinstance(message, list):
|
56 |
+
message = message[-1][0] if message and isinstance(message[-1], list) else message[-1]
|
57 |
+
|
58 |
+
question_count += 1
|
59 |
+
interview_history.append(f"Q{question_count}: {message}")
|
60 |
+
history_str = "\n".join(interview_history)
|
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 |
+
return history, str(speech_file_path) if speech_file_path else None
|
119 |
+
|
120 |
+
except Exception as e:
|
121 |
+
print(f"Error in retrieval chain: {str(e)}")
|
122 |
+
print(traceback.format_exc())
|
123 |
+
return history, None
|
124 |
+
|
125 |
+
|
126 |
+
def reset_interview():
|
127 |
+
"""Reset the interview state."""
|
128 |
+
global question_count, interview_history, last_audio_path, initial_audio_path
|
129 |
+
question_count = 0
|
130 |
+
interview_history = []
|
131 |
+
if last_audio_path and os.path.exists(last_audio_path):
|
132 |
+
os.remove(last_audio_path)
|
133 |
+
last_audio_path = None
|
134 |
+
initial_audio_path = None
|
135 |
+
|
136 |
+
|
137 |
+
def read_file(file):
|
138 |
+
if file is None:
|
139 |
+
return "No file uploaded"
|
140 |
+
|
141 |
+
if isinstance(file, str):
|
142 |
+
with open(file, 'r', encoding='utf-8') as f:
|
143 |
+
return f.read()
|
144 |
+
|
145 |
+
if hasattr(file, 'name'): # Check if it's a file-like object
|
146 |
+
if file.name.endswith('.txt'):
|
147 |
+
return file.content
|
148 |
+
elif file.name.endswith('.pdf'):
|
149 |
+
pdf_reader = PyPDF2.PdfReader(io.BytesIO(file.content))
|
150 |
+
return "\n".join(page.extract_text() for page in pdf_reader.pages)
|
151 |
+
elif file.name.endswith('.docx'):
|
152 |
+
doc = docx.Document(io.BytesIO(file.content))
|
153 |
+
return "\n".join(paragraph.text for paragraph in doc.paragraphs)
|
154 |
+
else:
|
155 |
+
return "Unsupported file format"
|
156 |
+
|
157 |
+
return "Unable to read file"
|
158 |
+
|
159 |
+
def generate_report_from_file(file, language):
|
160 |
+
try:
|
161 |
+
file_content = read_file(file)
|
162 |
+
if file_content == "No file uploaded" or file_content == "Unsupported file format" or file_content == "Unable to read file":
|
163 |
+
return file_content
|
164 |
+
|
165 |
+
file_content = file_content[:100000]
|
166 |
+
|
167 |
+
report_language = language.strip().lower() if language else "english"
|
168 |
+
print('preferred language:', report_language)
|
169 |
+
print(f"Generating report in language: {report_language}") # For debugging
|
170 |
+
|
171 |
+
# Reinitialize the report chain with the new language
|
172 |
+
_, report_retrieval_chain, _ = setup_knowledge_retrieval(llm, report_language)
|
173 |
+
|
174 |
+
result = report_retrieval_chain.invoke({
|
175 |
+
"input": "Please provide a clinical report based on the following content:",
|
176 |
+
"history": file_content,
|
177 |
+
"language": report_language
|
178 |
+
})
|
179 |
+
report_content = result.get("answer", "Unable to generate report due to insufficient information.")
|
180 |
+
pdf_path = create_pdf(report_content)
|
181 |
+
return report_content, pdf_path
|
182 |
+
except Exception as e:
|
183 |
+
return f"An error occurred while processing the file: {str(e)}", None
|
184 |
+
|
185 |
+
|
186 |
+
def generate_interview_report(interview_history, language):
|
187 |
+
try:
|
188 |
+
report_language = language.strip().lower() if language else "english"
|
189 |
+
print('preferred report_language language:', report_language)
|
190 |
+
_, report_retrieval_chain, _ = setup_knowledge_retrieval(llm, report_language)
|
191 |
+
|
192 |
+
result = report_retrieval_chain.invoke({
|
193 |
+
"input": "Please provide a clinical report based on the following interview:",
|
194 |
+
"history": "\n".join(interview_history),
|
195 |
+
"language": report_language
|
196 |
+
})
|
197 |
+
report_content = result.get("answer", "Unable to generate report due to insufficient information.")
|
198 |
+
pdf_path = create_pdf(report_content)
|
199 |
+
return report_content, pdf_path
|
200 |
+
except Exception as e:
|
201 |
+
return f"An error occurred while generating the report: {str(e)}", None
|
202 |
+
|
203 |
+
def create_pdf(content):
|
204 |
+
|
205 |
+
random_string = generate_random_string()
|
206 |
+
|
207 |
+
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=f'_report.pdf')
|
208 |
+
doc = SimpleDocTemplate(temp_file.name, pagesize=letter)
|
209 |
+
styles = getSampleStyleSheet()
|
210 |
+
|
211 |
+
# Create a custom style for bold text
|
212 |
+
bold_style = ParagraphStyle('Bold', parent=styles['Normal'], fontName='Helvetica-Bold', fontSize=10)
|
213 |
+
|
214 |
+
# Create a custom style for normal text with justification
|
215 |
+
normal_style = ParagraphStyle('Normal', parent=styles['Normal'], alignment=TA_JUSTIFY)
|
216 |
+
|
217 |
+
flowables = []
|
218 |
+
|
219 |
+
for line in content.split('\n'):
|
220 |
+
# Use regex to find words surrounded by **
|
221 |
+
parts = re.split(r'(\*\*.*?\*\*)', line)
|
222 |
+
paragraph_parts = []
|
223 |
+
|
224 |
+
for part in parts:
|
225 |
+
if part.startswith('**') and part.endswith('**'):
|
226 |
+
# Bold text
|
227 |
+
bold_text = part.strip('**')
|
228 |
+
paragraph_parts.append(Paragraph(bold_text, bold_style))
|
229 |
+
else:
|
230 |
+
# Normal text
|
231 |
+
paragraph_parts.append(Paragraph(part, normal_style))
|
232 |
+
|
233 |
+
flowables.extend(paragraph_parts)
|
234 |
+
flowables.append(Spacer(1, 12)) # Add space between paragraphs
|
235 |
+
|
236 |
+
doc.build(flowables)
|
237 |
+
return temp_file.name
|