ilang-ai commited on
Commit
39fe1a0
·
1 Parent(s): ecdeed3

fix: safe Gemini response extraction + startup logging

Browse files

- r.text throws ValueError when response is blocked, now caught
- _safe_text() extracts from candidates as fallback
- Startup logs show which .ilang files loaded and their size
- Fallback system prompt if .ilang files not found
- Detailed AI response logging for debugging

Files changed (1) hide show
  1. modules/chat.py +46 -13
modules/chat.py CHANGED
@@ -9,7 +9,7 @@ logger = logging.getLogger(__name__)
9
 
10
  genai.configure(api_key=config.GEMINI_API_KEY)
11
 
12
- # Relax Gemini safety filters — bot needs to understand slang and adult humor
13
  SAFETY_SETTINGS = [
14
  {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"},
15
  {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE"},
@@ -23,13 +23,20 @@ def _load_prompt(name):
23
  path = os.path.join(os.path.dirname(os.path.dirname(__file__)), d, name)
24
  if os.path.exists(path):
25
  with open(path, "r", encoding="utf-8") as f:
26
- return f.read()
 
 
 
27
  return ""
28
 
29
  SYSTEM_PROMPT = _load_prompt("persona.ilang")
30
  ANTISPAM_TEXT_PROMPT = _load_prompt("antispam.ilang")
31
  VISION_PROMPT = _load_prompt("vision.ilang")
32
 
 
 
 
 
33
  GROUP_WELCOME = (
34
  "I-Lang Guard is here\n\n"
35
  "I auto-clean spam. No config needed.\n"
@@ -90,6 +97,24 @@ def _ctx(history, info):
90
  return "\n".join(parts)
91
 
92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  def _deflect():
94
  lines = [
95
  "That's a tough one. What else can I help with?",
@@ -104,15 +129,23 @@ async def ai_text(text, history=None, context_info=""):
104
  c = _ctx(history, context_info)
105
  prompt = c + "\nuser: " + text if c else "user: " + text
106
  r = await model.generate_content_async(prompt)
107
- raw = r.text.strip() if r.text else ""
108
  if not raw:
109
- # Log the block reason if available
110
- if hasattr(r, 'prompt_feedback') and r.prompt_feedback:
111
- logger.warning("AI blocked: " + str(r.prompt_feedback))
 
 
 
 
 
 
 
112
  return ("chat", None, _deflect())
 
113
  return _parse(raw)
114
  except Exception as e:
115
- logger.warning("AI text: " + str(e))
116
  return ("chat", None, _deflect())
117
 
118
 
@@ -123,7 +156,7 @@ async def ai_vision(image_bytes, caption="", history=None, context_info=""):
123
  if caption:
124
  prompt += "\nuser: " + caption
125
  r = await vision_model.generate_content_async([prompt, {"mime_type": "image/jpeg", "data": image_bytes}])
126
- return _parse(r.text if r.text else "")
127
  except Exception as e:
128
  logger.warning("AI vision: " + str(e))
129
  return ("chat", None, "Couldn't read that image. Try another one?")
@@ -134,7 +167,7 @@ async def ai_voice(audio_bytes, mime_type="audio/ogg", history=None, context_inf
134
  c = _ctx(history, context_info)
135
  prompt = SYSTEM_PROMPT + "\n" + c + "\nUser sent a voice message:"
136
  r = await vision_model.generate_content_async([prompt, {"mime_type": mime_type, "data": audio_bytes}])
137
- return _parse(r.text if r.text else "")
138
  except Exception as e:
139
  logger.warning("AI voice: " + str(e))
140
  return ("chat", None, "Didn't catch that. Try again or type it out.")
@@ -144,7 +177,7 @@ async def ai_judge_group_message(text):
144
  try:
145
  prompt = ANTISPAM_TEXT_PROMPT + "\n\nMessage content: " + text[:1000]
146
  r = await vision_model.generate_content_async(prompt)
147
- result = r.text.strip().lower() if r.text else "ok"
148
  return "spam" in result
149
  except Exception:
150
  return False
@@ -156,7 +189,7 @@ async def ai_judge_group_image(image_bytes, caption=""):
156
  if caption:
157
  prompt += "\nCaption: " + caption[:500]
158
  r = await vision_model.generate_content_async([prompt, {"mime_type": "image/jpeg", "data": image_bytes}])
159
- result = r.text.strip().lower() if r.text else "ok"
160
  return "spam" in result
161
  except Exception:
162
  return False
@@ -171,7 +204,7 @@ async def ai_group_vision(image_bytes, caption="", history=None):
171
  else:
172
  prompt += "\nuser: [shared an image]"
173
  r = await vision_model.generate_content_async([prompt, {"mime_type": "image/jpeg", "data": image_bytes}])
174
- raw = r.text.strip() if r.text else ""
175
  if not raw:
176
  return _deflect()
177
  intent, device, reply = _parse(raw)
@@ -187,7 +220,7 @@ async def ai_group_reply(text, history=None):
187
  ctx = _ctx(history, "GROUP_CHAT: You were @mentioned in a group. Reply directly, 1-2 sentences.")
188
  prompt = ctx + "\nuser: " + text
189
  r = await model.generate_content_async(prompt)
190
- raw = r.text.strip() if r.text else ""
191
  if not raw:
192
  return _deflect()
193
  intent, device, reply = _parse(raw)
 
9
 
10
  genai.configure(api_key=config.GEMINI_API_KEY)
11
 
12
+ # Relax Gemini safety filters
13
  SAFETY_SETTINGS = [
14
  {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"},
15
  {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE"},
 
23
  path = os.path.join(os.path.dirname(os.path.dirname(__file__)), d, name)
24
  if os.path.exists(path):
25
  with open(path, "r", encoding="utf-8") as f:
26
+ content = f.read()
27
+ logger.info("Loaded prompt: " + path + " (" + str(len(content)) + " chars)")
28
+ return content
29
+ logger.warning("Prompt not found: " + name)
30
  return ""
31
 
32
  SYSTEM_PROMPT = _load_prompt("persona.ilang")
33
  ANTISPAM_TEXT_PROMPT = _load_prompt("antispam.ilang")
34
  VISION_PROMPT = _load_prompt("vision.ilang")
35
 
36
+ if not SYSTEM_PROMPT:
37
+ SYSTEM_PROMPT = "You are TelegramGuard, a helpful AI assistant on Telegram. Reply concisely in the user's language. JSON format: {\"intent\": \"chat\", \"device\": null, \"reply\": \"your text\"}"
38
+ logger.warning("Using fallback system prompt")
39
+
40
  GROUP_WELCOME = (
41
  "I-Lang Guard is here\n\n"
42
  "I auto-clean spam. No config needed.\n"
 
97
  return "\n".join(parts)
98
 
99
 
100
+ def _safe_text(response):
101
+ """Safely extract text from Gemini response — r.text throws ValueError when blocked."""
102
+ try:
103
+ if response.text:
104
+ return response.text.strip()
105
+ except (ValueError, AttributeError):
106
+ pass
107
+ # Try extracting from candidates
108
+ try:
109
+ if response.candidates:
110
+ for c in response.candidates:
111
+ if hasattr(c, 'content') and c.content and c.content.parts:
112
+ return c.content.parts[0].text.strip()
113
+ except Exception:
114
+ pass
115
+ return ""
116
+
117
+
118
  def _deflect():
119
  lines = [
120
  "That's a tough one. What else can I help with?",
 
129
  c = _ctx(history, context_info)
130
  prompt = c + "\nuser: " + text if c else "user: " + text
131
  r = await model.generate_content_async(prompt)
132
+ raw = _safe_text(r)
133
  if not raw:
134
+ feedback = ""
135
+ if hasattr(r, 'prompt_feedback'):
136
+ feedback = str(r.prompt_feedback)
137
+ if hasattr(r, 'candidates') and r.candidates:
138
+ for cand in r.candidates:
139
+ if hasattr(cand, 'finish_reason'):
140
+ feedback += " finish:" + str(cand.finish_reason)
141
+ if hasattr(cand, 'safety_ratings'):
142
+ feedback += " safety:" + str(cand.safety_ratings)
143
+ logger.warning("AI empty response. feedback=" + feedback + " prompt_len=" + str(len(prompt)))
144
  return ("chat", None, _deflect())
145
+ logger.info("AI raw[" + str(len(raw)) + "]: " + raw[:200])
146
  return _parse(raw)
147
  except Exception as e:
148
+ logger.warning("AI text exception: " + str(e))
149
  return ("chat", None, _deflect())
150
 
151
 
 
156
  if caption:
157
  prompt += "\nuser: " + caption
158
  r = await vision_model.generate_content_async([prompt, {"mime_type": "image/jpeg", "data": image_bytes}])
159
+ return _parse(_safe_text(r))
160
  except Exception as e:
161
  logger.warning("AI vision: " + str(e))
162
  return ("chat", None, "Couldn't read that image. Try another one?")
 
167
  c = _ctx(history, context_info)
168
  prompt = SYSTEM_PROMPT + "\n" + c + "\nUser sent a voice message:"
169
  r = await vision_model.generate_content_async([prompt, {"mime_type": mime_type, "data": audio_bytes}])
170
+ return _parse(_safe_text(r))
171
  except Exception as e:
172
  logger.warning("AI voice: " + str(e))
173
  return ("chat", None, "Didn't catch that. Try again or type it out.")
 
177
  try:
178
  prompt = ANTISPAM_TEXT_PROMPT + "\n\nMessage content: " + text[:1000]
179
  r = await vision_model.generate_content_async(prompt)
180
+ result = (_safe_text(r) or "ok").lower()
181
  return "spam" in result
182
  except Exception:
183
  return False
 
189
  if caption:
190
  prompt += "\nCaption: " + caption[:500]
191
  r = await vision_model.generate_content_async([prompt, {"mime_type": "image/jpeg", "data": image_bytes}])
192
+ result = (_safe_text(r) or "ok").lower()
193
  return "spam" in result
194
  except Exception:
195
  return False
 
204
  else:
205
  prompt += "\nuser: [shared an image]"
206
  r = await vision_model.generate_content_async([prompt, {"mime_type": "image/jpeg", "data": image_bytes}])
207
+ raw = _safe_text(r)
208
  if not raw:
209
  return _deflect()
210
  intent, device, reply = _parse(raw)
 
220
  ctx = _ctx(history, "GROUP_CHAT: You were @mentioned in a group. Reply directly, 1-2 sentences.")
221
  prompt = ctx + "\nuser: " + text
222
  r = await model.generate_content_async(prompt)
223
+ raw = _safe_text(r)
224
  if not raw:
225
  return _deflect()
226
  intent, device, reply = _parse(raw)