ItsCxdy commited on
Commit
b6ce223
Β·
verified Β·
1 Parent(s): 087fb69

Update telegram_bot.py

Browse files
Files changed (1) hide show
  1. telegram_bot.py +202 -202
telegram_bot.py CHANGED
@@ -1,22 +1,37 @@
 
1
  import os
2
  import requests
3
  import json
4
  from dotenv import load_dotenv
5
- from telegram import Update, ReplyKeyboardMarkup, ReplyKeyboardRemove
6
  import datetime
7
- from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes, ConversationHandler
8
  import logging
9
 
10
- # Using the dedicated packages as recommended by LangChain warnings
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  from langchain_huggingface import HuggingFaceEmbeddings
12
- from langchain_community.vectorstores import Chroma
13
 
14
- # Load environment variables
15
- # NOTE: load_dotenv() is mainly for local development. In deployment,
16
- # environment variables are set by the hosting platform (Hugging Face Secrets).
17
  load_dotenv()
18
 
19
- # Enable logging
20
  logging.basicConfig(
21
  format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
22
  level=logging.INFO
@@ -27,37 +42,51 @@ logger = logging.getLogger(__name__)
27
  DESCRIBING_SYMPTOMS = 1
28
 
29
  class TelegramHomeopathyBot:
30
- def __init__(self):
31
- # Create logs directory if not exists
32
  self.logs_dir = "UserChatLogs"
33
  os.makedirs(self.logs_dir, exist_ok=True)
34
-
35
- # Switched to a standard, reliable embedding model.
36
- TINY_EMBEDDING_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
37
-
38
- # Initialize embeddings and vector store
39
- self.embeddings = HuggingFaceEmbeddings(
40
- model_name=TINY_EMBEDDING_MODEL,
41
- # Explicitly set device to 'cpu' for memory constrained environments
42
- model_kwargs={'device': 'cpu'}
43
- )
44
- # Using a reliable embedding function is critical for RAG performance
45
- self.vector_store = Chroma(
46
- persist_directory="./vector_db",
47
- embedding_function=self.embeddings
48
- )
49
- self.retriever = self.vector_store.as_retriever(search_kwargs={"k": 4})
50
-
51
- # Store user sessions: includes chat_history
52
  self.user_sessions = {}
53
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  def log_chat(self, user_id: int, username: str, message: str, is_bot: bool = False):
55
  """Log user/bot messages to individual files"""
56
  try:
57
  log_file = os.path.join(self.logs_dir, f"user_{user_id}.log")
58
  timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
59
  direction = "BOT" if is_bot else "USER"
60
-
61
  with open(log_file, "a", encoding="utf-8") as f:
62
  f.write(f"[{timestamp}] {direction} ({username}): {message}\n")
63
  except Exception as e:
@@ -72,35 +101,42 @@ class TelegramHomeopathyBot:
72
  'last_query': None
73
  }
74
  return self.user_sessions[user_id]
75
-
76
- def get_relevant_context(self, query, history):
77
  """
78
- Retrieve relevant context from the vector database using the latest query
79
- and the accumulated user history for better RAG results.
80
  """
81
- user_history = [msg['content'] for msg in history if msg['role'] == 'user']
 
 
 
 
82
  history_summary = " ".join(user_history)
83
-
84
  composite_query = f"Patient's case summary: {history_summary} {query}" if history_summary else query
85
-
86
- docs = self.retriever.invoke(composite_query)
87
- context = "\n".join([doc.page_content for doc in docs])
88
- return context
89
-
90
- def query_ai(self, user_message, context, history):
91
- """Query the AI model via Chute.ai"""
 
 
 
 
 
92
  api_key = os.getenv("CHUTEAI_API_KEY")
93
-
94
  if not api_key:
95
- logger.error("🚨 CRITICAL API ERROR: CHUTEAI_API_KEY is missing or empty during function call.")
96
  return "❌ Configuration Error: The AI service API key (CHUTEAI_API_KEY) is missing. Please check your setup."
97
 
98
  headers = {
99
  "Authorization": f"Bearer {api_key}",
100
  "Content-Type": "application/json"
101
  }
102
-
103
- # System prompt remains the same
104
  system_prompt = """You are an expert Homeopathy Doctor. Your goal is to find the best possible remedy from the provided Context.
105
 
106
  **DIAGNOSTIC PROCESS & CONSTRAINTS:**
@@ -112,95 +148,85 @@ class TelegramHomeopathyBot:
112
  4. **Safety:** If unsure or the context doesn't contain a relevant remedy, admit it honestly.
113
  5. **Tone:** Always be professional, caring, and responsible.
114
  """
 
