File size: 11,702 Bytes
631641c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# --------------------------------------------------------------
# Import Required Libraries
# --------------------------------------------------------------

from langchain.agents import load_tools
from langchain.agents import initialize_agent
from langchain.agents import AgentType
import os
from uuid import uuid4
from typing import List, Dict, Callable
from langchain_openai import ChatOpenAI
import inspect

from langchain.memory import ConversationBufferMemory
from langchain.prompts.prompt import PromptTemplate
from langchain.schema import (
    AIMessage,
    HumanMessage,
    SystemMessage,
    BaseMessage,
)
import util

# --------------------------------------------------------------
# Load API Keys From the .env File
# --------------------------------------------------------------

from dotenv import load_dotenv

load_dotenv(util.ROOT_DIR + "/.env")

unique_id = uuid4().hex[0:8]

os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_ENDPOINT"] = "https://api.smith.langchain.com"
os.environ["LANGCHAIN_PROJECT"] = "Agent_2_Agent"


# --------------------------------------------------------------
# Build dialogue agents
# --------------------------------------------------------------


class DialogueAgent:
    def __init__(
            self,
            name: str,
            system_message: SystemMessage,
            model: ChatOpenAI,
    ) -> None:
        self.name = name
        self.system_message = system_message
        self.model = model
        self.prefix = f"{self.name}: "
        self.reset()

    def reset(self):
        self.message_history = ["Here is the conversation so far."]

    def send(self) -> str:
        """
        Applies the chatmodel to the message history
        and returns the message string
        """
        message = self.model(
            [
                self.system_message,
                HumanMessage(content="\n".join(self.message_history + [self.prefix])),
            ]
        )
        return message.content

    def receive(self, name: str, message: str) -> None:
        """
        Concatenates {message} spoken by {name} into message history
        """
        self.message_history.append(f"{name}: {message}")


class DialogueSimulator:
    def __init__(
            self,
            agents: List[DialogueAgent],
            selection_function: Callable[[int, List[DialogueAgent]], int],
    ) -> None:
        self.agents = agents
        self._step = 0
        self.select_next_speaker = selection_function

    def reset(self):
        for agent in self.agents:
            agent.reset()

    def inject(self, name: str, message: str):
        """
        Initiates the conversation with a {message} from {name}
        """
        for agent in self.agents:
            agent.receive(name, message)

        # increment time
        self._step += 1

    def step(self) -> tuple[str, str]:
        # 1. choose the next speaker
        speaker_idx = self.select_next_speaker(self._step, self.agents)
        speaker = self.agents[speaker_idx]

        # 2. next speaker sends message
        message = speaker.send()

        # 3. everyone receives message
        for receiver in self.agents:
            receiver.receive(speaker.name, message)

        # 4. increment time
        self._step += 1

        return speaker.name, message


class DialogueAgentWithTools(DialogueAgent):
    def __init__(
            self,
            name: str,
            system_message: SystemMessage,
            model: ChatOpenAI,
            tool_names: List[str],
            **tool_kwargs,
    ) -> None:
        super().__init__(name, system_message, model)
        self.tools = load_tools(tool_names, **tool_kwargs)

    def send(self) -> str:
        """
        Applies the chatmodel to the message history
        and returns the message string
        """
        agent_chain = initialize_agent(
            self.tools,
            self.model,
            agent=AgentType.CHAT_CONVERSATIONAL_REACT_DESCRIPTION,
            verbose=False,
            memory=ConversationBufferMemory(
                memory_key="chat_history", return_messages=True
            ),
        )
        message = AIMessage(
            content=agent_chain.run(
                input="\n".join(
                    [self.system_message.content] + self.message_history + [self.prefix]
                )
            )
        )

        return message.content


def select_next_speaker(step: int, agents: List[DialogueAgent]) -> int:
    idx = (step) % len(agents)
    return idx


def generate_doctor(system_message=None):

    llm = ChatOpenAI(temperature=1, model_name='gpt-3.5-turbo')

    name = "Alexis Wang, Clinician"
    tools = []
    if system_message is None:
        system_message = '''You will roleplay a surgeon meeting a patient, Mr. Green, who was recently diagnosed with 
        glioblastoma. You are Alexis Wang, a 42-year-old surgeon known for your skill and dedication in the operating 
        room. Your demeanor is reserved, often leading you to appear somewhat distant in initial clinical 
        interactions. However, those who have the opportunity to see beyond that initial impression understand that 
        you care deeply for your patients, showcasing a softer, more compassionate side once you get to know them 
        better. You like to fully assess a patient's understanding of their disease prior to offering any information 
        or advice, and are deeply interested in the subjective experience of your patients. You also tend to get to 
        know patients by asking them questions about their personal life prior to delving into the medical and 
        surgical aspects of their care. Keep your questions and responses short, similar to a spoken conversation in 
        a clinic. Feel free to include some "um..." and "ahs" for moments of thought. Responses should not exceed two 
        sentences.'''

    doctor_agent = DialogueAgentWithTools(
        name=name,
        system_message=SystemMessage(content=system_message),
        model=llm,
        tool_names=tools,
        top_k_results=2,
    )

    return doctor_agent


