File size: 14,149 Bytes
c23a7cd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
import os
import pandas as pd
import streamlit as st
# from langchain.chat_models import AzureChatOpenAI
from langchain_openai import AzureChatOpenAI
from langchain_core.output_parsers import StrOutputParser, PydanticOutputParser
from langchain_core.prompts.chat import SystemMessagePromptTemplate, HumanMessagePromptTemplate, ChatPromptTemplate
from pydantic import BaseModel, Field, validator
from langchain.output_parsers.enum import EnumOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnableParallel
from enum import Enum
from helpers import read_md_files_from_doctors


# os.environ["LANGCHAIN_TRACING_V2"]="true"
# os.environ["LANGCHAIN_ENDPOINT"]="https://api.smith.langchain.com"
# LANGCHAIN_API_KEY = st.secrets['LANGCHAIN_API_KEY']
# os.environ["LANGCHAIN_PROJECT"]="UC2e2e"

# LLM Langchain Definition
OPENAI_API_KEY = st.secrets['azure_api_key']
OPENAI_API_TYPE = "azure"
OPENAI_API_BASE = "https://davidfearn-gpt4.openai.azure.com"
OPENAI_API_VERSION = "2024-08-01-preview"
OPENAI_MODEL = "gpt-4o-mini"

llm = AzureChatOpenAI(
    openai_api_version=OPENAI_API_VERSION,
    openai_api_key=OPENAI_API_KEY,
    azure_endpoint=OPENAI_API_BASE,
    openai_api_type=OPENAI_API_TYPE,
    deployment_name=OPENAI_MODEL,
    temperature=0,
    )


# Function to read file contents
def read_file(folder, file):
    """
    Reads the content of a text file and returns it as a string.
    :param file: The file name to read from the 'assets' directory.
    :return: The content of the file as a string or None if an error occurs.
    """
    fp = f"{folder}/{file}.md"
    try:
        with open(fp, 'r', encoding='utf-8') as file:
            content = file.read()
        return content
    except FileNotFoundError:
        print(f"The file at {fp} was not found.")
    except IOError:
        print(f"An error occurred while reading the file at {fp}.")
    return None

# Function to generate structured insights
def converse_with_patient(converstaional_history, latest_message):
    # test coversational history if empty return string "first iteraction" else return converstaional history
    if converstaional_history == False:
        converstaional_history = "first iteraction"

    SystemMessage = read_file("conversation", "intake_system")
    UserMessage = "This is the conversational history between the patient and the virtual doctor if one exists {converstaional_history} and this is the latest message from the patient {latest_message}"

    # class symptoms_capture(BaseModel):
    #     follow_up_question: str = Field(description="This feild is used to ask a question to refine your understanding of users symtoms")

    system_message_template = SystemMessagePromptTemplate.from_template(SystemMessage)
    # structured_llm = llm.with_structured_output(symptoms_capture)
    prompt = ChatPromptTemplate.from_messages([system_message_template, UserMessage])

    chain = prompt | llm | StrOutputParser()

    
    return chain.invoke({"converstaional_history": converstaional_history, "latest_message": latest_message})



def diagnosis_consensus(diagnosis_results, conversational_history):
    # Take the diagnosis results from the different models and generate a consensus diagnosis. The consensus diagnosis should be based on the confidence levels of the different models. The model with the highest confidence level should be given the most weight in the consensus diagnosis. The consensus diagnosis should be returned as a string.
    class Diagnosis_Consensus(BaseModel):
        diagnosis: str = Field(description="This field is used to give the diagnosis")
        explanation: str = Field(description="This field is used to explain the diagnosis")
        confidence: int = Field(description="This field is used to show the confidence level of the diagnosis")
        

        
    system_message_template = SystemMessagePromptTemplate.from_template("You are a medical manager you will be given a number of diagnosis from different doctors as well as the original conversation that occurred between the triaging doctor and the patient. You will need to provide a consensus diagnosis based on the confidence levels of the different doctors. You need to provide a final diagnosis, an explanation of the diagnosis and you overall confidence in the diagnosis. ONLY USE THE INFORMATION PROVIDED TO YOU. ONLY CHOOSE ONE DIAGNOSIS")    
    structured_llm_consensus = llm.with_structured_output(Diagnosis_Consensus)
    StandardUserMessage = "This is the conversational history between the patient and the virtual doctor {conversational_history}"
    prompt = ChatPromptTemplate.from_messages([system_message_template, StandardUserMessage])
    chain = prompt | structured_llm_consensus
    response = chain.invoke({"conversational_history": diagnosis_results})

    return {
        "diagnosis": response.diagnosis,
        "explanation": response.explanation,
        "confidence": response.confidence
    }