115
  messages = [
116
  {"role": "system", "content": system_prompt},
117
  *history,
118
  {"role": "user", "content": f"Context from homeopathy book:\n{context}\n\nPatient Complaint: {user_message}"}
119
  ]
120
-
121
  data = {
122
- "model": "meituan-longcat/LongCat-Flash-Chat-FP8",
123
  "messages": messages,
124
  "temperature": 0.2,
125
  "max_tokens": 500
126
  }
127
-
128
  api_url = "https://llm.chutes.ai/v1/chat/completions"
129
 
130
  try:
131
- # Set connection and read timeouts for the AI query request
132
  response = requests.post(
133
  api_url,
134
  headers=headers,
135
  json=data,
136
- timeout=(5, 30) # 5s connect timeout, 30s read timeout
137
  )
138
-
139
  if response.status_code == 200:
140
- return response.json()["choices"][0]["message"]["content"]
 
141
  else:
142
- error_data = response.json()
143
- error_message = error_data.get("error", {}).get("message", "No detailed error message.")
144
-
 
 
145
  if response.status_code == 401:
146
- logger.error("🚨 CRITICAL API ERROR (401 Unauthorized) 🚨: The CHUTEAI_API_KEY is likely invalid or missing. Please check your setup.")
147
-
148
  logger.error(f"Chute.ai Error {response.status_code}: {error_message}")
149
  return f"❌ I'm having technical difficulties. Please try again later. (Error: {response.status_code})"
150
-
151
  except requests.exceptions.Timeout:
152
- logger.error("Connection Error: Chute.ai request timed out.")
153
  return "❌ Connection error: The AI service took too long to respond. Please try again."
154
  except Exception as e:
155
- logger.error(f"Connection Error: {e}")
156
  return f"❌ Connection error. Please try again. Error: {str(e)}"
157
 
158
- # Create bot instance
159
- # We wrap the initialization in a try/except block to catch memory-related failures early
160
  try:
161
- homeopathy_bot = TelegramHomeopathyBot()
162
  except Exception as e:
163
- logger.error(f"Failed to initialize TelegramHomeopathyBot (likely due to memory): {e}")
164
  homeopathy_bot = None
165
 
166
- # Telegram Bot Handlers
 
167
  async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
168
- """Send welcome message when the command /start is issued."""
169
  if not homeopathy_bot:
170
  await update.message.reply_text("❌ Initialization Error: The AI service failed to start due to resource constraints. Please check the logs.")
171
  return ConversationHandler.END
172
-
173
- user = update.effective_user
174
- welcome_text = f"""
175
- πŸ‘‹ Hello *{user.first_name}*! I'm your AI Homeopathy Doctor πŸ€–
176
 
177
- I can help you analyze symptoms and suggest potential homeopathic remedies based on medical knowledge.
 
178
 
179
  πŸ’‘ *How to use:*
180
  1. Describe your symptoms in detail
181
  2. I'll ask clarifying questions (max 3 in the first turn)
182
  3. I'll suggest potential homeopathic remedies within 3 turns
183
 
184
- ⚠️ *Important Disclaimer:*
185
- This is for educational purposes only. Always consult a qualified homeopath or medical professional for proper diagnosis and treatment.
186
-
187
  Type your symptoms below to begin...
188
- """
189
-
190
- # Reset session for a clean start
191
  user_id = update.effective_user.id
192
- homeopathy_bot.user_sessions[user_id] = {'chat_history': [], 'consultation_count': 0}
193
-
194
  await update.message.reply_text(welcome_text, parse_mode='Markdown')
195
- # Log start command
196
- homeopathy_bot.log_chat(
197
- user.id,
198
- user.username or f"{user.first_name} {user.last_name or ''}".strip(),
199
- "/start command received",
200
- is_bot=False
201
- )
202
  return DESCRIBING_SYMPTOMS
203
 
 
204
  async def handle_symptoms(update: Update, context: ContextTypes.DEFAULT_TYPE):
205
  """Handle user's symptom description and continue the diagnosis."""
206
  if not homeopathy_bot:
@@ -208,112 +234,95 @@ async def handle_symptoms(update: Update, context: ContextTypes.DEFAULT_TYPE):
208
  return ConversationHandler.END
209
 
210
  user_id = update.effective_user.id
