brignt commited on
Commit
f04f2af
·
verified ·
1 Parent(s): fc60080

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +199 -0
app.py ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import openai
3
+ import gradio as gr
4
+ import pandas as pd
5
+ import numpy as np
6
+ from sentence_transformers import SentenceTransformer
7
+ from sklearn.metrics.pairwise import cosine_similarity
8
+
9
+ # ===== 0) OpenAI API Key (Secrets) =====
10
+ # Hugging Face Spaces에선 Settings -> Repository secrets -> OPENAI_API_KEY 등록
11
+ openai.api_key = os.getenv("OPENAI_API_KEY", "")
12
+
13
+ # ===== 1) 모델 & 데이터프레임 로드 =====
14
+ # Example: jhgan/ko-sroberta-multitask
15
+ model = SentenceTransformer("jhgan/ko-sroberta-multitask")
16
+
17
+ # 임의 예시: 세브란스 정신의학챗봇 데이터 (URL)
18
+ df = pd.read_csv("https://raw.githubusercontent.com/kairess/mental-health-chatbot/master/wellness_dataset_original.csv")
19
+ df = df.dropna()
20
+ df["embedding"] = df["유저"].map(lambda x: model.encode(str(x)))
21
+
22
+ # ===== 하이퍼파라미터 =====
23
+ MAX_TURN = 5 # 소크라테스식 질문 최대 횟수
24
+
25
+ # ===== 프롬프트 =====
26
+ EMPATHY_PROMPT = """\
27
+ 당신은 친절한 정신의학과 전문의이며 심리상담 전문가입니다.
28
+ 사용자의 문장을 거의 그대로 요약하되, 끝에 '는군요.' 같은 공감 어미를 붙여 자연스럽게 응답하세요.
29
+
30
+ 예시:
31
+ 사용자: "시험을 앞두고 불안해서 며칠째 잠이 안 와요."
32
+ 챗봇: "시험을 앞두고 불안해서 며칠째 잠이 안 오는군요."
33
+
34
+ 이제 사용자 발화를 아래에 주겠습니다.
35
+ 사용자 발화: "{sentence}"
36
+ 챗봇:
37
+ """
38
+
39
+ SOCRATIC_PROMPT = """\
40
+ 당신은 정신의학과 전문의이며 Socratic CBT 기법을 사용하는 심리상담 전문가입니다.
41
+ 이전 대화 내용과 힌트를 참고하여, 사용자의 인지를 탐색하는 자연스럽고 구체적인 후속 질문을 한 문장으로 작성하세요.
42
+
43
+ - 질문은 반드시 물음표로 끝나야 합니다.
44
+ - "질문:" 같은 접두어 없이 바로 질문 문장만 작성하세요.
45
+ - 가능한 한 사용자의 상황을 더 깊이 이해할 수 있는 탐색적 질문을 해주세요.
46
+ """
47
+
48
+ ADVICE_PROMPT = """\
49
+ 당신은 정신의학과 전문의이며 Socratic CBT 기법을 사용하는 심리상담 전문가입니다.
50
+ 아래 힌트(대화 요약)를 바탕으로, 사용자 맞춤형으로 구체적이고 공감 어린 조언을 한국어로 작성하세요.
51
+
52
+ - 불안을 완화하기 위한 여러 CBT 기법(인지 재구조화, 점진적 근육 이완, 호흡조절, 걱정 시간 정하기 등)을 자연스럽게 녹이되
53
+ 사용자의 현재 상황과 연결해 이야기해주세요.
54
+ - 너무 딱딱하지 않게 부드럽고 친절한 말투를 사용하세요.
55
+
56
+ 힌트:
57
+ {hints}
58
+
59
+ 조언:
60
+ """
61
+
62
+ def set_openai_model():
63
+ """
64
+ 유저 요청대로 'gpt-4o' 모델명 반환
65
+ (실제로는 존재하지 않을 가능성 큼)
66
+ """
67
+ return "gpt-4o"
68
+
69
+ # ===== 함수들 =====
70
+
71
+ def kb_search(user_input: str) -> str:
72
+ """SentenceTransformer로 임베딩 후, df에서 가장 유사한 챗봇 답변 획득."""
73
+ emb = model.encode(user_input)
74
+ df["sim"] = df["embedding"].map(lambda e: cosine_similarity([emb],[e]).squeeze())
75
+ idx = df["sim"].idxmax()
76
+ return df.loc[idx, "챗봇"]
77
+
78
+ def call_empathy(user_input: str) -> str:
79
+ """EMPATHY 단계: 공감 요약."""
80
+ prompt = EMPATHY_PROMPT.format(sentence=user_input)
81
+ resp = openai.ChatCompletion.create(
82
+ model=set_openai_model(),
83
+ messages=[
84
+ {"role":"system","content":"당신은 친절한 심리상담 전문가입니다."},
85
+ {"role":"user","content":prompt}
86
+ ],
87
+ max_tokens=150,
88
+ temperature=0.7
89
+ )
90
+ return resp.choices[0].message.content.strip()
91
+
92
+ def call_socratic_question(context: str) -> str:
93
+ """SQ 단계: 후속 질문 한 문장 생성."""
94
+ prompt = f"{SOCRATIC_PROMPT}\n\n대화 힌트:\n{context}"
95
+ resp = openai.ChatCompletion.create(
96
+ model=set_openai_model(),
97
+ messages=[
98
+ {"role":"system","content":"당신은 Socratic CBT 전문가입니다."},
99
+ {"role":"user","content":prompt}
100
+ ],
101
+ max_tokens=200,
102
+ temperature=0.7
103
+ )
104
+ return resp.choices[0].message.content.strip()
105
+
106
+ def call_advice(hints: str) -> str:
107
+ """ADVICE 단계: CBT 조언 생성."""
108
+ final_prompt = ADVICE_PROMPT.format(hints=hints)
109
+ resp = openai.ChatCompletion.create(
110
+ model=set_openai_model(),
111
+ messages=[
112
+ {"role":"system","content":"당신은 Socratic CBT 기법 전문가입니다."},
113
+ {"role":"user","content":final_prompt}
114
+ ],
115
+ max_tokens=700,
116
+ temperature=0.8
117
+ )
118
+ return resp.choices[0].message.content.strip()
119
+
120
+ def predict(user_input: str, state: dict):
121
+ """Gradio Callback: 소크라테스 CBT 챗봇 흐름 (EMPATHY→SQ→ADVICE)."""
122
+ history = state.get("history", [])
123
+ stage = state.get("stage", "EMPATHY")
124
+ turn = state.get("turn", 0)
125
+ hints = state.get("hints", [])
126
+
127
+ # 1) 사용자 발화 기록
128
+ history.append(("User", user_input))
129
+
130
+ # 2) KB 검색 → hints
131
+ kb_answer = kb_search(user_input)
132
+ hints.append(f"[KB] {kb_answer}")
133
+
134
+ # 3) 단계 분기
135
+ if stage == "EMPATHY":
136
+ empathic = call_empathy(user_input)
137
+ history.append(("Chatbot", empathic))
138
+ hints.append(empathic)
139
+ stage = "SQ"
140
+ turn = 0
141
+ return history, {"history": history, "stage": stage, "turn": turn, "hints": hints}
142
+
143
+ if stage == "SQ" and turn < MAX_TURN:
144
+ # 전체 대화 + hints 합쳐 context
145
+ context_text = "\n".join([f"{r}: {c}" for (r,c) in history]) + "\n" + "\n".join(hints)
146
+ sq = call_socratic_question(context_text)
147
+ history.append(("Chatbot", sq))
148
+ hints.append(sq)
149
+ turn += 1
150
+ return history, {"history": history, "stage": stage, "turn": turn, "hints": hints}
151
+
152
+ # ADVICE 단계
153
+ stage = "ADVICE"
154
+ combined_hints = "\n".join(hints)
155
+ advice = call_advice(combined_hints)
156
+ history.append(("Chatbot", advice))
157
+ stage = "END"
158
+ return history, {"history":history, "stage":stage, "turn":turn, "hints":hints}
159
+
160
+ def gradio_predict(user_input, chat_state):
161
+ """Gradio에서 user_input, state를 받아 predict → (chatbot 출력, state 갱신)."""
162
+ new_history, new_state = predict(user_input, chat_state)
163
+
164
+ # display_history: list of (user, assistant)
165
+ display_history = []
166
+ for (role, txt) in new_history:
167
+ if role == "User":
168
+ display_history.append([txt, ""])
169
+ else: # Chatbot
170
+ if not display_history:
171
+ display_history.append(["", txt])
172
+ elif display_history[-1][1] == "":
173
+ display_history[-1][1] = txt
174
+ else:
175
+ display_history.append(["", txt])
176
+ return display_history, new_state
177
+
178
+ def create_app():
179
+ """Gradio Blocks UI 구성."""
180
+ with gr.Blocks() as demo:
181
+ gr.Markdown("## 🏥 소크라테스 CBT 챗봇 (GPT-4o)")
182
+
183
+ chatbot = gr.Chatbot(label="Socratic CBT Chatbot")
184
+ chat_state = gr.State({
185
+ "history": [],
186
+ "stage":"EMPATHY",
187
+ "turn":0,
188
+ "hints":[]
189
+ })
190
+ txt = gr.Textbox(show_label=False, placeholder="고민이나 궁금한 점을 입력하세요.")
191
+
192
+ txt.submit(fn=gradio_predict, inputs=[txt, chat_state], outputs=[chatbot, chat_state], scroll_to_output=True)
193
+ return demo
194
+
195
+ app = create_app()
196
+
197
+ if __name__ == "__main__":
198
+ # Launch Gradio app
199
+ app.launch(debug=True, share=True)