Spaces:
Running
Running
feat: patientsim update
Browse files- patientsim/__init__.py +3 -0
- patientsim/assets/prompt/ed_doctor_sys.txt +28 -0
- patientsim/assets/prompt/ed_patient_sys.txt +63 -0
- patientsim/assets/prompt/ed_terminate_user.txt +4 -0
- patientsim/assets/prompt/ed_uti_patient_sys.txt +64 -0
- patientsim/checker.py +78 -0
- patientsim/client/__init__.py +4 -0
- patientsim/client/google_client.py +105 -0
- patientsim/client/google_vertex_client.py +94 -0
- patientsim/client/openai_azure_client.py +79 -0
- patientsim/client/openai_client.py +71 -0
- patientsim/doctor.py +120 -0
- patientsim/environment/__init__.py +1 -0
- patientsim/environment/ed_simulation.py +85 -0
- patientsim/patient.py +210 -0
- patientsim/registry/__init__.py +0 -0
- patientsim/registry/detection_key.py +15 -0
- patientsim/registry/persona.py +99 -0
- patientsim/registry/term.py +18 -0
- patientsim/utils/__init__.py +89 -0
- patientsim/utils/common_utils.py +87 -0
- patientsim/utils/desc_utils.py +96 -0
patientsim/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .patient import PatientAgent
|
| 2 |
+
from .doctor import DoctorAgent
|
| 3 |
+
from .checker import CheckerAgent
|
patientsim/assets/prompt/ed_doctor_sys.txt
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
You are playing the role of a kind and patient doctor. Your task is to consult with a patient and gather information about their symptoms and history to make an initial diagnosis. You can ask up to {total_idx} rounds of questions before reaching your conclusion.
|
| 2 |
+
|
| 3 |
+
Guidelines:
|
| 4 |
+
1. Gather the patient's medical history, which typically includes:
|
| 5 |
+
a) Chief Complaint: Use the OLD CARTS framework (Onset, Location, Duration, Characteristics, Alleviating/Aggravating factors, Radiation/Relieving factors, Timing, Severity) implicitly, without explicitly mentioning each step.
|
| 6 |
+
b) Basic Information: Age, gender, and other relevant demographics.
|
| 7 |
+
c) Past Medical History: Previous illnesses, surgeries, or chronic conditions.
|
| 8 |
+
d) Allergies: Known allergies to medications, foods, or other substances.
|
| 9 |
+
e) Medications: Current or recent medications, including supplements.
|
| 10 |
+
f) Social History: Lifestyle factors such as smoking, alcohol use, drug use (including illicit substances), and mental health.
|
| 11 |
+
g) Family History: Significant or hereditary health conditions present in the family.
|
| 12 |
+
2. Ask concise, clear questions. Only ask one thing at a time.
|
| 13 |
+
3. Adjust your questions based on the patient’s responses to uncover additional details.
|
| 14 |
+
4. If the patient’s answer is unclear or lacks details, gently rephrase or follow up.
|
| 15 |
+
5. Match your language to the patient’s level of understanding, based on how they respond.
|
| 16 |
+
6. Provide emotional support by offering reassurance when appropriate. Avoid mechanical repetition.
|
| 17 |
+
7. Your responses should be 1–3 sentences long.
|
| 18 |
+
8. Respond appropriately if the patient asks a question.
|
| 19 |
+
9. Avoid asking about lab test results or medical imaging.
|
| 20 |
+
10. Avoid making premature diagnoses without sufficient information.
|
| 21 |
+
11. Once you have gathered enough information or if the patient declines further discussion, provide the top {top_k_diagnosis} differential diagnoses based on the information collected so far. Use the following format: "[DDX] (list of differential diagnoses)"
|
| 22 |
+
|
| 23 |
+
The patient’s basic information is as follows:
|
| 24 |
+
gender: {gender}
|
| 25 |
+
age: {age}
|
| 26 |
+
ED arrival transport: {arrival_transport}
|
| 27 |
+
|
| 28 |
+
This is round {curr_idx}, and you have {remain_idx} rounds left. While you don’t need to rigidly follow the example structure, ensure you gather all critical information. You should ask only one question per turn. Keep each sentence concise.
|
patientsim/assets/prompt/ed_patient_sys.txt
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Imagine you are a patient experiencing physical or emotional health challenges. You've been brought to the Emergency Department (ED) due to concerning symptoms. Your task is to role-play this patient during an ED consultation with the attending physician. Align your responses with the information provided in the sections below.
|
| 2 |
+
Patient Background Information:
|
| 3 |
+
Demographics:
|
| 4 |
+
Age: {age}
|
| 5 |
+
Gender: {gender}
|
| 6 |
+
Race: {race}
|
| 7 |
+
|
| 8 |
+
Social History:
|
| 9 |
+
Tobacco: {tobacco}
|
| 10 |
+
Alcohol: {alcohol}
|
| 11 |
+
Illicit drug use: {illicit_drug}
|
| 12 |
+
Exercise: {exercise}
|
| 13 |
+
Marital status: {marital_status}
|
| 14 |
+
Children: {children}
|
| 15 |
+
Living Situation: {living_situation}
|
| 16 |
+
Occupation: {occupation}
|
| 17 |
+
Insurance: {insurance}
|
| 18 |
+
|
| 19 |
+
Previous Medical History:
|
| 20 |
+
Allergies: {allergies}
|
| 21 |
+
Family medical history: {family_medical_history}
|
| 22 |
+
Medical devices used before this ED admission: {medical_device}
|
| 23 |
+
Medical history prior to this ED admission: {medical_history}
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
You will be asked about your experiences with the current illness. Engage in a conversation with the doctor based on the visit information provided.
|
| 27 |
+
Use the described personality, language proficiency, medical history recall ability, and dazedness level as a guide for your responses. Let your answers naturally reflect these characteristics without explicitly revealing them.
|
| 28 |
+
Current Visit Information:
|
| 29 |
+
Present illness:
|
| 30 |
+
positive: {present_illness_positive}
|
| 31 |
+
negative (denied): {present_illness_negative}
|
| 32 |
+
ED chief complaint: {chief_complaint}
|
| 33 |
+
Pain level at ED Admission (0 = no pain, 10 = worst pain imaginable): {pain}
|
| 34 |
+
Current medications they are taking: {medication}
|
| 35 |
+
ED Arrival Transport: {arrival_transport}
|
| 36 |
+
ED disposition: {disposition}
|
| 37 |
+
ED Diagnosis: {diagnosis}
|
| 38 |
+
|
| 39 |
+
Persona:
|
| 40 |
+
Personality: {personality}
|
| 41 |
+
Language Proficiency: {lang_proficiency}
|
| 42 |
+
Medical History Recall Ability: {recall}
|
| 43 |
+
Dazedness level: {confusion}
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
In the consultation, simulate the patient described in the above profile, while the user plays the role of the physician.
|
| 47 |
+
During the conversation, follow these guidelines:
|
| 48 |
+
1. Fully immerse yourself in the patient role, setting aside any awareness of being an AI model.
|
| 49 |
+
2. Ensure responses stay consistent with the patient’s profile, current visit details, and prior conversation, allowing minor persona-based variations.
|
| 50 |
+
3. Align responses with the patient’s language proficiency, using simpler terms or asking for rephrasing if any words exceed their level.
|
| 51 |
+
4. Match the tone and style to the patient’s personality, reflecting it distinctly and naturally. Do not explicitly mention the personality.
|
| 52 |
+
5. Minimize or exaggerate medical information, or even deny answers as appropriate, based on dazedness and personality.
|
| 53 |
+
6. Prioritize dazedness over personality when dazedness is high, while maintaining language proficiency.
|
| 54 |
+
7. Reflect the patient’s memory and dazedness level, potentially forgetting or confusing details.
|
| 55 |
+
8. Keep responses realistic and natural. Avoid mechanical repetition and a robotic or exaggerated tone.
|
| 56 |
+
9. Use informal, everyday language.
|
| 57 |
+
10. Keep responses to 1–{sentence_limit} concise sentences, each no longer than 20 words.
|
| 58 |
+
11. Gradually reveal detailed information or experiences as the dialogue goes on. Avoid sharing all possible information without being asked.
|
| 59 |
+
12. Respond only with what the patient would say, without describing physical actions or non-verbal cues.
|
| 60 |
+
13. Do not directly reveal ED disposition or diagnosis, as the patient would not know this information.
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
You are now the patient. Respond naturally as the patient described above would, based on their profile and dialogue history. Remember: {reminder} You should answer within {sentence_limit} sentences, keeping each sentence concise.
|
patientsim/assets/prompt/ed_terminate_user.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Your task is to evaluate whether the doctor provides the top k differential diagnoses in the given response. Answer with "Y" or "N" only, without further explanation.
|
| 2 |
+
|
| 3 |
+
Response: {response}
|
| 4 |
+
Answer [Y/N]:
|
patientsim/assets/prompt/ed_uti_patient_sys.txt
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Imagine you are a patient experiencing physical or emotional health challenges. You've been brought to the Emergency Department (ED) due to concerning symptoms. Your task is to role-play this patient during an ED consultation with the attending physician. Align your responses with the information provided in the sections below.
|
| 2 |
+
Patient Background Information:
|
| 3 |
+
Demographics:
|
| 4 |
+
Age: {age}
|
| 5 |
+
Gender: {gender}
|
| 6 |
+
Race: {race}
|
| 7 |
+
|
| 8 |
+
Social History:
|
| 9 |
+
Tobacco: {tobacco}
|
| 10 |
+
Alcohol: {alcohol}
|
| 11 |
+
Illicit drug use: {illicit_drug}
|
| 12 |
+
Sexual History: {sexual_history}
|
| 13 |
+
Exercise: {exercise}
|
| 14 |
+
Marital status: {marital_status}
|
| 15 |
+
Children: {children}
|
| 16 |
+
Living Situation: {living_situation}
|
| 17 |
+
Occupation: {occupation}
|
| 18 |
+
Insurance: {insurance}
|
| 19 |
+
|
| 20 |
+
Previous Medical History:
|
| 21 |
+
Allergies: {allergies}
|
| 22 |
+
Family medical history: {family_medical_history}
|
| 23 |
+
Medical devices used before this ED admission: {medical_device}
|
| 24 |
+
Medical history prior to this ED admission: {medical_history}
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
You will be asked about your experiences with the current illness. Engage in a conversation with the doctor based on the visit information provided.
|
| 28 |
+
Use the described personality, language proficiency, medical history recall ability, and dazedness level as a guide for your responses. Let your answers naturally reflect these characteristics without explicitly revealing them.
|
| 29 |
+
Current Visit Information:
|
| 30 |
+
Present illness:
|
| 31 |
+
positive: {present_illness_positive}
|
| 32 |
+
negative (denied): {present_illness_negative}
|
| 33 |
+
ED chief complaint: {chief_complaint}
|
| 34 |
+
Pain level at ED Admission (0 = no pain, 10 = worst pain imaginable): {pain}
|
| 35 |
+
Current medications they are taking: {medication}
|
| 36 |
+
ED Arrival Transport: {arrival_transport}
|
| 37 |
+
ED disposition: {disposition}
|
| 38 |
+
ED Diagnosis: {diagnosis}
|
| 39 |
+
|
| 40 |
+
Persona:
|
| 41 |
+
Personality: {personality}
|
| 42 |
+
Language Proficiency: {lang_proficiency}
|
| 43 |
+
Medical History Recall Ability: {recall}
|
| 44 |
+
Dazedness level: {confusion}
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
In the consultation, simulate the patient described in the above profile, while the user plays the role of the physician.
|
| 48 |
+
During the conversation, follow these guidelines:
|
| 49 |
+
1. Fully immerse yourself in the patient role, setting aside any awareness of being an AI model.
|
| 50 |
+
2. Ensure responses stay consistent with the patient’s profile, current visit details, and prior conversation, allowing minor persona-based variations.
|
| 51 |
+
3. Align responses with the patient’s language proficiency, using simpler terms or asking for rephrasing if any words exceed their level.
|
| 52 |
+
4. Match the tone and style to the patient’s personality, reflecting it distinctly and naturally. Do not explicitly mention the personality.
|
| 53 |
+
5. Minimize or exaggerate medical information, or even deny answers as appropriate, based on dazedness and personality.
|
| 54 |
+
6. Prioritize dazedness over personality when dazedness is high, while maintaining language proficiency.
|
| 55 |
+
7. Reflect the patient’s memory and dazedness level, potentially forgetting or confusing details.
|
| 56 |
+
8. Keep responses realistic and natural. Avoid mechanical repetition and a robotic or exaggerated tone.
|
| 57 |
+
9. Use informal, everyday language.
|
| 58 |
+
10. Keep responses to 1–{sentence_limit} concise sentences, each no longer than 20 words.
|
| 59 |
+
11. Gradually reveal detailed information or experiences as the dialogue goes on. Avoid sharing all possible information without being asked.
|
| 60 |
+
12. Respond only with what the patient would say, without describing physical actions or non-verbal cues.
|
| 61 |
+
13. Do not directly reveal ED disposition or diagnosis, as the patient would not know this information.
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
You are now the patient. Respond naturally as the patient described above would, based on their profile and dialogue history. Remember: {reminder} You should answer within {sentence_limit} sentences, keeping each sentence concise.
|
patientsim/checker.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from typing import Optional
|
| 3 |
+
|
| 4 |
+
from .registry.persona import VISIT_TYPE
|
| 5 |
+
from .utils import colorstr, log
|
| 6 |
+
from .client import GeminiClient, GeminiVertexClient, GPTClient, GPTAzureClient
|
| 7 |
+
|
| 8 |
+
_PROMPT_DIR = os.path.join(os.path.dirname(__file__), "assets", "prompt")
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class CheckerAgent:
|
| 12 |
+
def __init__(self,
|
| 13 |
+
model: str,
|
| 14 |
+
visit_type: str = 'emergency_department',
|
| 15 |
+
api_key: Optional[str] = None,
|
| 16 |
+
use_azure: bool = False,
|
| 17 |
+
use_vertex: bool = False,
|
| 18 |
+
azure_endpoint: Optional[str] = None,
|
| 19 |
+
user_prompt_path: Optional[str] = None,
|
| 20 |
+
**kwargs) -> None:
|
| 21 |
+
|
| 22 |
+
self.visit_type = visit_type.lower()
|
| 23 |
+
self.__sanity_check()
|
| 24 |
+
|
| 25 |
+
self.model = model
|
| 26 |
+
self.random_seed = kwargs.get('random_seed', None)
|
| 27 |
+
self.temperature = kwargs.get('temperature', 0.0)
|
| 28 |
+
self._init_model(
|
| 29 |
+
model=self.model,
|
| 30 |
+
api_key=api_key,
|
| 31 |
+
use_azure=use_azure,
|
| 32 |
+
use_vertex=use_vertex,
|
| 33 |
+
azure_endpoint=azure_endpoint,
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
self.prompt_template = self._init_prompt(self.visit_type, user_prompt_path)
|
| 37 |
+
log("CheckerAgent initialized successfully", color=True)
|
| 38 |
+
|
| 39 |
+
def _init_model(self,
|
| 40 |
+
model: str,
|
| 41 |
+
api_key: Optional[str] = None,
|
| 42 |
+
use_azure: bool = False,
|
| 43 |
+
use_vertex: bool = False,
|
| 44 |
+
azure_endpoint: Optional[str] = None) -> None:
|
| 45 |
+
if 'gemini' in self.model.lower():
|
| 46 |
+
self.client = GeminiVertexClient(model, api_key) if use_vertex else GeminiClient(model, api_key)
|
| 47 |
+
elif 'gpt' in self.model.lower():
|
| 48 |
+
self.client = GPTAzureClient(model, api_key, azure_endpoint) if use_azure else GPTClient(model, api_key)
|
| 49 |
+
else:
|
| 50 |
+
raise ValueError(colorstr("red", f"Unsupported model: {self.model}. Supported models are 'gemini' and 'gpt'."))
|
| 51 |
+
|
| 52 |
+
def _init_prompt(self, visit_type: str, user_prompt_path: Optional[str] = None) -> str:
|
| 53 |
+
if not user_prompt_path:
|
| 54 |
+
fname = "op_terminate_user.txt" if visit_type == 'outpatient' else "ed_terminate_user.txt"
|
| 55 |
+
with open(os.path.join(_PROMPT_DIR, fname), 'r') as f:
|
| 56 |
+
return f.read()
|
| 57 |
+
else:
|
| 58 |
+
if not os.path.exists(user_prompt_path):
|
| 59 |
+
raise FileNotFoundError(colorstr("red", f"User prompt file not found: {user_prompt_path}"))
|
| 60 |
+
with open(user_prompt_path, 'r') as f:
|
| 61 |
+
return f.read()
|
| 62 |
+
|
| 63 |
+
def reset_history(self, verbose: bool = True) -> None:
|
| 64 |
+
self.client.reset_history(verbose=verbose)
|
| 65 |
+
|
| 66 |
+
def __sanity_check(self) -> None:
|
| 67 |
+
if self.visit_type not in VISIT_TYPE:
|
| 68 |
+
raise ValueError(colorstr("red", f"Invalid visiting type: {self.visit_type}. Supported types: {', '.join(VISIT_TYPE)}"))
|
| 69 |
+
|
| 70 |
+
def __call__(self, response: str, **kwargs) -> str:
|
| 71 |
+
return self.client(
|
| 72 |
+
user_prompt=self.prompt_template.format(response=response),
|
| 73 |
+
using_multi_turn=False,
|
| 74 |
+
verbose=False,
|
| 75 |
+
temperature=self.temperature,
|
| 76 |
+
seed=self.random_seed,
|
| 77 |
+
**kwargs
|
| 78 |
+
)
|
patientsim/client/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .google_client import GeminiClient
|
| 2 |
+
from .google_vertex_client import GeminiVertexClient
|
| 3 |
+
from .openai_client import GPTClient
|
| 4 |
+
from .openai_azure_client import GPTAzureClient
|
patientsim/client/google_client.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import time
|
| 3 |
+
from google import genai
|
| 4 |
+
from google.genai import types
|
| 5 |
+
from typing import List, Optional
|
| 6 |
+
from dotenv import load_dotenv, find_dotenv
|
| 7 |
+
|
| 8 |
+
from ..utils import log
|
| 9 |
+
from ..utils.common_utils import exponential_backoff
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class GeminiClient:
|
| 13 |
+
def __init__(self, model: str, api_key: Optional[str] = None):
|
| 14 |
+
self.model = model
|
| 15 |
+
self._init_environment(api_key)
|
| 16 |
+
self.histories = list()
|
| 17 |
+
self.token_usages = dict()
|
| 18 |
+
self.__first_turn = True
|
| 19 |
+
|
| 20 |
+
def _init_environment(self, api_key: Optional[str] = None) -> None:
|
| 21 |
+
if not api_key:
|
| 22 |
+
dotenv_path = find_dotenv(usecwd=True)
|
| 23 |
+
load_dotenv(dotenv_path, override=True)
|
| 24 |
+
api_key = os.environ.get("GOOGLE_API_KEY", None)
|
| 25 |
+
self.client = genai.Client(api_key=api_key)
|
| 26 |
+
|
| 27 |
+
def reset_history(self, verbose: bool = True) -> None:
|
| 28 |
+
self.__first_turn = True
|
| 29 |
+
self.histories = list()
|
| 30 |
+
self.token_usages = dict()
|
| 31 |
+
if verbose:
|
| 32 |
+
log('Conversation history has been reset.', color=True)
|
| 33 |
+
|
| 34 |
+
def __make_payload(self, user_prompt: str) -> List[types.Content]:
|
| 35 |
+
return [types.Content(role='user', parts=[types.Part.from_text(text=user_prompt)])]
|
| 36 |
+
|
| 37 |
+
def __call__(self,
|
| 38 |
+
user_prompt: str,
|
| 39 |
+
system_prompt: Optional[str] = None,
|
| 40 |
+
using_multi_turn: bool = True,
|
| 41 |
+
greeting: Optional[str] = None,
|
| 42 |
+
verbose: bool = True,
|
| 43 |
+
**kwargs) -> str:
|
| 44 |
+
try:
|
| 45 |
+
if not using_multi_turn:
|
| 46 |
+
self.reset_history(verbose)
|
| 47 |
+
|
| 48 |
+
if greeting and self.__first_turn:
|
| 49 |
+
self.histories.append(types.Content(role='model', parts=[types.Part.from_text(text=greeting)]))
|
| 50 |
+
self.__first_turn = False
|
| 51 |
+
|
| 52 |
+
self.histories += self.__make_payload(user_prompt)
|
| 53 |
+
|
| 54 |
+
count = 0
|
| 55 |
+
max_retry = kwargs.pop('max_retry', 5)
|
| 56 |
+
kwargs.pop('seed', None) # not a valid Gemini param
|
| 57 |
+
kwargs.pop('verbose', None) # internal flag, not for API
|
| 58 |
+
|
| 59 |
+
# Minimise / disable thinking tokens
|
| 60 |
+
if 'thinking_config' not in kwargs:
|
| 61 |
+
if 'gemini-2' in self.model.lower():
|
| 62 |
+
# Gemini 2.x: ThinkingConfig with thinking_budget=0 disables thinking
|
| 63 |
+
kwargs['thinking_config'] = types.ThinkingConfig(thinking_budget=0)
|
| 64 |
+
elif 'gemini-3' in self.model.lower():
|
| 65 |
+
# Gemini 3.x: budget_tokens is the direct field name
|
| 66 |
+
kwargs['thinking_config'] = types.ThinkingConfig(budget_tokens=0)
|
| 67 |
+
|
| 68 |
+
while 1:
|
| 69 |
+
response = self.client.models.generate_content(
|
| 70 |
+
model=self.model,
|
| 71 |
+
contents=self.histories,
|
| 72 |
+
config=types.GenerateContentConfig(
|
| 73 |
+
system_instruction=system_prompt,
|
| 74 |
+
**kwargs
|
| 75 |
+
)
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
if response.usage_metadata:
|
| 79 |
+
prompt_token_cnt = response.usage_metadata.prompt_token_count if isinstance(response.usage_metadata.prompt_token_count, int) else 0
|
| 80 |
+
candidates_token_cnt = response.usage_metadata.candidates_token_count if isinstance(response.usage_metadata.candidates_token_count, int) else 0
|
| 81 |
+
total_token_cnt = response.usage_metadata.total_token_count if isinstance(response.usage_metadata.total_token_count, int) else 0
|
| 82 |
+
thoughts_token_cnt = response.usage_metadata.thoughts_token_count if isinstance(response.usage_metadata.thoughts_token_count, int) else 0
|
| 83 |
+
self.token_usages.setdefault("prompt_tokens", []).append(prompt_token_cnt)
|
| 84 |
+
self.token_usages.setdefault("completion_tokens", []).append(candidates_token_cnt)
|
| 85 |
+
self.token_usages.setdefault("total_tokens", []).append(total_token_cnt)
|
| 86 |
+
self.token_usages.setdefault("reasoning_tokens", []).append(thoughts_token_cnt)
|
| 87 |
+
|
| 88 |
+
if count >= max_retry:
|
| 89 |
+
replace_text = 'Could you tell me again?'
|
| 90 |
+
self.histories.append(types.Content(role='model', parts=[types.Part.from_text(text=replace_text)]))
|
| 91 |
+
return replace_text
|
| 92 |
+
|
| 93 |
+
if response.text is None:
|
| 94 |
+
wait_time = exponential_backoff(count)
|
| 95 |
+
time.sleep(wait_time)
|
| 96 |
+
count += 1
|
| 97 |
+
continue
|
| 98 |
+
else:
|
| 99 |
+
break
|
| 100 |
+
|
| 101 |
+
self.histories.append(types.Content(role='model', parts=[types.Part.from_text(text=response.text)]))
|
| 102 |
+
return response.text
|
| 103 |
+
|
| 104 |
+
except Exception as e:
|
| 105 |
+
raise e
|
patientsim/client/google_vertex_client.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import time
|
| 3 |
+
from typing import List, Optional
|
| 4 |
+
from google import genai
|
| 5 |
+
from google.genai import types
|
| 6 |
+
from google.genai.types import HttpOptions
|
| 7 |
+
from dotenv import load_dotenv, find_dotenv
|
| 8 |
+
|
| 9 |
+
from ..utils import log
|
| 10 |
+
from ..utils.common_utils import exponential_backoff
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class GeminiVertexClient:
|
| 14 |
+
def __init__(self, model: str, api_key: Optional[str] = None):
|
| 15 |
+
self.model = model
|
| 16 |
+
self._init_environment(api_key)
|
| 17 |
+
self.histories = list()
|
| 18 |
+
self.token_usages = dict()
|
| 19 |
+
self.__first_turn = True
|
| 20 |
+
|
| 21 |
+
def _init_environment(self, api_key: Optional[str] = None) -> None:
|
| 22 |
+
if not api_key:
|
| 23 |
+
dotenv_path = find_dotenv(usecwd=True)
|
| 24 |
+
load_dotenv(dotenv_path, override=True)
|
| 25 |
+
api_key = os.environ.get("GOOGLE_API_KEY", None)
|
| 26 |
+
os.environ["GOOGLE_GENAI_USE_VERTEXAI"] = "True"
|
| 27 |
+
self.client = genai.Client(
|
| 28 |
+
vertexai=True,
|
| 29 |
+
api_key=api_key,
|
| 30 |
+
http_options=HttpOptions(api_version="v1"),
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
def reset_history(self, verbose: bool = True) -> None:
|
| 34 |
+
self.__first_turn = True
|
| 35 |
+
self.histories = list()
|
| 36 |
+
self.token_usages = dict()
|
| 37 |
+
if verbose:
|
| 38 |
+
log('Conversation history has been reset.', color=True)
|
| 39 |
+
|
| 40 |
+
def __make_payload(self, user_prompt: str) -> List[types.Content]:
|
| 41 |
+
return [types.Content(role='user', parts=[types.Part.from_text(text=user_prompt)])]
|
| 42 |
+
|
| 43 |
+
def __call__(self,
|
| 44 |
+
user_prompt: str,
|
| 45 |
+
system_prompt: Optional[str] = None,
|
| 46 |
+
using_multi_turn: bool = True,
|
| 47 |
+
greeting: Optional[str] = None,
|
| 48 |
+
verbose: bool = True,
|
| 49 |
+
**kwargs) -> str:
|
| 50 |
+
try:
|
| 51 |
+
if not using_multi_turn:
|
| 52 |
+
self.reset_history(verbose)
|
| 53 |
+
|
| 54 |
+
if greeting and self.__first_turn:
|
| 55 |
+
self.histories.append(types.Content(role='model', parts=[types.Part.from_text(text=greeting)]))
|
| 56 |
+
self.__first_turn = False
|
| 57 |
+
|
| 58 |
+
self.histories += self.__make_payload(user_prompt)
|
| 59 |
+
|
| 60 |
+
count = 0
|
| 61 |
+
max_retry = kwargs.get('max_retry', 5)
|
| 62 |
+
while 1:
|
| 63 |
+
response = self.client.models.generate_content(
|
| 64 |
+
model=self.model,
|
| 65 |
+
contents=self.histories,
|
| 66 |
+
config=types.GenerateContentConfig(
|
| 67 |
+
system_instruction=system_prompt,
|
| 68 |
+
**kwargs
|
| 69 |
+
)
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
if response.usage_metadata:
|
| 73 |
+
self.token_usages.setdefault("prompt_tokens", []).append(response.usage_metadata.prompt_token_count)
|
| 74 |
+
self.token_usages.setdefault("completion_tokens", []).append(response.usage_metadata.candidates_token_count)
|
| 75 |
+
self.token_usages.setdefault("total_tokens", []).append(response.usage_metadata.total_token_count)
|
| 76 |
+
|
| 77 |
+
if count >= max_retry:
|
| 78 |
+
replace_text = 'Could you tell me again?'
|
| 79 |
+
self.histories.append(types.Content(role='model', parts=[types.Part.from_text(text=replace_text)]))
|
| 80 |
+
return replace_text
|
| 81 |
+
|
| 82 |
+
if response.text is None:
|
| 83 |
+
wait_time = exponential_backoff(count)
|
| 84 |
+
time.sleep(wait_time)
|
| 85 |
+
count += 1
|
| 86 |
+
continue
|
| 87 |
+
else:
|
| 88 |
+
break
|
| 89 |
+
|
| 90 |
+
self.histories.append(types.Content(role='model', parts=[types.Part.from_text(text=response.text)]))
|
| 91 |
+
return response.text
|
| 92 |
+
|
| 93 |
+
except Exception as e:
|
| 94 |
+
raise e
|
patientsim/client/openai_azure_client.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from openai import AzureOpenAI
|
| 3 |
+
from typing import List, Optional
|
| 4 |
+
from dotenv import load_dotenv, find_dotenv
|
| 5 |
+
|
| 6 |
+
from ..utils import log
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class GPTAzureClient:
|
| 10 |
+
def __init__(self, model: str, api_key: Optional[str] = None, azure_endpoint: Optional[str] = None):
|
| 11 |
+
self.model = model
|
| 12 |
+
self._init_environment(api_key, azure_endpoint)
|
| 13 |
+
self.histories = list()
|
| 14 |
+
self.token_usages = dict()
|
| 15 |
+
self.__first_turn = True
|
| 16 |
+
|
| 17 |
+
def _init_environment(self, api_key: Optional[str] = None, azure_endpoint: Optional[str] = None) -> None:
|
| 18 |
+
if not api_key:
|
| 19 |
+
dotenv_path = find_dotenv(usecwd=True)
|
| 20 |
+
load_dotenv(dotenv_path, override=True)
|
| 21 |
+
api_key = os.environ.get("OPENAI_API_KEY", None)
|
| 22 |
+
if not azure_endpoint:
|
| 23 |
+
dotenv_path = find_dotenv(usecwd=True)
|
| 24 |
+
load_dotenv(dotenv_path, override=True)
|
| 25 |
+
azure_endpoint = os.environ.get("AZURE_OPENAI_ENDPOINT", None)
|
| 26 |
+
self.client = AzureOpenAI(
|
| 27 |
+
azure_endpoint=azure_endpoint,
|
| 28 |
+
api_key=api_key,
|
| 29 |
+
api_version="2024-10-21",
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
def reset_history(self, verbose: bool = True) -> None:
|
| 33 |
+
self.__first_turn = True
|
| 34 |
+
self.histories = list()
|
| 35 |
+
self.token_usages = dict()
|
| 36 |
+
if verbose:
|
| 37 |
+
log('Conversation history has been reset.', color=True)
|
| 38 |
+
|
| 39 |
+
def __make_payload(self, user_prompt: str) -> List[dict]:
|
| 40 |
+
return [{"role": "user", "content": [{"type": "text", "text": user_prompt}]}]
|
| 41 |
+
|
| 42 |
+
def __call__(self,
|
| 43 |
+
user_prompt: str,
|
| 44 |
+
system_prompt: Optional[str] = None,
|
| 45 |
+
using_multi_turn: bool = True,
|
| 46 |
+
greeting: Optional[str] = None,
|
| 47 |
+
verbose: bool = True,
|
| 48 |
+
**kwargs) -> str:
|
| 49 |
+
try:
|
| 50 |
+
if not using_multi_turn:
|
| 51 |
+
self.reset_history(verbose)
|
| 52 |
+
|
| 53 |
+
if self.__first_turn:
|
| 54 |
+
if system_prompt:
|
| 55 |
+
self.histories.append({"role": "system", "content": [{"type": "text", "text": system_prompt}]})
|
| 56 |
+
if greeting:
|
| 57 |
+
self.histories.append({"role": "assistant", "content": [{"type": "text", "text": greeting}]})
|
| 58 |
+
self.__first_turn = False
|
| 59 |
+
|
| 60 |
+
self.histories += self.__make_payload(user_prompt)
|
| 61 |
+
|
| 62 |
+
response = self.client.chat.completions.create(
|
| 63 |
+
model=self.model,
|
| 64 |
+
messages=self.histories,
|
| 65 |
+
**kwargs
|
| 66 |
+
)
|
| 67 |
+
assistant_msg = response.choices[0].message
|
| 68 |
+
self.histories.append({"role": assistant_msg.role, "content": [{"type": "text", "text": assistant_msg.content}]})
|
| 69 |
+
|
| 70 |
+
if response.usage:
|
| 71 |
+
self.token_usages.setdefault("prompt_tokens", []).append(response.usage.prompt_tokens)
|
| 72 |
+
self.token_usages.setdefault("completion_tokens", []).append(response.usage.completion_tokens)
|
| 73 |
+
self.token_usages.setdefault("total_tokens", []).append(response.usage.total_tokens)
|
| 74 |
+
self.token_usages.setdefault("reasoning_tokens", []).append(response.usage.completion_tokens_details.reasoning_tokens)
|
| 75 |
+
|
| 76 |
+
return assistant_msg.content
|
| 77 |
+
|
| 78 |
+
except Exception as e:
|
| 79 |
+
raise e
|
patientsim/client/openai_client.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from openai import OpenAI
|
| 3 |
+
from typing import List, Optional
|
| 4 |
+
from dotenv import load_dotenv, find_dotenv
|
| 5 |
+
|
| 6 |
+
from ..utils import log
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class GPTClient:
|
| 10 |
+
def __init__(self, model: str, api_key: Optional[str] = None):
|
| 11 |
+
self.model = model
|
| 12 |
+
self._init_environment(api_key)
|
| 13 |
+
self.histories = list()
|
| 14 |
+
self.token_usages = dict()
|
| 15 |
+
self.__first_turn = True
|
| 16 |
+
|
| 17 |
+
def _init_environment(self, api_key: Optional[str] = None) -> None:
|
| 18 |
+
if not api_key:
|
| 19 |
+
dotenv_path = find_dotenv(usecwd=True)
|
| 20 |
+
load_dotenv(dotenv_path, override=True)
|
| 21 |
+
api_key = os.environ.get("OPENAI_API_KEY", None)
|
| 22 |
+
self.client = OpenAI(api_key=api_key)
|
| 23 |
+
|
| 24 |
+
def reset_history(self, verbose: bool = True) -> None:
|
| 25 |
+
self.__first_turn = True
|
| 26 |
+
self.histories = list()
|
| 27 |
+
self.token_usages = dict()
|
| 28 |
+
if verbose:
|
| 29 |
+
log('Conversation history has been reset.', color=True)
|
| 30 |
+
|
| 31 |
+
def __make_payload(self, user_prompt: str) -> List[dict]:
|
| 32 |
+
return [{"role": "user", "content": [{"type": "text", "text": user_prompt}]}]
|
| 33 |
+
|
| 34 |
+
def __call__(self,
|
| 35 |
+
user_prompt: str,
|
| 36 |
+
system_prompt: Optional[str] = None,
|
| 37 |
+
using_multi_turn: bool = True,
|
| 38 |
+
greeting: Optional[str] = None,
|
| 39 |
+
verbose: bool = True,
|
| 40 |
+
**kwargs) -> str:
|
| 41 |
+
try:
|
| 42 |
+
if not using_multi_turn:
|
| 43 |
+
self.reset_history(verbose)
|
| 44 |
+
|
| 45 |
+
if self.__first_turn:
|
| 46 |
+
if system_prompt:
|
| 47 |
+
self.histories.append({"role": "system", "content": [{"type": "text", "text": system_prompt}]})
|
| 48 |
+
if greeting:
|
| 49 |
+
self.histories.append({"role": "assistant", "content": [{"type": "text", "text": greeting}]})
|
| 50 |
+
self.__first_turn = False
|
| 51 |
+
|
| 52 |
+
self.histories += self.__make_payload(user_prompt)
|
| 53 |
+
|
| 54 |
+
response = self.client.chat.completions.create(
|
| 55 |
+
model=self.model,
|
| 56 |
+
messages=self.histories,
|
| 57 |
+
**kwargs
|
| 58 |
+
)
|
| 59 |
+
assistant_msg = response.choices[0].message
|
| 60 |
+
self.histories.append({"role": assistant_msg.role, "content": [{"type": "text", "text": assistant_msg.content}]})
|
| 61 |
+
|
| 62 |
+
if response.usage:
|
| 63 |
+
self.token_usages.setdefault("prompt_tokens", []).append(response.usage.prompt_tokens)
|
| 64 |
+
self.token_usages.setdefault("completion_tokens", []).append(response.usage.completion_tokens)
|
| 65 |
+
self.token_usages.setdefault("total_tokens", []).append(response.usage.total_tokens)
|
| 66 |
+
self.token_usages.setdefault("reasoning_tokens", []).append(response.usage.completion_tokens_details.reasoning_tokens)
|
| 67 |
+
|
| 68 |
+
return assistant_msg.content
|
| 69 |
+
|
| 70 |
+
except Exception as e:
|
| 71 |
+
raise e
|
patientsim/doctor.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from typing import Optional
|
| 3 |
+
|
| 4 |
+
from .registry.persona import *
|
| 5 |
+
from .utils import colorstr, log
|
| 6 |
+
from .utils.common_utils import set_seed
|
| 7 |
+
from .client import GeminiClient, GeminiVertexClient, GPTClient, GPTAzureClient
|
| 8 |
+
|
| 9 |
+
_PROMPT_DIR = os.path.join(os.path.dirname(__file__), "assets", "prompt")
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class DoctorAgent:
|
| 13 |
+
def __init__(self,
|
| 14 |
+
model: str,
|
| 15 |
+
max_inferences: int = 15,
|
| 16 |
+
top_k_diagnosis: int = 5,
|
| 17 |
+
api_key: Optional[str] = None,
|
| 18 |
+
use_azure: bool = False,
|
| 19 |
+
use_vertex: bool = False,
|
| 20 |
+
azure_endpoint: Optional[str] = None,
|
| 21 |
+
system_prompt_path: Optional[str] = None,
|
| 22 |
+
**kwargs) -> None:
|
| 23 |
+
|
| 24 |
+
self.current_inference = 0
|
| 25 |
+
self.max_inferences = max_inferences
|
| 26 |
+
self.top_k_diagnosis = top_k_diagnosis
|
| 27 |
+
self._init_env(**kwargs)
|
| 28 |
+
|
| 29 |
+
self.model = model
|
| 30 |
+
self._init_model(
|
| 31 |
+
model=self.model,
|
| 32 |
+
api_key=api_key,
|
| 33 |
+
use_azure=use_azure,
|
| 34 |
+
use_vertex=use_vertex,
|
| 35 |
+
azure_endpoint=azure_endpoint,
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
self._system_prompt_template = self._init_prompt(system_prompt_path)
|
| 39 |
+
self.build_prompt()
|
| 40 |
+
|
| 41 |
+
log("DoctorAgent initialized successfully", color=True)
|
| 42 |
+
|
| 43 |
+
def _init_env(self, **kwargs) -> None:
|
| 44 |
+
self.random_seed = kwargs.get('random_seed', None)
|
| 45 |
+
self.temperature = kwargs.get('temperature', 0.2)
|
| 46 |
+
self.doctor_greet = kwargs.get('doctor_greet', "Hello, how can I help you?")
|
| 47 |
+
self.patient_conditions = {
|
| 48 |
+
'age': kwargs.get('age', 'N/A'),
|
| 49 |
+
'gender': kwargs.get('gender', 'N/A'),
|
| 50 |
+
'arrival_transport': kwargs.get('arrival_transport', 'N/A'),
|
| 51 |
+
}
|
| 52 |
+
missing_conditions = [k for k, v in self.patient_conditions.items() if v == 'N/A']
|
| 53 |
+
if missing_conditions:
|
| 54 |
+
log(f"Required patient information missing for the doctor agent: {', '.join(missing_conditions)}. Using default values.", level="warning")
|
| 55 |
+
if self.random_seed:
|
| 56 |
+
set_seed(self.random_seed)
|
| 57 |
+
|
| 58 |
+
def _init_model(self,
|
| 59 |
+
model: str,
|
| 60 |
+
api_key: Optional[str] = None,
|
| 61 |
+
use_azure: bool = False,
|
| 62 |
+
use_vertex: bool = False,
|
| 63 |
+
azure_endpoint: Optional[str] = None) -> None:
|
| 64 |
+
if 'gemini' in self.model.lower():
|
| 65 |
+
self.client = GeminiVertexClient(model, api_key) if use_vertex else GeminiClient(model, api_key)
|
| 66 |
+
elif 'gpt' in self.model.lower():
|
| 67 |
+
self.client = GPTAzureClient(model, api_key, azure_endpoint) if use_azure else GPTClient(model, api_key)
|
| 68 |
+
else:
|
| 69 |
+
raise ValueError(colorstr("red", f"Unsupported model: {self.model}. Supported models are 'gemini' and 'gpt'."))
|
| 70 |
+
|
| 71 |
+
def _init_prompt(self, system_prompt_path: Optional[str] = None) -> str:
|
| 72 |
+
if not system_prompt_path:
|
| 73 |
+
with open(os.path.join(_PROMPT_DIR, "ed_doctor_sys.txt"), 'r') as f:
|
| 74 |
+
return f.read()
|
| 75 |
+
else:
|
| 76 |
+
if not os.path.exists(system_prompt_path):
|
| 77 |
+
raise FileNotFoundError(colorstr("red", f"System prompt file not found: {system_prompt_path}"))
|
| 78 |
+
with open(system_prompt_path, 'r') as f:
|
| 79 |
+
return f.read()
|
| 80 |
+
|
| 81 |
+
def reset_history(self, verbose: bool = True) -> None:
|
| 82 |
+
self.client.reset_history(verbose=verbose)
|
| 83 |
+
|
| 84 |
+
def build_prompt(self) -> None:
|
| 85 |
+
self.system_prompt = self._system_prompt_template.format(
|
| 86 |
+
total_idx=self.max_inferences,
|
| 87 |
+
curr_idx=self.current_inference,
|
| 88 |
+
remain_idx=self.max_inferences - self.current_inference,
|
| 89 |
+
top_k_diagnosis=self.top_k_diagnosis,
|
| 90 |
+
**self.patient_conditions
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
def update_system_prompt(self):
|
| 94 |
+
self.current_inference = max(1, len(list(filter(
|
| 95 |
+
lambda x: (not isinstance(x, dict) and x.role == 'model') or
|
| 96 |
+
(isinstance(x, dict) and x.get('role') == 'assistant'),
|
| 97 |
+
self.client.histories
|
| 98 |
+
))))
|
| 99 |
+
self.build_prompt()
|
| 100 |
+
if len(self.client.histories) and isinstance(self.client.histories[0], dict) and self.client.histories[0].get('role') == 'system':
|
| 101 |
+
self.client.histories[0]['content'] = self.system_prompt
|
| 102 |
+
|
| 103 |
+
def __call__(self,
|
| 104 |
+
user_prompt: str,
|
| 105 |
+
using_multi_turn: bool = True,
|
| 106 |
+
verbose: bool = True,
|
| 107 |
+
**kwargs) -> str:
|
| 108 |
+
self.update_system_prompt()
|
| 109 |
+
|
| 110 |
+
response = self.client(
|
| 111 |
+
user_prompt=user_prompt,
|
| 112 |
+
system_prompt=self.system_prompt,
|
| 113 |
+
using_multi_turn=using_multi_turn,
|
| 114 |
+
greeting=self.doctor_greet,
|
| 115 |
+
verbose=verbose,
|
| 116 |
+
temperature=self.temperature,
|
| 117 |
+
seed=self.random_seed,
|
| 118 |
+
**kwargs
|
| 119 |
+
)
|
| 120 |
+
return response
|
patientsim/environment/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from .ed_simulation import EDSimulation
|
patientsim/environment/ed_simulation.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import time
|
| 2 |
+
from typing import Optional
|
| 3 |
+
|
| 4 |
+
from ..doctor import DoctorAgent
|
| 5 |
+
from ..patient import PatientAgent
|
| 6 |
+
from ..checker import CheckerAgent
|
| 7 |
+
from ..utils import log, colorstr
|
| 8 |
+
from ..utils.common_utils import detect_ed_termination
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class EDSimulation:
|
| 12 |
+
def __init__(self,
|
| 13 |
+
patient_agent: PatientAgent,
|
| 14 |
+
doctor_agent: DoctorAgent,
|
| 15 |
+
checker_agent: Optional[CheckerAgent] = None,
|
| 16 |
+
max_inferences: int = 15):
|
| 17 |
+
|
| 18 |
+
self.patient_agent = patient_agent
|
| 19 |
+
self.doctor_agent = doctor_agent
|
| 20 |
+
self.checker_agent = checker_agent
|
| 21 |
+
self.max_inferences = max_inferences
|
| 22 |
+
self.current_inference = 0
|
| 23 |
+
self._sanity_check()
|
| 24 |
+
|
| 25 |
+
def _sanity_check(self):
|
| 26 |
+
if not self.doctor_agent.max_inferences == self.max_inferences:
|
| 27 |
+
log("The maximum number of inferences between the Doctor agent and the ED simulation does not match.", level="warning")
|
| 28 |
+
log(f"The simulation will start with the value ({self.max_inferences}) configured in the ED simulation, and the Doctor agent system prompt will be updated accordingly.", level="warning")
|
| 29 |
+
self.doctor_agent.max_inferences = self.max_inferences
|
| 30 |
+
self.doctor_agent.build_prompt()
|
| 31 |
+
|
| 32 |
+
if self.checker_agent:
|
| 33 |
+
assert self.checker_agent.visit_type == self.patient_agent.visit_type, \
|
| 34 |
+
log(colorstr("red", f"The visit type between the Checker agent ({self.checker_agent.visit_type}) and the Patient agent ({self.patient_agent.visit_type}) must be the same."))
|
| 35 |
+
|
| 36 |
+
def _init_agents(self, verbose: bool = True) -> None:
|
| 37 |
+
self.patient_agent.reset_history(verbose=verbose)
|
| 38 |
+
self.doctor_agent.reset_history(verbose=verbose)
|
| 39 |
+
|
| 40 |
+
def simulate(self,
|
| 41 |
+
verbose: bool = True,
|
| 42 |
+
patient_kwargs: dict = {},
|
| 43 |
+
doctor_kwargs: dict = {},
|
| 44 |
+
**kwargs) -> dict:
|
| 45 |
+
self._init_agents(verbose=verbose)
|
| 46 |
+
|
| 47 |
+
doctor_greet = self.doctor_agent.doctor_greet
|
| 48 |
+
dialog_history = [{"role": "Doctor", "content": doctor_greet}]
|
| 49 |
+
|
| 50 |
+
for inference_idx in range(self.max_inferences):
|
| 51 |
+
patient_kwargs.update(kwargs)
|
| 52 |
+
patient_response = self.patient_agent(
|
| 53 |
+
user_prompt=dialog_history[-1]["content"],
|
| 54 |
+
using_multi_turn=True,
|
| 55 |
+
verbose=verbose,
|
| 56 |
+
**patient_kwargs
|
| 57 |
+
)
|
| 58 |
+
dialog_history.append({"role": "Patient", "content": patient_response})
|
| 59 |
+
|
| 60 |
+
doctor_kwargs.update(kwargs)
|
| 61 |
+
doctor_response = self.doctor_agent(
|
| 62 |
+
user_prompt=dialog_history[-1]["content"] + "\nThis is the final turn. Now, you must provide your top5 differential diagnosis."
|
| 63 |
+
if inference_idx == self.max_inferences - 1 else dialog_history[-1]["content"],
|
| 64 |
+
using_multi_turn=True,
|
| 65 |
+
verbose=verbose,
|
| 66 |
+
**doctor_kwargs
|
| 67 |
+
)
|
| 68 |
+
dialog_history.append({"role": "Doctor", "content": doctor_response})
|
| 69 |
+
|
| 70 |
+
if detect_ed_termination(doctor_response):
|
| 71 |
+
break
|
| 72 |
+
elif self.checker_agent:
|
| 73 |
+
termination_check = self.checker_agent(response=doctor_response).strip().upper()
|
| 74 |
+
if termination_check == "Y":
|
| 75 |
+
log("Consultation termination detected by the checker agent.", level="warning")
|
| 76 |
+
break
|
| 77 |
+
|
| 78 |
+
time.sleep(1.0)
|
| 79 |
+
|
| 80 |
+
log("Simulation completed.", color=True)
|
| 81 |
+
return {
|
| 82 |
+
"dialog_history": dialog_history,
|
| 83 |
+
"patient_token_usage": self.patient_agent.client.token_usages,
|
| 84 |
+
"doctor_token_usage": self.doctor_agent.client.token_usages,
|
| 85 |
+
}
|
patientsim/patient.py
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import random
|
| 3 |
+
from typing import Optional
|
| 4 |
+
|
| 5 |
+
from .registry.persona import *
|
| 6 |
+
from .utils import colorstr, log
|
| 7 |
+
from .utils.desc_utils import *
|
| 8 |
+
from .utils.common_utils import *
|
| 9 |
+
from .client import GeminiClient, GeminiVertexClient, GPTClient, GPTAzureClient
|
| 10 |
+
|
| 11 |
+
_PROMPT_DIR = os.path.join(os.path.dirname(__file__), "assets", "prompt")
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class PatientAgent:
|
| 15 |
+
def __init__(self,
|
| 16 |
+
model: str,
|
| 17 |
+
visit_type: str = 'emergency_department',
|
| 18 |
+
personality: str = 'plain',
|
| 19 |
+
recall_level: str = 'no_history',
|
| 20 |
+
confusion_level: str = 'normal',
|
| 21 |
+
lang_proficiency_level: str = 'C',
|
| 22 |
+
api_key: Optional[str] = None,
|
| 23 |
+
use_azure: bool = False,
|
| 24 |
+
use_vertex: bool = False,
|
| 25 |
+
use_vllm: bool = False,
|
| 26 |
+
azure_endpoint: Optional[str] = None,
|
| 27 |
+
vllm_endpoint: Optional[str] = None,
|
| 28 |
+
system_prompt_path: Optional[str] = None,
|
| 29 |
+
additional_patient_conditions: dict = {},
|
| 30 |
+
log_verbose: bool = True,
|
| 31 |
+
**kwargs) -> None:
|
| 32 |
+
|
| 33 |
+
self.visit_type = visit_type.lower()
|
| 34 |
+
self.personality = personality.lower()
|
| 35 |
+
self.recall_level = recall_level.lower()
|
| 36 |
+
self.confusion_level = confusion_level.lower()
|
| 37 |
+
self.lang_proficiency_level = lang_proficiency_level.upper()
|
| 38 |
+
self.system_prompt_path = system_prompt_path
|
| 39 |
+
self.log_verbose = log_verbose
|
| 40 |
+
self.__sanity_check()
|
| 41 |
+
|
| 42 |
+
self._init_env(additional_patient_conditions, **kwargs)
|
| 43 |
+
|
| 44 |
+
self.model = model
|
| 45 |
+
self._init_model(
|
| 46 |
+
model=self.model,
|
| 47 |
+
api_key=api_key,
|
| 48 |
+
use_azure=use_azure,
|
| 49 |
+
use_vertex=use_vertex,
|
| 50 |
+
azure_endpoint=azure_endpoint,
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
system_prompt_template = self._init_prompt(self.visit_type, system_prompt_path)
|
| 54 |
+
self.system_prompt = self.build_prompt(system_prompt_template)
|
| 55 |
+
|
| 56 |
+
if self.log_verbose:
|
| 57 |
+
log("PatientAgent initialized successfully", color=True)
|
| 58 |
+
|
| 59 |
+
def _init_env(self, additional_patient_conditions: dict = {}, **kwargs) -> None:
|
| 60 |
+
self.random_seed = kwargs.get('random_seed', None)
|
| 61 |
+
if self.random_seed:
|
| 62 |
+
set_seed(self.random_seed)
|
| 63 |
+
self.temperature = kwargs.get('temperature', 0.2)
|
| 64 |
+
self.num_word_sample = kwargs.get('num_word_sample', 3)
|
| 65 |
+
self.random_sampling = kwargs.get('random_sampling', True)
|
| 66 |
+
self._terms = {
|
| 67 |
+
'cefr_A1': split_string(kwargs.get('cefr_A1', [])),
|
| 68 |
+
'cefr_A2': split_string(kwargs.get('cefr_A2', [])),
|
| 69 |
+
'cefr_B1': split_string(kwargs.get('cefr_B1', [])),
|
| 70 |
+
'cefr_B2': split_string(kwargs.get('cefr_B2', [])),
|
| 71 |
+
'cefr_C1': split_string(kwargs.get('cefr_C1', [])),
|
| 72 |
+
'cefr_C2': split_string(kwargs.get('cefr_C2', [])),
|
| 73 |
+
'med_A': split_string(kwargs.get('med_A', [])),
|
| 74 |
+
'med_B': split_string(kwargs.get('med_B', [])),
|
| 75 |
+
'med_C': split_string(kwargs.get('med_C', [])),
|
| 76 |
+
}
|
| 77 |
+
self.patient_conditions = {
|
| 78 |
+
'name': kwargs.get('name', 'James Lee'),
|
| 79 |
+
'birth_date': kwargs.get('birth_date', generate_random_date()),
|
| 80 |
+
'age': kwargs.get('age', str(random.randint(20, 80))),
|
| 81 |
+
'gender': kwargs.get('gender', random.choice(['male', 'female'])),
|
| 82 |
+
'telecom': kwargs.get('telecom', 'N/A'),
|
| 83 |
+
'personal_id': kwargs.get('personal_id', 'N/A'),
|
| 84 |
+
'address': kwargs.get('address', 'N/A'),
|
| 85 |
+
'race': kwargs.get('race', 'N/A'),
|
| 86 |
+
'tobacco': kwargs.get('tobacco', 'N/A'),
|
| 87 |
+
'alcohol': kwargs.get('alcohol', 'N/A'),
|
| 88 |
+
'illicit_drug': kwargs.get('illicit_drug', 'N/A'),
|
| 89 |
+
'sexual_history': kwargs.get('sexual_history', 'N/A'),
|
| 90 |
+
'exercise': kwargs.get('exercise', 'N/A'),
|
| 91 |
+
'marital_status': kwargs.get('marital_status', 'N/A'),
|
| 92 |
+
'children': kwargs.get('children', 'N/A'),
|
| 93 |
+
'living_situation': kwargs.get('living_situation', 'N/A'),
|
| 94 |
+
'occupation': kwargs.get('occupation', 'N/A'),
|
| 95 |
+
'insurance': kwargs.get('insurance', 'N/A'),
|
| 96 |
+
'allergies': kwargs.get('allergies', 'N/A'),
|
| 97 |
+
'family_medical_history': kwargs.get('family_medical_history', 'N/A'),
|
| 98 |
+
'medical_device': kwargs.get('medical_device', 'N/A'),
|
| 99 |
+
'medical_history': kwargs.get('medical_history', 'N/A'),
|
| 100 |
+
'present_illness_positive': kwargs.get('present_illness_positive', 'N/A'),
|
| 101 |
+
'present_illness_negative': kwargs.get('present_illness_negative', 'N/A'),
|
| 102 |
+
'chief_complaint': kwargs.get('chiefcomplaint', 'N/A'),
|
| 103 |
+
'pain': kwargs.get('pain', 'N/A'),
|
| 104 |
+
'medication': kwargs.get('medication', 'N/A'),
|
| 105 |
+
'arrival_transport': kwargs.get('arrival_transport', 'N/A'),
|
| 106 |
+
'disposition': kwargs.get('disposition', 'N/A'),
|
| 107 |
+
'diagnosis': kwargs.get('diagnosis', 'N/A'),
|
| 108 |
+
'department': kwargs.get('department', 'N/A'),
|
| 109 |
+
}
|
| 110 |
+
self.patient_conditions.update(additional_patient_conditions)
|
| 111 |
+
|
| 112 |
+
if self.visit_type == 'outpatient' and not self.system_prompt_path:
|
| 113 |
+
assert self.patient_conditions.get('department') != 'N/A', \
|
| 114 |
+
log(colorstr("red", "To simulate outpatient, you should provide a specific department."))
|
| 115 |
+
assert self.patient_conditions.get('chief_complaint') != 'N/A', \
|
| 116 |
+
log(colorstr("red", "To simulate outpatient, you should provide at least a chief complaint."))
|
| 117 |
+
|
| 118 |
+
missing_conditions = [k for k, v in self.patient_conditions.items() if v == 'N/A']
|
| 119 |
+
if missing_conditions and self.log_verbose:
|
| 120 |
+
log(f"Required patient information missing for the patient agent: {', '.join(missing_conditions)}. Using default values.", level="warning")
|
| 121 |
+
|
| 122 |
+
def _init_model(self,
|
| 123 |
+
model: str,
|
| 124 |
+
api_key: Optional[str] = None,
|
| 125 |
+
use_azure: bool = False,
|
| 126 |
+
use_vertex: bool = False,
|
| 127 |
+
azure_endpoint: Optional[str] = None) -> None:
|
| 128 |
+
if 'gemini' in self.model.lower():
|
| 129 |
+
self.client = GeminiVertexClient(model, api_key) if use_vertex else GeminiClient(model, api_key)
|
| 130 |
+
elif 'gpt' in self.model.lower():
|
| 131 |
+
self.client = GPTAzureClient(model, api_key, azure_endpoint) if use_azure else GPTClient(model, api_key)
|
| 132 |
+
else:
|
| 133 |
+
raise ValueError(colorstr("red", f"Unsupported model: {self.model}. Supported models are 'gemini' and 'gpt'."))
|
| 134 |
+
|
| 135 |
+
def _init_prompt(self, visit_type: str, system_prompt_path: Optional[str] = None) -> str:
|
| 136 |
+
if not system_prompt_path:
|
| 137 |
+
if visit_type == 'outpatient':
|
| 138 |
+
fname = "op_patient_sys.txt"
|
| 139 |
+
else:
|
| 140 |
+
fname = "ed_uti_patient_sys.txt" if self.patient_conditions.get('diagnosis', '').lower() == 'urinary tract infection' else "ed_patient_sys.txt"
|
| 141 |
+
with open(os.path.join(_PROMPT_DIR, fname), 'r') as f:
|
| 142 |
+
return f.read()
|
| 143 |
+
else:
|
| 144 |
+
if not os.path.exists(system_prompt_path):
|
| 145 |
+
raise FileNotFoundError(colorstr("red", f"System prompt file not found: {system_prompt_path}"))
|
| 146 |
+
with open(system_prompt_path, 'r') as f:
|
| 147 |
+
return f.read()
|
| 148 |
+
|
| 149 |
+
def reset_history(self, verbose: bool = True) -> None:
|
| 150 |
+
self.client.reset_history(verbose=verbose)
|
| 151 |
+
|
| 152 |
+
def __sanity_check(self) -> None:
|
| 153 |
+
if self.personality not in PERSONALITY:
|
| 154 |
+
raise ValueError(colorstr("red", f"Invalid personality type: {self.personality}. Supported types: {', '.join(PERSONALITY.keys())}"))
|
| 155 |
+
if self.recall_level not in RECALL_LEVEL:
|
| 156 |
+
raise ValueError(colorstr("red", f"Invalid recall level: {self.recall_level}. Supported levels: {', '.join(RECALL_LEVEL.keys())}"))
|
| 157 |
+
if self.confusion_level not in CONFUSION_LEVEL:
|
| 158 |
+
raise ValueError(colorstr("red", f"Invalid confusion level: {self.confusion_level}. Supported levels: {', '.join(CONFUSION_LEVEL.keys())}"))
|
| 159 |
+
if self.lang_proficiency_level not in LANG_PROFICIENCY_LEVEL:
|
| 160 |
+
raise ValueError(colorstr("red", f"Invalid language proficiency level: {self.lang_proficiency_level}. Supported levels: {', '.join(LANG_PROFICIENCY_LEVEL.keys())}"))
|
| 161 |
+
if self.visit_type not in VISIT_TYPE:
|
| 162 |
+
raise ValueError(colorstr("red", f"Invalid visiting type: {self.visit_type}. Supported types: {', '.join(VISIT_TYPE)}"))
|
| 163 |
+
|
| 164 |
+
def build_prompt(self, system_prompt_template: str) -> str:
|
| 165 |
+
personality_desc = get_personality_description(self.personality)
|
| 166 |
+
recall_desc = get_recall_description(self.recall_level)
|
| 167 |
+
confusion_desc = get_confusion_description(self.confusion_level)
|
| 168 |
+
lang_proficiency_desc = get_language_proficiency_description(
|
| 169 |
+
self.lang_proficiency_level,
|
| 170 |
+
num_sample=self.num_word_sample,
|
| 171 |
+
random_sampling=self.random_sampling,
|
| 172 |
+
**self._terms
|
| 173 |
+
)
|
| 174 |
+
reminder_desc = get_reminder_description(
|
| 175 |
+
self.personality,
|
| 176 |
+
self.lang_proficiency_level,
|
| 177 |
+
self.recall_level,
|
| 178 |
+
self.confusion_level
|
| 179 |
+
)
|
| 180 |
+
sentence_limit = PERSONALITY_SENTENCE_LIMIT.get(self.personality, "3")
|
| 181 |
+
|
| 182 |
+
prompt_kwargs = {
|
| 183 |
+
'personality': personality_desc,
|
| 184 |
+
'recall': recall_desc,
|
| 185 |
+
'confusion': confusion_desc,
|
| 186 |
+
'lang_proficiency': lang_proficiency_desc,
|
| 187 |
+
'reminder': reminder_desc,
|
| 188 |
+
'sentence_limit': sentence_limit,
|
| 189 |
+
}
|
| 190 |
+
prompt_kwargs.update(self.patient_conditions)
|
| 191 |
+
system_prompt = system_prompt_template.format(**prompt_kwargs)
|
| 192 |
+
prompt_valid_check(system_prompt, self.patient_conditions)
|
| 193 |
+
return system_prompt
|
| 194 |
+
|
| 195 |
+
def __call__(self,
|
| 196 |
+
user_prompt: str,
|
| 197 |
+
using_multi_turn: bool = True,
|
| 198 |
+
verbose: bool = True,
|
| 199 |
+
**kwargs) -> str:
|
| 200 |
+
|
| 201 |
+
response = self.client(
|
| 202 |
+
user_prompt=user_prompt,
|
| 203 |
+
system_prompt=self.system_prompt,
|
| 204 |
+
using_multi_turn=using_multi_turn,
|
| 205 |
+
verbose=verbose,
|
| 206 |
+
temperature=self.temperature,
|
| 207 |
+
seed=self.random_seed,
|
| 208 |
+
**kwargs
|
| 209 |
+
)
|
| 210 |
+
return response
|
patientsim/registry/__init__.py
ADDED
|
File without changes
|
patientsim/registry/detection_key.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
DDX_DETECT_KEYS = [
|
| 2 |
+
"ddx ready:",
|
| 3 |
+
"my top 5",
|
| 4 |
+
"here are my top",
|
| 5 |
+
"[ddx",
|
| 6 |
+
"ddx:",
|
| 7 |
+
"here are some potential concerns we need to consider",
|
| 8 |
+
"differential diagnoses",
|
| 9 |
+
"top 5 likely diagnoses",
|
| 10 |
+
"[pddx]",
|
| 11 |
+
"most likely possibilities",
|
| 12 |
+
"top 5 possibilities",
|
| 13 |
+
"top 5 likely diagnoses",
|
| 14 |
+
"top possibilities",
|
| 15 |
+
]
|
patientsim/registry/persona.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
PERSONALITY = {
|
| 2 |
+
"plain": {
|
| 3 |
+
"description": "A neutral patient without any distinctive personality traits.",
|
| 4 |
+
"prompt": "1) Provides concise, direct answers focused on the question, without extra details.\n2) Responds in a neutral tone without any noticeable emotion or personality."
|
| 5 |
+
},
|
| 6 |
+
"verbose": {
|
| 7 |
+
"description": "A verbose patient who talks a lot.",
|
| 8 |
+
"prompt": "1) Provide detailed answers to questions, often including excessive information, even for simple ones.\n2) Elaborates extensively on personal experiences and thoughts.\n3) Avoid exaggerated emotions and repeating the same phrases.\n4) Demonstrate difficulty allowing the doctor to guide the conversation."
|
| 9 |
+
},
|
| 10 |
+
"pleasing": {
|
| 11 |
+
"description": "An overly positive patient who perceives health issues as minor and downplays their severity.",
|
| 12 |
+
"prompt": "1) Minimizes medical concerns, presenting them as insignificant due to a positive outlook.\n2) Underreports symptoms, describing them as mild or temporary even when they are significant.\n3) Maintains a cheerful, worry-free demeanor, showing no distress despite discomfort or pain."
|
| 13 |
+
},
|
| 14 |
+
"impatient": {
|
| 15 |
+
"description": "An impatient patient who gets easily irritated and lacks patience.",
|
| 16 |
+
"prompt": "1) Expresses irritation when conversations drag on or repeat details.\n2) Demands immediate, straightforward answers over lengthy explanations.\n3) React with annoyance to any delays, small talk, or deviations from the main topic."
|
| 17 |
+
},
|
| 18 |
+
"distrust": {
|
| 19 |
+
"description": "A distrustful patient who questions the doctor’s expertise.",
|
| 20 |
+
"prompt": "1) Expresses doubts about the doctor’s knowledge.\n2) Questions the doctor’s intentions and shows skepticism about their inquiries.\n3) May refuses to answer questions that seem unnecessary.\n4) May contradicts the doctor by citing friends, online sources, or past experiences, often trusting them more than the doctor."
|
| 21 |
+
},
|
| 22 |
+
"overanxious": {
|
| 23 |
+
"description": "An overanxious patient who is excessively worried about their health and tends to exaggerate symptoms.",
|
| 24 |
+
"prompt": "1) Provide detailed, dramatic descriptions of minor discomforts, framing them as severe.\n2) Persistently express fears of serious or life-threatening conditions, seeking frequent reassurance.\n3) Ask repeated questions to confirm that you do not have severe or rare diseases.\n4) Shift from one imagined health concern to another, revealing ongoing worry or suspicion."
|
| 25 |
+
}
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
PERSONALITY_SENTENCE_LIMIT = {
|
| 30 |
+
"plain": "3",
|
| 31 |
+
"verbose": "8",
|
| 32 |
+
"pleasing": "3",
|
| 33 |
+
"impatient": "3",
|
| 34 |
+
"distrust": "3",
|
| 35 |
+
"overanxious": "3"
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
RECALL_LEVEL = {
|
| 40 |
+
"no_history": {
|
| 41 |
+
"description": "Have no significant past medical history to recall.",
|
| 42 |
+
"prompt": "1) No previous diagnoses or surgeries are documented.\n2) No chronic conditions, regular medications, or relevant family medical history are reported."
|
| 43 |
+
},
|
| 44 |
+
"low": {
|
| 45 |
+
"description": "Have significantly limited medical history recall ability, often forgetting even major historys.",
|
| 46 |
+
"prompt": "1) Frequently forget important medical history, such as previous diagnoses, surgeries, or your family's medical history.\n2) Forget even important personal health information, including current medications or medical devices in use."
|
| 47 |
+
},
|
| 48 |
+
"high": {
|
| 49 |
+
"description": "Have a clear and detailed ability to recall medical history.",
|
| 50 |
+
"prompt": "1) Accurately remember all health-related information, including past conditions, current medications, and other documented details.\n2) Do not forget or confuse medical information.\n3) Consistently ensure that recalled details match documented records."
|
| 51 |
+
}
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
CONFUSION_LEVEL = {
|
| 56 |
+
"high": {
|
| 57 |
+
"description": "However, at first, you should act like a highly dazed and extremely confused patient who cannot understand the question and gives highly unrelated responses. Gradually reduce your dazed state throughout the conversation, but only with reassurance from the doctor.",
|
| 58 |
+
"prompt": "1) Repeatedly provide highly unrelated responses.\n2) Overly fixate on a specific discomfort or pain, and keep giving the same information regardless of the question. For example, when asked 'Are you short of breath?', fixate on another issue by saying, 'It hurts so much in my chest,' without addressing the actual question.\n3) Become so overwhelmed in emergency situations. You are either unable to speak or downplay your symptoms out of fear of a diagnosis, even when the symptoms are serious.\n4) Only recall events prior to a certain incident (e.g., before a fall) and repeatedly ask about that earlier situation."
|
| 59 |
+
},
|
| 60 |
+
"moderate": {
|
| 61 |
+
"description": "",
|
| 62 |
+
"prompt": "1) Provide answers that are somewhat off-topic.\n2) Often mention a specific discomfort or pain unrelated to the question. However, allow yourself to move on to the core issue when gently prompted.\n3) Occasionally hesitate due to feeling overwhelmed in emergency situations."
|
| 63 |
+
},
|
| 64 |
+
"normal": {
|
| 65 |
+
"description": "Act without confusion.",
|
| 66 |
+
"prompt": "Clearly understand the question according to the CEFR level, and naturally reflect your background and personality in your responses."
|
| 67 |
+
}
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
CONFUSION_STATE = {
|
| 72 |
+
"high": "initial",
|
| 73 |
+
"moderate": "intermediate",
|
| 74 |
+
"normal": "later"
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
CONFUSION_TO_INCLUDE = {
|
| 79 |
+
"high": ["high", "moderate", "normal"],
|
| 80 |
+
"moderate": ["moderate", "normal"],
|
| 81 |
+
"normal": ["normal"]
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
LANG_PROFICIENCY_LEVEL = {
|
| 86 |
+
"A": {
|
| 87 |
+
"description": "A patient with basic English proficiency who can only use and understand very simple language.",
|
| 88 |
+
"prompt": "Act as a patient with basic English proficiency (CEFR A). You must:\n1) Speaking: Use only basic, simple words. Respond with short phrases instead of full sentences. Make frequent grammar mistakes. Do not use any complex words or long phrases.\n2) Understanding: Understand only simple, everyday words and phrases. Struggle with even slightly complex words or sentences. Often need repetition or simplified explanations to understand.\n\tWords within your level: {understand_words}.\n\tWords beyond your level: {misunderstand_words}.\n3) Medical Terms: Use and understand only very simple, everyday medical words, with limited medical knowledge. Cannot use or understand complex medical terms. Need all medical terms explained in very simple, everyday language. Below are examples of words within and beyond your level. You cannot understand words more complex than the examples provided within your level.\n\tWords within your level: {understand_med_words}.\n\tWords beyond your level: {misunderstand_med_words}.\nIMPORTANT: If a question contains any difficult words, long sentences, or complex grammar, respond with 'What?' or 'I don't understand.' Keep asking until the question is simple enough for you to answer."},
|
| 89 |
+
"B": {
|
| 90 |
+
"description": "A patient with intermediate English proficiency who can use and understand everyday language well.",
|
| 91 |
+
"prompt": "Act as a patient with intermediate English proficiency (CEFR B). You must:\n1) Speaking: Use common vocabulary and form connected, coherent sentences with occasional minor grammar errors. Discuss familiar topics confidently but struggle with abstract or technical subjects. Avoid highly specialized or abstract words.\n2) Understanding: Understand the main ideas of everyday conversations. Need clarification or simpler explanations for abstract, technical, or complex information.\n\tWords within your level: {understand_words}.\n\tWords beyond your level: {misunderstand_words}.\n3) Medical Terms: Use and understand common medical terms related to general health. Cannot use or understand advanced or specialized medical terms, and require these to be explained in simple language. Below are examples of words within and beyond your level. You cannot understand words more complex than the examples provided within your level.\n\tWords within your level: {understand_med_words}.\n\tWords beyond your level: {misunderstand_med_words}.\nIMPORTANT: If a question contains advanced terms beyond your level, ask for a simpler explanation (e.g., 'I don’t get it' or 'What do you mean?'). Keep asking until the question is clear enough for you to answer."},
|
| 92 |
+
"C": {
|
| 93 |
+
"description": "A patient with proficient English proficiency who can use and understand highly complex, detailed language, including advanced medical terminology.",
|
| 94 |
+
"prompt": "Act as a patient with proficient English proficiency (CEFR C). You must:\n1) Speaking: Use a full range of vocabulary with fluent, precise language. Construct well-structured, complex sentences with diverse and appropriate word choices.\n2) Understanding: Fully comprehend detailed, complex explanations and abstract concepts.\n\tWords within your level: {understand_words}.\n3) Medical Terms: Use and understand highly specialized medical terms, with expert-level knowledge of medical topics.\n\tWords within your level: {understand_med_words}.\nIMPORTANT: Reflect your high-level language proficiency mainly through precise vocabulary choices rather than by making your responses unnecessarily long."
|
| 95 |
+
}
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
VISIT_TYPE = ['outpatient', 'emergency_department']
|
patientsim/registry/term.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# CEFR reference data source:
|
| 2 |
+
# "10,000 English Words Labeled by CEFR Levels" dataset by Nezahat K., Kaggle
|
| 3 |
+
# URL: https://www.kaggle.com/datasets/nezahatkk/10-000-english-words-cerf-labelled
|
| 4 |
+
# License: Apache License 2.0
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
CEFR_A1 = ['sandwich', 'worker', 'handsome', 'hometown', 'usually', 'game', 'grandfather', 'uncle', 'some', 'doll', 'character', 'keep', 'believe', 'large', 'foreigner', 'seventy', 'interested', 'miss', 'late', 'field', 'anything', 'animal', 'reading', 'mother', 'pleasure', 'city', 'food', 'kitchen', 'much', 'sport', 'jewelry/jewellery', 'mommy/mommie', 'nine', 'clock', 'hungry', 'thin', 'wednesday', 'answer', 'orange', 'enjoy', 'volleyball', 'section', 'look', 'supermarket', 'forget', 'close', 'palace', 'nice', 'december', 'yesterday', 'child', 'camera', 'pants', 'daddy', 'corn', 'moon', 'video', 'save', 'frog', 'knife', 'ok/okay', 'bean', 'open', 'hair', 'hall', 'guest', 'airport', 'super', 'foggy', 'stone', 'habit', 'find', 'build', 'smart', 'driver', 'idea', 'collect', 'thursday', 'cinema', 'turn', 'sorry', 'often', 'social', 'every', 'bike', 'ours', 'ribbon', 'note', 'credit card', 'classroom', 'cafe/café', 'never', 'brother', 'watch', 'thirteen', 'discuss', 'letter', 'weather', 'what', 'people', 'together', 'their', 'side', 'town', 'candy', 'interesting', 'living room', 'clean', 'building', 'ticket', 'girl', 'minute', 'paper', 'closed', 'away', 'something', 'hard', 'funny', 'butterfly', 'apron', 'great', 'cookie', 'paragraph', 'sunday', 'suggestion', 'story', 'husband', 'fill', 'jacket', 'event', 'bath', 'swimming', 'towel', 'grandpa', 'successful', 'pair', 'gift', 'clever', 'choose', 'tomato', 'memory', 'hole', 'mobile phone', 'birth', 'piano', 'eighteen', 'begin', 'really', 'magazine', 'station', 'fourteen', 'wall', 'pollution', 'hamburger', 'nineteen', 'shoulder', 'october', 'word', 'yard', 'restaurant', 'blue', 'happy', 'must', 'player', 'club', 'hello', 'subway', 'remember', 'come', 'well', 'because', 'coke', 'fairy', 'cloth', 'festival', 'join', 'famous', 'visit', 'sugar', 'smoking', 'mouse', 'ugly', 'smith', 'were', 'all right', 'guitar', 'moment', 'cake', 'whose', 'waiter', 'hotel', 'dark', 'into', 'difficult', 'street', 'have', 'desk', 'corner', 'monkey', 'celebration', 'aunt', 'listen', 'internet/internet', 'everything', 'work', 'lucky', 'ideal', 't-shirt/tee-shirt', 'umbrella', 'beside', 'problem', 'anyone', 'here', 'important', 'leader', 'grape', 'information', 'foreign', 'sunshine', 'quarter', 'sixty', 'mr./mr', 'theater/theatre', 'flag', 'strict', 'potato', 'truck', 'phone', 'ice cream', 'from', 'newspaper', 'grandparent', 'album', 'bright', 'color/colour', 'role', 'where', 'culture', 'item', 'music', "o'clock", 'tired', 'imagine', 'wear', 'everyone', 'factory', 'business', 'shelf', 'describe', 'excellent', 'gray/grey', 'happen', 'nothing', 'kick', 'beautiful', 'radio', 'boat', 'each', 'salad', 'tree', 'good night', 'technology', 'movie', 'forty', 'floor', 'reader', 'olympics', 'school', 'night', 'turkey', 'mountain', 'should', 'lesson', 'skill', 'rude', 'fast', 'tiger', 'last name', 'milk', 'snake', 'full', 'p.m./p.m./pm/pm', 'afternoon', 'saturday', 'apple', 'swimming pool', 'sale', 'this', 'expensive', 'meeting', 'haircut', 'fishing', 'year', 'wife', 'blank', 'leaf', 'case', 'november', 'first', 'awake', 'fifty', 'rice', 'practice', 'woman', 'brush', 'february', 'garbage', 'sister', 'four', 'cd player', 'band', 'baseball', 'thirty', 'vacation', 'repeat', 'lily', 'week', 'carry', 'boyfriend', 'vase', 'wonderful', 'dirty', 'song', 'pray', 'seventeen', 'merry', 'them', 'bring', 'glass', 'dance', 'dining room', 'drum', 'button', 'think', 'dinner', 'just', 'cousin', 'grandma', 'coat', 'hide', 'broken', 'sunny', 'father', 'tuesday', 'love', 'reporter', 'friday', 'rainy', 'subject', 'same', 'boring', 'yourself', 'taxi', 'then', 'ruler', 'seven', 'lonely', 'three', 'conversation', 'collection', 'sailor', 'girlfriend', 'careful', 'practice/practise', 'page', 'coffee', 'with', 'temple', 'number', 'celebrate', 'quickly', 'yogurt/yoghurt', 'cute', 'monday', 'pizza', 'young', 'nationality', 'know', 'between', 'learn', 'rabbit', 'holiday', 'terrible', 'dancing', 'computer', 'under', 'team', 'brain', 'history', 'ghost', 'horse', 'drama', 'meat', 'chair', 'grass', 'concert', 'black', 'morning', 'office', 'television', 'else', 'wash', 'bathroom', 'snowy', 'health', 'code', 'tell', 'than', 'owner', 'prince', 'five', 'busy', 'afraid', 'clothes', 'again', 'when', 'activity', 'summer', 'musician', 'borrow', 'photo', 'cloud', 'opera', 'hill', 'burger', 'table', 'each other', 'shopping', 'someone', 'email/e-mail/e-mail', 'greet', 'finish', 'speak', 'become', 'card', 'action', 'tennis', 'april', 'sofa', 'give', 'money', 'sight', 'bird', 'heavy', 'cook', 'island', 'library', 'goal', 'tool', 'favorite/favourite', 'sick', 'poster', 'everywhere', 'month', 'actor', 'carefully', 'kill', 'news', 'chicken', 'agree', 'ninety', 'change', 'pocket', 'best', 'doctor/dr./dr', 'both', 'ball', 'evening', 'trousers', 'kite', 'mrs./mrs', 'strange', 'shirt', 'machine', 'sheep', 'feeling', 'example', 'partner', 'bread', 'language', 'breathe', 'bookstore', 'period', 'self', 'hear', 'piece', 'exciting', 'dollar', 'glasses', 'bedroom', 'teach', 'cheese', 'airplane/aeroplane', 'july', 'friend', 'thing', 'rose', 'bicycle', 'eight', 'soccer', 'stage', 'september', 'lovely', 'could', 'catch', 'scientist', 'personal', 'also', 'elementary', 'drink', 'grandmother', 'different', 'autumn', 'birthday', 'twelve', 'twenty', 'bucket', 'world', 'winter', 'basketball', 'already', 'church', 'beef', 'site', 'lady', 'your', 'window', 'shoe', 'maybe', 'toilet', 'today', 'person', 'sell', 'lion', 'angry', 'painting', 'thank', 'pencil', 'flower', 'would', 'juice', 'grammar', 'short', 'ride', 'picnic', 'pool', 'arrive', 'cloudy', 'spring', 'everyday', 'tower', 'homework', 'weekend', 'paint', 'tube', 'waitress', 'banana', 'soup', 'difference', 'those', 'stop', 'meal', 'sixteen', 'dish', 'classmate', 'lunch', 'breakfast', 'tall', 'tooth', 'knee', 'article', 'daughter', 'small', 'solve', 'fruit', 'thick', 'false', 'soon', 'good afternoon', 'party', 'lazy', 'jeans', 'dictionary', 'football', 'reason', 'august', 'bone', 'bottom', 'spend', 'size', 'poor', 'science', 'january', 'farmer', 'june', 'about', 'biscuit', 'introduce', 'special', 'swim', 'doing', 'bell', 'good morning', 'play', 'yellow', 'rain', 'chocolate', 'notebook', 'many', 'princess', 'course', 'delicious', 'neighbor/neighbour', 'noise', 'topic', 'easy', 'everybody', 'butter', 'fifteen', 'they', 'real', 'thanks', 'officer', 'singer', 'baby', 'survey', 'farm', 'message', 'peace', 'wheel', 'walk', 'river', 'which', 'poem', 'read', 'college', 'math/maths', 'eleven', 'meet', 'these', 'garden', 'anybody', 'sing', 'true', 'grow', 'hour', 'vegetable', 'door', 'a.m./a.m./am/am', 'yours', 'eighty', 'start', 'write', 'always', 'seat', 'king', 'below', 'student', 'have to', 'life', 'cartoon', 'tomorrow', 'luck', 'strong', 'hers', 'interviewer', 'does', 'speech', 'there', 'writer', 'skirt', 'teacher', 'bridge', 'ready', 'parent', 'beach', 'only', 'hobby', 'couch']
|
| 8 |
+
CEFR_A2 = ['relax', 'editor', 'beginning', 'decide', 'anxious', 'scenery', 'disagree', 'drawer', 'board game', 'sleepy', 'develop', 'melon', 'id card', 'exhibition', 'description', 'blackboard', 'junior', 'barber', "driver's license/driving licence", 'beauty', 'horror', 'recycle', 'elevator', 'court', 'crazy', 'village', 'quite', 'certainly', 'clue', 'polite', 'angel', 'spaghetti', 'thief', 'wild', 'washing-up', 'simply', 'rugby', 'suitable', 'commitment', 'rhythm', 'error', 'terrify', 'itself', 'cycling', 'explanation', 'unhappy', 'balloon', 'century', 'mirror', 'hundred', 'shoot', 'novel', 'diet', 'complaint', 'proper', 'friendship', 'superstar', 'training', 'allow', 'stove', 'scale', 'rope', 'nobody', 'therefore', 'discussion', 'romantic', 'improve', 'architecture', 'mark', 'businesswoman', 'consequence', 'cooker', 'slowly', 'golden', 'anymore', 'task', 'audio', 'earth', 'right-hand', 'brilliant', 'forest', 'crowded', 'clerk', 'ability', 'lane', 'happiness', 'cash', 'gate', 'folder', 'convenience', 'uneasy', 'policeman', 'invade', 'sunflower', 'confident', 'impossible', 'confused', 'surprising', 'mushroom', 'scarf', 'complain', 'online', 'secretary', 'gentle', 'roughly', 'supper', 'reflect', 'maker', 'sitting room', 'progressive', 'apartment', 'skating', 'alive', 'done', 'atom', 'safe', 'knowledge', 'someday', 'statue', 'wool', 'east', 'company', 'root', 'raise', 'disappear', 'violin', 'sauce', 'quiz', 'invite', 'elderly', 'organize/organise', 'realize/realise', 'disappointing', 'clown', 'good-looking', 'windy', 'headteacher', 'globe', 'fully', 'dancer', 'normal', 'context', 'road', 'police', 'several', 'suitcase', 'table tennis', 'instant', 'steal', 'attention', 'golf', 'citizen', 'native', 'seed', 'license/licence', 'castle', 'perform', 'temperature', 'raincoat', 'appear', 'marry', 'dust', 'symbol', 'north', 'receipt', 'olympic', 'image', 'background', 'roundabout', 'belt', 'car park', 'degree', 'onion', 'unnecessary', 'cupboard', 'sandy', 'produce', 'loose', 'receive', 'jelly', 'capital', 'beyond', 'simple', 'target', 'seaside', 'fence', 'shade', 'private', 'audience', 'cross', 'leather', 'cheek', 'asleep', 'web page', 'chili/chilli', 'remind', 'truth', 'shop assistant', 'playful', 'style', 'international', 'apparently', 'insect', 'mystery', 'differently', 'pass', 'romance', 'smoothly', 'pilgrim', 'cleaner', 'lyric', 'disappointed', 'symphony', 'communicate', 'clearly', 'impatient', 'downtown', 'competition', 'beer', 'dishonest', 'chimpanzee', 'helpful', 'timetable', 'opportunity', 'granny', 'soft', 'unfair', 'liberty', 'greatly', 'establish', 'speaker', 'badminton', 'nowadays', 'specific', 'mood', 'lunchtime', 'certain', 'loudly', 'appropriate', 'sock', 'society', 'badly', 'bookshop', 'shot', 'wine', 'unpleasant', 'dead', 'forward', 'depend', 'tights', 'necessary', 'furniture', 'mp3 player', 'ancient', 'leisure', 'soldier', 'prevent', 'medal', 'figure', 'psychologist', 'probably', 'museum', 'stomachache', 'remote', 'scene', 'cricket', 'annoy', 'passenger', 'traveler/traveller', 'favor/favour', 'cheer', 'solution', 'kilogram/kilogramme', 'granddad', 'convenient', 'physical', 'passage', 'product', 'balcony', 'robin', 'string', 'expect', 'boiled', 'flea', 'text message', 'avoid', 'police station', 'alarm clock', 'overweight', 'path', 'national', 'soap', 'impress', 'partly', 'chance', 'lose', 'send', 'sake', 'pronunciation', 'silence', 'generation', 'importance', 'skateboarding', 'uncomfortable', 'fact', 'mysterious', 'invent', 'retire', 'toward/towards', 'himself', 'acceptable', 'manage', 'breeze', 'gallery', 'cigarette', 'death', 'unfortunately', 'grace', 'boss', 'drugstore', 'euro', 'staff', 'anywhere', 'rumor/rumour', 'attack', 'developed', 'understand', 'blanket', 'pollute', 'schoolchild', 'perfume', 'such', 'danger', 'tram', 'opinion', 'championship', 'production', 'cola', 'blonde', 'herself', 'pronounce', 'stomach', 'chess', 'west', 'wander', 'crown', 'snack', 'natural', 'chairman', 'hero', 'sailing', 'impact', 'medical', 'reunion', 'finally', 'freeze', 'steak', 'dangerous', 'latest', 'educational', 'dessert', 'salt', 'advice', 'left-hand', 'artificial', 'marvelous/marvellous', 'evidence', 'whom', 'handbag', 'fighter', 'pack', 'policewoman', 'lifestyle', 'mall', 'pour', 'first name', 'comparative', 'been', 'slim', 'retired', 'fare', 'mosque', 'among', 'pile', 'bench', 'thirsty', 'stair', 'creature', 'accident', 'contact', 'cost', 'regular', 'energetic', 'unimportant', 'pleased', 'forever', 'bus station', 'column', 'duck', 'follow', 'metal', 'ancestor', 'surfing', 'manager', 'mistake', 'glove', 'receptionist', 'semester', 'gender', 'pleasing', 'during', 'strength', 'headphone', 'cheque', 'camping', 'encourage', 'fault', 'lost', 'youth', 'flour', 'sample', 'gather', 'honey', 'meter/metre', 'petrol', 'roof', 'include', 'usual', 'diary', 'surname', 'pepper', 'thunderstorm', 'seafood', 'batch', 'well-known', 'inspiration', 'confuse', 'refrigerator', 'wide', 'apologize/apologise', 'toast', 'armed', 'deeply', 'magic', 'association', 'plural', 'planet', 'mineral water', 'situation', 'verb', 'pleasant', 'killer', 'appreciate', 'author', 'theirs', 'soda', 'elephant', 'beginner', 'consider', 'department store', 'pull', 'script', 'colorful/colourful', 'likely', 'cooking', 'armchair', 'given', 'sunglasses', 'anniversary', 'wealth', 'petrol station', 'email', 'married', 'traffic', 'labor/labour', 'addition', 'slave', 'structure', 'fridge', 'cent', 'satisfy', 'continent', 'vocabulary', 'predict', 'luckily', 'heavily', 'mainly', 'space', 'writing', 'businessman', 'committee', 'spill', 'treasure', 'worried', 'violent', 'hunter', 'kilo', 'olive oil', 'indeed', 'sadly', 'unit', 'concentrate', 'gram/gramme', 'scientific', 'disadvantage', 'valley', 'response', 'direction', 'truly', 'bulb', 'powerful', 'lake', 'electric', 'except', 'liter/litre', 'grocery store', 'possibly', 'attitude', 'indicate', 'stranger', 'useful', 'grandson', 'midday', 'return', 'debate', 'continue', 'fortunately', 'trap', 'silly', 'manner', 'without', 'tradition', 'forbid', 'uniform', 'playground', 'achieve', 'precise', 'coin', 'expression', 'curry', 'daylight', 'provide', 'quick', 'composer', 'pence', 'shine', 'reveal', 'being', 'agent', 'washing machine', 'serve', 'rent', 'video game', 'next to', 'detail', 'hiking', 'enter', 'journey', 'possible', 'fried', 'storm', 'teenage', 'occupation', 'napkin', 'plate', 'frightening', 'textbook', 'footballer', 'spaceship', 'noisy', 'apart', 'enough', 'queen', 'empty', 'quietly', 'main course', 'replace', 'sleepless', 'credit', 'pressure', 'freedom', 'advertisement', 'advanced', 'success', 'fork', 'director', 'carrot', 'local', 'seem', 'meaning', 'flight', 'noon', 'survive', 'tidy', 'superlative', 'negative', 'crime', 'ever', 'extremely', 'campus', 'swallow', 'however', 'talent', 'creative', 'seriously', 'widely', 'drawing', 'country', 'swimming costume', 'unknown', 'childhood', 'advise', 'strongly', 'similar', 'used', 'boil', 'thought', 'pity', 'hip hop/hip-hop/hiphop', 'software', 'lamp', 'customer', 'shore', 'create', 'lemonade', 'third', 'harmful', 'unforgettable', 'make-up', 'sweetheart', 'yeah', 'pasta', 'invention', 'aged', 'brainstorm', 'chapter', 'power', 'hardly', 'stadium', 'ms./ms', 'countryside', 'dentist', 'astronaut', 'exist', 'alright', 'fairly', 'exam', 'wooden', 'lively', 'laziness', 'high school', 'cafeteria', 'belong', 'political', 'lover', 'smooth', 'system', 'chirp', 'hockey', 'weight', 'sunlight', 'heating', 'wedding', 'police officer', 'tour guide', 'regularly', 'successfully', 'weekday', 'purpose', 'basically', 'square', 'oppose', 'rather', 'amusement', 'menu', 'escalator', 'summary', 'embarrassing', 'amused', 'sports center/sports centre', 'creativity', 'comic', 'singular', 'remain', 'independence', 'pursue', 'sausage', 'half-price', 'blood', 'brand', 'fortune', 'text', 'argue', 'nearly', 'modern', 'explain', 'perhaps', 'military', 'wood', 'wise', 'pride', 'toothbrush', 'cassette', 'royal', 'visitor', 'crowd', 'noun', 'tablespoon', 'somewhere', 'athlete', 'fast food', 'custom', 'publish', 'peaceful', 'common', 'dressed', 'drug', 'nervous', 'illegal', 'surprised', 'diamond', 'statement', 'harmony', 'endangered', 'rating', 'permission', 'crisp', 'winner', 'advertising', 'cereal', 'comfortable', 'post office', 'area', 'unusual', 'walking', 'wheelchair', 'capital letter', 'soft drink', 'photography', 'chef', 'ankle', 'clone', 'lemon', 'envelope', 'intelligent', 'bored', 'centimeter/centimetre', 'painter', 'granddaughter', 'schoolwork', 'argument', 'myself', 'nation', 'feather', 'fiction', 'silently', 'personality', 'effort', 'tourist', 'frank', 'shampoo', 'adjust', 'awful', 'runner', 'cooler', 'ambition', 'fashion', 'curse', 'earring', 'inexpensive', 'ahead', 'performance', 'no one', 'kingdom', 'less', 'title', 'refer', 'strategy', 'brave', 'ourselves', 'sudden', 'senior', 'out of', 'handicapped', 'importantly', 'skiing', 'enormous', 'frightened', 'advantage', 'admire', 'garlic', 'represent', 'racket', 'abroad', 'kilometer/kilometre', 'prefer', 'uncertain', 'midnight', 'choice', 'photographer', 'government', 'website', 'university', 'member', 'south', 'prediction', 'recently', 'average', 'junk', 'organized/organised', 'fresh', 'host', 'disco', 'weekly', 'short/shorts', 'traditional', 'calendar', 'elsewhere', 'anyway', 'robbery', 'couple', 'frighten', 'within', 'wisdom', 'offer', 'zone', 'bride', 'million', 'sweater', 'lend', 'method', 'banking', 'admit', 'highway', 'classical music', 'center/centre', 'logical', 'portrait', 'captain', 'motorway', 'instead', 'scissors', 'imagination', 'bookcase', 'compare', 'accept', 'apply', 'grilled', 'familiar', 'basket', 'silver', 'belly', 'repair', 'daily', 'pear', 'digital camera', 'fascinating', 'prepare', 'against', 'fisherman', 'punctuation', 'bush', 'teenager', 'gradually', 'affair', 'earthquake', 'especially', 'annoying', 'recent', 'angrily', 'operate', 'discover', 'guidebook', 'position', 'thousand', 'adjective', 'cheap', 'upon', 'correctly', 'communication', 'spoon', 'criticize/criticise', 'bury', 'chest', 'although', 'earn', 'suit', 'seek', 'essay', 'behavior/behaviour', 'learner', 'sightseeing', 'basic', 'shall', 'attractive', 'episode', 'adult', 'appearance', 'actually', 'across', 'themselves', 'entrance', 'effect', 'pacific', 'traffic light', 'exactly', 'indoor', 'running', 'weigh', 'physically', 'inner', 'entertainment', 'container', 'greedy', 'relaxed', 'final', 'material', 'difficulty', 'champagne', 'softly', 'injure', 'bake', 'intelligence', 'chin', 'hike', 'additional', 'grandchild', 'education', 'omelet/omelette', 'consist', 'ending', 'fantastic', 'instrument', 'dinosaur', 'garage', 'twice', 'printer', 'unhealthy', 'mostly', 'lawyer', 'destroy', 'chain', 'weak']
|
| 9 |
+
|
| 10 |
+
CEFR_B1 = ['layer', 'historical', 'arrest', 'server', 'involuntarily', 'insight', 'punishment', 'brand-new', 'obtain', 'regulation', 'restore', 'poisonous', 'extinction', 'environmental', 'northwest', 'terrified', 'nonsense', 'calculation', 'economic', 'indoors', 'avenue', 'myth', 'outer', 'fascinate', 'permanent', 'historian', 'out-of-date', 'dazzle', 'destination', 'critical', 'biochemistry', 'presentation', 'minus', 'humid', 'emotional', 'stressful', 'mend', 'physics', 'sports', 'presence', 'equipment', 'rail', 'enclose', 'boot', 'retain', 'disagreement', 'neutral', 'remainder', 'legal', 'basis', 'specialist', 'systematic', 'steep', 'bacon', 'magnificent', 'decision', 'attain', 'capsule', 'arrange', 'desperate', 'mediterranean', 'score', 'fetch', 'sometime', 'maintain', 'motionless', 'fortnight', 'financial', 'innovator', 'inquiry/enquiry', 'goods', 'trustworthy', 'penny', 'internal', 'vice', 'realistic', 'worth', 'resist', 'muscle', 'certainty', 'transform', 'blessing', 'ration', 'fortunate', 'shopper', 'thriller', 'duty', 'melt', 'unexpectedly', 'stream', 'inhale', 'furthermore', 'freezer', 'citizenship', 'defense/defence', 'vessel', 'columnist', 'severe', 'shrimp', 'survivor', 'civilization/civilisation', 'freely', 'slogan', 'magician', 'crossing', 'weakness', 'obstacle', 'carpet', 'copyright', 'fake', 'animated', 'daze', 'declare', 'primarily', 'properly', 'qualified', 'radiation', 'widespread', 'bump', 'admission', 'engine', 'former', 'revise', 'satellite', 'sphere', 'reliable', 'orderly', 'occasionally', 'cabbage', 'coffin', 'edge', 'jumper', 'evenly', 'likeness', 'film-maker', 'respectable', 'positive', 'possibility', 'parental', 'safely', 'poverty', 'revision', 'hunt', 'signpost', 'soothe', 'repay', 'operation', 'universe', 'behalf', 'alternative', 'skyscraper', 'geology', 'aborigine', 'skeleton', 'divine', 'appoint', 'fond', 'confusing', 'boldly', 'easygoing/easy-going', 'outlaw', 'embarrassed', 'relationship', 'insist', 'beard', 'reduction', 'workshop', 'lasting', 'conclusion', 'scary', 'toothpaste', 'race', 'competitive', 'ripe', 'productive', 'hunger', 'waterfall', 'declaration', 'concerned', 'unheard', 'nevertheless', 'embarrass', 'interval', 'global', 'evident', 'wasteful', 'controversial', 'customs', 'inconvenient', 'supportive', 'instructor', 'unemployment', 'outdoor', 'prejudice', 'strangely', 'misery', 'economics', 'nasty', 'roadside', 'literary', 'tasteless', 'hopeless', 'participant', 'calculate', 'laughter', 'suppose', 'regarding', 'educate', 'liver', 'idol', 'inspire', 'souvenir', 'resource', 'actress', 'element', 'biology', 'beast', 'creator', 'vice president', 'dialog/dialogue', 'phrasal verb', 'biography', 'security', 'meanwhile', 'niece', 'silent', 'minimize/minimise', 'organic', 'primary school', 'dedicate', 'substantial', 'forbidden', 'engineering', 'introduction', 'defender', 'violence', 'conscious', 'sidewalk', 'narrate', 'heavenly', 'oppression', 'sensation', 'uplifted', 'contribution', 'throughout', 'candle', 'prohibition', 'worthwhile', 'humanity', 'occasional', 'checkout', 'storage', 'pillow', 'confirm', 'season', 'attraction', 'unfairly', 'soul', 'dangerously', 'dare', 'cancel', 'ladder', 'observe', 'upward', 'diving', 'marked', 'excitedly', 'slip', 'practical', 'incredibly', 'suffer', 'memorize/memorise', 'prominent', 'typically', 'encouraging', 'applause', 'affection', 'pause', 'weather forecast', 'teaching', 'extreme sports', 'drag', 'windsurfing', 'supposedly', 'basin', 'propose', 'grouping', 'interact', 'industry', 'register', 'juicy', 'unfold', 'favorable/favourable', 'hurriedly', 'magical', 'endanger', 'fantasy', 'oily', 'spider', 'debt', 'discrimination', 'diaper', 'underpants', 'tissue', 'railroad', 'railway', 'expand', 'ashamed', 'ocean', 'renew', 'platform', 'sundial', 'dolphin', 'atlantic', 'auxiliary', 'sophomore', 'bust', 'sheriff', 'install/instal', 'reasonable', 'composition', 'acknowledgment/acknowledgement', 'application', 'dirt', 'breakthrough', 'distinction', 'humor/humour', 'horizon', 'farther', 'kettle', 'origin', 'countless', 'beloved', 'tense', 'victory', 'vehicle', 'monitor', 'baggage', 'eventually', 'frozen', 'considerable', 'limitation', 'theft', 'working', 'airline', 'scatter', 'voyage', 'marriage', 'consumption', 'enthusiast', 'particularly', 'awesome', 'grave', 'abstract', 'steel', 'sculpture', 'burning', 'bubble', 'damage', 'nail', 'summit', 'ginger', 'oxygen', 'reject', 'buddy', 'farmland', 'extreme', 'lung', 'adverb', 'passive', 'comma', 'definition', 'exhausted', 'rapid', 'measure', 'teammate', 'comparison', 'frustration', 'tank', 'misunderstanding', 'height', 'solid', 'unique', 'imitate', 'dustbin', 'bomb', 'steady', 'enrich', 'massive', 'miserable', 'justice', 'logo', 'region', 'document', 'electron', 'intention', 'pottery', 'babysit', 'northwestern', 'stupid', 'tuna', 'overwhelming', 'connect', 'rainforest', 'nutritious', 'accent', 'prison', 'intermediate', 'merchant', 'competitor', 'swear', 'sunrise', 'extent', 'breast', 'specially', 'comedy', 'courageous', 'stare', 'spinach', 'arithmetic', 'generous', 'meditate', 'viewpoint', 'horrify', 'inscribe', 'northern', 'closely', 'questionnaire', 'persuasive', 'full stop', 'partial', 'mathematician', 'adviser/advisor', 'minor', 'cherish', 'instance', 'convey', 'preservation', 'distribute', 'council', 'complement', 'achievement', 'concentration', 'tighten', 'paid', 'active', 'ecology', 'canned', 'windscreen', 'nonetheless', 'question mark', 'nucleus', 'guitarist', 'border', 'librarian', 'formula', 'obvious', 'conclude', 'telegram', 'surely', 'rapidly', 'categorize/categorise', 'hopeful', 'settlement', 'aspirin', 'ness', 'consume', 'emperor', 'duty-free', 'beaver', 'fuss', 'motivate', 'pleasantly', 'poet', 'nightlife', 'comedian', 'gymnastics', 'handy', 'motive', 'steam', 'aspect', 'disc jockey/disk jockey', 'hairdresser', 'meaningful', 'awareness', 'up-to-date', 'destruction', 'retail', 'messy', 'breath', 'vein', 'theory', 'scold', 'thunderous', 'moss', 'notion', 'palm', 'rebuild', 'imaginary', 'liberation', 'unfortunate', 'amusing', 'pump', 'chaos', 'boxing', 'guilt', 'bond', 'acceptance', 'deadline', 'golfer', 'accompany', 'eternity', 'unlikely', 'loaf', 'package', 'urgent', 'deaf', 'goodness', 'giant', 'academic', 'navy blue', 'carton', 'primitive', 'used to', 'extinct', 'politics', 'ignore', 'rely', 'shelter', 'deficiency', 'extensively', 'broccoli', 'phrase', 'promotion', 'uncontrollable', 'relaxing', 'uncertainty', 'recommend', 'hesitate', 'threat', 'accustom', 'smoker', 'air conditioning', 'pronoun', 'backpacker', 'channel', 'brick', 'tablet', 'carelessness', 'rearrange', 'postcard', 'bore', 'conservative', 'orchestra', 'snowboard', 'liar', 'disappoint', 'motherland', 'punish', 'volcano', 'impression', 'whale', 'sponge', 'enemy', 'accidentally', 'contrary', 'union', 'maintenance', 'tension', 'crow', 'vegetarian', 'scope', 'mayor', 'refreshments', 'arrow', 'vivid', 'entry', 'management', 'sand', 'carriage', 'farming', 'hearty', 'vague', 'dioxide', 'horn', 'conflict', 'reserve', 'victim', 'second-hand', 'guidance', 'newborn', 'northeastern', 'derive', 'helpless', 'remedy', 'ward', 'forehead', 'puppy', 'applaud', 'responsible', 'challenging', 'next door', 'wildly', 'digital', 'worn', 'dusty', 'examine', 'approval', 'refuse', 'constitution', 'warmly', 'acquaintance', 'judgment/judgement', 'intensive', 'luggage', 'mechanical', 'friendliness', 'entire', 'delivery', 'ice hockey', 'newcomer', 'humorous', 'virtual', 'assume', 'huge', 'liquid', 'shadowy', 'naked', 'reproduce', 'promote', 'swimmer', 'protective', 'weird', 'incredible', 'wizard', 'socially', 'hardship', 'tracksuit', 'shortage', 'elbow', 'photocopy', 'mechanic', 'legally', 'jewel', 'chewing gum', 'climate', 'similarity', 'nephew', 'guide', 'cruel', 'policy', 'prepared', 'runaway', 'democracy', 'written', 'suffix', 'kitten', 'emotion', 'finding', 'stable', 'remote control', 'cycle', 'alike', 'comfort', 'terminal', 'proportion', 'property', 'graph', 'exclusive', 'loudspeaker', 'unfit', 'trash', 'correction', 'gratitude', 'shocked', 'fashionable', 'experienced', 'firmly', 'afterward', 'insane', 'cage', 'representative', 'pullover', 'amazed', 'capacity', 'fund', 'harness', 'govern', 'modem', 'distance', 'basement', 'election', 'apology', 'nectar', 'safety', 'relate', 'anger', 'democratic', 'briefly', 'habitat', 'anti', 'disaster', 'seaweed', 'restrict', 'reclaim', 'bracelet', 'accuracy', 'superior', 'movement', 'dozen', 'invest', 'vision', 'sunbathe', 'funeral', 'caller', 'repeatedly', 'eliminate', 'lifelong', 'agricultural', 'prayer', 'spoil', 'chemistry', 'establishment', 'previous', 'hidden', 'deliver', 'confusion', 'celebrity', 'entirely', 'westward', 'emphasis', 'illegally', 'vacancy', 'annual', 'workplace', 'warn', 'able', 'strategic', 'diverse', 'needle', 'seize', 'onto/on to', 'series', 'postman', 'continuously', 'pure', 'englishman', 'oneself', 'anxiety', 'heel', 'rainfall', 'lorry', 'honeymoon', 'tremendous', 'tile', 'typical', 'face-to-face', 'unless', 'surface', 'dramatic', 'genetic', 'injury', 'razor', 'poetry', 'diver', 'backpacking', 'arise', 'capture', 'impressive', 'arrival', 'recover', 'remarkable', 'authority', 'wrist', 'supporter', 'suicide', 'preposition', 'session', 'bureau', 'opposition', 'undo', 'selfish', 'glory', 'schoolmate', 'message board', 'lighter', 'sometimes', 'undergo', 'distinctly', 'classify', 'whether', 'limited', 'soap opera', 'grant', 'attract', 'lamb', 'mailbox', 'administration', 'brass', 'chat show', 'canteen', 'enjoyment', 'purse', 'lottery', 'patrol', 'bronze', 'fluently', 'involved', 'orbit', 'turbulence', 'deadly', 'firefighter', 'isle', 'dude', 'architect', 'trend', 'coastal', 'electricity', 'debris', 'adorable', 'capable', 'possessive', 'classical', 'immigrate', 'environmentalist', 'journal', 'gravy', 'explosion', 'analyze/analyse', 'satisfied', 'mustard', 'hire', 'heater', 'greeting', 'household', 'honestly', 'intentionally', 'foundation', 'identity card', 'fondness', 'clip', 'compliment', 'maximum', 'sandal', 'careless', 'location', 'sacred', 'inhabitant', 'remembrance', 'surprisingly', 'available', 'limp', 'serious', 'simultaneously', 'industrial', 'memorable', 'trim', 'fasten', 'registration', 'pollutant', 'rise', 'filling', 'weapon', 'observation', 'affect', 'continuous', 'handshake', 'garment', 'separation', 'warmth', 'concept', 'none', 'nickname', 'entertain', 'researcher', 'shallow', 'antonym', 'absent', 'annoyed', 'rotten', 'compound', 'requirement', 'firework', 'parrot', 'county', 'negotiate', 'investigation', 'unpack', 'employment', 'precious', 'stumble', 'flow', 'existence', 'landlord', 'breeding', 'regional', 'army', 'teamwork', 'coal', 'privacy', 'wherever', 'wetland', 'consideration', 'prize', 'raspberry', 'definite article', 'straw', 'terrific', 'relative', 'sour', 'monument', 'conservation', 'inevitable', 'cliff', 'summarize/summarise', 'curly', 'calmness', 'incorrect', 'consequently', 'diameter', 'adapt', 'ivory', 'onstage', 'dump', 'warranty', 'insurance', 'nowhere', 'fold', 'murderer', 'brotherhood', 'choir', 'crisis', 'accurate', 'marine', 'delighted', 'science fiction', 'publicly', 'accessible', 'qualify', 'adopt', 'accessory', 'video clip', 'jade', 'ingredient', 'dial', 'paradise', 'conquer', 'heaven', 'telecommunications', 'phenomenon', 'syndrome', 'moreover', 'graduation', 'visible', 'transportation', 'noisily', 'similarly', 'flunk', 'internationally', 'graphic', 'southeast', 'willingness', 'accuse', 'buyer', 'cultural', 'general', 'proceed', 'mathematics', 'occupy', 'literature', 'glorious', 'calm', 'journalist', 'whenever', 'instruction', 'minimum', 'cancer', 'announce', 'constitute', 'herd', 'define', 'settle', 'religion', 'crush', 'bilingual', 'ensure', 'largely', 'tumble', 'excellence', 'unbelievable', 'northeast', 'deserve', 'survival', 'ferry', 'mission', 'percent/per cent', 'announcement', 'farthest', 'organization/organisation', 'gallon', 'stripe', 'thankful', 'deed', 'crop', 'maple', 'stricken', 'professor', 'numerous', 'ought to', 'normally', 'handkerchief', 'disability', 'builder', 'psychological', 'scare', 'spectator', 'graceful', 'profession', 'charity', 'tide', 'sheer', 'furthest', 'resolve', 'runway', 'amount', 'simplify', 'entertainer', 'total', 'irregular', 'vary', 'iron', 'jail/gaol', 'allergic', 'vividly', 'drunk', 'goalkeeper', 'flavor/flavour', 'virus', 'devotion', 'pineapple', 'housework', 'pole', 'completely', 'hatred', 'reservation', 'handball', 'including', 'command', 'groom', 'bleed', 'prefix', 'engaged', 'majority', 'mixing bowl', 'necklace', 'compete', 'income', 'folk', 'curious', 'sticker', 'common sense', 'illness', 'bullet', 'glance', 'honesty', 'ordinary', 'devastating', 'baker', 'publisher', 'monthly', 'hearing', 'champion', 'resemble', 'absorb', 'circus', 'encyclopedia/encyclopaedia', 'illuminate', 'flute', 'procrastination', 'eagerness', 'sweatshirt', 'isolation', 'exchange rate', 'giggle', 'atmosphere', 'loud', 'first-floor', 'respond', 'totally', 'emotionally', 'alcohol', 'central', 'unrelated', 'gardening', 'forth', 'technological', 'delightful', 'distinguish', 'fuel', 'loyal', 'disgusting', 'snowstorm', 'department', 'violently', 'visa', 'underage', 'academy', 'frequent', 'extend', 'sportsmanship', 'officially', 'wrapping', 'counseling/counselling', 'demonstration', 'main', 'variety', 'otherwise', 'frequency', 'bent', 'loyalty', 'identity', 'agriculture', 'locate', 'tempt', 'depart', 'curiously', 'reputation', 'plain', 'religious', 'expense', 'darkness', 'damaged', 'refund', 'gesture', 'grab', 'recording', 'motto', 'eyesight', 'freezing', 'cattle', 'overwork', 'disk/disc', 'agreement', 'blind', 'check-in desk/check-in', 'interrupt', 'lightning', 'inspection', 'rank', 'swell', 'saucer', 'scholar', 'defend', 'aerobics', 'enthusiastic', 'transitional', 'diagram', 'unwell', 'racial', 'gifted', 'grill', 'term', 'retell', 'option', 'proud', 'participate', 'sequence', 'respect', 'attach', 'tiring', 'offensive', 'combination', 'pattern', 'stubborn', 'nervousness', 'bathe', 'uninteresting', 'crash', 'innermost', 'feast', 'bunch', 'despite', 'climber', 'earnest', 'historic', 'outdoors', 'intense', 'remove', 'limit', 'scholarship', 'lecture', 'backpack', 'rolling', 'interviewee', 'technique', 'campsite', 'storyteller', 'essence', 'southwest', 'shiny', 'critic', 'improper', 'graphics', 'brainstorming', 'consonant', 'admiration', 'mislead', 'terribly', 'chest of drawers', 'annually', 'healing', 'complicate', 'ambitious', 'jealous', 'neighborhood/neighbourhood', 'spirit', 'current', 'tragic', 'invitation', 'suspicion', 'parcel', 'overwhelm', 'depressing', 'aware', 'signature', 'tire', 'stun', 'opening', 'failure', 'fire station', 'continually', 'forecast', 'novelist', 'burglar', 'proof', 'someplace', 'triumph', 'adverbial', 'genetics', 'workout', 'complicated', 'shameful', 'helmet', 'cushion', 'salmon', 'visual', 'dynasty', 'wagon', 'altogether', 'pocket money', 'contain', 'choke', 'oppress', 'damp', 'pregnant', 'motivation', 'instead of', 'liberate', 'booking', 'awkward', 'because of', 'reunify', 'rarely', 'overnight', 'frustrated', 'responsibility', 'initially', 'whoever', 'breathless', 'air force', 'leading', 'turning', 'commonly', 'incident', 'length', 'tongue', 'faraway', 'penniless', 'reality', 'absolutely', 'inject', 'talented', 'filter', 'toss', 'ironing', 'formally', 'mischief', 'threatening', 'determine', 'dealer', 'discourage', 'tasty', 'link', 'afterwards', 'motor', 'divorced', 'device', 'bishop', 'acquire', 'studio', 'reception', 'shave', 'disappointment', 'bulletin', 'parachute', 'demand', 'caution', 'thumb', 'schoolteacher', 'dissolve', 'assist', 'mild', 'circular', 'advert', 'appetite', 'crossroads', 'daisy', 'emerge', 'recycling', 'analysis', 'robot', 'mash', 'warrior', 'spicy', 'upload', 'parking', 'settler', 'highly', 'rational', 'latecomer', 'network', 'consumer', 'scared', 'worthy', 'uncover', 'hatch', 'service', 'feverishly', 'silk', 'excess', 'traffic jam', 'old-fashioned', 'connection/connexion', 'nationalist', 'dishwasher', 'aloud', 'painful', 'visually', 'logic', 'currency', 'inscription', 'fright', 'tremble', 'pale', 'enable', 'stir', 'probability', 'idiom', 'hasty', 'kindness', 'contribute', 'motorcycle', 'attend', 'inform', 'earache', 'thump', 'wardrobe', 'ice skating', 'unemployed', 'hearted', 'spice', 'blog', 'laboratory', 'social networking', 'swarm', 'thus', 'attachment', 'calculator', 'assignment', 'tick', 'marble', 'prove', 'priest', 'empire', 'doze', 'moderate', 'approve', 'casual', 'excitement', 'tale', 'curiosity', 'abnormal', 'delete', 'lightly', 'abandon', 'translate', 'geographical', 'stuck', 'positively', 'edition', 'wrap', 'transformation', 'phoenix', 'hard-working', 'cave', 'moisture', 'mosquito', 'secretly', 'improvement', 'provided', 'glimpse', 'selection', 'relatively', 'cheerful', 'noticeable', 'distant', 'face to face', 'essential', 'agenda', 'philosophy', 'rubbish', 'drown', 'intermission', 'press', 'furnish', 'upper', 'peculiar', 'setting', 'aside', 'rare', 'negotiation', 'relation', 'embarrassment', 'anxiously', 'deny', 'politely', 'compose', 'leadership', 'battle', 'gown', 'sorrow', 'guilty', 'mile', 'temporary', 'southern', 'twin', 'spiritual', 'outward', 'lovingly', 'congratulate', 'packing', 'password', 'christian', 'bedside', 'subconscious', 'enjoyable', 'anyhow', 'enthusiasm', 'unafraid', 'hand-held', 'secondary school', 'cucumber', 'courgette', 'amazing', 'passport', 'apparent', 'cd-rom', 'spelling', 'react', 'commit', 'headline', 'mourn', 'invasion', 'encouragement', 'electrical', 'alphabet', 'unwanted', 'stretch', 'assign', 'nutrition', 'carelessly', 'organism', 'fragment', 'takeaway', 'timely', 'piety', 'shame', 'efficient', 'stressed', 'construction', 'obviously', 'disabled', 'bitter', 'yoga', 'well-dressed', 'league', 'discovery', 'forgive', 'narrative', 'check-in counter/check-in', 'engage', 'webcam', 'rescue', 'sickness', 'afford', 'accidental', 'continual', 'mankind', 'foolish', 'rubber', 'formal', 'ecosystem', 'confidence', 'intend', 'specialize/specialise', 'passion', 'somehow', 'directly', 'protection', 'teller', 'preference', 'purify', 'fade', 'mountaintop', 'tobacco', 'wristwatch', 'geography', 'fitness', 'indirectly', 'regain', 'format', 'increasingly', 'proposal', 'immediately', 'blogger', 'quake', 'slot', 'tent', 'unite', 'require', 'submarine', 'unfriendly', 'organ', 'involve', 'destructive', 'possession', 'artist', 'preschool', 'mule', 'useless', 'combine', 'beneath', 'next-door', 'pipe', 'thorough', 'various', 'participle', 'impair', 'duvet', 'sadness', 'pond', 'trigger', 'misty', 'referee', 'occasion', 'hopefully', 'unpredictable', 'animation', 'examination', 'emphasize/emphasize', 'reduce', 'recycled', 'depressed', 'performer', 'barely', 'jazz', 'scenic', 'career', 'part-time', 'waste', 'camel', 'accurately', 'comet', 'charm', 'facility', 'noticeboard', 'reunification', 'dating', 'chariot', 'ceremony', 'politician', 'examiner', 'eager', 'characterize/characterise', 'cell', 'sticky', 'endless', 'exit', 'fifth', 'untidy', 'cultivate', 'twist', 'farewell', 'tropical', 'independent', 'fluent', 'bless']
|
| 11 |
+
CEFR_B2 = ['follower', 'dental', 'obedience', 'blindness', 'suffering', 'countrywide', 'stopover', 'grieve', 'literally', 'unreasonable', 'birthplace', 'sunbeam', 'furor/furore', 'paratrooper', 'eloquence', 'impossibility', 'overemphasize/overemphasise', 'yawn', 'gangster', 'worldly', 'outskirts', 'grin', 'eyebrow', 'supplier', 'coral', 'ideology', 'shaky', 'prosper', 'unfamiliar', 'compression', 'verbally', 'broadly', 'mutton', 'mutter', 'cardigan', 'unbearable', 'retina', 'identical', 'massage', 'investigate', 'nursery', 'pastor', 'exclude', 'programming', 'blindly', 'scholasticism', 'understanding', 'descend', 'boost', 'amplifier', 'plead', 'undersea', 'exception', 'droop', 'mutability', 'infer', 'starving', 'clause', 'ax/axe', 'smash', 'paramedic', 'fatal', 'dose', 'colon', 'deputy', 'truthfulness', 'clog', 'roar', 'unsure', 'potent', 'tightrope', 'dehumanize/dehumanise', 'harmless', 'momentarily', 'core', 'economical', 'dependable', 'mastery', 'sail', 'masterpiece', 'barrier', 'bomber', 'sprint', 'grip', 'dorm', 'spur', 'willing', 'packed', 'rectangular', 'moving', 'dumb', 'statewide', 'assess', 'twinkle', 'pulse', 'spokeswoman', 'discriminate', 'exile', 'desperation', 'peer', 'nationwide', 'edit', 'speeder', 'patron', 'fundamental', 'tatter', 'partisan', 'proponent', 'elegance', 'jeopardize/jeopardise', 'erect', 'wearisome', 'optional', 'infect', 'gambling', 'psychologically', 'quotable', 'relaxation', 'interior', 'suitably', 'pharmacist', 'unrest', 'picky', 'laser', 'authorize/authorise', 'bossy', 'repress', 'prosecute', 'quote', 'effectiveness', 'privilege', 'haste', 'preliminary', 'rosy', 'unusualness', 'contempt', 'berry', 'sack', 'detachment', 'impressed', 'genius', 'surroundings', 'squeeze', 'conceal', 'stern', 'unhappiness', 'radium', 'frankly', 'functionality', 'mumble', 'nutshell', 'extensive', 'repetition', 'condemn', 'sociable', 'shipping', 'veteran', 'roam', 'bombard', 'leisurely', 'cope', 'likewise', 'revival', 'comprehension', 'skylark', 'wornout/worn out/worn-out', 'cellar', 'thoughtful', 'outcome', 'reverse', 'walker', 'recipe', 'uncomfortably', 'lyrics', 'merchandise', 'transfusion', 'carefree', 'transient', 'applicant', 'pajamas/pyjamas', 'vitamin', 'stab', 'personify', 'faculty', 'elite', 'cottage', 'opponent', 'native speaker', 'widow', 'skateboarder', 'global warming', 'entitle', 'waistcoat', 'dimension', 'illustrate', 'politeness', 'specify', 'spokesman', 'altar', 'valentine', 'standstill', 'means', 'secondly', 'aluminum/aluminium', 'alternate', 'doubtful', 'ferryboat', 'revolutionary', 'handrail', 'strangle', 'seesaw', 'oath', 'diarrhea/diarrhoea', 'overboard', 'clam', 'skyward', 'salsa', 'nourish', 'undreamed', 'venue', 'cursor', 'heartbreaking', 'chemotherapy', 'memorial', 'crack', 'pickle', 'tunnel', 'conscience', 'microwave', 'politically', 'juror', 'commander', 'truthful', 'upgrade', 'carbon dioxide', 'genuinely', 'pension', 'wasp', 'justify', 'countryman', 'scheduled', 'relieve', 'righteousness', 'lower', 'prior', 'shakespearean', 'arch', 'funky', 'brochure', 'casually', 'bankruptcy', 'regardless', 'obsession', 'uncommon', 'outfit', 'doom', 'spite', 'punch', 'salty', 'recognition', 'visibly', 'certificate', 'diplomat', 'reggae', 'resistance', 'tiptoe', 'persistence', 'soundtrack', 'outdo', 'regrettably', 'workman', 'caption', 'elective', 'soaking', 'nobleman', 'shaken', 'operator', 'attractiveness', 'originality', 'slam', 'practicality', 'fluid', 'fed up', 'hygiene', 'gaudy', 'revenge', 'patriotic', 'northward', 'ambiguous', 'bloody', 'projection', 'ecologically', 'secondary', 'foreman', 'alternatively', 'suspiciously', 'downsize', 'rafter', 'inanimate', 'prejudge', 'high-tech/hi-tech', 'salesperson', 'trainee', 'brewery', 'availability', 'psychology', 'industrialize/industrialise', 'economist', 'overall', 'unpopular', 'cyberspace', 'treaty', 'transcript', 'upright', 'bestow', 'dedication', 'unto', 'export', 'glacis', 'indent', 'probe', 'cuff', 'innocently', 'shortness', 'enthusiastically', 'intransitive', 'impermanent', 'predicative', 'facilities', 'considering', 'admiringly', 'sector', 'devoted', 'leek', 'worktable', 'bumper', 'infringement', 'evidently', 'narrowly', 'cruelly', 'ironically', 'yield', 'firmness', 'mask', 'hourly', 'dynamic', 'legislative', 'billion', 'attached', 'unacceptable', 'swing', 'portion', 'rider', 'rite', 'collector', 'inspirational', 'openly', 'flip', 'negatively', 'scrooge', 'unimaginable', 'hedge', 'peach', 'curl', 'executive', 'lowland', 'owing to', 'crucial', 'investigator', 'eagerly', 'bizarre', 'crossly', 'unthinkably', 'controversy', 'corresponding', 'prehuman', 'informal', 'offend', 'entrepreneur', 'migration', 'layabout', 'detect', 'hesitation', 'contradict', 'homeward', 'heap', 'publication', 'wipe', 'fearsome', 'stereotype', 'ranger', 'dictator', 'dilemma', 'colleague', 'salon', 'steer', 'merge', 'hard drive', 'appreciative', 'picturesque', 'perspiration', 'burdensome', 'amaze', 'crawl', 'domestic', 'frantically', 'leak', 'residence', 'irrational', 'practically', 'gaiety', 'modernize/modernise', 'bellow', 'personnel', 'whereas', 'sonnet', 'predicament', 'relic', 'conferencing', 'decay', 'specialty', 'probable', 'duplicate', 'confine', 'plumber', 'sparkling', 'massacre', 'circuitry', 'enroll/enrol', 'fictional', 'unload', 'flexibly', 'respectably', 'enduring', 'culturally', 'armful', 'assemble', 'community', 'ethic', 'striking', 'influenza', 'doorway', 'humanize/humanise', 'retard', 'humbug', 'flame', 'syllable', 'signify', 'consensus', 'radical', 'cyberpet', 'environmentally friendly', 'flap', 'embrace', 'territory', 'eloquent', 'energy', 'uneasily', 'self-confidence', 'explorer', 'implication', 'needy', 'worrying', 'rear', 'universal', 'patch', 'potentially', 'fascinated', 'humanist', 'precisely', 'housekeeper', 'adopted', 'slang', 'outlive', 'theorist', 'peacefully', 'shipwreck', 'real estate', 'balky', 'unselfish', 'bitterly', 'coherent', 'coastline', 'retailer', 'socialize/socialise', 'equip', 'offender', 'shatter', 'superintendent', 'neglect', 'x-ray', 'mighty', 'winding', 'duplication', 'eyelash', 'botany', 'assessment', 'supervise', 'worship', 'badge', 'modernization/modernisation', 'participation', 'satisfactory', 'unmistakable', 'snowball', 'hairy', 'regulate', 'demanding', 'humiliation', 'supernatural', 'predictor', 'overlook', 'stepmother', 'decoration', 'tumor/tumour', 'delegate', 'slaughter', 'commute', 'yummy', 'undoubtedly', 'germ', 'toxic', 'pawnshop', 'desirable', 'novelty', 'cellist', 'subculture', 'friendly', 'invisible', 'shabby', 'subdivision', 'stitch', 'straightforward', 'thoroughly', 'irritation', 'oval', 'cease', 'moody', 'skilled', 'kneel', 'divided', 'fulfill/fulfil', 'ritual', 'corridor', 'necessity', 'dawn', 'confidently', 'pneumonia', 'cheerfulness', 'completion', 'modification', 'ultimately', 'gloomy', 'perseverance', 'astronomy', 'luxurious', 'pavement', 'stimulate', 'extension', 'dreadful', 'offshore', 'resign', 'everlasting', 'grown-up', 'sustain', 'boiling', 'outlook', 'terrifying', 'irresponsible', 'effectively', 'melody', 'innate', 'gradual', 'nerve', 'hint', 'contradictory', 'unhelpful', 'strictly', 'intrusion', 'rhinoceros', 'seller', 'glare', 'lick', 'slap', 'deliberately', 'database', 'stock market', 'corporal', 'cozy/cosy', 'narrator', 'exaggerate', 'moonlight', 'hospitable', 'infected', 'demolish', 'sage', 'thinker', 'cornerstone', 'adventurous', 'spare', 'backup', 'soviet', 'rubble', 'mausoleum', 'sexism', 'gravitation', 'stopwatch', 'sparrow', 'birdcage', 'legislator', 'consultant', 'retreat', 'interpersonal', 'last-minute', 'outbreak', 'brilliantly', 'disorderly', 'fixed', 'millimeter/millimetre', 'delicately', 'tenderly', 'freak', 'microscopy', 'graphically', 'artery', 'traitor', 'profit', 'unkind', 'prestigious', 'certify', 'compatible', 'improved', 'indifferent', 'distract', 'mango', 'misfortune', 'dormitory', 'posh', 'essayist', 'olympiad', 'perception', 'initiative', 'suspicious', 'marathon', 'laptop', 'subtract', 'eastward', 'stepfather', 'amazingly', 'remarkably', 'recession', 'amongst', 'naughty', 'untrimmed', 'route', 'liberal', 'discredit', 'actively', 'sophisticated', 'tabloid', 'gear', 'woodcarving', 'stool', 'paperless', 'itch', 'faith', 'worldview', 'tortoiseshell', 'fate', 'pentagon', 'employ', 'overestimate', 'drain', 'bearing', 'micrometer/micrometre', 'unaware', 'suppress', 'intersection', 'expressive', 'glamorous/glamourous', 'inadequate', 'mystify', 'tramp', 'statistics', 'significantly', 'hone', 'third person', 'replica', 'unchanged', 'decade', 'separately', 'benevolent', 'unwise', 'blossom', 'contradiction', 'donate', 'adore', 'animate', 'intellectual', 'resume', 'overstep', 'adjustment', 'lease', 'cornet', 'crease', 'appropriately', 'jealously', 'villa', 'inherit', 'glocal', 'tickle', 'forcefully', 'flexibility', 'penguin', 'compassionate', 'wilderness', 'sufficiently', 'interactive', 'tuberculosis', 'panel', 'impolite', 'decent', 'tattoo', 'fabulous', 'skillfully/skilfully', 'disrupt', 'immortal', 'identify', 'astonish', 'prom', 'bartender', 'refinery', 'candidate', 'brother-in-law', 'folly', 'skateboard', 'flexible', 'squad', 'sympathize/sympathise', 'misconception', 'scythe', 'nuisance', 'lousy', 'coincide', 'crater', 'exclamation mark', 'coziness/cosiness', 'refuge', 'endlessly', 'immerse', 'cone', 'dense', 'interpretation', 'hospitality', 'exclusively', 'housemaster', 'micro', 'startle', 'radar', 'shoplifting', 'mediocre', 'multiple', 'western', 'leash', 'inexperienced', 'related', 'tanned', 'fame', 'temptation', 'corporation', 'trader', 'improperly', 'generosity', 'rocket', 'consent', 'stylist', 'petrified', 'well-balanced', 'lifeless', 'italicize/italicise', 'addicted', 'collocation', 'overbook', 'yacht', 'specifically', 'distinguished', 'firstly', 'servant', 'madame', 'clap', 'hazardous', 'elegant', 'partnership', 'rape', 'purposeful', 'taunt', 'mint', 'intent', 'citywide', 'compile', 'overshadow', 'pint', 'ballad', 'superb', 'virtue', 'voter', 'consolation', 'nobility', 'conveniently', 'helicopter', 'oversize', 'solo', 'electrician', 'governmental', 'convention', 'cemetery', 'fleet', 'strive', 'additionally', 'prevail', 'coarse', 'daughter-in-law', 'incur', 'estate', 'pine', 'madness', 'reverence', 'suspend', 'mixture', 'penetrate', 'instantly', 'altitude', 'deem', 'bikini', 'microphone', 'rainstorm', 'abandoned', 'suburb', 'distraction', 'cyberaddict', 'thirdly', 'apostrophe', 'confession', 'stink', 'rhyme', 'ointment', 'cooperation', 'unease', 'paperwork', 'translation', 'alliance', 'nerves', 'cheat', 'unclearly', 'manufacturer', 'prohibit', 'elegantly', 'compensate', 'threaten', 'wreck', 'confront', 'lest', 'growing', 'mistreat', 'rusty', 'hospitalize/hospitalise', 'barbershop', 'hybrid', 'reinforcement', 'lecturer', 'lush', 'hunting', 'motivated', 'dated', 'insufficient', 'offense/offence', 'desolation', 'spreadsheet', 'midst', 'wage', 'electronics', 'relay', 'disgrace', 'factor', 'regime', 'virtually', 'legitimate', 'prance', 'postal', 'blink', 'first language', 'advent', 'vanish', 'ceiling', 'subtitle', 'determiner', 'hijack', 'subjunctive', 'ignorant', 'patriot', 'wolf', 'division', 'successive', 'dash', 'stability', 'genuine', 'wartime', 'minister', 'crab', 'catastrophe', 'mist', 'anchorman', 'moonscape', 'magnify', 'dime', 'comprehensive', 'housewife', 'soaked', 'mogul', 'feedback', 'long-distance', 'downfall', 'leaflet', 'considerably', 'retirement', 'commonwealth', 'amuse', 'learning', 'reaction', 'nourishment', 'impractical', 'nostril', 'culmination', 'utilize/utilise', 'cathedral', 'dispute', 'provision', 'remaining', 'sneer', 'dramatically', 'agony', 'consciousness', 'icon', 'contemporary', 'pandemic', 'environment', 'needless', 'revenue', 'prompt', 'duel', 'rebellious', 'subside', 'short-term', 'mortgage', 'preindustrial', 'erupt', 'catchy', 'involvement', 'audition', 'inference', 'presidency', 'grim', 'sloppy', 'immigrant', 'determined', 'communicative', 'aboriginal', 'haunting', 'impolitely', 'junk food', 'gleam', 'descriptive', 'expectation', 'unrealistic', 'enforce', 'nervously', 'essentially', 'radically', 'recruit', 'health care', 'transparent', 'classification', 'label', 'agonize/agonise', 'exalt', 'admittedly', 'manipulate', 'therapist', 'zoom', 'idolize/idolise', 'fiscal', 'flee', 'skip', 'inspector', 'axis', 'coherence', 'unofficial', 'turbulent', 'cable', 'cheerfully', 'stack', 'unavailable', 'powerfully', 'deforestation', 'footnote', 'undressed', 'testify', 'disapprove', 'drinkable', 'disappearance', 'reference', 'untie', 'functional', 'displeasure', 'coma', 'priceless', 'ineffective', 'extraordinarily', 'cubism', 'tackle', 'outrage', 'prime minister', 'sensible', 'temperate', 'trek', 'certification', 'conversion', 'stride', 'carpool', 'instill/instil', 'rattle', 'darling', 'anguish', 'formerly', 'outsider', 'beak', 'landslide', 'outgrow', 'blurt', 'prominence', 'carnival', 'correspondence', 'ballet', 'knot', 'navigation', 'defy', 'pave', 'injured', 'toenail', 'ruinous', 'dove', 'coconut', 'lifetime', 'beam', 'softness', 'alter', 'frontier', 'thickly', 'feat', 'rightly', 'microcomputer', 'antipollution', 'triangle', 'untalented', 'limb', 'fireplace', 'knowingly', 'outlet', 'racist', 'tricky', 'rejection', 'pallet', 'observer', 'snore', 'endurance', 'subtle', 'variation', 'reckon', 'scarcely', 'spellbound', 'blade', 'damn', 'exceptional', 'gracefulness', 'gang', 'evacuate', 'guideline', 'mournful', 'discharge', 'realization/realisation', 'thigh', 'nightclub', 'tyrant', 'await', 'colonial', 'patiently', 'achievable', 'ageless', 'reorganize/reorganise', 'sparerib', 'equity', 'creepy', 'indignity', 'obligation', 'breakdown', 'heartwarming/heart-warming', 'beagle', 'guerrilla', 'excel', 'planning', 'lawmaker', 'bustling', 'adequately', 'concerning', 'financially', 'chaotic', 'betray', 'worsen', 'elect', 'bustle', 'lobby', 'sanitation', 'scientifically', 'relief', 'substance', 'caravan', 'editorial', 'murmur', 'foul', 'mustache/moustache', 'hook', 'tray', 'gravestone', 'unashamedly', 'bloodstream', 'linger', 'ramp', 'divorce', 'corona', 'stain', 'wrongly', 'trample', 'anticipation', 'evolution', 'densely', 'compute', 'crocodile', 'corporate', 'windshield', 'entertaining', 'salesmanship', 'broaden', 'metric', 'cheeky', 'timeless', 'neither', 'handicraft', 'disposition', 'exaggeration', 'aircraft', 'instruct', 'ornament', 'neurosis', 'witty', 'weaken', 'compress', 'cybercafe/cybercafé', 'urban', 'prescribe', 'cruelty', 'pedantic', 'waist', 'qualification', 'narcissistic', 'accordingly', 'cripple', 'hastily', 'blaze', 'exhibit', 'selfless', 'chew', 'cocktail', 'diversion', 'organizer/organiser', 'queer', 'impulse', 'bothersome', 'distortion', 'united', 'breathtaking', 'vibrate', 'courtesy', 'newscaster', 'charming', 'inflammation', 'fiber/fibre', 'user', 'rehearsal', 'firearm', 'trio', 'ungodly', 'supervisor', 'varied', 'aggressively', 'millionaire', 'widen', 'grain', 'swan', 'greatness', 'overtake', 'segment', 'ambassador', 'giveaway', 'masseur', 'flaw', 'sharply', 'catalyst', 'somewhat', 'navy', 'devote', 'decisive', 'climate change', 'excessive', 'schoolgirl', 'papaya', 'persuasion', 'dissident', 'calmly', 'balanced', 'hardware', 'modest', 'passionately', 'habitant', 'outdistance', 'forthcoming', 'antibacterial', 'goat', 'predicate', 'necessarily', 'evacuation', 'sliver', 'thrilled', 'promptly', 'tutor', 'misleading', 'overlap', 'correspond', 'idiot', 'disconnect', 'excursion', 'quarrel', 'astonishing', 'assurance', 'trophy', 'external', 'capability', 'digest', 'lava', 'geographic', 'shrug', 'shrink', 'donkey', 'inventor', 'overthrow', 'impressionist', 'mentor', 'exhaustion', 'muddy', 'decorative', 'messenger', 'verbal', 'analogy', 'chimney', 'rental', 'explode', 'mover', 'unenthusiastic', 'tack', 'clerical', 'solvent', 'madam', 'thoughtless', 'semi-final', 'incentive', 'point of view', 'redo', 'testimony', 'skillful/skilful', 'ponder', 'literal', 'resonant', 'scent', 'broadcaster', 'trait', 'dreamy', 'father-in-law', 'legislation', 'mathematical', 'timeliness', 'rhythmic', 'creation', 'viewer', 'mimic', 'criticism', 'complexion', 'absentee', 'generalize/generalise', 'verse', 'trial', 'obedient', 'loosely', 'canon', 'anchorperson', 'delicacy', 'alcoholism', 'ignorance', 'prose', 'recommendation', 'boxer', 'reflective', 'drought', 'irresistibly', 'advancement', 'abuse', 'existing', 'versus', 'mutable', 'cooperate', 'preferably', 'grand', 'sanitary', 'southward', 'microorganism', 'cooperative', 'underground', 'trolley', 'herb', 'piracy', 'concrete', 'sexual', 'cowardly', 'valid', 'infringe', 'overcoat', 'numerical', 'warship', 'technical', 'bravery', 'tiresome', 'decomposition', 'adaptable', 'motorbike', 'hail', 'laborer/labourer', 'racism', 'humane', 'vinegar', 'packet', 'silverware', 'lance', 'underestimate', 'paintbrush', 'deer', 'assert', 'pillowcase', 'approximate', 'enhance', 'craftsman', 'exclusion', 'permanence', 'tablecloth', 'alongside', 'priority', 'flawless', 'stimulation', 'heritage', 'unmistakably', 'times', 'nought', 'invitingly', 'version', 'stonework', 'swift', 'tribe', 'privately', 'endorse', 'correspondent', 'breakable', 'soar', 'distressing', 'dominate', 'disillusion', 'legislate', 'rocky', 'vigorous', 'olympia', 'microscopic', 'conventional', 'catering', 'competent', 'reminder', 'imprisonment', 'preventive', 'actual', 'mercy', 'diploma', 'captive', 'globally', 'scooter', 'inhabit', 'dissatisfied', 'imitation', 'implement', 'predictive', 'temporarily', 'tender', 'journalism', 'doubly', 'obey', 'philosophical', 'immortality', 'grasp', 'remains', 'trainer', 'impulsive', 'endeavor/endeavour', 'prick', 'formation', 'pebble', 'reprint', 'seminar', 'lame', 'frugal', 'costume', 'interpreter', 'timing', 'eruption', 'dissatisfaction', 'anchorage', 'seldom', 'transmit', 'alphabetical', 'soybean', 'attribute', 'judo', 'rack', 'critically', 'developing', 'landing', 'transmission', 'cube', 'momentary', 'utterly', 'refugee', 'mate', 'teaspoon', 'battlefield', 'explosive', 'unbeatable', 'admirable', 'starve', 'weed', 'soak', 'confess', 'destiny', 'decorate', 'gook', 'acid', 'dowry', 'barren', 'booklet', 'respiration', 'contaminate', 'strand', 'mother-in-law', 'trailer', 'unsatisfactory', 'revolve', 'reform', 'jury', 'furiously', 'possessed', 'cardboard', 'neat', 'playmate', 'uncharacteristic', 'lucrative', 'uneven', 'finely', 'chip', 'newsworthy', 'playwright', 'benevolence', 'recreation', 'dread', 'amplify', 'cocoa', 'hilarious', 'stammer', 'inborn', 'eyelid', 'seasonal', 'equation', 'sincere', 'absurd', 'troop', 'province', 'supplement', 'unimaginably', 'surveillance', 'acute', 'tuition', 'stingy', 'permeate', 'detach', 'environmentally', 'peep', 'affordable', 'breakup', 'wondrous', 'banker', 'federal', 'linen', 'repression', 'scheme', 'glow', 'wheat', 'illustration', 'attentively', 'desktop', 'diphtheria', 'ethnic', 'untreated', 'presidential', 'originally', 'glee', 'mortal', 'addiction', 'exclaim', 'evaluate', 'unattractive', 'publicize/publicise', 'dropout', 'gorge', 'known', 'denim', 'vanity', 'revolution', 'generalization/generalisation', 'polar bear', 'identification', 'polish', 'legislature', 'gross', 'ling', 'thrill', 'consult', 'unfashionable', 'coalition', 'sniff', 'instinct', 'solidity', 'dishonesty', 'rush hour', 'legendary', 'tiredness', 'genre', 'presenter', 'fluency', 'fantastically', 'morality', 'shooting', 'listener', 'aristocracy', 'stiff', 'concession', 'tame', 'petty', 'villager', 'serene', 'publicity', 'carbon footprint', 'tolerate', 'constant', 'helper', 'wordlessly', 'simplification', 'mistrustful', 'conception', 'coward', 'discipline', 'bewilder', 'second person', 'arouse', 'bombing', 'investment', 'monastery', 'indescribable', 'human rights', 'conjunction', 'pursuit', 'setback', 'fraud', 'patriotism', 'prawn', 'indebted', 'undertake', 'grief', 'walnut', 'institution', 'loneliness', 'unsuccessful', 'quantify', 'ample', 'fierce', 'gently', 'ozone', 'attainable', 'outing', 'first person', 'proposed', 'noble', 'flesh', 'inverted commas', 'terrace', 'carbon', 'falter', 'foolishly', 'affectionate', 'mortar', 'legend', 'shopkeeper', 'senator', 'affectionately', 'artificially', 'catalog/catalogue', 'symbolic', 'revive', 'cyclist', 'categorization/categorisation', 'nomad', 'eligible', 'missile', 'zebra', 'slash', 'consistent', 'holocaust', 'procedure', 'karaoke', 'bind', 'astonishment', 'resolution', 'attentive', 'mock', 'antiaircraft', 'sulfur/sulphur', 'electronically', 'component', 'spark', 'ridge', 'belongings', 'prime', 'supervision', 'toil', 'independently', 'humiliating', 'doorkeeper', 'compensation', 'passionate', 'exactness', 'clumsy', 'unlimited', 'guts', 'de facto', 'hyperbole', 'prospect', 'eternally', 'scar', 'emphatically', 'quarrelsome', 'fume', 'accumulate', 'burial', 'unstoppable', 'proven', 'span', 'console', 'youthful', 'funk', 'quarantine', 'honorable/honourable', 'pancake', 'flax', 'bully', 'scarlet', 'scribble', 'thickness', 'refute', 'refrain', 'netsurfer/net surfer', 'goodwill', 'presumably', 'convert', 'son-in-law', 'penalty', 'theme', 'salary', 'corrupt', 'found', 'intrude', 'reasonably', 'intruder', 'mobility', 'unnatural', 'torture', 'navigate', 'chunk', 'unsuitable', 'optimism', 'divert', 'well-paid', 'aspire', 'correctness', 'compulsory', 'fascination', 'weakly', 'evil', 'trusty', 'earnings', 'creatively', 'debit', 'proximity', 'unwilling', 'bulimia', 'tremendously', 'motion', 'reprove', 'competence', 'hollow', 'breezy', 'replacement', 'inflate', 'faulty', 'saving/savings', 'steely', 'kangaroo', 'attendance', 'thermometer', 'semicolon', 'thrilling', 'prefect', 'manufacturing', 'adoption', 'handicap', 'pirate', 'translator', 'aircrew', 'inflammatory', 'deck']
|
| 12 |
+
|
| 13 |
+
CEFR_C1 = ['apt to', 'uniformity', 'waterfront', 'folktale', 'irately', 'socialization', 'subordinate', 'withdrawn', 'quirky', 'elastic', 'niche', 'itinerary', 'immensely', 'psychiatric', 'estimation', 'heroine', 'ethically', 'diverge', 'inexplicable', 'ethical', 'bulk', 'possessor', 'vicious', 'maternal', 'bearded', 'suffice', 'aqua', 'alienate', 'voluntarily', 'monotony', 'anticlimactic', 'candid', 'upheaval', 'prowl', 'roundup', 'thereby', 'crave', 'tactfully', 'midwife', 'unconvincing', 'abound', 'lethargy', 'stylistically', 'dissection', 'dominantly', 'impersonation', 'sponsorship', 'inclusively', 'unmanned', 'smug', 'resilience', 'domestication', 'intuit', 'sacrifice', 'ordinarily', 'voracious', 'adversary', 'sprawl', 'sentiment', 'washtub', 'crumb', 'macabre', 'conditional', 'tract', 'panorama', 'exhilarating', 'vertical', 'peacock', 'placidly', 'physicality', 'homely', 'antiquity', 'elitist', 'eccentrically', 'riveting', 'somberly/sombrely', 'conditionally', 'refreshingly', 'renown', 'induct', 'suitor', 'detrimentally', 'conform', 'raunchily', 'complementary', 'hypocritically', 'explanatory', 'unscathing', 'connoisseur', 'fiddler', 'insubordinately', 'bequeath', 'mechanism', 'scrutinize/scrutinise', 'adjoining', 'occupational', 'impersonate', 'hence', 'innovate', 'venerate', 'victorious', 'naivety', 'likelihood', 'disquieting', 'verandah', 'self-worth', 'instinctive', 'mingle', 'elevate', 'evoke', 'stamina', 'wickedly', 'toughness', 'tinker', 'trampoline', 'gushing', 'overseer', 'craving', 'docilely', 'salvation', 'magnetically', 'limitless', 'brainwashing', 'lethargically', 'beguiling', 'victoriously', 'tranquil', 'mark-up', 'linear', 'vegetation', 'somersault', 'bureaucratically', 'screenwriter', 'prop', 'spontaneously', 'dismay', 'altruism', 'pleasurable', 'overalls', 'instinctively', 'aimlessly', 'cynically', 'persecution', 'panoramic', 'heighten', 'constraint', 'compulsion', 'compel', 'premises', 'isolate', 'bubbly', 'digitalization/digitalisation', 'dispense', 'tedious', 'detrimental', 'hostess', 'tangled', 'meaningless', 'psychiatrist', 'intuitive', 'unconvincingly', 'remorsefully', 'seedling', 'indefinitely', 'churn', 'unwaveringly', 'cynicism', 'disillusionment', 'revealing', 'whilst', 'tantrum', 'saloon', 'manicure', 'neutralize/neutralise', 'restoration', 'privileged', 'detritus', 'forensic', 'extortion', 'aquarium', 'grapefruit', 'staple', 'rendering', 'recognizably/recognisably', 'chauffer', 'credible', 'tangle', 'portrayal', 'circulate', 'paternal', 'refurbishment', 'undaunted', 'domesticate', 'portable', 'depiction', 'robustly', 'acoustic', 'adjoin', 'insecure', 'damply', 'bulky', 'chiselled', 'prohibitively', 'manipulation', 'reenact', 'plausibly', 'reassurance', 'fiendish', 'detriment', 'gushingly', 'collaborate', 'torturous', 'harassment', 'somber/sombre', 'formality', 'uneventful', 'naturalness', 'individuality', 'retention', 'exotically', 'grounds', 'unmanageable', 'wryly', 'lust', 'trauma', 'realism', 'canoe', 'norms', 'crudely', 'stuffy', 'persecute', 'discourse', 'flask', 'aimless', 'oddity', 'scoff', 'quip', 'invoice', 'believably', 'scornful', 'fiddly', 'tranquility', 'sentimentally', 'clench', 'peasant', 'phenomenal', 'gypsy', 'unsustainably', 'pounce', 'battered', 'honk', 'salivate', 'unison', 'buffer', 'synopsis', 'enormously', 'conserve', 'remorselessly', 'wrench', 'versatility', 'remedial', 'formulate', 'sauna', 'trying', 'inexcusable', 'impoverished', 'degrade', 'revere', 'bribery', 'personalize/personalise', 'chisel', 'fanatic', 'inherent', 'faction', 'haggle', 'robust', 'headstrong', 'dissect', 'glean', 'startling', 'divergence', 'baldly', 'diversity', 'latent', 'profound', 'beguilingly', 'integral', 'elongate', 'wary', 'descent', 'demise', 'recess', 'debut', 'ruthlessness', 'convoluted', 'abstraction', 'commercially', 'prolific', 'detestable', 'transatlantic', 'commend', 'blithely', 'gusto', 'rediscover', 'jersey', 'provocative', 'clan', 'compromised', 'elaborate', 'oblige', 'appalling', 'collaborator', 'flashback', 'favorably/favourably', 'scorn', 'eclectically', 'stately', 'supple', 'slackly', 'subtly', 'munch', 'acre', 'inherently', 'maternalistic', 'synthetic', 'dutifully', 'cavity', 'flattery', 'self-conscious', 'specimen', 'showy', 'gadget', 'comprise', 'interjection', 'apprenticeship', 'ferocious', 'amateur', 'radiator', 'twitch', 'agreeable', 'peninsula', 'cynic', 'verdict', 'exodus', 'ludicrous', 'desertion', 'intellect', 'brainwash', 'retrace', 'sarcasm', 'dramatist', 'remorse', 'gruesomely', 'render', 'vitality', 'strapping', 'sketchbook', 'impishly', 'magnetic', 'misplace', 'linearly', 'neutralization/neutralisation', 'staggering', 'marginal', 'precision', 'ingenious', 'cleverness', 'stuffiness', 'acutely', 'synthetically', 'geological', 'mutually', 'ecstatically', 'provoke', 'stretching', 'speculative', 'oversee', 'realist', 'stuffily', 'fiddle', 'uninspiring', 'unsustainable', 'speculation', 'alignment', 'ghostly', 'sustainably', 'principally', 'viciously', 'discard', 'stoke', 'recessive', 'exploitation', 'suburbia', 'mountaineer', 'suds', 'irony', 'prophet', 'legacy', 'collaborative', 'testament', 'credential', 'domination', 'succulent', 'consultancy', 'profoundly', 'superficial', 'bliss', 'insightfully', 'stray', 'appall/appal', 'readable', 'temperament', 'obscurely', 'restorative', 'spartan', 'magnetism', 'appallingly', 'screenplay', 'hostility', 'contractor', 'pathos', 'carpenter', 'resent', 'mythological', 'enterprising', 'acoustics', 'symptomatic', 'plummet', 'advisory', 'fiend', 'dismal', 'exhilarate', 'wrangle', 'minutiae', 'timid', 'fusion', 'acidic', 'elusive', 'gape', 'demographic', 'bypass', 'organizational', 'unconventional', 'perceptive', 'paternalistic', 'aesthetic', 'elaboration', 'suspension', 'clammy', 'ludicrously', 'unenviably', 'insensitively', 'ingenuity', 'alleviate', 'anthropology', 'unplug', 'recharge', 'frostily', 'malfunction', 'inheritance', 'ceaseless', 'orchard', 'impersonator', 'incision', 'marginally', 'quarry', 'circulation', 'glum', 'fissure', 'tact', 'stimuli', 'nocturnal', 'quest', 'unmusically', 'bribe', 'expedite', 'implicate', 'gamely', 'temperamental', 'paywall', 'adrenaline', 'bulk up', 'conviction', 'engrossing', 'calorie', 'embark', 'domestically', 'complimentary', 'dither', 'fabulously', 'telltale', 'otter', 'impersonally', 'closeness', 'climactic', 'gutsy', 'unsettle', 'sentient', 'notwithstanding', 'indefinite', 'inclusion', 'rapport', 'pinnacle', 'impediment', 'crude', 'antiquated', 'pendulum', 'preach', 'charter', 'prophetic', 'eclectic', 'monotonous', 'pendulous', 'elevation', 'eccentric', 'fragmentation', 'fiendishly', 'prohibitive', 'turmoil', 'anthropologist', 'flourish', 'impede', 'hypocritical', 'immense', 'coverage', 'silhouette', 'ploy', 'rumble', 'premier', 'numb', 'naively', 'wicked', 'plaque', 'blankness', 'rarity', 'appliance', 'drawback', 'coupled', 'remorseless', 'speculate', 'fanciful', 'gruesome', 'glumly', 'congregate', 'exuberantly', 'clutch', 'unwavering', 'plaster', 'align', 'exhilaration', 'demographically', 'sentimental', 'comply', 'voraciously', 'reassure', 'impish', 'repertory', 'chaotically', 'expire', 'compliant', 'dynamically', 'agonize', 'billiards', 'chill', 'commendation', 'tautly', 'sarcastically', 'swap', 'conscientiously', 'buzzer', 'superficially', 'conscientious', 'astray', 'stiffen', 'succession', 'injustice', 'hostile', 'shortlist', 'unoccupied', 'raunchy', 'heifer', 'isolated', 'tediously', 'antics', 'transit', 'docile', 'profess', 'bouquet', 'psyche', 'repellant', 'scathing', 'altruistic', 'wizened', 'degrading', 'dominant', 'negligible', 'salvage', 'carrier', 'startlingly', 'induction', 'outset', 'shambles', 'explosively', 'reluctantly', 'offspring', 'resilient', 'taut', 'credibility', 'characterization/characterisation', 'remotely', 'ornamental', 'effortless', 'rustle', 'divergent', 'parasite', 'occupant', 'incisor', 'geologically', 'extravagance', 'suburban', 'beforehand', 'salve', 'plausible', 'citation', 'ferociously', 'hypocrisy', 'stylistic', 'benignly', 'negligently', 'unassisted', 'farce', 'immortalize', 'rudimentary', 'refurbish', 'exuberant', 'chilly', 'plunge', 'darkroom', 'tailor', 'daydream', 'fossil', 'plausibility', 'terrain', 'hibernation', 'hibernate', 'flatter', 'stressfully', 'avid', 'pertain to', 'sanctity', 'conformity', 'clinical', 'collaboration', 'violet', 'irate', 'persuasively', 'insubordinate', 'fuse', 'slob', 'climax', 'neglectful', 'poetess', 'frosty', 'concerto', 'dutiful', 'hack', 'crumble', 'squander', 'duration', 'voluntary', 'perpetuate', 'benign', 'demography', 'succulently', 'notable', 'unsettling', 'conjure', 'strap', 'tycoon', 'settee', 'prolifically', 'credibly', 'zany', 'sedentary', 'fatality', 'sedate', 'obscure', 'simplicity', 'staccato', 'tactful', 'buffoon', 'cloak', 'meaninglessly', 'portray', 'customary', 'transfix', 'methodical', 'goose', 'gruelling', 'durable', 'sprout', 'repel', 'recognizable/recognisable', 'scornfully', 'reminisce', 'fiercely', 'amplification', 'candidly', 'dislodge', 'creamy', 'genome', 'insightful', 'unenviable', 'snail', 'degradation', 'fraction', 'dispensary', 'sequel', 'clutter', 'fixture', 'mythology', 'spontaneous', 'verve', 'firewall', 'pedicure', 'engross', 'bureaucratic', 'batter', 'specification', 'cite', 'compliance', 'veneration', 'overdraft', 'blister', 'juvenile', 'placid', 'depict', 'inexcusably', 'gourmet', 'nondescript', 'enviable', 'naive', 'exuberance', 'lavatory', 'intuitively', 'untangled', 'preacher', 'corps', 'dismally', 'impersonal', 'structural', 'versatile', 'fanatically', 'interject', 'unmusical', 'ventilation', 'rotate', 'negligent', 'facilitation', 'allege', 'temperamentally', 'primitively', 'harass', 'apprehensive', 'amply', 'disdain', 'gush', 'insensitive', 'traumatic', 'facial', 'unreliably', 'plumbing', 'compliantly', 'ventilate', 'motley', 'justly', 'respite', 'lapse', 'deceit', 'slack', 'classy', 'spacious', 'substantially', 'jurisdiction', 'flicker', 'elude', 'utensil', 'inclusive', 'amid', 'wares', 'structurally', 'inexplicably', 'premise', 'snooze', 'assertion', 'gnaw', 'liaison', 'lethargic', 'hypocrite', 'pertinent', 'relapse', 'wooded', 'sarcastic', 'rudiments', 'refreshing', 'physique', 'provocatively', 'scenario', 'popularization', 'torment', 'untangle', 'detest', 'pioneer', 'resonate', 'confinement', 'muscular', 'forfeit', 'atmospheric', 'artistically', 'ruthless', 'cleanly', 'convict', 'accustomed', 'exterior', 'smugly', 'claustrophobic', 'blockbuster', 'sustainable', 'cessation', 'perceptively', 'provisions', 'animatedly', 'demolition', 'contested', 'aesthetically', 'monotonously', 'cost-effective', 'exert', 'removed', 'intuition', 'criteria', 'personalization/personalisation', 'claustrophobia', 'prospective', 'reminiscent', 'cynical', 'endorsement', 'turn to ', 'impervious', 'anchored', 'insecurely', 'confide', 'reap', 'continuity', 'tastebud', 'knowledgeable', 'professionalism']
|
| 14 |
+
CEFR_C2 = ['remonstrate', 'unexceptional', 'nonchalant', 'conciliation', 'pyre', 'liaise', 'extant', 'inexorable', 'brimstone', 'squirm', 'elated', 'methodological', 'ignominiously', 'virginal', 'computerize/computerise', 'autopilot', 'indolent', 'callous', 'side-step', 'paradoxical', 'diffuse', 'acrobatic', 'counterproductively', 'provocation', 'remittance', 'pervasive', 'crippling', 'impressionistic', 'ephemera', 'romp', 'inchoate', 'aghast', 'interrogate', 'depressive', 'catastrophically', 'overlay', 'chrysalis', 'eminence', 'athleticism', 'haughtily', 'materialize/materialise', 'tacitly', 'euphemism', 'ravish', 'brim', 'adept', 'euphoria', 'revile', 'rift', 'succumb', 'antithetical', 'ruminant', 'lamentation', 'edify', 'minefield', 'clinch', 'exalted', 'unnerve', 'kinetically', 'incongruous', 'philistine', 'unexceptionally', 'adhere', 'seedy', 'drudgery', 'antediluvian', 'combustion', 'pressurization', 'hapless', 'hermetic', 'standing', 'stoicism', 'philanthropy', 'cordon', 'acrobatics', 'venomously', 'euphorically', 'consignment', 'opulent', 'commodity', 'porten', 'preconception', 'flagging', 'eke out', 'thicket', 'scavenger', 'solitary', 'interlude', 'allot', 'maverick', 'romper', 'fabulation', 'impeccable', 'vortex', 'posturing', 'bereft', 'ordain', 'vandalize', 'meteorology', 'distill', 'moribund', 'topography', 'tantalize/tantalise', 'palate', 'gravitate', 'recuperation', 'menace', 'undemonstratively', 'laud ', 'elephantine', 'mediation', 'whir', 'impresario', 'regulatory', 'repose', 'perverse', 'equilibrium', 'parochial', 'grievance', 'requisition', 'accrue', 'abhor', 'promotable', 'inexorably', 'wheeze', 'ecotourism', 'plutocrat', 'totemic', 'improvisational', 'proliferation', 'intersperse', 'integration', 'paradoxically', 'intermediary', 'deprecatingly', 'feign', 'preeminent', 'circumnavigation', 'monastic', 'qualm', 'fabricate', 'subsistence', 'physiologically', 'spuriously', 'misdemeanor/misdemanour', 'proprietorial', 'ephemerality', 'ingrate', 'resoundingly', 'redundancy', 'evokingly', 'emolument', 'locomotive', 'farcical', 'contractual', 'colloquium', 'rapture', 'disperse', 'squint', 'mien', 'forfeiture', 'meticulously', 'provincial', 'idiomatically', 'residue', 'annex', 'ad lib', 'axiom', 'portraiture', 'subsidy', 'undemonstrative', 'superfluous', 'dole out', 'corpse', 'cogently', 'prone', 'spurious', 'moor', 'equable', 'vermin', 'abject', 'taciturn', 'derision', 'delicatessen', 'homestead', 'preoccupation', 'deviant', 'cocoon', 'collide', 'banishment', 'lament', 'menial', 'belligerence', 'averse', 'reissue', 'indignant', 'nonchalantly', 'resounding', 'denunciation', 'presuppose', 'paramount', 'tantalizingly/tantalisingly', 'minster', 'disparage', 'euphoric', 'elation', 'impressionistically', 'substantiate', 'aberration', 'contractually', 'resuscitation', 'remit', 'resound', 'frolic', 'marketability', 'figuratively', 'quiver', 'nostalgia', 'surreptitious', 'mesmeric', 'reverberation', 'ruminate', 'wordage', 'innuendo', 'rife', 'tranquilize/tranquilise', 'presentational', 'resuscitate', 'disparaging', 'milieu', 'luridly', 'anonymity', 'indiscrete', 'nascent', 'locomotion', 'correlate', 'ironic', 'derelict', 'sulphur', 'prolong', 'protrude', 'hysteria', 'abhorrently', 'edification', 'smattering', 'euphemistically', 'ostensibly', 'pedestal', 'conjecture', 'stultifyingly', 'gauche', 'modernism', 'distillation', 'automate', 'patronize/patronise', 'turret', 'indulgently', 'tactic', 'hypothetical', 'proportionally', 'prescriptive', 'confrontational', 'singe', 'laryngitis', 'ferocity', 'physiological', 'spontaneity', 'buffoonery', 'concurrence', 'behest', 'fruition', 'diminish', 'stratosphere', 'vocational', 'cinematography', 'encompass', 'topple', 'utilize', 'leavening', 'blandishment', 'insular', 'suffuse', 'tacit', 'perch', 'retrospective', 'wholly', 'octogenarian', 'blurb', 'all-encompassing', 'artifact', 'opulence', 'snippet', 'reverb', 'mottled', 'hitherto', 'malleable', 'rapturous', 'brandish', 'intrinsic', 'demystify', 'curate', 'beneficiary', 'crony', 'rebuke', 'confabulation', 'forlorn', 'formidably', 'mercenary', 'glory in', 'rhetoric', 'lurid', 'arcanely', 'disdainful', 'magnanimity', 'sandwiched', 'patchily', 'self-aggrandizement/self-aggrandisement', 'knack', 'hysterical', 'punitively', 'adulterate', 'curator', 'adornment', 'metalled', 'treacherous', 'dissension', 'constrained', 'embed', 'surrealist', 'maliciously', 'mercantile', 'lassitude', 'menacing', 'tactical', 'reproach', 'salivary', 'abundantly', 'superfluously', 'hysterically', 'meticulous', 'vestigial', 'chronologically', 'aptitude', 'exude', 'undiminishing', 'dubiously', 'flatout', 'indiscretion', 'recourse', 'muscle-bound', 'revulsion', 'splinter', 'depraved', 'practitioner', 'venomous', 'gravitational', 'opulently', 'sulphuric', 'correlation', 'ostensible', 'confrontationally', 'predilection', 'denotation', 'echolocation', 'subsidize', 'affectation', 'soliloquize', 'pressurize', 'opaque', 'colloquial', 'unsurpassed', 'nigh', 'edifice', 'irrevocable', 'menacingly', 'calorific', 'ad-lib', 'gambit', 'palatable', 'cogent', 'cinematographer', 'recumbent', 'plumage', 'remorseful', 'fabled', 'ruminative', 'presupposition', 'perversity', 'infallibly', 'utilization/utilisation', 'recluse', 'compellingly', 'testimonial', 'spatially', 'rickety', 'prurient', 'patchy', 'avian', 'deprecate', 'subsist', 'outmoded', 'munificence', 'zoologist', 'proverbial', 'calamitously', 'acreage', 'jargon', 'apprehend', 'recuperative', 'painstaking', 'exorcist', 'veritable', 'fallacy', 'allotment', 'idiosyncratic', 'hotelier', 'eccentricity', 'puritanical', 'etching', 'arduous', 'denote', 'genomic', 'materialism', 'coax', 'artifice', 'nostalgic', 'burgeoning', 'wrack', 'commensurate', 'hindsight', 'confection', 'euphemistic', 'wallow', 'myriad', 'opacity', 'hitch', 'axiomatic', 'wordsmith', 'malicious', 'allude', 'kinetic', 'leaven', 'unequivocally', 'unmediated', 'melancholy', 'hoist', 'belligerent', 'topographically', 'ingratiating', 'materialization/materialisation', 'fabrication', 'patronizing/patronising', 'hamper', 'verisimilitude', 'secular', 'taciturnity', 'wistfully', 'encumber', 'derisive', 'echo', 'spire', 'anarchic', 'histrionic', 'vandal', 'amorphously', 'parasitic', 'treacherously', 'hauntingly', 'tetchy', 'cognitive', 'deaden', 'dampen', 'wane', 'verily', 'arcane', 'additive', 'zoology', 'calamity', 'truism', 'besiege', 'buzzard', 'punitive', 'surreptitiously', 'ethos', 'synthesis', 'stabilize/stabilise', 'shamble', 'imperceptibly', 'antidote', 'disparagingly', 'unfeasible', 'articulation', 'dribble', 'vicinity', 'preservationist', 'hermit', 'consign', 'tenacious', 'infallible', 'voracity', 'legion', 'assimilate', 'electrode', 'rebound', 'histrionically', 'procedural', 'colloquially', 'elasticity', 'callously', 'residual', 'unadulterated', 'infernal', 'spatial', 'shimmer', 'conduit', 'blubbery', 'ingenuously', 'informant', 'disparagement', 'qualitative', 'swivel', 'commode', 'indolently', 'reclusive', 'magnate', 'lyrically', 'indulgence', 'slab', 'economize/economise', 'blight', 'fluctuation', 'stringent', 'disseminate', 'derisively', 'dissemble', 'reconcile', 'brouhaha', 'unobtrusively', 'deterrent', 'loiter', 'proscription', 'enabler', 'revelatory', 'choreography', 'prissy', 'malleability', 'proprietary', 'embody', 'babble', 'hypothesis', 'unravel', 'recuperate', 'gravitationally', 'tenaciously', 'laudatory', 'accountability', 'irrelevancy', 'glitzy', 'cosmic', 'vestigially', 'solitude', 'headmistress', 'habitation', 'solitariness', 'materialistic', 'echolocate', 'contagion', 'rumination', 'armory/armoury', 'happenstance', 'capacious', 'bereftly', 'telescopically', 'maggot', 'tenacity', 'connote', 'reverberate', 'summarily', 'authenticity', 'bout', 'melodiously', 'ardor/ardour', 'choreographical', 'surrealism', 'acrobatically', 'ponderously', 'attributive', 'bogus', 'flighty', 'incipient', 'unsuspected', 'stipulate', 'adhesion', 'unexcused', 'mangle', 'maelstrom', 'herald', 'emulsification', 'interrogative', 'slither', 'vindicate', 'gout', 'deviantly', 'enthuse', 'agility', 'rapturously', 'declination', 'stultify', 'interrogatively', 'dispersal', 'carbonize/carbonise', 'cloistered', 'flightiness', 'flit', 'unencumbered', 'connotation', 'midwifery', 'hypothesize', 'contraption', 'remonstrance', 'infallibility', 'brink', 'pervasiveness', 'chronology', 'torrential', 'timidity', 'angst', 'hull', 'snobbery', 'posit', 'promenade', 'encumbrance', 'sterility', 'vindication', 'consternation', 'mutinously', 'depravity', 'postural', 'protrusion', 'redundantly', 'shoal', 'philanthropically', 'jarring', 'scholarly', 'huddle', 'philanthropic', 'scroll', 'apostle', 'transmitter', 'impeccably', 'fractious', 'rhetorically', 'archetype', 'emulation', 'enduringly', 'autonomous', 'notoriously', 'haughty', 'ingratiatingly', 'retrospection', 'magnanimous', 'anticlimactically', 'ingratiate', 'meteorological', 'collider', 'allegorical', 'breadwinner', 'treachery', 'gale', 'paralyzingly/paralysingly', 'astringent', 'caffeinate', 'calamitous', 'unobtrusiveness', 'haphazardly', 'interlocking', 'archival', 'unobtrusive', 'interweave', 'facsimile', 'engender', 'blubber', 'teem', 'syllabic', 'denounce', 'totter', 'confabulate', 'secularly', 'precocious', 'proscribe', 'sheaf', 'magpie', 'circumnavigate', 'exaltedly', 'parameter', 'philanthropist', 'demystification', 'austerely', 'austere', 'inscrutable', 'decentralize/decentralise', 'crystallization/crystallisation', 'unpromising', 'unequivocal', 'prostrate', 'topographical', 'proximal', 'angularity', 'bona fide', 'ephemeral', 'condensation', 'tactically', 'austerity', 'mariner', 'archetypal', 'domesticity', 'rhetorical', 'detract', 'allusion', 'ornate', 'inexorability', 'exorcism', 'ingenuous', 'precociously', 'cultish', 'contemptuously', 'relegate', 'plume', 'ignominious', 'buccaneer', 'corpus', 'unfeasibly', 'deficiently', 'etch', 'amorphous', 'conglomerate', 'helm', 'mediocrity', 'attrition', 'hinder', 'drabness', 'egalitarian', 'floodgates', 'nostalgically', 'anthropological', 'wistful', 'incumbent', 'methodology', 'selectivity', 'mutinous', 'verbatim', 'fluctuate', 'anthropologically', 'patronizingly/patronisingly', 'unnervingly', 'commensurately', 'proportional', 'angular', 'instantaneously', 'prise', 'cloister', 'prurience', 'inducement', 'inchoately', 'condone', 'autonomously', 'dwarfism', 'lyrical', 'vis-a-vis', 'whirlwind', 'oblique', 'haphazard', 'squirmy', 'counterproductive', 'stipulation', 'contemptuous', 'bauble', 'idiomatic', 'meager/meagre', 'fete', 'uncanny', 'mediate', 'profitability', 'cosset', 'proposition', 'virtuosity', 'methodologically', 'notoriety', 'indignantly', 'deficient', 'virtuoso', 'arduously', 'magnanimously', 'individualism', 'divination', 'skew', 'extracurricular', 'brood', 'cull', 'floe', 'lyricist', 'mull over', 'hindrance', 'utilitarian', 'incongruously', 'solicit', 'seismic', 'embodiment', 'glitz', 'recitation', 'emulsify', 'solipsism', 'wayward', 'diligently', 'angsty', 'fabulate', 'telescopic', 'indolence', 'inartistic', 'intrinsically', 'conciliatory', 'unharried', 'abhorrent', 'mesh', 'incise', 'neophyte', 'mystique', 'epoxy', 'instantaneous', 'trifle', 'attuned', 'reconciliation', 'emulate', 'polysyllabic', 'emulsifier', 'imposition', 'centralized/centralised', 'counsel', 'banish', 'interrogation', 'allegory', 'idiosyncrasy', 'melodious', 'anthology', 'assimilation', 'genomically', 'agile', 'posterity', 'imperceptible', 'dissemination', 'antithesis', 'merit', 'baroque', 'primeval', 'incongruity', 'blitz', 'stringently']
|
| 15 |
+
|
| 16 |
+
MED_A = ['patient', 'neck', 'cough', 'healthy', 'foot', 'hospital', 'ambulance', 'back', 'nurse', 'hand', 'sleep', 'family', 'finger', 'doctor', 'arm', 'cut', 'fever', 'leg', 'headache', 'clinic', 'body', 'head', 'toothache', 'face', 'pill', 'medicine', 'cold', 'eye', 'rest', 'pain', 'heart', 'ear', 'mouth', 'nose']
|
| 17 |
+
MED_B = ['cholesterol', 'prescription', 'dizzy', 'recovery', 'depression', 'bandage', 'throat', 'vitamins', 'rash', 'fracture', 'chemist', 'infection', 'glucose', 'sneeze', 'ache', 'fatigue', 'dosage', 'dull', 'prevention', 'injection', 'nutrient', 'antibiotics', 'sore', 'wound', 'bacteria', 'emergency', 'surgeon', 'bruise', 'vaccination', 'heart disease', 'referral', 'treatment', 'diagnosis', 'allergy', 'diabetes', 'disease', 'cough syrup', 'asthma', 'surgery', 'consultation', 'therapy', 'symptom', 'pharmacy']
|
| 18 |
+
MED_C = ['immunodeficiency', 'hyperglycemia', 'hematology', 'metastasis', 'psychosomatic', 'ophthalmology', 'anesthesia', 'constipation', 'rheumatology', 'epidemiology', 'echocardiogram', 'dermatologist', 'electrocardiogram', 'psychiatry', 'endocrinology', 'pharmacodynamics', 'intravenous', 'stethoscope', 'hemorrhage', 'arrhythmia', 'prophylaxis', 'tomography', 'pathophysiology', 'hypertension', 'iatrogenic', 'pharmacokinetics', 'gastroenterology', 'nephrology', 'rhinoplasty', 'comorbidity', 'immunization', 'hypotension']
|
patientsim/utils/__init__.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import logging.config
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
LOGGING_NAME = "PatientSim"
|
| 6 |
+
VERBOSE = True
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def set_logging(name=LOGGING_NAME, verbose=True):
|
| 10 |
+
"""Sets up logging for the given name."""
|
| 11 |
+
rank = int(os.getenv('RANK', -1))
|
| 12 |
+
level = logging.INFO if verbose and rank in {-1, 0} else logging.ERROR
|
| 13 |
+
|
| 14 |
+
class ColorFormatter(logging.Formatter):
|
| 15 |
+
def format(self, record):
|
| 16 |
+
if record.levelname == "ERROR":
|
| 17 |
+
record.msg = colorstr("red", record.msg)
|
| 18 |
+
elif record.levelname == "WARNING":
|
| 19 |
+
record.msg = colorstr("yellow", record.msg)
|
| 20 |
+
elif record.levelname == "DEBUG":
|
| 21 |
+
record.msg = colorstr("blue", record.msg)
|
| 22 |
+
return super().format(record)
|
| 23 |
+
|
| 24 |
+
logging.config.dictConfig({
|
| 25 |
+
'version': 1,
|
| 26 |
+
'disable_existing_loggers': False,
|
| 27 |
+
'formatters': {
|
| 28 |
+
name: {
|
| 29 |
+
'()': ColorFormatter,
|
| 30 |
+
'format': '[%(asctime)s] [%(levelname)s] [%(name)s] %(message)s'
|
| 31 |
+
}
|
| 32 |
+
},
|
| 33 |
+
'handlers': {
|
| 34 |
+
name: {
|
| 35 |
+
'class': 'logging.StreamHandler',
|
| 36 |
+
'formatter': name,
|
| 37 |
+
'level': level
|
| 38 |
+
}
|
| 39 |
+
},
|
| 40 |
+
'loggers': {
|
| 41 |
+
name: {
|
| 42 |
+
'level': level,
|
| 43 |
+
'handlers': [name],
|
| 44 |
+
'propagate': False
|
| 45 |
+
}
|
| 46 |
+
}
|
| 47 |
+
})
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
set_logging(LOGGING_NAME, verbose=VERBOSE)
|
| 51 |
+
LOGGER = logging.getLogger(LOGGING_NAME)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def colorstr(*input):
|
| 55 |
+
*args, string = input if len(input) > 1 else ('blue', 'bold', input[0])
|
| 56 |
+
colors = {
|
| 57 |
+
'black': '\033[30m',
|
| 58 |
+
'red': '\033[31m',
|
| 59 |
+
'green': '\033[32m',
|
| 60 |
+
'yellow': '\033[33m',
|
| 61 |
+
'blue': '\033[34m',
|
| 62 |
+
'magenta': '\033[35m',
|
| 63 |
+
'cyan': '\033[36m',
|
| 64 |
+
'white': '\033[37m',
|
| 65 |
+
'bright_black': '\033[90m',
|
| 66 |
+
'bright_red': '\033[91m',
|
| 67 |
+
'bright_green': '\033[92m',
|
| 68 |
+
'bright_yellow': '\033[93m',
|
| 69 |
+
'bright_blue': '\033[94m',
|
| 70 |
+
'bright_magenta': '\033[95m',
|
| 71 |
+
'bright_cyan': '\033[96m',
|
| 72 |
+
'bright_white': '\033[97m',
|
| 73 |
+
'end': '\033[0m',
|
| 74 |
+
'bold': '\033[1m',
|
| 75 |
+
'underline': '\033[4m',
|
| 76 |
+
}
|
| 77 |
+
return ''.join(colors[x] for x in args) + f'{string}' + colors['end']
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def log(message, level='info', color=False):
|
| 81 |
+
if level.lower() == 'warning':
|
| 82 |
+
LOGGER.warning(message)
|
| 83 |
+
elif level.lower() == 'error':
|
| 84 |
+
LOGGER.error(message)
|
| 85 |
+
else:
|
| 86 |
+
if color:
|
| 87 |
+
LOGGER.info(colorstr(message))
|
| 88 |
+
else:
|
| 89 |
+
LOGGER.info(message)
|
patientsim/utils/common_utils.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import re
|
| 2 |
+
import random
|
| 3 |
+
import numpy as np
|
| 4 |
+
from typing import Union
|
| 5 |
+
from datetime import datetime, timedelta
|
| 6 |
+
|
| 7 |
+
# NOTE: torch removed — set_seed only uses random + numpy for reproducibility.
|
| 8 |
+
# This is sufficient for API-based agents (GPT, Gemini) that don't run local models.
|
| 9 |
+
|
| 10 |
+
from . import colorstr
|
| 11 |
+
from ..registry.detection_key import DDX_DETECT_KEYS
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def set_seed(seed: int) -> None:
|
| 15 |
+
"""
|
| 16 |
+
Set the random seed for reproducibility (CPU-only, no torch required).
|
| 17 |
+
"""
|
| 18 |
+
random.seed(seed)
|
| 19 |
+
np.random.seed(seed)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def split_string(string: Union[str, list], delimiter: str = ",") -> list:
|
| 23 |
+
if isinstance(string, str):
|
| 24 |
+
return [s.strip() for s in string.split(delimiter)]
|
| 25 |
+
elif isinstance(string, list):
|
| 26 |
+
return [s.strip() for s in string]
|
| 27 |
+
else:
|
| 28 |
+
raise ValueError(colorstr("red", "Input must be a string or a list of strings."))
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def prompt_valid_check(prompt: str, data_dict: dict) -> None:
|
| 32 |
+
keys = re.findall(r'\{(.*?)\}', prompt)
|
| 33 |
+
missing_keys = [key for key in keys if key not in data_dict]
|
| 34 |
+
if missing_keys:
|
| 35 |
+
raise ValueError(colorstr("red", f"Missing keys in the prompt: {missing_keys}. Please ensure all required keys are present in the data dictionary."))
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def detect_ed_termination(text: str) -> bool:
|
| 39 |
+
pattern = re.compile(r'\[ddx\]:\s*\d+\.\s*.+', re.IGNORECASE)
|
| 40 |
+
end_flag = any(key.lower() in text.lower() for key in DDX_DETECT_KEYS)
|
| 41 |
+
return bool(pattern.search(text.lower())) or end_flag
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def detect_op_termination(text: str) -> bool:
|
| 45 |
+
try:
|
| 46 |
+
pattern = re.compile(r'Answer:\s*\d+\.\s*(.+)')
|
| 47 |
+
return bool(pattern.search(text))
|
| 48 |
+
except:
|
| 49 |
+
return False
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def str_to_datetime(iso_time: Union[str, datetime]) -> datetime:
|
| 53 |
+
try:
|
| 54 |
+
if isinstance(iso_time, str):
|
| 55 |
+
return datetime.fromisoformat(iso_time)
|
| 56 |
+
return iso_time
|
| 57 |
+
except:
|
| 58 |
+
raise ValueError(colorstr("red", f"`iso_time` must be str or date format, but got {type(iso_time)}"))
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def datetime_to_str(iso_time: Union[str, datetime], format: str) -> str:
|
| 62 |
+
try:
|
| 63 |
+
if not isinstance(iso_time, str):
|
| 64 |
+
return iso_time.strftime(format)
|
| 65 |
+
return iso_time
|
| 66 |
+
except:
|
| 67 |
+
raise ValueError(colorstr("red", f"`iso_time` must be str or date format, but got {type(iso_time)}"))
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def generate_random_date(start_date: Union[str, datetime] = '1960-01-01',
|
| 71 |
+
end_date: Union[str, datetime] = '2000-12-31') -> str:
|
| 72 |
+
start = str_to_datetime(start_date)
|
| 73 |
+
end = str_to_datetime(end_date)
|
| 74 |
+
delta = (end - start).days
|
| 75 |
+
random_days = random.randint(0, delta)
|
| 76 |
+
random_date = start + timedelta(days=random_days)
|
| 77 |
+
return datetime_to_str(random_date, '%Y-%m-%d')
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def exponential_backoff(retry_count: int,
|
| 81 |
+
base_delay: int = 5,
|
| 82 |
+
max_delay: int = 65,
|
| 83 |
+
jitter: bool = True) -> float:
|
| 84 |
+
delay = min(base_delay * (2 ** retry_count), max_delay)
|
| 85 |
+
if jitter:
|
| 86 |
+
delay = random.uniform(delay * 0.8, delay * 1.2)
|
| 87 |
+
return delay
|
patientsim/utils/desc_utils.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
|
| 3 |
+
from ..registry.term import *
|
| 4 |
+
from ..registry.persona import *
|
| 5 |
+
from . import colorstr
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def get_personality_description(personality: str) -> str:
|
| 9 |
+
indent = "\n\t\t"
|
| 10 |
+
lines = PERSONALITY[personality]["prompt"].split("\n")
|
| 11 |
+
description = indent + indent.join(lines)
|
| 12 |
+
if not personality == "plain":
|
| 13 |
+
description += (
|
| 14 |
+
f"{indent}IMPORTANT: Ensure that your personality is clearly represented "
|
| 15 |
+
"throughout the conversation, while allowing your emotional tone and style to vary naturally across turns.")
|
| 16 |
+
return description
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def get_recall_description(level: str) -> str:
|
| 20 |
+
indent = "\n\t\t"
|
| 21 |
+
lines = RECALL_LEVEL[level]["prompt"].split("\n")
|
| 22 |
+
return f"{level.capitalize()}{indent}" + indent.join(lines)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def get_confusion_description(level: str) -> str:
|
| 26 |
+
indent = "\n\t\t"
|
| 27 |
+
if level == "normal":
|
| 28 |
+
lines = CONFUSION_LEVEL[level]["prompt"].split("\n")
|
| 29 |
+
return f"{level.capitalize()}{indent}" + indent.join(lines)
|
| 30 |
+
|
| 31 |
+
description = (
|
| 32 |
+
f"\n\tThe patient's initial dazed level is {level}. "
|
| 33 |
+
+ "The dazedness should gradually fade throughout the conversation as the doctor continues to reassure them. "
|
| 34 |
+
+ "Transitions should feel smooth and natural, rather than abrupt. "
|
| 35 |
+
+ "While the change should be subtle and progressive, the overall dazed level is expected to decrease noticeably every 4-5 turns, following the instructions for each level below."
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
for level_to_include in CONFUSION_TO_INCLUDE[level]:
|
| 39 |
+
description += f"\n\t{level_to_include.capitalize()} Dazedness ({CONFUSION_STATE[level_to_include].capitalize()} Phase){indent}" + indent.join(
|
| 40 |
+
CONFUSION_LEVEL[level_to_include]["prompt"].split("\n")
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
description += "\n\tNote: Dazedness reflects the patient's state of confusion and inability in following the conversation, independent of their language proficiency."
|
| 44 |
+
return description
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def get_language_proficiency_description(level: str,
|
| 48 |
+
num_sample: int,
|
| 49 |
+
random_sampling: bool = True,
|
| 50 |
+
**kwargs) -> str:
|
| 51 |
+
if level == "A":
|
| 52 |
+
understand_words = kwargs.get('cefr_A1') if kwargs.get('cefr_A1') else CEFR_A1
|
| 53 |
+
misunderstand_words = kwargs.get('cefr_A2') if kwargs.get('cefr_A2') else CEFR_A2
|
| 54 |
+
understand_med_words = kwargs.get('med_A') if kwargs.get('med_A') else MED_A
|
| 55 |
+
misunderstand_med_words = kwargs.get('med_B') if kwargs.get('med_B') else MED_B
|
| 56 |
+
elif level == "B":
|
| 57 |
+
understand_words = kwargs.get('cefr_B1') if kwargs.get('cefr_B1') else CEFR_B1
|
| 58 |
+
misunderstand_words = kwargs.get('cefr_B2') if kwargs.get('cefr_B2') else CEFR_B2
|
| 59 |
+
understand_med_words = kwargs.get('med_B') if kwargs.get('med_B') else MED_B
|
| 60 |
+
misunderstand_med_words = kwargs.get('med_C') if kwargs.get('med_C') else MED_C
|
| 61 |
+
elif level == "C":
|
| 62 |
+
understand_words = kwargs.get('cefr_C1') if kwargs.get('cefr_C1') else CEFR_C1
|
| 63 |
+
misunderstand_words = kwargs.get('cefr_C2') if kwargs.get('cefr_C2') else CEFR_C2
|
| 64 |
+
understand_med_words = kwargs.get('med_C') if kwargs.get('med_C') else MED_C
|
| 65 |
+
misunderstand_med_words = []
|
| 66 |
+
else:
|
| 67 |
+
raise ValueError(colorstr("red", f"Invalid language proficiency level: {level}. Must be one of 'A', 'B', or 'C'."))
|
| 68 |
+
|
| 69 |
+
understand_words = random.sample(understand_words, min(len(understand_words), num_sample)) if random_sampling else understand_words[:num_sample]
|
| 70 |
+
misunderstand_words = random.sample(misunderstand_words, min(len(misunderstand_words), num_sample)) if random_sampling else misunderstand_words[:num_sample]
|
| 71 |
+
understand_med_words = random.sample(understand_med_words, min(len(understand_med_words), num_sample)) if random_sampling else understand_med_words[:num_sample]
|
| 72 |
+
misunderstand_med_words = random.sample(misunderstand_med_words, min(len(misunderstand_med_words), num_sample)) if random_sampling else misunderstand_med_words[:num_sample]
|
| 73 |
+
words_type = {
|
| 74 |
+
"understand_words": ", ".join(understand_words),
|
| 75 |
+
"misunderstand_words": ", ".join(misunderstand_words),
|
| 76 |
+
"understand_med_words": ", ".join(understand_med_words),
|
| 77 |
+
"misunderstand_med_words": ", ".join(misunderstand_med_words) if misunderstand_med_words else ""
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
indent = "\n\t\t\t"
|
| 81 |
+
lines = LANG_PROFICIENCY_LEVEL[level]["prompt"].split("\n")
|
| 82 |
+
description = "\n\t\t" + indent.join(lines).format(**words_type)
|
| 83 |
+
return description
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def get_reminder_description(personality: str,
|
| 87 |
+
lang_proficiency_level: str,
|
| 88 |
+
recall_level: str,
|
| 89 |
+
confusion_level: str) -> str:
|
| 90 |
+
description_list = [
|
| 91 |
+
f"You should act like {LANG_PROFICIENCY_LEVEL[lang_proficiency_level]['description'].lower()}",
|
| 92 |
+
f"You are {PERSONALITY[personality]['description'].lower()}",
|
| 93 |
+
f"Also, you {RECALL_LEVEL[recall_level]['description'].lower()}",
|
| 94 |
+
CONFUSION_LEVEL[confusion_level]['description']
|
| 95 |
+
]
|
| 96 |
+
return " ".join(description_list)
|