211
- user_input = update.message.text
212
-
213
- # Get user session
214
  session = homeopathy_bot.get_user_session(user_id)
215
  session['consultation_count'] += 1
216
-
217
- # Send typing action
218
- await update.message.reply_chat_action(action="typing")
219
-
220
- # Send processing message
 
 
221
  processing_msg = await update.message.reply_text("πŸ” Analyzing your symptoms...")
222
-
223
  try:
224
- # Get relevant context (using the history to enrich the retrieval query)
225
  context_text = homeopathy_bot.get_relevant_context(user_input, session['chat_history'])
226
-
227
- # Get AI response
228
  response = homeopathy_bot.query_ai(user_input, context_text, session['chat_history'])
229
-
230
- # Update chat history
231
- if not response.startswith("❌"): # Only update history if not an error message
232
  session['chat_history'].append({"role": "user", "content": user_input})
233
  session['chat_history'].append({"role": "assistant", "content": response})
234
-
235
- # Log user input and bot response
236
  user = update.effective_user
237
- homeopathy_bot.log_chat(
238
- user.id,
239
- user.username or f"{user.first_name} {user.last_name or ''}".strip(),
240
- user_input,
241
- is_bot=False
242
- )
243
- homeopathy_bot.log_chat(
244
- user.id,
245
- user.username or f"{user.first_name} {user.last_name or ''}".strip(),
246
- response,
247
- is_bot=True
248
- )
249
-
250
- # Keep only last 6 messages (3 user + 3 assistant) to prevent context overflow
251
  if len(session['chat_history']) > 6:
252
  session['chat_history'] = session['chat_history'][-6:]
253
-
254
- # Send response using Markdown for clear formatting
255
  try:
256
- await processing_msg.delete() # Remove processing message
257
- except Exception as e:
258
- logger.warning(f"Could not delete processing message: {e}")
259
- await update.message.reply_text(
260
- f"🩺 *Homeopathy Doctor:*\n\n{response}",
261
- parse_mode='Markdown' # Use Markdown for formatting
262
- )
263
-
264
- # Add quick actions
265
  quick_actions = [["πŸ” Describe more symptoms or answer questions"], ["πŸ”„ Start a new consultation"]]
266
  reply_markup = ReplyKeyboardMarkup(quick_actions, one_time_keyboard=True, resize_keyboard=True)
267
  await update.message.reply_text("What would you like to do next?", reply_markup=reply_markup)
268
-
269
  except Exception as e:
270
  logger.error(f"Error processing message: {e}")
271
  try:
272
  await processing_msg.delete()
273
- except:
274
  pass
275
  await update.message.reply_text("❌ Sorry, I encountered an error. Please try again.")
276
-
277
  return DESCRIBING_SYMPTOMS
278
 
 
279
  async def handle_quick_actions(update: Update, context: ContextTypes.DEFAULT_TYPE):
280
  """Handle quick action buttons."""
281
- user_input = update.message.text
282
-
283
  if user_input in ["πŸ” Describe more symptoms or answer questions", "Reset please"]:
284
- if len(homeopathy_bot.get_user_session(update.effective_user.id)['chat_history']) >= 4: # If 2 turns have passed
285
- await update.message.reply_text("Please provide any additional details or clarify the Doctor's previous questions...")
286
- else:
287
- await update.message.reply_text("Please provide any additional details or clarify the Doctor's previous questions...")
288
-
289
  elif user_input in ["πŸ”„ Start a new consultation", "Reset please"]:
290
  user_id = update.effective_user.id
291
  if user_id in homeopathy_bot.user_sessions:
292
- # Clear history and reset embeddings for a completely fresh start
293
- homeopathy_bot.user_sessions[user_id] = {
294
- 'chat_history': [],
295
- 'consultation_count': 0,
296
- 'last_query': None # Reset any cached query context
297
- }
298
- # Reinitialize the retriever to clear any cached context
299
- homeopathy_bot.retriever = homeopathy_bot.vector_store.as_retriever(search_kwargs={"k": 4})
300
  await update.message.reply_text("πŸ”„ Starting new consultation. Please describe your symptoms...", reply_markup=ReplyKeyboardRemove())
301
-
302
  return DESCRIBING_SYMPTOMS
303
 
 
304
  async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE):
305
  """Cancel the conversation."""
306
  user_id = update.effective_user.id
307
  if user_id in homeopathy_bot.user_sessions:
308
  del homeopathy_bot.user_sessions[user_id]