def evaluate_diagnosis(diagnosis_results, conversational_history):
    #this is a simularity function that will compare the diagnosis results and the conversational history to known symtoms for this diagnosis and return a confidence level of the diagnosis
    class Diagnosis_Evaluation(BaseModel):
        confidence: int = Field(description="This field is used to show the confidence level of the diagnosis")
        explanation: str = Field(description="This field is used to explain the confidence level of the diagnosis")

    system_message_template = SystemMessagePromptTemplate.from_template(read_file("evaluator", "eval_system_message"))
    StandardUserMessage = "This is the conversational history between the patient and the virtual doctor: {conversational_history}, this is the diagnosis our AI system has come up with: {diagnosis_results}"
    structured_llm_consensus = llm.with_structured_output(Diagnosis_Evaluation)
    prompt = ChatPromptTemplate.from_messages([system_message_template, StandardUserMessage])
    chain = prompt | structured_llm_consensus
    response = chain.invoke({"diagnosis_results": diagnosis_results, "conversational_history": conversational_history})

    return {
        "confidence": response.confidence,
        "explanation": response.explanation
    }

def next_best_action(diagnosis, conversational_history):
    # Take the diagnosis and return the next best action for the patient. The next best action should be based on the diagnosis and the conversational history. The next best action should be returned as a string.
    # Next best actions available are: Goto the GP / family doctor, goto a specialist, goto the hospital, go to the pharmacist or stay at home and rest. 
    
    class actions(str, Enum):
        GP = "GP / family doctor"
        SPECIALIST = "specialist"
        HOSPITAL = "hospital"
        PHARMACIST = "pharmacist"
        REST = "stay at home and rest"
        
    # Define the Pydantic model for the structured output
    
    class next_best_action(BaseModel):
        action: actions = Field(description="This field is used to give the next best action for the patient")
        explanation: str = Field(description="This field is used to explain the next best action for the patient")
        confidence: int = Field(description="This field is used to show the confidence level of the next best action")
    
    system_message_template = SystemMessagePromptTemplate.from_template(read_file("nba", "nba_system"))
    structured_llm_action = llm.with_structured_output(next_best_action)
    StandardUserMessage = "This is the conversational history between the patient and the virtual doctor: {conversational_history}, and this is the diagnosis: {diagnosis}"
    prompt = ChatPromptTemplate.from_messages([system_message_template, StandardUserMessage])
    chain = prompt | structured_llm_action

    response = chain.invoke({"diagnosis": diagnosis, "conversational_history": conversational_history})

    return {"action": response.action.value, "explanation": response.explanation, "confidence": response.confidence}


