Esmaill1 commited on
Commit
8c16fc3
·
1 Parent(s): b2c9746

Feat: Add PostgreSQL-backed chat memory using Neon DB

Browse files
Files changed (2) hide show
  1. bot.py +103 -7
  2. requirements.txt +1 -0
bot.py CHANGED
@@ -5,6 +5,8 @@ import logging
5
  import socket
6
  import time
7
  from io import BytesIO
 
 
8
  from dotenv import load_dotenv
9
  from telegram import Update
10
  from telegram.ext import Application, MessageHandler, filters, ContextTypes
@@ -29,6 +31,79 @@ OLLAMA_HOST = os.getenv("OLLAMA_HOST", "http://localhost:11434")
29
  OLLAMA_API_KEY = os.getenv("OLLAMA_API_KEY", "") # Optional: for cloud/authenticated services
30
  VISION_MODEL = os.getenv("VISION_MODEL", "llava") # Model for image analysis
31
  CHAT_MODEL = os.getenv("CHAT_MODEL", "mistral") # Model for quiz generation
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
 
34
  async def extract_text_from_image(image_bytes: bytes) -> str:
@@ -325,8 +400,8 @@ async def handle_file(update: Update, context: ContextTypes.DEFAULT_TYPE):
325
  await processing_msg.edit_text(f"❌ حصل مشكلة: {str(e)[:100]}")
326
 
327
 
328
- async def generate_chat_response(text: str) -> str:
329
- """Generate a conversational response using Ollama API."""
330
  system_prompt = """You are the AI Quiz Bot Assistant. 🧠
331
  Your goal is to be friendly, helpful, and encourage users to study.
332
  - If the user greets you, reply warmly in the same language (Arabic or English).
@@ -334,13 +409,22 @@ Your goal is to be friendly, helpful, and encourage users to study.
334
  - Be concise and fun.
335
  - You are NOT generating a quiz right now, just chatting."""
336
 
 
 
 
 
 
 
 
 
 
 
 
 
337
  try:
338
  payload = {
339
  "model": CHAT_MODEL,
340
- "messages": [
341
- {"role": "system", "content": system_prompt},
342
- {"role": "user", "content": text}
343
- ],
344
  "stream": False
345
  }
346
 
@@ -374,9 +458,18 @@ async def handle_text(update: Update, context: ContextTypes.DEFAULT_TYPE):
374
 
375
  # If text is short (< 50 chars), treat it as conversation
376
  if len(text.strip()) < 50:
 
 
 
377
  # Show typing indicator
378
  await context.bot.send_chat_action(chat_id=chat_id, action="typing")
379
- response = await generate_chat_response(text)
 
 
 
 
 
 
380
  await message.reply_text(response)
381
  return
382
 
@@ -513,6 +606,9 @@ def wait_for_network():
513
 
514
  def main():
515
  """Start the bot."""
 
 
 
516
  # Fix for running in background thread: Ensure an event loop exists
517
  try:
518
  loop = asyncio.get_event_loop()
 
5
  import socket
6
  import time
7
  from io import BytesIO
8
+ from datetime import datetime
9
+ import psycopg2
10
  from dotenv import load_dotenv
11
  from telegram import Update
12
  from telegram.ext import Application, MessageHandler, filters, ContextTypes
 
31
  OLLAMA_API_KEY = os.getenv("OLLAMA_API_KEY", "") # Optional: for cloud/authenticated services
32
  VISION_MODEL = os.getenv("VISION_MODEL", "llava") # Model for image analysis
33
  CHAT_MODEL = os.getenv("CHAT_MODEL", "mistral") # Model for quiz generation