309
-
310
  await update.message.reply_text(
311
- "πŸ‘‹ Consultation ended. Thank you for using Homeopathy AI Doctor!\n\n"
312
- "Remember to consult a qualified homeopath for proper treatment. 🌿",
313
  reply_markup=ReplyKeyboardRemove()
314
  )
315
  return ConversationHandler.END
316
 
 
317
  async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
318
  """Send help message."""
319
  help_text = """
@@ -327,75 +336,73 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
327
  *How to get the best results:*
328
  β€’ Describe symptoms in detail
329
  β€’ Mention location, intensity, and timing
330
- β€’ Share what makes symptoms better/worse
331
  β€’ Be specific about associated feelings
332
 
333
  *Disclaimer:* This bot provides educational information only. Always consult qualified medical professionals.
334
- """
335
  await update.message.reply_text(help_text, parse_mode='Markdown')
336
 
337
- # FIX: Add a new handler to catch unhandled text messages and guide the user.
338
  async def generic_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
339
  """Handles any plain text message received outside the ConversationHandler."""
340
  await update.message.reply_text(
341
- "πŸ‘‹ Welcome! To begin a new consultation and describe your symptoms, please use the */start* command. Thank you!",
342
  parse_mode='Markdown'
343
  )
344
 
345
- async def error_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
 
346
  """Log errors and send a friendly message."""
347
  logger.error(f"Update {update} caused error {context.error}")
348
- if update and update.message:
349
- await update.message.reply_text(
350
- "❌ Sorry, I encountered an unexpected error. Please try again or use /start to begin a new consultation."
351
- )
 
 
 
 
352
 
353
  def check_dependencies():
354
  """Checks for required environment variables and the vector database."""
355
-
356
  if not os.path.exists("./vector_db"):
357
- print("❌ Vector database not found. Please ensure 'vector_db' folder is uploaded.")
358
- return False
359
-
360
  if not os.getenv("CHUTEAI_API_KEY"):
361
- print("❌ CHUTEAI_API_KEY not found in environment. AI queries will fail.")
362
- return True # Allow start, but AI will fail
363
-
364
  return True
365
 
 
366
  def main():
367
- """Start the bot."""
368
-
369
- # 🚨 CRITICAL CHECK: Ensure the token is available
370
  TELEGRAM_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
371
  if not TELEGRAM_TOKEN:
372
  print("FATAL ERROR: TELEGRAM_BOT_TOKEN is missing from environment secrets. Cannot initialize the Telegram client.")
373
  return
374
-
375
- # NEW: Sanity check to confirm the token is read and has a valid length
376
  if len(TELEGRAM_TOKEN) < 40:
377
  print(f"WARNING: TELEGRAM_BOT_TOKEN has suspicious length ({len(TELEGRAM_TOKEN)}). Double-check the secret value.")
378
-
379
  if not check_dependencies():
380
  return
381
-
382
  print("πŸš€ Starting Telegram Homeopathy Bot...")
383
-
384
- # Create Application
385
- # CRITICAL FIX: The 'request_kwargs' must be passed to .build() method, not chained on the builder object.
 
 
 
 
 
386
  application = (
387
  Application.builder()
388
  .token(TELEGRAM_TOKEN)
389
- .build(
390
- # Explicitly set low timeouts to force failures and retries faster if DNS fails
391
- request_kwargs={
392
- 'connect_timeout': 5.0, # 5 seconds to establish connection
393
- 'read_timeout': 15.0 # 15 seconds to receive the first byte
394
- }
395
- )
396
  )
397
-
398
- # Add conversation handler
399
  conv_handler = ConversationHandler(
400
  entry_points=[CommandHandler('start', start)],
401
  states={
@@ -406,27 +413,20 @@ def main():
406
  },
407
  fallbacks=[CommandHandler('cancel', cancel)],
408
  )
409
-
410
- # Add handlers
411
  application.add_handler(conv_handler)
412
  application.add_handler(CommandHandler('help', help_command))
413
  application.add_handler(CommandHandler('cancel', cancel))
414
-
415
- # Handler for general text outside the conversation flow
416
  application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, generic_message))
417
-
418
- # Add error handler
419
  application.add_error_handler(error_handler)
420
-
421
- # Start the Bot
422
  print("βœ… Bot is running... Press Ctrl+C to stop")
423
- # Using bootstrap_retries to handle the recurrent network failure during startup
424
- application.run_polling(
425
- allowed_updates=Update.ALL_TYPES,
426
- bootstrap_retries=5,
427
- )
428
 
