Skip to content

Commit 8fa564a

Browse files
committed
feat: no limit on saves to mongo
1 parent 3f93e38 commit 8fa564a

File tree

1 file changed

+40
-40
lines changed

1 file changed

+40
-40
lines changed

bot.py

Lines changed: 40 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
API_URL = API_BASE + "/api/answer"
2424
API_KEY = os.getenv("API_KEY")
2525
TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
26+
API_CONTEXT_MESSAGES_COUNT = 20 # Number of messages (10 pairs) to use for API context
2627

2728
# --- Storage Configuration ---
2829
STORAGE_TYPE = os.getenv("STORAGE_TYPE", "memory") # Default to in-memory
@@ -39,11 +40,11 @@
3940
if STORAGE_TYPE.lower() == "mongodb":
4041
if not MONGODB_URI:
4142
logger.error("STORAGE_TYPE is 'mongodb' but MONGODB_URI is not set. Exiting.")
42-
exit(1) # Or fallback to memory: STORAGE_TYPE = "memory"; logger.warning(...)
43+
exit(1)
4344
try:
4445
logger.info(f"Attempting to connect to MongoDB: {MONGODB_URI[:15]}... DB: {MONGODB_DB_NAME}")
45-
mongo_client = MongoClient(MONGODB_URI, serverSelectionTimeoutMS=5000) # 5 second timeout
46-
mongo_client.admin.command('ismaster') # Check connection
46+
mongo_client = MongoClient(MONGODB_URI, serverSelectionTimeoutMS=5000)
47+
mongo_client.admin.command('ismaster')
4748
db = mongo_client[MONGODB_DB_NAME]
4849
mongo_collection = db[MONGODB_COLLECTION_NAME]
4950
logger.info(f"Successfully connected to MongoDB and selected collection '{MONGODB_COLLECTION_NAME}'.")
@@ -71,47 +72,44 @@ async def get_chat_data(chat_id: int) -> dict:
7172
"""
7273
Fetches chat history, conversation ID, and user info from the configured storage.
7374
The user info stored pertains to the *last known user* who interacted in this chat.
75+
History fetched is the complete history.
7476
"""
7577
chat_id_str = str(chat_id)
76-
# Default structure now includes user_info
7778
default_data = {"history": [], "conversation_id": None, "user_info": None}
7879

7980
if STORAGE_TYPE == "mongodb" and mongo_collection is not None:
8081
try:
8182
doc = mongo_collection.find_one({"_id": chat_id_str})
82-
8383
if doc:
8484
history = doc.get("conversation_history", [])
8585
conv_id = doc.get("conversation_id", None)
86-
user_info = doc.get("user_info", None) # Get user info
86+
user_info = doc.get("user_info", None)
8787
return {"history": history, "conversation_id": conv_id, "user_info": user_info}
8888
else:
8989
return default_data
9090
except Exception as e:
9191
logger.error(f"MongoDB Error fetching data for chat_id {chat_id_str}: {e}", exc_info=True)
92-
return default_data # Return default on error
92+
return default_data
9393
else:
94-
# Use .get with default for user_info for backward compatibility if key missing
9594
data = in_memory_storage.get(chat_id_str, default_data)
9695
return {
9796
"history": data.get("history", []),
9897
"conversation_id": data.get("conversation_id", None),
99-
"user_info": data.get("user_info", None) # Get user info
98+
"user_info": data.get("user_info", None)
10099
}
101100

102101

103102
async def save_chat_data(chat_id: int, history: list, conversation_id: str | None, user_info: dict | None):
104103
"""
105-
Saves chat history, conversation ID, and user info to the configured storage.
104+
Saves the complete chat history, conversation ID, and user info to the configured storage.
106105
"""
107106
chat_id_str = str(chat_id)
108-
max_history_len_pairs = 10 # Store last 10 pairs (20 messages)
109-
limited_history = history[-(max_history_len_pairs * 2):]
107+
# The 'history' argument is the full history, which will be saved entirely.
110108