34
+ DATABASE_URL = os.getenv("DATABASE_URL") # Neon connection string
35
+
36
+
37
+ def init_db():
38
+ """Initialize the database table."""
39
+ if not DATABASE_URL:
40
+ logger.warning("DATABASE_URL not set. Chat memory will be disabled.")
41
+ return
42
+
43
+ try:
44
+ conn = psycopg2.connect(DATABASE_URL)
45
+ cur = conn.cursor()
46
+ cur.execute("""
47
+ CREATE TABLE IF NOT EXISTS chat_history (
48
+ id SERIAL PRIMARY KEY,
49
+ chat_id BIGINT NOT NULL,
50
+ role VARCHAR(10) NOT NULL,
51
+ content TEXT NOT NULL,
52
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
53
+ );
54
+ """)
55
+ conn.commit()
56
+ cur.close()
57
+ conn.close()
58
+ logger.info("Database initialized successfully.")
59
+ except Exception as e:
60
+ logger.error(f"Error initializing database: {e}")
61
+
62
+
63
+ def save_chat_message(chat_id: int, role: str, content: str):
64
+ """Save a chat message to the database."""
65
+ if not DATABASE_URL:
66
+ return
67
+
68
+ try:
69
+ conn = psycopg2.connect(DATABASE_URL)
70
+ cur = conn.cursor()
71
+ cur.execute(
72
+ "INSERT INTO chat_history (chat_id, role, content) VALUES (%s, %s, %s)",
73
+ (chat_id, role, content)
74
+ )
75
+ conn.commit()
76
+ cur.close()
77
+ conn.close()
78
+ except Exception as e:
79
+ logger.error(f"Error saving chat message: {e}")
80
+
81
+
82
+ def get_chat_history(chat_id: int, limit: int = 10):
83
+ """Get recent chat history for a chat_id."""
84
+ if not DATABASE_URL:
85
+ return []
86
+
87
+ try:
88
+ conn = psycopg2.connect(DATABASE_URL)
89
+ cur = conn.cursor()
90
+ cur.execute(
91
+ """
92
+ SELECT role, content FROM chat_history
93
+ WHERE chat_id = %s
94
+ ORDER BY timestamp DESC
95
+ LIMIT %s
96
+ """,
97
+ (chat_id, limit)
98
+ )
99
+ rows = cur.fetchall()
100
+ cur.close()
101
+ conn.close()
102
+ # Return reversed list (oldest to newest)
103
+ return [{"role": row[0], "content": row[1]} for row in rows][::-1]
104
+ except Exception as e:
105
+ logger.error(f"Error getting chat history: {e}")
106
+ return []
107
 
108
 
109
  async def extract_text_from_image(image_bytes: bytes) -> str:
 
400
  await processing_msg.edit_text(f"❌ حصل مشكلة: {str(e)[:100]}")
401
 
402
 
403
+ async def generate_chat_response(chat_id: int, text: str) -> str:
404
+ """Generate a conversational response using Ollama API with history."""
405
  system_prompt = """You are the AI Quiz Bot Assistant. 🧠
406
  Your goal is to be friendly, helpful, and encourage users to study.
407
  - If the user greets you, reply warmly in the same language (Arabic or English).
 
409
  - Be concise and fun.
410
  - You are NOT generating a quiz right now, just chatting."""
411
 
412
+ # Get recent history (limit 6 messages for context)
413
+ history = get_chat_history(chat_id, limit=6)
414
+
415
+ messages = [{"role": "system", "content": system_prompt}]
416
+
417
+ # Add history
418
+ for msg in history:
419
+ messages.append(msg)
420
+
421
+ # Add current user message
422
+ messages.append({"role": "user", "content": text})
423
+
424
  try:
425
  payload = {
426
  "model": CHAT_MODEL,
427
+ "messages": messages,
 
 
 
428
  "stream": False
429
  }
430
 
 
458
 
459
  # If text is short (< 50 chars), treat it as conversation
460
  if len(text.strip()) < 50:
461
+ # Save user message to memory
462
+ save_chat_message(chat_id, "user", text)
463
+
464
  # Show typing indicator
465
  await context.bot.send_chat_action(chat_id=chat_id, action="typing")
466
+
467
+ # Generate response with history
468
+ response = await generate_chat_response(chat_id, text)
469
+
470
+ # Save bot response to memory
471
+ save_chat_message(chat_id, "assistant", response)
472
+
473
  await message.reply_text(response)
474
  return
475
 
 
606
 
607
  def main():
608
  """Start the bot."""
609
+ # Initialize database
610
+ init_db()
611
+
612
  # Fix for running in background thread: Ensure an event loop exists
613
  try:
614
  loop = asyncio.get_event_loop()
requirements.txt CHANGED
@@ -4,3 +4,4 @@ python-dotenv>=1.0.0
4
  httpx>=0.24.0
5
  fastapi
6
  uvicorn
 
 
4
  httpx>=0.24.0
5
  fastapi
6
  uvicorn
7
+ psycopg2-binary