429
  if __name__ == '__main__':
430
- # Ensure all initialization warnings are visible before starting the main loop
431
  main()
432
-
 
1
+ # telegram_bot.py
2
  import os
3
  import requests
4
  import json
5
  from dotenv import load_dotenv
 
6
  import datetime
 
7
  import logging
8
 
9
+ # Telegram imports (PTB v20+)
10
+ from telegram import Update, ReplyKeyboardMarkup, ReplyKeyboardRemove
11
+ from telegram.request import HTTPXRequest
12
+ from telegram.constants import ChatAction
13
+ from telegram.ext import (
14
+ Application, CommandHandler, MessageHandler, filters,
15
+ ContextTypes, ConversationHandler
16
+ )
17
+
18
+ # LangChain embeddings + Chroma. Provide a fallback import for the new package name.
19
+ try:
20
+ # older usage
21
+ from langchain_community.vectorstores import Chroma
22
+ except Exception:
23
+ try:
24
+ # attempt newer package import if available
25
+ from langchain_chroma import Chroma
26
+ except Exception:
27
+ Chroma = None
28
+
29
  from langchain_huggingface import HuggingFaceEmbeddings
 
30
 
31
+ # Load environment variables (useful for local dev)
 
 
32
  load_dotenv()
33
 
34
+ # Logging
35
  logging.basicConfig(
36
  format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
37
  level=logging.INFO
 
42
  DESCRIBING_SYMPTOMS = 1
43
 
44
  class TelegramHomeopathyBot:
45
+ def __init__(self, use_vector_db: bool = True):
 
46
  self.logs_dir = "UserChatLogs"
47
  os.makedirs(self.logs_dir, exist_ok=True)
48
+
49
+ self.embeddings = None
50
+ self.vector_store = None
51
+ self.retriever = None
52
+
53
+ # store user sessions in memory
 
 
 
 
 
 
 
 
 
 
 
 
54
  self.user_sessions = {}
55
+
56
+ # Initialize embeddings & vector store lazily to reduce startup memory spike
57
+ if use_vector_db and Chroma is not None:
58
+ try:
59
+ # embedding model (cpu)
60
+ TINY_EMBEDDING_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
61
+ self.embeddings = HuggingFaceEmbeddings(
62
+ model_name=TINY_EMBEDDING_MODEL,
63
+ model_kwargs={'device': 'cpu'}
64
+ )
65
+ # initialize Chroma vector store (will create directory if missing)
66
+ self.vector_store = Chroma(
67
+ persist_directory="./vector_db",
68
+ embedding_function=self.embeddings
69
+ )
70
+ self.retriever = self.vector_store.as_retriever(search_kwargs={"k": 4})
71
+ logger.info("Vector DB and retriever initialized.")
72
+ except Exception as e:
73
+ # If vector DB fails, keep running but log the issue
74
+ logger.error(f"Failed to initialize vector DB or embeddings: {e}")
75
+ self.embeddings = None
76
+ self.vector_store = None
77
+ self.retriever = None
78
+ else:
79
+ if Chroma is None:
80
+ logger.warning("Chroma import is not available. Skipping vector DB initialization.")
81
+ else:
82
+ logger.info("Vector DB initialization disabled by flag.")
83
+
84
  def log_chat(self, user_id: int, username: str, message: str, is_bot: bool = False):
85
  """Log user/bot messages to individual files"""
86
  try:
87
  log_file = os.path.join(self.logs_dir, f"user_{user_id}.log")
88
  timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
89
  direction = "BOT" if is_bot else "USER"
 
90
  with open(log_file, "a", encoding="utf-8") as f:
91
  f.write(f"[{timestamp}] {direction} ({username}): {message}\n")
92
  except Exception as e:
 
101
  'last_query': None
102
  }
103
  return self.user_sessions[user_id]
104
+
105
+ def get_relevant_context(self, query: str, history: list):
106
  """
107
+ Retrieve relevant context from the vector database.
108
+ Returns a string (concatenated page_content) or empty string.
109
  """
110
+ if not self.retriever:
111
+ return ""
112
+
113
+ # Build a composite query using user history to improve retrieval
114
+ user_history = [msg['content'] for msg in history if msg.get('role') == 'user']
115
  history_summary = " ".join(user_history)
 
116
  composite_query = f"Patient's case summary: {history_summary} {query}" if history_summary else query