111109
if STORAGE_TYPE == "mongodb" and mongo_collection is not None:
112110
try:
113111
update_data = {
114-
"conversation_history": limited_history,
112+
"conversation_history": history, # Save the full history
115113
"conversation_id": conversation_id,
116114
}
117115
if user_info:
@@ -124,23 +122,23 @@ async def save_chat_data(chat_id: int, history: list, conversation_id: str | Non
124122
mongo_collection.update_one(
125123
{"_id": chat_id_str},
126124
update_doc,
127-
upsert=True # Create document if it doesn't exist
125+
upsert=True
128126
)
129-
logger.debug(f"Saved data for chat {chat_id_str} with user_info: {bool(user_info)}")
127+
logger.debug(f"Saved full history for chat {chat_id_str} with user_info: {bool(user_info)}")
130128
except Exception as e:
131129
logger.error(f"MongoDB Error saving data for chat_id {chat_id_str}: {e}", exc_info=True)
132-
else:
130+
else: # in-memory storage
133131
if chat_id_str not in in_memory_storage:
134-
in_memory_storage[chat_id_str] = {} # Initialize if new
132+
in_memory_storage[chat_id_str] = {}
135133

136-
in_memory_storage[chat_id_str].update({ # Use update to merge data
137-
"history": limited_history,
134+
in_memory_storage[chat_id_str].update({
135+
"history": history, # Save the full history
138136
"conversation_id": conversation_id,
139-
"last_updated": datetime.datetime.now(datetime.timezone.utc) # Timestamp for memory store
137+
"last_updated": datetime.datetime.now(datetime.timezone.utc)
140138
})
141139
if user_info:
142140
in_memory_storage[chat_id_str]["user_info"] = user_info
143-
logger.debug(f"Saved in-memory data for chat {chat_id_str} with user_info: {bool(user_info)}")
141+
logger.debug(f"Saved full in-memory history for chat {chat_id_str} with user_info: {bool(user_info)}")
144142

145143

146144
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
@@ -161,17 +159,23 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No
161159
await update.message.reply_text(help_text)
162160

163161
async def generate_answer(question: str, messages: list, conversation_id: str | None) -> dict:
164-
"""Generates an answer using the external DocsGPT API."""
162+
"""
163+
Generates an answer using the external DocsGPT API.
164+
Uses only the last API_CONTEXT_MESSAGES_COUNT messages for context.
165+
"""
165166
if not API_KEY:
166167
logger.warning("API_KEY is not set. Cannot call DocsGPT API.")
167168
return {"answer": "Error: Backend API key is not configured.", "conversation_id": conversation_id}
168169

170+
# Use only the last API_CONTEXT_MESSAGES_COUNT messages for the API call context
171+
context_messages = messages[-API_CONTEXT_MESSAGES_COUNT:]
172+
169173
try:
170-
formatted_history = format_history_for_api(messages)
174+
formatted_history = format_history_for_api(context_messages) # Use limited context_messages
171175
history_json = json.dumps(formatted_history)
172176
except TypeError as e:
173-
logger.error(f"Failed to serialize history to JSON: {e}. History: {messages}", exc_info=True)
174-
history_json = json.dumps([])
177+
logger.error(f"Failed to serialize history to JSON: {e}. History slice: {context_messages}", exc_info=True)
178+
history_json = json.dumps([]) # Fallback to empty history for API
175179

176180
payload = {
177181
"question": question,
@@ -182,13 +186,13 @@ async def generate_answer(question: str, messages: list, conversation_id: str |
182186
headers = {
183187
"Content-Type": "application/json; charset=utf-8"
184188
}
185-
timeout = 120.0 # Set a reasonable timeout for the API call
189+
timeout = 120.0
186190
default_error_msg = "Sorry, I couldn't get an answer from the backend service."
187191

188192
try:
189193
async with httpx.AsyncClient() as client:
190194
response = await client.post(API_URL, json=payload, headers=headers, timeout=timeout)
191-
response.raise_for_status() # Raise HTTPStatusError for 4xx/5xx
195+
response.raise_for_status()
192196

193197
data = response.json()
194198
answer = data.get("answer", default_error_msg)
@@ -208,7 +212,7 @@ async def generate_answer(question: str, messages: list, conversation_id: str |
208212
logger.error(f"Network error calling DocsGPT API: {exc}")
209213
return {"answer": f"{default_error_msg} (Network Error)", "conversation_id": conversation_id}
210214
except json.JSONDecodeError as exc:
211-
logger.error(f"Failed to decode JSON response from DocsGPT API: {exc}. Response text: {response.text}", exc_info=True) # Log response text
215+
logger.error(f"Failed to decode JSON response from DocsGPT API: {exc}. Response text: {response.text}", exc_info=True)
212216
return {"answer": f"{default_error_msg} (Invalid Response Format)", "conversation_id": conversation_id}
213217
except Exception as e:
214218
logger.error(f"Unexpected error in generate_answer: {e}", exc_info=True)
@@ -219,20 +223,18 @@ async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
219223
"""Handles non-command messages: get history, query API, save history & user info, reply."""
220224
if not update.message or not update.message.text or not update.effective_chat or not update.effective_user:
221225
logger.warning("Echo handler received an update without message, text, chat, or user.")
222-
return # Ignore updates without essential info
226+
return
223227

224228
chat_id = update.effective_chat.id
225229
user = update.effective_user
226230
question = update.message.text
227231
logger.info(f"Received message from user {user.id} ({user.username or user.first_name}) in chat_id {chat_id}")
228232

229-
# --- Send Typing Action ---
230233
try:
231234
await context.bot.send_chat_action(chat_id=chat_id, action=ChatAction.TYPING)
232235
logger.debug(f"Sent typing action to chat {chat_id}")
233236
except Exception as e:
234237
logger.warning(f"Failed to send typing action to chat {chat_id}: {e}")
235-
# This is non-critical, we can continue processing even if typing doesn't show
236238

237239
user_info_dict = {
238240
"id": user.id,
@@ -244,22 +246,23 @@ async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
244246
}
245247
user_info_dict = {k: v for k, v in user_info_dict.items() if v is not None}
246248

249+
# Get full history
247250
chat_data = await get_chat_data(chat_id)
248-
current_history = chat_data["history"]
251+
current_history = chat_data["history"] # This is the full history
249252
current_conversation_id = chat_data["conversation_id"]
250253

251254
current_history.append({"role": "user", "content": question})
252255

253-
# This is the potentially long-running call that we show typing for
256+
# generate_answer will use a slice of current_history for API context
254257
response_doc = await generate_answer(question, current_history, current_conversation_id)
255258
answer = response_doc["answer"]
256-
new_conversation_id = response_doc["conversation_id"] # Use the ID returned by API
259+
new_conversation_id = response_doc["conversation_id"]
257260

258261
current_history.append({"role": "assistant", "content": answer})
259262

263+
# Save the full, updated current_history
260264
await save_chat_data(chat_id, current_history, new_conversation_id, user_info_dict)
261265

262-
# Sending the reply will automatically stop the typing indicator
263266
try:
264267
await update.message.reply_text(answer, parse_mode=ParseMode.MARKDOWN)
265268
except Exception as e:
@@ -279,18 +282,15 @@ def format_history_for_api(messages: list) -> list:
279282
api_history = []
280283
i = 0
281284
while i < len(messages):
282-
# Look for a user message
283285
if messages[i].get("role") == "user" and "content" in messages[i]:
284286
prompt_content = messages[i]["content"]
285287
response_content = None
286-
# Check if the next message is a corresponding assistant response
287288
if i + 1 < len(messages) and messages[i+1].get("role") == "assistant" and "content" in messages[i+1]:
288289
response_content = messages[i+1]["content"]
289-
# Add the pair to history
290290
api_history.append({"prompt": prompt_content, "response": response_content})
291-
i += 2 # Move past both user and assistant message
291+
i += 2
292292
else:
293-
i += 1 # Move past the user message only
293+
i += 1
294294
else:
295295
i += 1
296296
return api_history

0 commit comments

Comments
 (0)