def create_diagnosis(converstaional_history):
    # Take the conversational history and generate a diagnosis. The diagnosis should pass the conversational history to a model specific to a medical specialty. This specialty will be provided by system messages in the doctors folder. The system messages will be in .md format.

    class Diagnosis(BaseModel):
        diagnosis: str = Field(description="This field is used to give the diagnosis")
        confidence: int = Field(description="This field is used to show the confidence level of the diagnosis")
        thinking: str = Field(description="This field is used to show the thinking of why you choose the diagnosis")
    

    system_message_df = read_md_files_from_doctors()
    structured_llm_diagnosis = llm.with_structured_output(Diagnosis)
    StandardUserMessage = "This is the conversational history between the patient and the virtual doctor {conversational_history}"

    # Extract the system messages from system_message_df content column and the name of the file from the filename column
    # In parallel, run the system messages through the model to get the diagnosis
    # For each system message in system_message_df, create a chain named by the content filename column
  

    chains = {}
    for index, row in system_message_df.iterrows():
        system_message_template = SystemMessagePromptTemplate.from_template(row['content'])
        prompt = ChatPromptTemplate.from_messages([system_message_template, StandardUserMessage])
        chains[row['filename']] = prompt | structured_llm_diagnosis

    map_chain = RunnableParallel(**chains)

    response = map_chain.invoke({"conversational_history": converstaional_history})
    diagnosis_results = {}
    for filename, result in response.items():
        filename_base = filename.split('.')[0]
        diagnosis_results[f"{filename_base}, diagnosis"] = result.diagnosis
        diagnosis_results[f"{filename_base}, thinking"] = result.thinking
        diagnosis_results[f"{filename_base}, confidence"] = result.confidence
        
    

   
    consensus = diagnosis_consensus(diagnosis_results, converstaional_history)
    # Return the diagnosis and the confidence level of the diagnosis
    # """{
    #     "confidence": response.confidence,
    #     "explanation": response.explanation
    # }"""
    print(consensus["confidence"])
    if consensus["confidence"] < 80:
        return "FAIL1", {
            "diagnosis_completion": "fail",
            "concensus_diagnosis": consensus["diagnosis"],
            "expert_distribution": diagnosis_results,
            "concensus_confidence": consensus["confidence"],
            "concensus_thinking": consensus["explanation"],
            "next_best_action_": "GP / family doctor",
            "Error": "The confidence level of the consensus diagnosis is below 80%"
        }


    final_diagnosis = evaluate_diagnosis(consensus, converstaional_history)
    # return the final diagnosis and the confidence level of the final diagnosis
    # """{
    #     "diagnosis": response.diagnosis,
    #     "explanation": response.explanation,
    #     "confidence": response.confidence
    # }"""
    
    if final_diagnosis["confidence"] < 80:
        return "FAIL2", {
            "diagnosis_completion": "fail",
            "concensus_diagnosis": consensus["diagnosis"],
            "expert_distribution": diagnosis_results,
            "concensus_confidence": consensus["confidence"],
            "concensus_thinking": consensus["explanation"],
            "evaluate_confidence": final_diagnosis["confidence"],
            "evaluate_explanation": final_diagnosis["explanation"],
            "next_best_action_": "GP / family doctor",
            "Error": "The confidence level of the final diagnosis is below 80%"
        }
    
    next_action = next_best_action(consensus["diagnosis"], converstaional_history)
    # return the next best action for the patient
    # {"action": response.action, "explanation": response.explanation, "confidence": response.confidence}

    return "SUCCESS", {
        "diagnosis_completion": "success",
        "concensus_diagnosis": consensus["diagnosis"],
        "expert_distribution": diagnosis_results,
        "concensus_confidence": consensus["confidence"],
        "concensus_thinking": consensus["explanation"],
        "evaluate_confidence": final_diagnosis["confidence"],
        "evaluate_explanation": final_diagnosis["explanation"],
        "next_best_action_": next_action["action"],
        "next_best_action_explanation": next_action["explanation"],
        "next_best_action_confidence": next_action["confidence"]
    }


# conversation = """{'conversation_id': '12345623', 'patient': 'John Doe', 'conversation': [{'role': 'user', 'content': 'I have a pain in my lower right hand side.'}, {'role': 'assistant', 'content': 'how bad is the pain on a scale of 1 to 10?'}, {'role': 'user', 'content': '10 out of 10 and its lasted 2 hours'}, {'role': 'assistant', 'content': 'Does the pain radiate to any other part of your body?'}, {'role': 'user', 'content': 'No its just in my abdomen.'}, {'role': 'assistant', 'content': 'have you had any other symptoms like nausea or vomiting?'}, {'role': 'user', 'content': 'I do feel a bit sick'}, {'role': 'assistant', 'content': 'thank you for this information'}]}
# ('PASS', 4, {'conversation_id': '12345623', 'patient': 'John Doe', 'conversation': [{'role': 'user', 'content': 'I have a pain in my lower right hand side.'}, {'role': 'assistant', 'content': 'how bad is the pain on a scale of 1 to 10?'}, {'role': 'user', 'content': '10 out of 10 and its lasted 2 hours'}, {'role': 'assistant', 'content': 'Does the pain radiate 
# to any other part of your body?'}, {'role': 'user', 'content': 'No its just in my abdomen.'}, {'role': 'assistant', 'content': 'have you had any other symptoms like nausea or vomiting?'}, {'role': 'user', 'content': 'I do feel a bit sick'}, {'role': 'assistant', 'content': 'thank you for this information'}]})"""

# print(create_diagnosis(conversation))