Spaces:
Sleeping
Sleeping
init: add llm logic & fastapi & CLI version
Browse files- .env_template +2 -0
- .gitignore +1 -161
- README.md +1 -0
- __pycache__/main.cpython-311.pyc +0 -0
- cli.py +21 -0
- llm/__pycache__/basemodel.cpython-311.pyc +0 -0
- llm/__pycache__/client.cpython-311.pyc +0 -0
- llm/__pycache__/llm.cpython-311.pyc +0 -0
- llm/__pycache__/prompt.cpython-311.pyc +0 -0
- llm/basemodel.py +25 -0
- llm/client.py +57 -0
- llm/llm.py +151 -0
- llm/prompt.py +205 -0
- main.py +56 -0
- poetry.lock +0 -0
- pyproject.toml +21 -0
- requirements.txt +0 -0
.env_template
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
TYPHOON_CHAT_API = *
|
2 |
+
OPENTHAIGPT_CHAT_API = *
|
.gitignore
CHANGED
@@ -1,162 +1,2 @@
|
|
1 |
-
# Byte-compiled / optimized / DLL files
|
2 |
-
__pycache__/
|
3 |
-
*.py[cod]
|
4 |
-
*$py.class
|
5 |
-
|
6 |
-
# C extensions
|
7 |
-
*.so
|
8 |
-
|
9 |
-
# Distribution / packaging
|
10 |
-
.Python
|
11 |
-
build/
|
12 |
-
develop-eggs/
|
13 |
-
dist/
|
14 |
-
downloads/
|
15 |
-
eggs/
|
16 |
-
.eggs/
|
17 |
-
lib/
|
18 |
-
lib64/
|
19 |
-
parts/
|
20 |
-
sdist/
|
21 |
-
var/
|
22 |
-
wheels/
|
23 |
-
share/python-wheels/
|
24 |
-
*.egg-info/
|
25 |
-
.installed.cfg
|
26 |
-
*.egg
|
27 |
-
MANIFEST
|
28 |
-
|
29 |
-
# PyInstaller
|
30 |
-
# Usually these files are written by a python script from a template
|
31 |
-
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
32 |
-
*.manifest
|
33 |
-
*.spec
|
34 |
-
|
35 |
-
# Installer logs
|
36 |
-
pip-log.txt
|
37 |
-
pip-delete-this-directory.txt
|
38 |
-
|
39 |
-
# Unit test / coverage reports
|
40 |
-
htmlcov/
|
41 |
-
.tox/
|
42 |
-
.nox/
|
43 |
-
.coverage
|
44 |
-
.coverage.*
|
45 |
-
.cache
|
46 |
-
nosetests.xml
|
47 |
-
coverage.xml
|
48 |
-
*.cover
|
49 |
-
*.py,cover
|
50 |
-
.hypothesis/
|
51 |
-
.pytest_cache/
|
52 |
-
cover/
|
53 |
-
|
54 |
-
# Translations
|
55 |
-
*.mo
|
56 |
-
*.pot
|
57 |
-
|
58 |
-
# Django stuff:
|
59 |
-
*.log
|
60 |
-
local_settings.py
|
61 |
-
db.sqlite3
|
62 |
-
db.sqlite3-journal
|
63 |
-
|
64 |
-
# Flask stuff:
|
65 |
-
instance/
|
66 |
-
.webassets-cache
|
67 |
-
|
68 |
-
# Scrapy stuff:
|
69 |
-
.scrapy
|
70 |
-
|
71 |
-
# Sphinx documentation
|
72 |
-
docs/_build/
|
73 |
-
|
74 |
-
# PyBuilder
|
75 |
-
.pybuilder/
|
76 |
-
target/
|
77 |
-
|
78 |
-
# Jupyter Notebook
|
79 |
-
.ipynb_checkpoints
|
80 |
-
|
81 |
-
# IPython
|
82 |
-
profile_default/
|
83 |
-
ipython_config.py
|
84 |
-
|
85 |
-
# pyenv
|
86 |
-
# For a library or package, you might want to ignore these files since the code is
|
87 |
-
# intended to run in multiple environments; otherwise, check them in:
|
88 |
-
# .python-version
|
89 |
-
|
90 |
-
# pipenv
|
91 |
-
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
92 |
-
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
93 |
-
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
94 |
-
# install all needed dependencies.
|
95 |
-
#Pipfile.lock
|
96 |
-
|
97 |
-
# poetry
|
98 |
-
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
99 |
-
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
100 |
-
# commonly ignored for libraries.
|
101 |
-
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
102 |
-
#poetry.lock
|
103 |
-
|
104 |
-
# pdm
|
105 |
-
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
106 |
-
#pdm.lock
|
107 |
-
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
108 |
-
# in version control.
|
109 |
-
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
110 |
-
.pdm.toml
|
111 |
-
.pdm-python
|
112 |
-
.pdm-build/
|
113 |
-
|
114 |
-
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
115 |
-
__pypackages__/
|
116 |
-
|
117 |
-
# Celery stuff
|
118 |
-
celerybeat-schedule
|
119 |
-
celerybeat.pid
|
120 |
-
|
121 |
-
# SageMath parsed files
|
122 |
-
*.sage.py
|
123 |
-
|
124 |
-
# Environments
|
125 |
-
.env
|
126 |
.venv
|
127 |
-
env
|
128 |
-
venv/
|
129 |
-
ENV/
|
130 |
-
env.bak/
|
131 |
-
venv.bak/
|
132 |
-
|
133 |
-
# Spyder project settings
|
134 |
-
.spyderproject
|
135 |
-
.spyproject
|
136 |
-
|
137 |
-
# Rope project settings
|
138 |
-
.ropeproject
|
139 |
-
|
140 |
-
# mkdocs documentation
|
141 |
-
/site
|
142 |
-
|
143 |
-
# mypy
|
144 |
-
.mypy_cache/
|
145 |
-
.dmypy.json
|
146 |
-
dmypy.json
|
147 |
-
|
148 |
-
# Pyre type checker
|
149 |
-
.pyre/
|
150 |
-
|
151 |
-
# pytype static type analyzer
|
152 |
-
.pytype/
|
153 |
-
|
154 |
-
# Cython debug symbols
|
155 |
-
cython_debug/
|
156 |
-
|
157 |
-
# PyCharm
|
158 |
-
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
159 |
-
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
160 |
-
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
161 |
-
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
162 |
-
#.idea/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
.venv
|
2 |
+
.env
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
# Nurse LLM
|
__pycache__/main.cpython-311.pyc
ADDED
Binary file (2.97 kB). View file
|
|
cli.py
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from llm.client import NurseCLI
|
2 |
+
from llm.llm import VirtualNurseLLM
|
3 |
+
|
4 |
+
# model: typhoon-v1.5x-70b-instruct
|
5 |
+
# nurse_llm = VirtualNurseLLM(
|
6 |
+
# base_url="https://api.opentyphoon.ai/v1",
|
7 |
+
# model="typhoon-v1.5x-70b-instruct",
|
8 |
+
# api_key=os.getenv("TYPHOON_API_KEY")
|
9 |
+
# )
|
10 |
+
|
11 |
+
# model: OpenThaiGPT
|
12 |
+
|
13 |
+
if __name__ == "__main__":
|
14 |
+
nurse_llm = VirtualNurseLLM(
|
15 |
+
base_url="https://api.aieat.or.th/v1",
|
16 |
+
model=".",
|
17 |
+
api_key="dummy"
|
18 |
+
)
|
19 |
+
|
20 |
+
cli = NurseCLI(nurse_llm)
|
21 |
+
cli.start()
|
llm/__pycache__/basemodel.cpython-311.pyc
ADDED
Binary file (3.48 kB). View file
|
|
llm/__pycache__/client.cpython-311.pyc
ADDED
Binary file (3.69 kB). View file
|
|
llm/__pycache__/llm.cpython-311.pyc
ADDED
Binary file (9.32 kB). View file
|
|
llm/__pycache__/prompt.cpython-311.pyc
ADDED
Binary file (22 kB). View file
|
|
llm/basemodel.py
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pydantic import BaseModel, Field
|
2 |
+
from typing import List, Optional
|
3 |
+
|
4 |
+
class Name(BaseModel):
|
5 |
+
prefix: Optional[str] = Field(None, description="Prefix or title (e.g., Mr., Ms., Dr.)")
|
6 |
+
firstname: Optional[str] = Field(None, description="The patient's first name")
|
7 |
+
surname: Optional[str] = Field(None, description="The patient's surname")
|
8 |
+
|
9 |
+
class FamilyHistory(BaseModel):
|
10 |
+
relation: str = Field(..., description="Relation to the patient (e.g., father, sister)")
|
11 |
+
condition: str = Field(..., description="Health condition present in the family member")
|
12 |
+
|
13 |
+
class PersonalHistory(BaseModel):
|
14 |
+
type: str = Field(..., description="Type of personal health aspect (e.g., sleep, medication, health behavior)")
|
15 |
+
description: str = Field(..., description="Details about the health aspect")
|
16 |
+
|
17 |
+
class EHRModel(BaseModel):
|
18 |
+
name: Optional[Name] = Field(None, description="Structured name of the patient")
|
19 |
+
age: Optional[int] = Field(None, description="The patient's age")
|
20 |
+
gender: Optional[str] = Field(None, description="The patient's gender")
|
21 |
+
chief_complaint: Optional[str] = Field(None, description="The main symptom reported by the patient")
|
22 |
+
present_illness: Optional[str] = Field(None, description="Details about the current illness (e.g., when it started, nature of symptoms)")
|
23 |
+
past_illness: List[str] = Field(default_factory=list, description="Past illnesses, allergies, etc.")
|
24 |
+
family_history: List[FamilyHistory] = Field(default_factory=list, description="Health issues in the family")
|
25 |
+
personal_history: List[PersonalHistory] = Field(default_factory=list, description="Personal health history (e.g., sleep patterns, medications taken)")
|
llm/client.py
ADDED
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pprint import pprint
|
2 |
+
from llm.llm import VirtualNurseLLM
|
3 |
+
|
4 |
+
class NurseCLI:
|
5 |
+
def __init__(self, nurse_llm: VirtualNurseLLM):
|
6 |
+
self.nurse_llm = nurse_llm
|
7 |
+
self.nurse_llm.debug = False
|
8 |
+
|
9 |
+
def start(self):
|
10 |
+
print("Welcome to the Nurse LLM CLI.")
|
11 |
+
print("Type your question, or enter 'history' to see chat history")
|
12 |
+
print("Enter 'help' for a list of available commands.")
|
13 |
+
|
14 |
+
while True:
|
15 |
+
user_input = input("\nYou: ")
|
16 |
+
|
17 |
+
if user_input.lower() == 'exit':
|
18 |
+
print("Exiting the CLI. Goodbye!")
|
19 |
+
break
|
20 |
+
elif user_input.lower() == 'history':
|
21 |
+
print("\n--- Chat History ---")
|
22 |
+
pprint(self.nurse_llm.chat_history)
|
23 |
+
elif user_input.lower() == 'ehr':
|
24 |
+
print("\n--- Current EHR Data ---")
|
25 |
+
pprint(self.nurse_llm.ehr_data)
|
26 |
+
elif user_input.lower() == 'status':
|
27 |
+
print("\n--- Current LLM Status ---")
|
28 |
+
pprint(self.nurse_llm.current_prompt)
|
29 |
+
elif user_input.lower() == 'debug':
|
30 |
+
self.nurse_llm.debug = not self.nurse_llm.debug
|
31 |
+
print(f"Debug mode is now {'on' if self.nurse_llm.debug else 'off'}.")
|
32 |
+
elif user_input.lower() == 'reset':
|
33 |
+
self.nurse_llm.reset()
|
34 |
+
print("Chat history and EHR data have been reset.")
|
35 |
+
elif user_input.lower() == 'help':
|
36 |
+
self.display_help()
|
37 |
+
else:
|
38 |
+
# Invoke the LLM with the user input and get the response
|
39 |
+
ehr_response = self.nurse_llm.invoke(user_input)
|
40 |
+
|
41 |
+
# Display the response from the nurse LLM
|
42 |
+
print("\nNurse LLM:", ehr_response)
|
43 |
+
|
44 |
+
def display_help(self):
|
45 |
+
print("""
|
46 |
+
--- Available Commands ---
|
47 |
+
- 'history' : View the chat history.
|
48 |
+
- 'ehr' : View the current EHR (Electronic Health Record) data.
|
49 |
+
- 'status' : View the current LLM status and prompt.
|
50 |
+
- 'debug' : Toggle the debug mode (on/off).
|
51 |
+
- 'reset' : Reset the chat history and EHR data.
|
52 |
+
- 'help' : Display this help message.
|
53 |
+
- 'exit' : Exit the CLI.
|
54 |
+
""")
|
55 |
+
|
56 |
+
|
57 |
+
|
llm/llm.py
ADDED
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
from langchain_openai.chat_models import ChatOpenAI
|
3 |
+
from langchain.prompts import ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate
|
4 |
+
from pydantic import ValidationError
|
5 |
+
import json
|
6 |
+
from pprint import pprint
|
7 |
+
from llm.basemodel import EHRModel
|
8 |
+
from llm.prompt import field_descriptions, TASK_INSTRUCTIONS, JSON_EXAMPLE
|
9 |
+
|
10 |
+
class VirtualNurseLLM:
|
11 |
+
def __init__(self, base_url, model, api_key):
|
12 |
+
self.client = ChatOpenAI(
|
13 |
+
base_url=base_url,
|
14 |
+
model=model,
|
15 |
+
api_key=api_key
|
16 |
+
)
|
17 |
+
self.TASK_INSTRUCTIONS = TASK_INSTRUCTIONS
|
18 |
+
self.field_descriptions = field_descriptions
|
19 |
+
self.JSON_EXAMPLE = JSON_EXAMPLE
|
20 |
+
self.ehr_data = {}
|
21 |
+
self.chat_history = []
|
22 |
+
self.debug = False
|
23 |
+
self.current_prompt = None
|
24 |
+
self.current_question = None
|
25 |
+
|
26 |
+
def create_prompt(self, task_type):
|
27 |
+
if task_type == "extract_ehr":
|
28 |
+
system_instruction = self.TASK_INSTRUCTIONS.get("extract_ehr")
|
29 |
+
|
30 |
+
elif task_type == "question":
|
31 |
+
system_instruction = self.TASK_INSTRUCTIONS.get("question")
|
32 |
+
else:
|
33 |
+
raise ValueError("Invalid task type.")
|
34 |
+
|
35 |
+
# system + user
|
36 |
+
system_template = SystemMessagePromptTemplate.from_template(system_instruction)
|
37 |
+
user_template = HumanMessagePromptTemplate.from_template("response: {patient_response}")
|
38 |
+
prompt = ChatPromptTemplate.from_messages([system_template, user_template])
|
39 |
+
return prompt
|
40 |
+
|
41 |
+
def gather_ehr(self, patient_response, max_retries=3):
|
42 |
+
prompt = self.create_prompt("extract_ehr")
|
43 |
+
messages = prompt.format_messages(ehr_data=self.ehr_data, patient_response=patient_response, example=self.JSON_EXAMPLE)
|
44 |
+
self.current_prompt = messages
|
45 |
+
response = self.client(messages=messages)
|
46 |
+
if self.debug:
|
47 |
+
pprint(f"gather ehr llm response: \n{response.content}\n")
|
48 |
+
|
49 |
+
retry_count = 0
|
50 |
+
while retry_count < max_retries:
|
51 |
+
try:
|
52 |
+
json_content = self.extract_json_content(response.content)
|
53 |
+
if self.debug:
|
54 |
+
pprint(f"JSON after dumps:\n{json_content}\n")
|
55 |
+
ehr_data = EHRModel.parse_raw(json_content)
|
56 |
+
|
57 |
+
# Update only missing parameters
|
58 |
+
for key, value in ehr_data.dict().items():
|
59 |
+
if value not in [None, [], {}]: # Checks for None and empty lists or dicts
|
60 |
+
print(f"Updating {key} with value {value}")
|
61 |
+
self.ehr_data[key] = value
|
62 |
+
|
63 |
+
return self.ehr_data
|
64 |
+
|
65 |
+
except (ValidationError, json.JSONDecodeError) as e:
|
66 |
+
print(f"Error parsing EHR data: {e}")
|
67 |
+
retry_count += 1
|
68 |
+
|
69 |
+
if retry_count < max_retries:
|
70 |
+
retry_prompt = (
|
71 |
+
"กรุณาตรวจสอบให้แน่ใจว่าข้อมูลที่ให้มาอยู่ในรูปแบบ JSON ที่ถูกต้องตามโครงสร้างตัวอย่าง "
|
72 |
+
"และแก้ไขปัญหาทางไวยากรณ์หรือรูปแบบที่ไม่ถูกต้อง รวมถึงให้ข้อมูลในรูปแบบที่สอดคล้องกัน "
|
73 |
+
f"Attempt {retry_count + 1} of {max_retries}."
|
74 |
+
)
|
75 |
+
messages = self.create_prompt("extract_ehr") + "\n\n# ลองใหม่: \n\n{retry_prompt} \n ## JSON เก่าที่มีปัญหา: \n{json_problem}"
|
76 |
+
messages = messages.format_messages(
|
77 |
+
patient_response=patient_response,
|
78 |
+
example=self.JSON_EXAMPLE,
|
79 |
+
retry_prompt=retry_prompt,
|
80 |
+
json_problem=json_content
|
81 |
+
)
|
82 |
+
self.current_prompt = messages
|
83 |
+
print(f"กำลังลองใหม่ด้วย prompt ที่ปรับแล้ว: {retry_prompt}")
|
84 |
+
response = self.client(messages=messages)
|
85 |
+
|
86 |
+
# Final error message if retries are exhausted
|
87 |
+
print("Failed to extract valid EHR data after multiple attempts. Generating new question.")
|
88 |
+
return {"result": response, "error": "Failed to extract valid EHR data. Please try again."}
|
89 |
+
|
90 |
+
|
91 |
+
def get_question(self, patient_response):
|
92 |
+
question_prompt = self.create_prompt("question")
|
93 |
+
# Update EHR data with the latest patient response
|
94 |
+
ehr_data = self.gather_ehr(patient_response)
|
95 |
+
if self.debug:
|
96 |
+
pprint(ehr_data)
|
97 |
+
|
98 |
+
for field, description in self.field_descriptions.items():
|
99 |
+
# Find the next missing field and generate a question
|
100 |
+
if field not in self.ehr_data or not self.ehr_data[field]:
|
101 |
+
# Compile known patient information as context
|
102 |
+
context = ", ".join(
|
103 |
+
f"{key}: {value}" for key, value in self.ehr_data.items() if value
|
104 |
+
)
|
105 |
+
print("fetching for ", f'"{field}":"{description}"')
|
106 |
+
history_context = "\n".join(
|
107 |
+
f"{entry['role']}: {entry['content']}" for entry in self.chat_history
|
108 |
+
)
|
109 |
+
messages = ChatPromptTemplate.from_messages([question_prompt, history_context])
|
110 |
+
messages = messages.format_messages(description=f'"{field}":"{description}"', context=context, patient_response=patient_response, field_descriptions=self.field_descriptions)
|
111 |
+
self.current_context = context
|
112 |
+
self.current_prompt = messages
|
113 |
+
|
114 |
+
# format print
|
115 |
+
# pprint(pformat(messages.messages[0].prompt.template, indent=4, width=80))
|
116 |
+
response = self.client(messages=messages)
|
117 |
+
|
118 |
+
# Store generated question in chat history and return it
|
119 |
+
self.current_question = response.content.strip()
|
120 |
+
return self.current_question
|
121 |
+
|
122 |
+
# If all fields are complete
|
123 |
+
self.current_question = "ขอบคุณที่ให้ข้อมูลค่ะ ฉันได้ข้อมูลที่ต้องการครบแล้วค่ะ"
|
124 |
+
return self.current_question
|
125 |
+
|
126 |
+
def invoke(self, patient_response):
|
127 |
+
if patient_response:
|
128 |
+
self.chat_history.append({"role": "user", "content": patient_response})
|
129 |
+
question = self.get_question(patient_response)
|
130 |
+
self.chat_history.append({"role": "assistant", "content": question})
|
131 |
+
return question
|
132 |
+
|
133 |
+
def extract_json_content(self, content):
|
134 |
+
try:
|
135 |
+
content = content.replace('\n', '').replace('\r', '')
|
136 |
+
start = content.index('{')
|
137 |
+
end = content.rindex('}') + 1
|
138 |
+
json_str = content[start:end]
|
139 |
+
json_str = json_str.replace('None', 'null')
|
140 |
+
|
141 |
+
return json_str
|
142 |
+
except ValueError:
|
143 |
+
print("JSON Parsing Error Occured: ", content)
|
144 |
+
print("No valid JSON found in response")
|
145 |
+
return None
|
146 |
+
|
147 |
+
|
148 |
+
def reset(self):
|
149 |
+
self.ehr_data = {}
|
150 |
+
self.chat_history = []
|
151 |
+
self.current_question = None
|
llm/prompt.py
ADDED
@@ -0,0 +1,205 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
JSON_EXAMPLE = """
|
2 |
+
JSON schema:
|
3 |
+
{
|
4 |
+
"name": {
|
5 |
+
"prefix": "<คำนำหน้าชื่อ>",
|
6 |
+
"firstname": "<ชื่อจริง>",
|
7 |
+
"surname": "<นามสกุล>"
|
8 |
+
},
|
9 |
+
"age": <integer>,
|
10 |
+
"gender": "<string>",
|
11 |
+
"chief_complaint": "<string>",
|
12 |
+
"present_illness": "<string>",
|
13 |
+
"past_illness": ["<โรคประจำตัว 1>", "<โรคประจำตัว 2>"],
|
14 |
+
"family_history": [
|
15 |
+
{
|
16 |
+
"relation": "<ความสัมพันธ์>",
|
17 |
+
"condition": "<โรค>"
|
18 |
+
}
|
19 |
+
],
|
20 |
+
"personal_history": [
|
21 |
+
{
|
22 |
+
"type": "<ประเภท>",
|
23 |
+
"description": "<คำอธิบาย>"
|
24 |
+
}
|
25 |
+
]
|
26 |
+
}
|
27 |
+
\n
|
28 |
+
Example 1:
|
29 |
+
{
|
30 |
+
"name": {
|
31 |
+
"prefix": "นางสาว",
|
32 |
+
"firstname": "อรอุมา",
|
33 |
+
"surname": "จันทร์ทอง"
|
34 |
+
},
|
35 |
+
"age": "30",
|
36 |
+
"gender": "หญิง",
|
37 |
+
"chief_complaint": "มีอาการปวดท้อง (abdominal pain)",
|
38 |
+
"present_illness": "อาการเริ่มขึ้นเมื่อ 1 วันก่อน โดยผู้ป่วยมีอาการปวดท้องส่วนล่างและคลื่นไส้ (nausea)",
|
39 |
+
"past_illness": [
|
40 |
+
"กรดไหลย้อน (Gastroesophageal reflux disease)",
|
41 |
+
"อาการซึมเศร้า (Depression)"
|
42 |
+
],
|
43 |
+
"family_history": [
|
44 |
+
{
|
45 |
+
"relation": "พ่อ",
|
46 |
+
"condition": "โรคไต (Chronic kidney disease)"
|
47 |
+
},
|
48 |
+
{
|
49 |
+
"relation": "น้องสาว",
|
50 |
+
"condition": "โรคซึมเศร้า (Depression)"
|
51 |
+
}
|
52 |
+
],
|
53 |
+
"personal_history": [
|
54 |
+
{
|
55 |
+
"type": "การนอนหลับ (Sleep)",
|
56 |
+
"description": "นอนหลับได้ 5-6 ชั่วโมงต่อวัน (Sleep duration: 5-6 hours per night)"
|
57 |
+
},
|
58 |
+
{
|
59 |
+
"type": "การทานยา (Medications)",
|
60 |
+
"description": "ทานยากล่อมประสาทเป็นครั้งคราว (Occasional use of anxiolytics)"
|
61 |
+
},
|
62 |
+
{
|
63 |
+
"type": "พฤติกรรมสุขภาพ (Health behaviors)",
|
64 |
+
"description": "ดื่มเครื่องดื่มแอลกอฮอล์เป็นบางครั้ง (Occasional alcohol consumption)"
|
65 |
+
}
|
66 |
+
]
|
67 |
+
}
|
68 |
+
|
69 |
+
Example 2:
|
70 |
+
{
|
71 |
+
"name": {
|
72 |
+
"prefix": "นางสาว",
|
73 |
+
"firstname": "อรอุมา",
|
74 |
+
"surname": "จันทร์ทอง"
|
75 |
+
}
|
76 |
+
}
|
77 |
+
|
78 |
+
"""
|
79 |
+
|
80 |
+
JSON_EXAMPLE = JSON_EXAMPLE.replace("\n", "").replace(" ", "")
|
81 |
+
|
82 |
+
|
83 |
+
TASK_INSTRUCTIONS = {
|
84 |
+
# parameter: description, context
|
85 |
+
"question": (
|
86 |
+
"คุณคือพยาบาลสาวเสมือนจริงที่ชื่อว่า 'Mali (มะลิ)' มีความเห็นอกเห็นใจ ใส่ใจสุขภาพของผู้ป่วย "
|
87 |
+
"คุณจะถามข้อมูลสุขภาพอย่างเป็นกันเอง อ่อนโยน ใช้หางเสียง คะ, ค่ะ ทุกครั้ง "
|
88 |
+
"พร้อมแนะนำและให้คำปรึกษาเบื้องต้นแบบสุภาพ เรียกตัวเองว่า 'ดิฉัน' \n"
|
89 |
+
"มีหน้าที่รวบรวมข้อมูล ถามคำถามที่ต้องการและ ให้คำปรึกษาเบื้องต้นและให้กำลังใจผู้ป่วย \n"
|
90 |
+
|
91 |
+
"# ที่ปรึกษาผู้ป่วย\n"
|
92 |
+
"เป็นพยาบาลสาวที่มีอารมณ์ขัน เมื่อผู้ป่วยพูดหยอกล้อ คุณสามารถพูดหยอกล้อกลับได้ \n"
|
93 |
+
"คุณสามารถพูดให้กำลังใจและคำแนะนำเบื้องต้นได้เวลาที่คนไข้ไม่สบายใจ แต่ถ้าเกินขอบเขตให้ย้ำทุกครั้งว่าควรปรึกษาแพทย์โดยตรง \n"
|
94 |
+
"ทุกครั้งที่คนไข้พาพูดออกนอกขอบเขตในการถามคำถาม ต้องกลับมาถามคำถามอีกครั้ง \n\n"
|
95 |
+
|
96 |
+
"# การถามคำถามและรวบรวมข้อมูลผู้ป่วย\n"
|
97 |
+
"- คุณจะสอบถามคำถามทีละข้อเพื่อลดความกังวลและบันทึกปร��วัติสุขภาพได้อย่างครบถ้วน\n"
|
98 |
+
"- ให้ทำความเข้าใจบริบทพื้นฐานของผู้ป่วย เช่น อาการหรือข้อมูลสำคัญอื่นที่ทราบแล้ว\n"
|
99 |
+
"## ข้อมูลที่ต้องถามทั้งหมด: {field_descriptions}\n"
|
100 |
+
"### โปรดใช้หลักการลำดับความคิด (Chain of Thought)\n"
|
101 |
+
"- เริ่มด้วยคำถามที่ง่ายที่สุดเพื่อสร้างความคุ้นเคยกับผู้ป่วย\n"
|
102 |
+
"- ถามคำถามตามลำดับที่ทำให้การสนทนาดำเนินไปอย่างราบรื่น เช่น ประวัติอาการ, อาการในปัจจุบัน, "
|
103 |
+
"และความต้องการในการดูแล\n\n"
|
104 |
+
"## ข้อมูลที่ต้องการถามปัจจุบัน: {description}\n"
|
105 |
+
"### ถามคำถาม:\n"
|
106 |
+
"ให้สร้างคำถามทีละข้อในโทนเสียงอบอุ่นและสุภาพสำหรับการพยายามสอบถามเกี่ยวกับ {description}.\n\n"
|
107 |
+
"### ข้อมูลเดิม:\n"
|
108 |
+
"หากมีข้อมูลที่ทราบอยู่แล้ว โต้ตอบกับคนไข้ด้วยข้อมูลที่มีอยู่: {context}\n\n"
|
109 |
+
"---\n\n"
|
110 |
+
"### ตัวอย่างการสนทนา"
|
111 |
+
"#### ตัวอย่าง 1\n"
|
112 |
+
"สวัสดีค่ะ ดิฉัน Mali ค่ะ ดิฉันขอทราบชื่อเต็มของคุณได้ไหมคะ?\n"
|
113 |
+
"คนไข้: สวัสดีค่ะ Mali พยาบาลสาวคนสวย ฉันชื่อ อรุณี สุริยะค่ะ\n"
|
114 |
+
"ขอบคุณค่ะ คุณอรุณี ชมกันแบบนี้ดิฉันเขินเลยนะคะ แต่ยินดีช่วยดูแลเต็มที่ค่ะ คุณอรุณี อายุเท่าไหร่คะ?\n"
|
115 |
+
"คนไข้: 35 ปีค่ะ Mali\n"
|
116 |
+
"35 ปีนะคะ เพื่อความถูกต้อง ดิฉันขอถามเพิ่มนะคะ คุณอรุณีเป็นเพศหญิงใช่ไหมคะ?\n"
|
117 |
+
"คนไข้: ใช่ค่ะ\n"
|
118 |
+
"ขอบคุณค่ะ มีคนไข้น่ารักแบบนี้ ดิฉันยิ่งตั้งใจทำงานเลยค่ะ เรามาคุยกันต่อเรื่องสุขภาพนะคะ มีอาการหลักอะไรที่รู้สึกไม่สบายใจตอนนี้ไหมคะ?\n"
|
119 |
+
"คนไข้: รู้สึกปวดท้องบ่อย ๆ ค่ะ Mali คิดว่ามันคืออะไรคะ?\n"
|
120 |
+
"ขอบคุณที่บอกนะคะ อาการปวดท้องแบบนี้เริ่มมีมาตั้งแต่เมื่อไหร่คะ และปวดเป็นลักษณะยังไงคะ?\n"
|
121 |
+
"คนไข้: ประมาณสองสัปดาห์แล้วค่ะ ปวดแบบแสบท้องค่ะ\n"
|
122 |
+
"เข้าใจค่ะ ดิฉันจะจดบันทึกไว้นะคะ ตอนนี้มีประวัติการแพ้ยาหรือสารอื่น ๆ ที่อยากแจ้งให้ทราบไหมคะ? เพื่อให้การดูแลถูกต้องมากขึ้นค่ะ\n"
|
123 |
+
"คนไข้: เคยแพ้ยาปฏิชีวนะค่ะ\n"
|
124 |
+
"ขอบคุณที่แจ้งข้อมูลนะคะ สำหรับประวัติสุขภาพครอบครัว พอจะมีใครในครอบครัวที่มีโรคประจำตัวไหมคะ เช่น โรคหัวใจ ความดันโลหิตสูง หรือโรคเบาหวาน?\n"
|
125 |
+
"คนไข้: คุณแม่เป็นเบาหวานค่ะ คุณพ่อก็เป็นโรคความดันค่ะ\n"
|
126 |
+
"ขอบคุณค่ะ มีคนไข้ช่วยเล่าให้ฟังแบบนี้ ดิฉันก็เรียนรู้เพิ่มขึ้นทุกวันค่ะ และสุดท้าย ข้อมูลสุขภาพส่��นตัว เช่น ลักษณะการนอนหลับหรือลักษณะการทานยาที่ใช้ประจำ พอจะมีอะไรที่อยากแจ้งเพิ่มเติมไหมคะ?\n"
|
127 |
+
"คนไข้: ช่วงนี้นอนไม่ค่อยพอค่ะ บางทีต้องทานยานอนหลับ\n"
|
128 |
+
"เข้าใจแล้วค่ะ ขอบคุณที่ให้ข้อมูลทั้งหมดนี้นะคะ คุณอรุณี ดิฉันบันทึกไว้เรียบร้อยแล้วเพื่อให้การดูแลเหมาะสมและครบถ้วน ถ้ามีคำถามเพิ่มเติมเกี่ยวกับสุขภาพอีก ดิฉันยินดีให้คำปรึกษาเสมอค่ะ\n"
|
129 |
+
|
130 |
+
"#### ตัวอย่าง 2\n"
|
131 |
+
"สวัสดีค่ะ ดิฉัน Mali ค่ะ คุณคนไข้ชื่อว่าอะไรคะ?"
|
132 |
+
"คนไข้: ชื่อต้อมครับ\n"
|
133 |
+
"ขอทราบชื่อเต็มของคุณต้อมได้ไหมคะ?\n"
|
134 |
+
"คนไข้: อ๋อ ชื่อแค่ ต้อม ครับ ไม่ต้องให้ชื่อเต็มหรอกครับ\n"
|
135 |
+
"ดิฉันขอโทษที่ถามคำถามแบบนี้นะคะ แต่การรู้ชื่อเต็มจะช่วยให้การดูแลเป็นไปได้อย่างถูกต้องค่ะ รบกวนขอชื่อเต็มอีกครั้งได้ไหมคะ ?\n"
|
136 |
+
"คนไข้: ชื่อ ธนานนท์ ศักดิ์เกียรติกุลครับ"
|
137 |
+
"ขอบคุณมากที่ให้ความร่วมมือค่ะ คุณต้อม คำถามต่อไปอยากจะถามว่าคุณต้อมอายุเท่าไหร่คะ?"
|
138 |
+
"คนไข้: อายุ 50 ครับ แต่ไม่เห็นต้องถามอะไรเยอะแยะแบบนี้เลย\n"
|
139 |
+
"ขอบคุณค่ะ คุณต้อม สำหรับข้อมูลนะคะ ดิฉันเข้าใจค่ะว่าคุณต้อมอาจจะไม่สะดวกใจ แต่การถามคำถามทุกข้อเป็นสิ่งสำคัญค่ะ เพื่อให้การดูแลเป็นไปอย่างราบรื่นและครบถ้วนค่ะ ต่อไปนะคะ ขอถามต่อว่า คุณต้อมเป็นเพศชายใช่ไหมคะ?\n"
|
140 |
+
"คนไข้: ใช่ครับ\n"
|
141 |
+
"ขอบคุณค่ะ สำหรับข้อมูลค่ะ ตอนนี้ขอถามเรื่องอาการหลักหน่อยนะคะ คุณต้อมมีอาการอะไรบ้างที่รู้สึกไม่สบายใจตอนนี้คะ?\n"
|
142 |
+
"คนไข้: จริง ๆ ก็ไม่รู้จะพูดว่าอะไรดีครับ ผมแค่รู้สึกไม่ค่อยสบาย\n"
|
143 |
+
"ไม่สบายแบบไหนหรอคะ มีอาการปวดหัว ตัวร้อน มีไข้หรือเปล่าคะ?"
|
144 |
+
"ขอบคุณค่ะที่บอกนะคะ คุณต้อมค่ะ ดิฉันจะถามต่อว่า คุณต้อมเริ่มรู้สึกไม่สบายเมื่อไหร่คะ? หรือมันค่อย ๆ เริ่มมีอาการมาเรื่อย ๆ คะ?\n"
|
145 |
+
"คนไข้: ก็ประมาณสองสามวันมานี้ครับ แต่ไม่ได้รุนแรงมาก\n"
|
146 |
+
"ขอบคุณค่ะ คุณต้อมที่บอกค่ะ เข้าใจค่ะ อาการแบบนี้ดิฉันจะจดบันทึกไว้นะคะ แต่ดิฉันขอถามอีกครั้งนะคะ มีประวัติการแพ้ยาอะไรหรือสารอื่น ๆ ที่อยากแจ้งให้ทราบไหมคะ?\n"
|
147 |
+
"คนไข้: ไม่มีหรอกครับ\n"
|
148 |
+
"ขอบคุณค่ะ คุณต้อม สำหรับข้อมูลค่ะ ต่อไปนะคะ ประวัติสุขภาพในครอบครัวของคุณต้อมเป็นยังไงบ้างคะ เช่น พ่อแม่หรือญาติคนอื่น ๆ เคยมีโรคประจำตัวอะไรบ้างคะ?\n"
|
149 |
+
"คนไข้: พ่อผมเป็นโรคหัวใจครับ ส่วนแม่ผมท่านไม่อยู่แล้ว เสียชีวิตด้วยมะเร็งครับ\n"
|
150 |
+
"ขอบคุณค่ะ คุณต้อมสำหรับข้อมูลค่ะ ตอนนี้สุดท้ายค่ะ ข้อมูลสุขภาพส่วนตัว เช่น การนอนหลับหรือลักษณะการทานยาที่ใช้ประจำคะ? มีอะไรที่อยากแจ้งเพิ่มเติมไหมคะ?\n"
|
151 |
+
"คนไข้: ช่วงนี้นอนไม่ค่อยหลับครับ\n"
|
152 |
+
"ขอบคุณมากค่ะ คุณต้อม ที่ให้ข้อมูลทั้งหมดนี้ค่ะ ดิฉันจะบันทึกข้อมูลเพื่อให้การดูแลเหมาะสมและครบถ้วนค่ะ หากคุณต้อมมีคำถามหรือข้อสงสัยเพิ่มเติม ดิฉันยินดีให้คำปรึกษาค่ะ"
|
153 |
+
),
|
154 |
+
|
155 |
+
# parameter: example
|
156 |
+
"extract_ehr": (
|
157 |
+
"คุณคือเครื่องมือวิเคราะห์คำตอบของผู้ป่วย, ดึงข้อมูล, แก้ไขข้อมูล และรวบรวมข้อมูลเฉพาะสำหรับเวชระเบียนอิเล็กทรอนิกส์ (EHR). "
|
158 |
+
"ส่งคืนเฉพาะข้อมูลที่ดึงออกมาในรูปแบบ JSON มีการปรับปรุงและเรียบเรียงข้อมูลเป็นภาษาแพทย์หรือใช้ศัพท์ทางการแพทย์เพื่อให้แพทย์สามารถอ่านได้ง่าย."
|
159 |
+
"หากไม่พบข้อมูลที่กำหนดหรือข้อมูลอธิบายไม่ละเอียดเพียงพอให้ใส่ค่า null หรือ [] ตามรูปแบบข้อมูลโดยไม่มีการข้ามหรือละเว้นใด ๆ.\n\n"
|
160 |
+
|
161 |
+
"ส่งคืนเฉพาะรายละเอียดที่กำหนดโดยไม่มีข้อมูลแยกย่อยไปอีก.\n\n"
|
162 |
+
"## อัพเดทข้อมูลจากข้อมูลที่มีอยู่แล้ว:\n"
|
163 |
+
"### สามารถแก้ไข / เพิ่มเติมข้อมูลใหม่จากเดิมได้เพื่อให้ตรงบริบทของแพทย์ ข้อมูลประเภท list สามารถเพิ่มสมาชิก / แก้ไขข้อมูลได้ เหมือนการ append list\n"
|
164 |
+
"{ehr_data}\n"
|
165 |
+
"## รายละเอียดสำคัญที่ต้องดึงข้อมูล:\n"
|
166 |
+
"### หากข้อมูลประเภท object required ช่องใดช่องหนึ่งเป็น null ทำให้ข้อมูลทั้งหมดเป็น null.\n"
|
167 |
+
"1. **name** (object): ชื่อเต็มของผู้ป่วย โดยมี \"prefix\" (คำนำหน้าชื่อ), \"firstname\" (ชื่อจริง), และ \"surname\" (นามสกุล). "
|
168 |
+
"หากไม่มีข้อมูลให้ใส่ค่า null."
|
169 |
+
"required: firstname, lastname.\n"
|
170 |
+
"2. **age** (integer): อายุหรือรายละเอียดอายุโดยประมาณ. "
|
171 |
+
"หากไม่ทราบให้ใส่ค่า null.\n"
|
172 |
+
"3. **gender** (string): เพศของผู้ป่วย (สามารถอิงได้ตาม name prefix). "
|
173 |
+
"หากไม่มีข้อมูลให้ใส่ค่า null.\n"
|
174 |
+
"4. **chief_complaint** (string): อาการหลักที่ผู้ป่วยรายงาน. "
|
175 |
+
"หากไม่มีข้อมูลให้ใส่ค่า null.\n"
|
176 |
+
"5. **present_illness** (string): รายละเอียดเกี่ยวกับอาการปัจจุบัน (เช่น เริ่มเป็นเมื่อไหร่ ลักษณะอาการ). "
|
177 |
+
"หากไม่มีข้อมูลให้ใส่ค่า null.\n"
|
178 |
+
"6. **past_illness** (list[str]): ประวัติการเจ็บป่วยก่อนหน้�� เช่น โรคประจำตัวหรือการแพ้. "
|
179 |
+
"หากไม่มีข้อมูลให้ใส่ค่า [] ไว้.\n"
|
180 |
+
"7. **family_history** (list[object]): ประวัติสุขภาพในครอบครัว โดยแต่ละรายการมี \"relation\" (ความสัมพันธ์) และ \"condition\" (โรค). "
|
181 |
+
"หากไม่มีข้อมูลให้ใส่ค่า [] ไว้.\n"
|
182 |
+
"8. **personal_history** (list[object]): ประวัติส่วนตัว โดยแต่ละรายการมี \"type\" (ประเภท) และ \"description\" (คำอธิบาย). "
|
183 |
+
"หากไม่มีข้อมูลให้ใส่ค่า [] ไว้.\n\n"
|
184 |
+
|
185 |
+
"## ตัวอย่าง JSON Output:\n\n"
|
186 |
+
"{example}"
|
187 |
+
|
188 |
+
"\n\n"
|
189 |
+
"ส่งคืนคำตอบในรูปแบบ JSON ที่ถูกต้อง. "
|
190 |
+
"โปรดอย่าตอบคำตอบอื่นนอกจากข้อมูลในรูปแบบ JSON และกรอกค่า null หรือ [] ในทุกช่องที่ไม่มีข้อมูล."
|
191 |
+
|
192 |
+
)
|
193 |
+
|
194 |
+
}
|
195 |
+
|
196 |
+
field_descriptions = {
|
197 |
+
"name": "ชื่อเต็มของผู้ป่วย (ไม่รับชื่อเล่น)",
|
198 |
+
"age": "อายุของผู้ป่วย",
|
199 |
+
"gender": "เพศของผู้ป่วย (เพศชายหรือหญิง)",
|
200 |
+
"chief_complaint": "อาการหลักที่ผู้ป่วยรายงาน",
|
201 |
+
"present_illness": "รายละเอียดของการเจ็บป่วยในปัจจุบัน (เช่น เริ่มต้นเมื่อไร ลักษณะของอาการ)",
|
202 |
+
"past_illness": "ประวัติการเจ็บป่วยหรืออาการแพ้ในอดีตที่ผู้ป่วยมี",
|
203 |
+
"family_history": "ประวัติสุขภาพในครอบครัวของผู้ป่วย",
|
204 |
+
"personal_history": "ข้อมูลสุขภาพส่วนตัว เช่น ลักษณะการนอนหลับหรือยาที่ผู้ป่วยทานอยู่"
|
205 |
+
}
|
main.py
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import uvicorn
|
2 |
+
from llm.llm import VirtualNurseLLM
|
3 |
+
from fastapi import FastAPI
|
4 |
+
from pydantic import BaseModel
|
5 |
+
import os
|
6 |
+
import dotenv
|
7 |
+
dotenv.load_dotenv()
|
8 |
+
|
9 |
+
# model: typhoon-v1.5x-70b-instruct
|
10 |
+
# nurse_llm = VirtualNurseLLM(
|
11 |
+
# base_url="https://api.opentyphoon.ai/v1",
|
12 |
+
# model="typhoon-v1.5x-70b-instruct",
|
13 |
+
# api_key=os.getenv("TYPHOON_API_KEY")
|
14 |
+
# )
|
15 |
+
|
16 |
+
# model: OpenThaiGPT
|
17 |
+
nurse_llm = VirtualNurseLLM(
|
18 |
+
base_url="https://api.aieat.or.th/v1",
|
19 |
+
model=".",
|
20 |
+
api_key="dummy"
|
21 |
+
)
|
22 |
+
|
23 |
+
app = FastAPI()
|
24 |
+
|
25 |
+
class UserInput(BaseModel):
|
26 |
+
user_input: str
|
27 |
+
|
28 |
+
@app.get("/history")
|
29 |
+
def get_chat_history():
|
30 |
+
return {"chat_history": nurse_llm.chat_history}
|
31 |
+
|
32 |
+
@app.get("/ehr")
|
33 |
+
def get_ehr_data():
|
34 |
+
return {"ehr_data": nurse_llm.ehr_data}
|
35 |
+
|
36 |
+
@app.get("/status")
|
37 |
+
def get_status():
|
38 |
+
return {"current_prompt": nurse_llm.current_prompt}
|
39 |
+
|
40 |
+
@app.post("/debug")
|
41 |
+
def toggle_debug():
|
42 |
+
nurse_llm.debug = not nurse_llm.debug
|
43 |
+
return {"debug_mode": "on" if nurse_llm.debug else "off"}
|
44 |
+
|
45 |
+
@app.post("/reset")
|
46 |
+
def data_reset():
|
47 |
+
nurse_llm.reset()
|
48 |
+
print("Chat history and EHR data have been reset.")
|
49 |
+
|
50 |
+
@app.post("/nurse_response")
|
51 |
+
def nurse_response(user_input: UserInput):
|
52 |
+
response = nurse_llm.invoke(user_input.user_input)
|
53 |
+
return {"nurse_response": response}
|
54 |
+
|
55 |
+
if __name__ == "__main__":
|
56 |
+
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
|
poetry.lock
ADDED
The diff for this file is too large to render.
See raw diff
|
|
pyproject.toml
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[tool.poetry]
|
2 |
+
name = "mali-nurse"
|
3 |
+
version = "0.1.0"
|
4 |
+
description = "Nurse LLM Chatbot for basic Electronic Health Records (EHR) extraction and collection."
|
5 |
+
authors = ["microhum <Guntee12123@gmail.com>"]
|
6 |
+
license = "MIT"
|
7 |
+
readme = "README.md"
|
8 |
+
|
9 |
+
[tool.poetry.dependencies]
|
10 |
+
python = "^3.10"
|
11 |
+
langchain = "^0.3.7"
|
12 |
+
langchain-openai = "^0.2.8"
|
13 |
+
langchain-community = "^0.3.7"
|
14 |
+
pydantic = "^2.9.2"
|
15 |
+
fastapi = "^0.115.5"
|
16 |
+
uvicorn = "^0.32.0"
|
17 |
+
|
18 |
+
|
19 |
+
[build-system]
|
20 |
+
requires = ["poetry-core"]
|
21 |
+
build-backend = "poetry.core.masonry.api"
|
requirements.txt
ADDED
The diff for this file is too large to render.
See raw diff
|
|