117
+
118
+ try:
119
+ # Proper LangChain retriever call
120
+ docs = self.retriever.get_relevant_documents(composite_query)
121
+ context = "\n".join([getattr(d, "page_content", str(d)) for d in docs])
122
+ return context
123
+ except Exception as e:
124
+ logger.error(f"Retrieval error: {e}")
125
+ return ""
126
+
127
+ def query_ai(self, user_message: str, context: str, history: list):
128
+ """Query the AI model via Chute.ai (or configured LLM endpoint)."""
129
  api_key = os.getenv("CHUTEAI_API_KEY")
130
+
131
  if not api_key:
132
+ logger.error("CRITICAL: CHUTEAI_API_KEY is missing.")
133
  return "❌ Configuration Error: The AI service API key (CHUTEAI_API_KEY) is missing. Please check your setup."
134
 
135
  headers = {
136
  "Authorization": f"Bearer {api_key}",
137
  "Content-Type": "application/json"
138
  }
139
+
 
140
  system_prompt = """You are an expert Homeopathy Doctor. Your goal is to find the best possible remedy from the provided Context.
141
 
142
  **DIAGNOSTIC PROCESS & CONSTRAINTS:**
 
148
  4. **Safety:** If unsure or the context doesn't contain a relevant remedy, admit it honestly.
149
  5. **Tone:** Always be professional, caring, and responsible.
150
  """
151
+
152
  messages = [
153
  {"role": "system", "content": system_prompt},
154
  *history,
155
  {"role": "user", "content": f"Context from homeopathy book:\n{context}\n\nPatient Complaint: {user_message}"}
156
  ]
157
+
158
  data = {
159
+ "model": "meituan-longcat/LongCat-Flash-Chat-FP8",
160
  "messages": messages,
161
  "temperature": 0.2,
162
  "max_tokens": 500
163
  }
164
+
165
  api_url = "https://llm.chutes.ai/v1/chat/completions"
166
 
167
  try:
 
168
  response = requests.post(
169
  api_url,
170
  headers=headers,
171
  json=data,
172
+ timeout=(5, 30) # connect + read
173
  )
 
174
  if response.status_code == 200:
175
+ j = response.json()
176
+ return j["choices"][0]["message"]["content"]
177
  else:
178
+ try:
179
+ error_data = response.json()
180
+ error_message = error_data.get("error", {}).get("message", "No detailed error message.")
181
+ except Exception:
182
+ error_message = response.text
183
  if response.status_code == 401:
184
+ logger.error("CHUTEAI_API_KEY is likely invalid or unauthorized (401).")
 
185
  logger.error(f"Chute.ai Error {response.status_code}: {error_message}")
186
  return f"❌ I'm having technical difficulties. Please try again later. (Error: {response.status_code})"
 
187
  except requests.exceptions.Timeout:
188
+ logger.error("Chute.ai request timed out.")
189
  return "❌ Connection error: The AI service took too long to respond. Please try again."
190
  except Exception as e:
191
+ logger.error(f"Connection error while querying AI: {e}")
192
  return f"❌ Connection error. Please try again. Error: {str(e)}"
193
 
194
+
195
+ # Create bot instance (lazy)
196
  try:
197
+ homeopathy_bot = TelegramHomeopathyBot(use_vector_db=True)
198
  except Exception as e:
199
+ logger.error(f"Failed to initialize TelegramHomeopathyBot: {e}")
200
  homeopathy_bot = None
201
 
202
+
203
+ # Telegram handlers (async)
204
  async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
205
+ """Send welcome message and reset session when /start is issued."""
206
  if not homeopathy_bot:
207
  await update.message.reply_text("❌ Initialization Error: The AI service failed to start due to resource constraints. Please check the logs.")
208
  return ConversationHandler.END
 
 
 
 
209
 
210
+ user = update.effective_user
211
+ welcome_text = f"""πŸ‘‹ Hello *{user.first_name}*! I'm your AI Homeopathy Doctor πŸ€–
212
 
213
  πŸ’‘ *How to use:*
214
  1. Describe your symptoms in detail
215
  2. I'll ask clarifying questions (max 3 in the first turn)
216
  3. I'll suggest potential homeopathic remedies within 3 turns
217
 
218
+ ⚠️ *Disclaimer:* This is for educational purposes only. Always consult a qualified homeopath or medical professional.
 
 
219
  Type your symptoms below to begin...
220
+ """
221
+ # Reset session
 
222
  user_id = update.effective_user.id
223
+ homeopathy_bot.user_sessions[user_id] = {'chat_history': [], 'consultation_count': 0, 'last_query': None}
224
+
225
  await update.message.reply_text(welcome_text, parse_mode='Markdown')