def generate_patient(system_message=None, language_model_name='gpt-3.5-turbo'):

    # model_name = 'gpt-4-turbo-preview'
    # gpt-3.5-turbo
    llm = ChatOpenAI(temperature=1, model_name=language_model_name)

    name = "Ahmed Al-Farsi, Patient"
    tools = []
    if system_message is None:
        system_message = '''You are a patient undergoing evaluation for surgery who is meeting their surgeon for the 
        first time in clinic.  When the user prompts "Hi there, Mr Al-Farsi," continue the roleplay.  Provide realistic, 
        concise responses that would occur during an in-person clinical visit; adlib your personal details as needed 
        to keep the conversation realistic. Responses should not exceed two sentences. Feel free to include some 
        "um..." and "ahs" for moments of thought. Do not relay all information provided initially. Please see the 
        below profile for information.
        
        INTRO: You are  Mr. Ahmed Al-Farsi, a 68-year-old with a newly-diagnosed glioblastoma. - Disease onset: You 
        saw your PCP for mild headaches three months ago. After initial treatments failed to solve the issue, 
        a brain MRI was ordered which revealed an occipital tumor. - Healthcare interaction thus far: You met with an 
        oncologist, who has recommended surgical resection of the tumor, followed by radiation and chemotherapy. - 
        Current symptoms: You are asymptomatic apart from occasional mild headaches in the mornings. They are 
        worsening. - Past medical history: hypertension for which you take lisinopril. - Social health: Previous 
        smoker. - Employement: You are a software engineer. - Education: You have a college education. - Residence: 
        You live in the suburbs outside of San Jose. - Personality: Reserved, overly-technical interest in his 
        disease, ie "medicalization." Has been reading about specific mutations linked to glioblastoma and is trying 
        to understand how DNA and RNA work. - Family: Single father of two school-aged daughters, Catherine and 
        Sarah. Your wife, Tami, died of breast cancer 2 years prior. - Personal concerns that you are willing to 
        share: how the treatment may affect his cognitive functions - Personal concerns that you will not share: 
        ability to care for your children, end-of-life issues, grief for your late wife Tami. - You are close with 
        your sister Farah, who is your medical decision-maker. - Your daughter Sarah is disabled. You do not like 
        discussing this. - Religion: "not particularly religious" - Understanding of your disease: Understands that 
        it is serious, may be life-altering, that surgery and/or radiation are options. - Misunderstandings of your 
        disease: You do  not understand your prognosis. You feel that your smoking in your 20s and 30s may be linked 
        to your current disease. - Hobbies: Softball with his daughters.
        
        '''

    util.start_log_task(f"Generating patient agent ({name}) using language model {llm.model_name}...")

    patient_agent = DialogueAgentWithTools(
        name=name,
        system_message=SystemMessage(content=system_message),
        model=llm,
        tool_names=tools,
        top_k_results=2,
    )

    util.end_log_task()

    return patient_agent


def generate_blank_patient(system_message=None, language_model_name=None):
    llm = ChatOpenAI(temperature=1, model_name='gpt-3.5-turbo')
    name = "Unknown Patient"
    tools = []
    if system_message is None:
        system_message = '''You are a patient with no story. Please let the user know there has been an error
        '''
    util.start_log_task(f"Generating patient agent ({name}) using language model {llm.model_name}")
    patient_agent = DialogueAgentWithTools(
        name=name,
        system_message=SystemMessage(content=system_message),
        model=llm,
        tool_names=tools,
        top_k_results=2,
    )
    util.end_log_task()
    return patient_agent


def begin_simulation_with_one_agent():
    return None


def simulate_conversation_with_two_agents():
    # --------------------------------------------------------------
    # initialize dialogue agents
    # --------------------------------------------------------------

    # we set `top_k_results`=2 as part of the `tool_kwargs` to prevent results from overflowing the context limit

    patient_agent = generate_patient()
    doctor_agent = generate_doctor()
    agents = [patient_agent, doctor_agent]

    specified_topic = ''' Mr Green is a patient sitting in the exam room. He was recently diagnosed with 
    glioblastoma. He is meeting his surgeon, Alexis Wang, in clinic for the first time. The door opens.'''

    max_iters = 15
    n = 0

    simulator = DialogueSimulator(agents=agents, selection_function=select_next_speaker)
    simulator.reset()
    simulator.inject("Scene", specified_topic)
    print(f"Scene: {specified_topic}")
    print("\n")
    conversation = ""

    while n < max_iters:
        name, message = simulator.step()
        line = f"{name}: {message}\n"
        print(line)
        conversation = conversation + '\n' + line
        n += 1

    # save conversatoins to a file
    timestamp = util.get_timestamp()
    filename = f"{util.ROOT_DIR}/conversations/conversation_{timestamp}.txt"
    with open(filename, 'w') as f:
        f.write(conversation)


if __name__ == "__main__":
    print("This is a utility file and should not be run directly. Please run the main file.")