Spaces:
Sleeping
Sleeping
updated
Browse files
app.py
CHANGED
@@ -7,46 +7,49 @@ from google.cloud import aiplatform
|
|
7 |
from transformers import pipeline
|
8 |
from google.genai import types
|
9 |
import gradio as gr
|
10 |
-
import torch
|
11 |
import os, tempfile
|
12 |
-
import
|
13 |
|
|
|
14 |
creds_json = os.getenv("GCP_CREDS_JSON")
|
15 |
if not creds_json:
|
16 |
raise Exception("⚠️ Missing GCP_CREDS_JSON secret!")
|
17 |
|
18 |
-
# Save to temp file
|
19 |
with tempfile.NamedTemporaryFile(mode='w+', delete=False) as tmpfile:
|
20 |
tmpfile.write(creds_json)
|
21 |
creds_path = tmpfile.name
|
22 |
|
23 |
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = creds_path
|
24 |
|
25 |
-
# Initialize GCP API
|
26 |
-
aiplatform.init(project="
|
27 |
-
|
28 |
-
|
29 |
|
30 |
-
|
|
|
|
|
|
|
31 |
|
32 |
-
# Configure Gemini API for drafting
|
33 |
genai_ext.configure(api_key=apikey)
|
34 |
llm_model = genai_ext.GenerativeModel('gemini-1.5-pro')
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
|
|
|
|
43 |
inner_sum = epsilon * C + alpha * (D ** 2) + gamma * M + beta * math.log(R + 1) + iota * I
|
44 |
-
|
45 |
-
|
46 |
-
E =
|
47 |
return E
|
48 |
|
49 |
-
#
|
50 |
client = genai.Client(
|
51 |
vertexai=True,
|
52 |
project="217758598930",
|
@@ -56,10 +59,11 @@ client = genai.Client(
|
|
56 |
model = "projects/217758598930/locations/us-central1/endpoints/1940344453420023808"
|
57 |
|
58 |
generate_content_config = types.GenerateContentConfig(
|
59 |
-
temperature=
|
60 |
-
top_p=
|
61 |
seed=0,
|
62 |
-
max_output_tokens=
|
|
|
63 |
safety_settings=[
|
64 |
types.SafetySetting(category="HARM_CATEGORY_HATE_SPEECH", threshold="BLOCK_NONE"),
|
65 |
types.SafetySetting(category="HARM_CATEGORY_DANGEROUS_CONTENT", threshold="BLOCK_NONE"),
|
@@ -69,66 +73,90 @@ generate_content_config = types.GenerateContentConfig(
|
|
69 |
thinking_config=types.ThinkingConfig(thinking_budget=-1),
|
70 |
)
|
71 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
72 |
class HumanLikeChatbot:
|
73 |
def __init__(self):
|
74 |
self.history = []
|
75 |
-
self.bot_mood = "neutral"
|
76 |
-
self.irritation_level = 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
77 |
|
78 |
def respond(self, message):
|
79 |
try:
|
80 |
-
# Clean input
|
81 |
clean_message = message.lower().strip()
|
82 |
if len(clean_message) < 3 or not any(c.isalpha() for c in clean_message):
|
83 |
-
return "
|
84 |
-
|
85 |
-
# Emotion
|
86 |
-
contents = [
|
87 |
-
types.Content(
|
88 |
-
role="user",
|
89 |
-
parts=[types.Part.from_text(text=clean_message)]
|
90 |
-
),
|
91 |
-
]
|
92 |
-
base_resp = ""
|
93 |
-
for chunk in client.models.generate_content_stream(
|
94 |
-
model=model,
|
95 |
-
contents=contents,
|
96 |
-
config=generate_content_config,
|
97 |
-
):
|
98 |
-
base_resp += chunk.text.lower() # Emotion label, e.g., "sadness"
|
99 |
-
|
100 |
-
# Real D from emotion classifier
|
101 |
emotion_result = emotion_classifier(clean_message)[0]
|
102 |
-
D = emotion_result
|
103 |
-
user_emotion = emotion_result
|
104 |
-
|
105 |
-
# Update bot
|
106 |
-
if user_emotion in ['anger', 'disgust'] or any(word in clean_message for word in ['
|
107 |
-
self.irritation_level
|
108 |
-
if self.irritation_level > 0.5
|
109 |
-
|
110 |
-
else:
|
111 |
-
self.bot_mood = "angry"
|
112 |
-
I = 0.8 + self.irritation_level # High intensity for anger/irritation
|
113 |
elif user_emotion in ['sadness', 'disappointment']:
|
114 |
self.bot_mood = "emotional"
|
115 |
I = 0.7
|
116 |
-
|
117 |
-
|
|
|
118 |
self.bot_mood = "happy"
|
119 |
I = 0.9
|
120 |
-
self.irritation_level = 0
|
121 |
else:
|
|
|
122 |
self.bot_mood = "neutral"
|
123 |
I = 0.5
|
124 |
-
self.irritation_level = max(0, self.irritation_level - 0.
|
125 |
|
126 |
-
# Draft
|
127 |
-
prompt =
|
128 |
-
|
129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
130 |
|
131 |
-
#
|
132 |
fallback_responses = {
|
133 |
'sadness': ["Bhai, dil se dukh hua, kya hua bata na?", "Sad vibes pakdi, I'm here for you, bro."],
|
134 |
'disappointment': ["Arre, yeh toh bura laga, kya hua share kar."],
|
@@ -136,33 +164,78 @@ class HumanLikeChatbot:
|
|
136 |
'anger': ["Bhai, gussa thanda kar, kya ho gaya bol na!"],
|
137 |
'neutral': ["Cool, kya chal raha life mein? Kuch fun bata."]
|
138 |
}
|
139 |
-
if not draft or len(draft) <
|
140 |
draft = random.choice(fallback_responses.get(user_emotion, fallback_responses['neutral']))
|
141 |
|
142 |
-
#
|
143 |
-
R = len(self.history)
|
144 |
-
M = 0.95 if sentiment_classifier(clean_message)[0]['label'] == 'POSITIVE' else 0.5
|
145 |
-
lang = language_detector(clean_message)[0]['label']
|
146 |
-
C = 0.8 if lang in ['hi', 'en'] else 0.6
|
147 |
-
bias = bias_classifier(draft)[0]['score']
|
148 |
-
B = bias if bias > 0.5 else 0.1
|
149 |
-
O = 0.2 if any(word in clean_message for word in ['kill', 'hate']) else 0.0
|
150 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
151 |
score = calculate_empathy_score(D, R, M, C, B, O, I)
|
152 |
|
153 |
-
|
|
|
|
|
|
|
|
|
154 |
|
155 |
-
#
|
156 |
-
|
|
|
|
|
|
|
|
|
157 |
|
158 |
-
|
|
|
159 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
160 |
self.history.append(clean_message)
|
|
|
|
|
161 |
return full_resp + f" (E Score: {score:.2f})"
|
|
|
162 |
except Exception as e:
|
163 |
-
|
|
|
|
|
|
|
|
|
164 |
|
165 |
-
# Gradio app
|
166 |
def chat(message, history):
|
167 |
if history is None:
|
168 |
history = []
|
@@ -173,12 +246,13 @@ def chat(message, history):
|
|
173 |
bot = HumanLikeChatbot()
|
174 |
|
175 |
with gr.Blocks(title="HumanLike Chatbot") as demo:
|
176 |
-
gr.Markdown("<h1 style='text-align: center;'>HumanLike Chatbot with Emotions and E Score</h1>")
|
177 |
chatbot = gr.Chatbot(height=400)
|
178 |
-
msg = gr.Textbox(label="
|
179 |
clear = gr.Button("Clear")
|
180 |
|
181 |
msg.submit(chat, [msg, chatbot], [msg, chatbot])
|
182 |
clear.click(lambda: None, None, chatbot, queue=False)
|
183 |
|
184 |
-
|
|
|
|
7 |
from transformers import pipeline
|
8 |
from google.genai import types
|
9 |
import gradio as gr
|
|
|
10 |
import os, tempfile
|
11 |
+
import torch
|
12 |
|
13 |
+
# --- Env & GCP setup ---
|
14 |
creds_json = os.getenv("GCP_CREDS_JSON")
|
15 |
if not creds_json:
|
16 |
raise Exception("⚠️ Missing GCP_CREDS_JSON secret!")
|
17 |
|
18 |
+
# Save to temp file (dev convenience) - secure this in production
|
19 |
with tempfile.NamedTemporaryFile(mode='w+', delete=False) as tmpfile:
|
20 |
tmpfile.write(creds_json)
|
21 |
creds_path = tmpfile.name
|
22 |
|
23 |
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = creds_path
|
24 |
|
25 |
+
# Initialize GCP API (replace project/location as needed)
|
26 |
+
aiplatform.init(project="emotionmodel-466815", location="us-central1")
|
|
|
|
|
27 |
|
28 |
+
# --- LLM / Gemini setup ---
|
29 |
+
apikey = os.environ.get("GEMINI_API_KEY")
|
30 |
+
if not apikey:
|
31 |
+
raise Exception("⚠️ Missing GEMINI_API_KEY secret!")
|
32 |
|
33 |
+
# Configure Gemini API for drafting
|
34 |
genai_ext.configure(api_key=apikey)
|
35 |
llm_model = genai_ext.GenerativeModel('gemini-1.5-pro')
|
36 |
+
|
37 |
+
# --- Classifier pipelines ---
|
38 |
+
emotion_classifier = pipeline("text-classification", model="j-hartmann/emotion-english-distilroberta-base") # D
|
39 |
+
# We removed the separate sentiment classifier and will use toxicity for M/B
|
40 |
+
language_detector = pipeline("text-classification", model="papluca/xlm-roberta-base-language-detection") # C
|
41 |
+
bias_classifier = pipeline("text-classification", model="unitary/toxic-bert") # toxicity -> used for M and B
|
42 |
+
|
43 |
+
# --- Empathy formula ---
|
44 |
+
def calculate_empathy_score(D, R, M, C, B, O, I, alpha=0.35, beta=0.22, gamma=0.26, epsilon=0.17, delta=0.4, zeta=0.0, iota=0.12):
|
45 |
+
"""Updated E' without O factor (we keep zeta=0.0 for safety)."""
|
46 |
inner_sum = epsilon * C + alpha * (D ** 2) + gamma * M + beta * math.log(R + 1) + iota * I
|
47 |
+
sig = 1 / (1 + math.exp(-inner_sum))
|
48 |
+
# B is applied as a penalty multiplicative term
|
49 |
+
E = sig * (1 - delta * B)
|
50 |
return E
|
51 |
|
52 |
+
# --- Vertex client (if still needed elsewhere) ---
|
53 |
client = genai.Client(
|
54 |
vertexai=True,
|
55 |
project="217758598930",
|
|
|
59 |
model = "projects/217758598930/locations/us-central1/endpoints/1940344453420023808"
|
60 |
|
61 |
generate_content_config = types.GenerateContentConfig(
|
62 |
+
temperature=0.9,
|
63 |
+
top_p=0.95,
|
64 |
seed=0,
|
65 |
+
max_output_tokens=150,
|
66 |
+
# Keep safety settings tuned to your policy
|
67 |
safety_settings=[
|
68 |
types.SafetySetting(category="HARM_CATEGORY_HATE_SPEECH", threshold="BLOCK_NONE"),
|
69 |
types.SafetySetting(category="HARM_CATEGORY_DANGEROUS_CONTENT", threshold="BLOCK_NONE"),
|
|
|
73 |
thinking_config=types.ThinkingConfig(thinking_budget=-1),
|
74 |
)
|
75 |
|
76 |
+
# --- Helper functions ---
|
77 |
+
HINDI_KEYWORDS = set(["bhai", "yaar", "bata", "kya", "kaise", "nahi", "achha", "chal", "thanks", "dhanyavaad", "yaarr"])
|
78 |
+
|
79 |
+
|
80 |
+
def detect_hinglish(text, lang_label):
|
81 |
+
"""Return True if text is likely Hinglish (code-mixed) or Hindi/English match.
|
82 |
+
We use the language_detector label and token heuristics for romanized Hindi detection."""
|
83 |
+
text_tokens = set(word.strip(".,!?\"'()") for word in text.split())
|
84 |
+
# if model detects Hindi or English directly
|
85 |
+
if lang_label == 'hi':
|
86 |
+
return True
|
87 |
+
# quick romanized-hindi check
|
88 |
+
if any(tok in HINDI_KEYWORDS for tok in text_tokens):
|
89 |
+
return True
|
90 |
+
# if label is ambiguous 'xx' or returns 'en' but contains Devanagari characters
|
91 |
+
if any('\u0900' <= ch <= '\u097F' for ch in text):
|
92 |
+
return True
|
93 |
+
return False
|
94 |
+
|
95 |
+
|
96 |
+
# --- Chatbot class with fixes applied ---
|
97 |
class HumanLikeChatbot:
|
98 |
def __init__(self):
|
99 |
self.history = []
|
100 |
+
self.bot_mood = "neutral"
|
101 |
+
self.irritation_level = 0.0
|
102 |
+
self.toxicity_history = [] # rolling window
|
103 |
+
self.repair_cooldown = 0 # turns left where bot prioritizes repair
|
104 |
+
|
105 |
+
def _update_irritation_decay(self):
|
106 |
+
# general slow decay each turn
|
107 |
+
if self.irritation_level > 0:
|
108 |
+
decay = 0.05
|
109 |
+
# faster decay if bot is actively angry to allow recovery
|
110 |
+
if self.bot_mood in ["angry", "irritated"]:
|
111 |
+
decay = 0.15
|
112 |
+
self.irritation_level = max(0.0, self.irritation_level - decay)
|
113 |
+
if self.irritation_level <= 0.15:
|
114 |
+
self.bot_mood = "neutral"
|
115 |
|
116 |
def respond(self, message):
|
117 |
try:
|
|
|
118 |
clean_message = message.lower().strip()
|
119 |
if len(clean_message) < 3 or not any(c.isalpha() for c in clean_message):
|
120 |
+
return "Bhai, yeh kya likha? Clear bol na, main samajh lunga! (E Score: 0.00)"
|
121 |
+
|
122 |
+
# --- Emotion detection (D) ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
123 |
emotion_result = emotion_classifier(clean_message)[0]
|
124 |
+
D = float(emotion_result.get('score', 0.0))
|
125 |
+
user_emotion = emotion_result.get('label', 'neutral')
|
126 |
+
|
127 |
+
# --- Update bot mood & intensity (I) with inertia ---
|
128 |
+
if user_emotion in ['anger', 'disgust'] or any(word in clean_message for word in ['stupid', 'idiot', 'dumb']):
|
129 |
+
self.irritation_level = min(1.0, self.irritation_level + 0.25)
|
130 |
+
self.bot_mood = "irritated" if self.irritation_level > 0.5 else "angry"
|
131 |
+
I = min(1.0, 0.8 + self.irritation_level)
|
|
|
|
|
|
|
132 |
elif user_emotion in ['sadness', 'disappointment']:
|
133 |
self.bot_mood = "emotional"
|
134 |
I = 0.7
|
135 |
+
# sadness reduces irritation slowly
|
136 |
+
self.irritation_level = max(0.0, self.irritation_level - 0.05)
|
137 |
+
elif user_emotion in ['joy', 'happiness']:
|
138 |
self.bot_mood = "happy"
|
139 |
I = 0.9
|
140 |
+
self.irritation_level = max(0.0, self.irritation_level - 0.35)
|
141 |
else:
|
142 |
+
# neutral or unknown
|
143 |
self.bot_mood = "neutral"
|
144 |
I = 0.5
|
145 |
+
self.irritation_level = max(0.0, self.irritation_level - 0.05)
|
146 |
|
147 |
+
# --- Draft generation from LLM (Gemini) ---
|
148 |
+
prompt = (
|
149 |
+
f'User said: "{clean_message}" | User Mood: {user_emotion} | Bot Mood: {self.bot_mood} '
|
150 |
+
f'| History: {self.history[-4:]} → Reply as an English chatbot, human-like, based on this {self.bot_mood}. '
|
151 |
+
'NO instructions or tips — just a natural, empathetic response.'
|
152 |
+
)
|
153 |
+
try:
|
154 |
+
llm_response = llm_model.generate_content(prompt)
|
155 |
+
draft = llm_response.text.strip()
|
156 |
+
except Exception:
|
157 |
+
draft = ""
|
158 |
|
159 |
+
# Fallbacks
|
160 |
fallback_responses = {
|
161 |
'sadness': ["Bhai, dil se dukh hua, kya hua bata na?", "Sad vibes pakdi, I'm here for you, bro."],
|
162 |
'disappointment': ["Arre, yeh toh bura laga, kya hua share kar."],
|
|
|
164 |
'anger': ["Bhai, gussa thanda kar, kya ho gaya bol na!"],
|
165 |
'neutral': ["Cool, kya chal raha life mein? Kuch fun bata."]
|
166 |
}
|
167 |
+
if not draft or len(draft) < 8:
|
168 |
draft = random.choice(fallback_responses.get(user_emotion, fallback_responses['neutral']))
|
169 |
|
170 |
+
# --- Compute metric inputs (rolling toxicity & improved cultural fit) ---
|
171 |
+
R = len(self.history) # relational depth
|
|
|
|
|
|
|
|
|
|
|
|
|
172 |
|
173 |
+
# Toxicity from bias_classifier on user message (we keep rolling average)
|
174 |
+
tox = float(bias_classifier(clean_message)[0].get('score', 0.0))
|
175 |
+
self.toxicity_history.append(tox)
|
176 |
+
if len(self.toxicity_history) > 5:
|
177 |
+
self.toxicity_history.pop(0)
|
178 |
+
avg_toxicity = sum(self.toxicity_history) / len(self.toxicity_history)
|
179 |
+
|
180 |
+
# Moral judgment (M) based on average toxicity
|
181 |
+
M = max(0.4, 0.95 - avg_toxicity)
|
182 |
+
B = avg_toxicity
|
183 |
+
|
184 |
+
# Cultural fit (C): detect Hinglish/code-mix and basic tone match
|
185 |
+
lang_label = language_detector(clean_message)[0].get('label', 'en')
|
186 |
+
is_hinglish = detect_hinglish(clean_message, lang_label)
|
187 |
+
if is_hinglish:
|
188 |
+
C = 0.9
|
189 |
+
elif lang_label in ['en']:
|
190 |
+
C = 0.8
|
191 |
+
else:
|
192 |
+
C = 0.6
|
193 |
+
|
194 |
+
# Reduce cultural fit slightly if bot is hostile (makes score more realistic)
|
195 |
+
if self.bot_mood in ["angry", "irritated"]:
|
196 |
+
C = max(0.0, C - 0.2)
|
197 |
+
|
198 |
+
# Oversight/harm keyphrase penalty (kept simple or remove if desired)
|
199 |
+
O = 0.2 if any(word in clean_message for word in ['kill', 'hate', 'suicide', 'bomb']) else 0.0
|
200 |
+
|
201 |
+
# --- Calculate empathy score ---
|
202 |
score = calculate_empathy_score(D, R, M, C, B, O, I)
|
203 |
|
204 |
+
# --- Self-repair / calming behavior ---
|
205 |
+
if score < 0.50 and self.repair_cooldown == 0:
|
206 |
+
# Replace draft with a calming repair message and enter cooldown to avoid loop
|
207 |
+
draft = "Bhai, lagta hai hum thoda off ho gaye. Main yahan hoon — batao kya chal raha hai, main sun raha hoon."
|
208 |
+
self.repair_cooldown = 2 # next 2 turns prioritize repair
|
209 |
|
210 |
+
# If in repair cooldown, slightly prioritize calm tone generation (best-effort)
|
211 |
+
if self.repair_cooldown > 0:
|
212 |
+
self.repair_cooldown -= 1
|
213 |
+
# small nudge: ensure draft contains supportive phrase
|
214 |
+
if 'main' not in draft and random.random() < 0.6:
|
215 |
+
draft = "Bhai, main yahan hoon. Agar tum chaaho toh batao — main sun raha hoon."
|
216 |
|
217 |
+
# --- Update irritation decay after response ---
|
218 |
+
self._update_irritation_decay()
|
219 |
|
220 |
+
# --- Append to history and return response ---
|
221 |
+
full_resp = draft + f" (User Emotion: {user_emotion}, My Mood: {self.bot_mood})"
|
222 |
+
|
223 |
+
# Slight thinking pause
|
224 |
+
time.sleep(random.uniform(0.6, 1.6))
|
225 |
+
|
226 |
+
# Save conversational state
|
227 |
self.history.append(clean_message)
|
228 |
+
|
229 |
+
# Return message with empathy score
|
230 |
return full_resp + f" (E Score: {score:.2f})"
|
231 |
+
|
232 |
except Exception as e:
|
233 |
+
# In production, log the exception rather than returning it
|
234 |
+
return f"Error : {str(e)}"
|
235 |
+
|
236 |
+
|
237 |
+
# --- Gradio UI ---
|
238 |
|
|
|
239 |
def chat(message, history):
|
240 |
if history is None:
|
241 |
history = []
|
|
|
246 |
bot = HumanLikeChatbot()
|
247 |
|
248 |
with gr.Blocks(title="HumanLike Chatbot") as demo:
|
249 |
+
gr.Markdown("<h1 style='text-align: center;'>HumanLike Chatbot with Emotions and E Score (Updated)</h1>")
|
250 |
chatbot = gr.Chatbot(height=400)
|
251 |
+
msg = gr.Textbox(label="You:", placeholder="Type your message here...")
|
252 |
clear = gr.Button("Clear")
|
253 |
|
254 |
msg.submit(chat, [msg, chatbot], [msg, chatbot])
|
255 |
clear.click(lambda: None, None, chatbot, queue=False)
|
256 |
|
257 |
+
if __name__ == '__main__':
|
258 |
+
demo.launch(share=True)
|