226
+ homeopathy_bot.log_chat(user.id, user.username or f"{user.first_name} {user.last_name or ''}".strip(), "/start command received", is_bot=False)
 
 
 
 
 
 
227
  return DESCRIBING_SYMPTOMS
228
 
229
+
230
  async def handle_symptoms(update: Update, context: ContextTypes.DEFAULT_TYPE):
231
  """Handle user's symptom description and continue the diagnosis."""
232
  if not homeopathy_bot:
 
234
  return ConversationHandler.END
235
 
236
  user_id = update.effective_user.id
237
+ user_input = update.message.text or ""
 
 
238
  session = homeopathy_bot.get_user_session(user_id)
239
  session['consultation_count'] += 1
240
+
241
+ # Send typing action properly
242
+ try:
243
+ await context.bot.send_chat_action(chat_id=update.effective_chat.id, action=ChatAction.TYPING)
244
+ except Exception:
245
+ pass
246
+
247
  processing_msg = await update.message.reply_text("πŸ” Analyzing your symptoms...")
248
+
249
  try:
250
+ # Retrieve context
251
  context_text = homeopathy_bot.get_relevant_context(user_input, session['chat_history'])
 
 
252
  response = homeopathy_bot.query_ai(user_input, context_text, session['chat_history'])
253
+
254
+ # Update chat history only if response isn't an error notice
255
+ if not response.startswith("❌"):
256
  session['chat_history'].append({"role": "user", "content": user_input})
257
  session['chat_history'].append({"role": "assistant", "content": response})
258
+
259
+ # Logging
260
  user = update.effective_user
261
+ homeopathy_bot.log_chat(user.id, user.username or f"{user.first_name} {user.last_name or ''}".strip(), user_input, is_bot=False)
262
+ homeopathy_bot.log_chat(user.id, user.username or f"{user.first_name} {user.last_name or ''}".strip(), response, is_bot=True)
263
+
264
+ # Keep only last 6 messages to prevent context overflow
 
 
 
 
 
 
 
 
 
 
265
  if len(session['chat_history']) > 6:
266
  session['chat_history'] = session['chat_history'][-6:]
267
+
268
+ # Delete processing message if possible
269
  try:
270
+ await processing_msg.delete()
271
+ except Exception:
272
+ pass
273
+
274
+ await update.message.reply_text(f"🩺 *Homeopathy Doctor:*\n\n{response}", parse_mode='Markdown')
275
+
276
+ # Quick action buttons
 
 
277
  quick_actions = [["πŸ” Describe more symptoms or answer questions"], ["πŸ”„ Start a new consultation"]]
278
  reply_markup = ReplyKeyboardMarkup(quick_actions, one_time_keyboard=True, resize_keyboard=True)
279
  await update.message.reply_text("What would you like to do next?", reply_markup=reply_markup)
280
+
281
  except Exception as e:
282
  logger.error(f"Error processing message: {e}")
283
  try:
284
  await processing_msg.delete()
285
+ except Exception:
286
  pass
287
  await update.message.reply_text("❌ Sorry, I encountered an error. Please try again.")
288
+
289
  return DESCRIBING_SYMPTOMS
290
 
291
+
292
  async def handle_quick_actions(update: Update, context: ContextTypes.DEFAULT_TYPE):
293
  """Handle quick action buttons."""
294
+ user_input = update.message.text or ""
295
+
296
  if user_input in ["πŸ” Describe more symptoms or answer questions", "Reset please"]:
297
+ await update.message.reply_text("Please provide any additional details or clarify the Doctor's previous questions...")
 
 
 
 
298
  elif user_input in ["πŸ”„ Start a new consultation", "Reset please"]:
299
  user_id = update.effective_user.id
300
  if user_id in homeopathy_bot.user_sessions:
301
+ homeopathy_bot.user_sessions[user_id] = {'chat_history': [], 'consultation_count': 0, 'last_query': None}
302
+ if homeopathy_bot.vector_store:
303
+ # reinitialize retriever to clear cache (if available)
304
+ try:
305
+ homeopathy_bot.retriever = homeopathy_bot.vector_store.as_retriever(search_kwargs={"k": 4})
306
+ except Exception:
307
+ pass
 
308
  await update.message.reply_text("πŸ”„ Starting new consultation. Please describe your symptoms...", reply_markup=ReplyKeyboardRemove())
309
+
310
  return DESCRIBING_SYMPTOMS
311
 
312
+
313
  async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE):
314
  """Cancel the conversation."""
315
  user_id = update.effective_user.id
316
  if user_id in homeopathy_bot.user_sessions:
317
  del homeopathy_bot.user_sessions[user_id]
318
+
319
  await update.message.reply_text(
320
+ "πŸ‘‹ Consultation ended. Thank you for using Homeopathy AI Doctor!\n\nRemember to consult a qualified homeopath for proper treatment. 🌿",
 
321
  reply_markup=ReplyKeyboardRemove()
322
  )
323
  return ConversationHandler.END
324
 
325
+
326
  async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
327
  """Send help message."""
328
  help_text = """
 
336
  *How to get the best results:*
337
  β€’ Describe symptoms in detail
338
  β€’ Mention location, intensity, and timing
 
339
  β€’ Be specific about associated feelings
340
 
341
  *Disclaimer:* This bot provides educational information only. Always consult qualified medical professionals.
342
+ """
343
  await update.message.reply_text(help_text, parse_mode='Markdown')
344
 
345
+
346
  async def generic_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
347
  """Handles any plain text message received outside the ConversationHandler."""
348
  await update.message.reply_text(
349
+ "πŸ‘‹ Welcome! To begin a new consultation and describe your symptoms, please use the */start* command.",
350
  parse_mode='Markdown'
351
  )
352
 
353
+
354
+ async def error_handler(update: object, context: ContextTypes.DEFAULT_TYPE):
355
  """Log errors and send a friendly message."""
356
  logger.error(f"Update {update} caused error {context.error}")
357
+ try:
358
+ if getattr(update, "message", None):
359
+ await update.message.reply_text(
360
+ "❌ Sorry, I encountered an unexpected error. Please try again or use /start to begin a new consultation."
361
+ )
362
+ except Exception:
363
+ pass
364
+
365
 
366
  def check_dependencies():
367
  """Checks for required environment variables and the vector database."""
 
368
  if not os.path.exists("./vector_db"):
369
+ print("❌ Vector database not found. If you rely on it, please upload 'vector_db' folder or disable use_vector_db.")
370
+ # do not block startup - vector DB optional
 
371
  if not os.getenv("CHUTEAI_API_KEY"):
372
+ print("❌ CHUTEAI_API_KEY not found in environment. AI queries will fail until it's set.")
 
 
373
  return True
374
 
375
+
376
  def main():
 
 
 
377
  TELEGRAM_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
378
  if not TELEGRAM_TOKEN:
379
  print("FATAL ERROR: TELEGRAM_BOT_TOKEN is missing from environment secrets. Cannot initialize the Telegram client.")
380
  return
381
+
382
+ # Sanity check token length
383
  if len(TELEGRAM_TOKEN) < 40:
384
  print(f"WARNING: TELEGRAM_BOT_TOKEN has suspicious length ({len(TELEGRAM_TOKEN)}). Double-check the secret value.")
385
+
386
  if not check_dependencies():
387
  return
388
+
389
  print("πŸš€ Starting Telegram Homeopathy Bot...")
390
+
391
+ # Create HTTPXRequest and pass it into Application.builder().request(...)
392
+ request = HTTPXRequest(
393
+ connection_pool_size=4,
394
+ connect_timeout=5.0,
395
+ read_timeout=15.0,
396
+ )
397
+
398
  application = (
399
  Application.builder()
400
  .token(TELEGRAM_TOKEN)
401
+ .request(request)
402
+ .build()
 
 
 
 
 
403
  )
404
+
405
+ # Conversation handler
406
  conv_handler = ConversationHandler(
407
  entry_points=[CommandHandler('start', start)],
408
  states={
 
413
  },
414
  fallbacks=[CommandHandler('cancel', cancel)],
415
  )
416
+
417
+ # Register handlers
418
  application.add_handler(conv_handler)
419
  application.add_handler(CommandHandler('help', help_command))
420
  application.add_handler(CommandHandler('cancel', cancel))
421
+ # generic message outside conversation flow - keep last so it doesn't override conv
 
422
  application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, generic_message))
423
+
424
+ # Error handler
425
  application.add_error_handler(error_handler)
426
+
 
427
  print("βœ… Bot is running... Press Ctrl+C to stop")
428
+ application.run_polling(allowed_updates=Update.ALL_TYPES, bootstrap_retries=5)
429
+
 
 
 
430
 
431
  if __name__ == '__main__':
 
432
  main()