add history
Browse files- app/message_processor.py +98 -62
- app/sheets.py +79 -164
app/message_processor.py
CHANGED
|
@@ -44,15 +44,21 @@ class MessageProcessor:
|
|
| 44 |
logger.info(f"[DEBUG] Không có message_text và attachments, không xử lý...")
|
| 45 |
return
|
| 46 |
|
| 47 |
-
#
|
| 48 |
loop = asyncio.get_event_loop()
|
| 49 |
sheets_client = self.channel.get_sheets_client()
|
| 50 |
-
history = []
|
| 51 |
history = await loop.run_in_executor(
|
| 52 |
None, lambda: sheets_client.get_conversation_history(sender_id, page_id)
|
| 53 |
)
|
| 54 |
-
logger.info(f"[DEBUG] history: {history}")
|
| 55 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
log_kwargs = {
|
| 57 |
'conversation_id': None,
|
| 58 |
'recipient_id': sender_id,
|
|
@@ -65,52 +71,16 @@ class MessageProcessor:
|
|
| 65 |
'originalaction': '',
|
| 66 |
'originalpurpose': '',
|
| 67 |
'originalquestion': '',
|
| 68 |
-
'
|
|
|
|
| 69 |
'isdone': False
|
| 70 |
}
|
| 71 |
-
|
| 72 |
-
logger.info(f"[DEBUG] Message cơ bản: {log_kwargs}")
|
| 73 |
-
conv = None
|
| 74 |
-
|
| 75 |
-
if history:
|
| 76 |
-
# 1. Chặn duplicate message (trùng sender_id, page_id, timestamp)
|
| 77 |
-
for row in history:
|
| 78 |
-
row_timestamps = self.flatten_timestamp(row.get('timestamp', []))
|
| 79 |
-
if isinstance(row_timestamps, list) and len(row_timestamps) == 1 and isinstance(row_timestamps[0], list):
|
| 80 |
-
row_timestamps = row_timestamps[0]
|
| 81 |
-
if (
|
| 82 |
-
str(timestamp) in [str(ts) for ts in row_timestamps]
|
| 83 |
-
and str(row.get('recipient_id')) == str(sender_id)
|
| 84 |
-
and str(row.get('page_id')) == str(page_id)
|
| 85 |
-
):
|
| 86 |
-
logger.info("[DUPLICATE] Message duplicate, skipping log.")
|
| 87 |
-
return
|
| 88 |
-
conv = {
|
| 89 |
-
'conversation_id': row.get('conversation_id'),
|
| 90 |
-
'recipient_id': row.get('recipient_id'),
|
| 91 |
-
'page_id': row.get('page_id'),
|
| 92 |
-
'originaltext': row.get('originaltext'),
|
| 93 |
-
'originalcommand': row.get('originalcommand'),
|
| 94 |
-
'originalcontent': row.get('originalcontent'),
|
| 95 |
-
'originalattachments': row.get('originalattachments'),
|
| 96 |
-
'originalvehicle': row.get('originalvehicle'),
|
| 97 |
-
'originalaction': row.get('originalaction'),
|
| 98 |
-
'originalpurpose': row.get('originalpurpose'),
|
| 99 |
-
'originalquestion': row.get('originalquestion'),
|
| 100 |
-
'timestamp': row_timestamps,
|
| 101 |
-
'isdone': row.get('isdone')
|
| 102 |
-
}
|
| 103 |
-
else:
|
| 104 |
-
# 2. Ghi conversation mới NGAY LẬP TỨC với thông tin cơ bản
|
| 105 |
-
conv = await loop.run_in_executor(None, lambda: sheets_client.log_conversation(**log_kwargs))
|
| 106 |
if not conv:
|
| 107 |
logger.error("Không thể tạo conversation mới!")
|
| 108 |
return
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
for key, value in log_kwargs.items():
|
| 112 |
-
if value not in (None, "", []) and conv.get(key) in (None, "", []):
|
| 113 |
-
conv[key] = value
|
| 114 |
# Thêm timestamp mới nếu chưa có
|
| 115 |
conv['timestamp'] = self.flatten_timestamp(conv['timestamp'])
|
| 116 |
if timestamp not in conv['timestamp']:
|
|
@@ -182,6 +152,7 @@ class MessageProcessor:
|
|
| 182 |
'originalaction': hanh_vi,
|
| 183 |
'originalpurpose': muc_dich,
|
| 184 |
'originalquestion': cau_hoi or "",
|
|
|
|
| 185 |
'timestamp': self.flatten_timestamp(conv['timestamp']),
|
| 186 |
'isdone': False
|
| 187 |
}
|
|
@@ -199,20 +170,47 @@ class MessageProcessor:
|
|
| 199 |
muc_dich_to_use = muc_dich
|
| 200 |
logger.info(f"[DEBUG] Định hướng mục đích xử lý: {muc_dich_to_use}")
|
| 201 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
response = None
|
| 203 |
if not command:
|
| 204 |
if muc_dich_to_use == "hỏi về mức phạt":
|
| 205 |
-
response = await self.handle_muc_phat(conv, page_token, sender_id)
|
| 206 |
elif muc_dich_to_use == "hỏi về quy tắc giao thông":
|
| 207 |
-
response = await self.handle_quy_tac(conv, message_text)
|
| 208 |
elif muc_dich_to_use == "hỏi về báo hiệu đường bộ":
|
| 209 |
-
response = await self.handle_bao_hieu(conv, message_text)
|
| 210 |
elif muc_dich_to_use == "hỏi về quy trình xử lý vi phạm giao thông":
|
| 211 |
-
response = await self.handle_quy_trinh(conv, message_text)
|
| 212 |
elif muc_dich_to_use == "thông tin cá nhân của AI":
|
| 213 |
-
response = await self.handle_ca_nhan(conv, message_text)
|
| 214 |
else:
|
| 215 |
-
response = await self.handle_khac(conv, message_text)
|
| 216 |
else:
|
| 217 |
# Có command
|
| 218 |
if command == "xong":
|
|
@@ -226,10 +224,10 @@ class MessageProcessor:
|
|
| 226 |
response = "Vui lòng cung cấp thêm thông tin và gõ lệnh \\xong khi hoàn tất."
|
| 227 |
conv['isdone'] = False
|
| 228 |
|
| 229 |
-
# 6. Gửi response và cập nhật final state
|
| 230 |
await self.facebook.send_message(message=response)
|
| 231 |
-
|
| 232 |
-
|
|
|
|
| 233 |
return
|
| 234 |
|
| 235 |
def flatten_timestamp(self, ts):
|
|
@@ -253,7 +251,7 @@ class MessageProcessor:
|
|
| 253 |
return k
|
| 254 |
return keyword
|
| 255 |
|
| 256 |
-
async def format_search_results(self, question: str, matches: List[Dict[str, Any]], page_token: str, sender_id: str) -> str:
|
| 257 |
if not matches:
|
| 258 |
return "Không tìm thấy kết quả phù hợp."
|
| 259 |
await self.facebook.send_message(message=get_random_message(FOUND_REGULATIONS_MESSAGES))
|
|
@@ -335,7 +333,10 @@ class MessageProcessor:
|
|
| 335 |
else:
|
| 336 |
result_text = "Không có kết quả phù hợp!"
|
| 337 |
prompt = (
|
| 338 |
-
"
|
|
|
|
|
|
|
|
|
|
| 339 |
"Chỉ sử dụng thông tin có trong các đoạn, không tự đoán.\n"
|
| 340 |
f"\nCác đoạn luật liên quan:\n{full_result_text}"
|
| 341 |
"\n\nHãy trả lời ngắn gọn, dễ hiểu, trích dẫn rõ ràng thông tin từ các đoạn luật nếu cần."
|
|
@@ -384,7 +385,7 @@ class MessageProcessor:
|
|
| 384 |
logger.info(f"[MOCK] Creating Facebook post for sender_id={sender_id} with history={history}")
|
| 385 |
return "https://facebook.com/mock_post_url"
|
| 386 |
|
| 387 |
-
async def handle_muc_phat(self, conv, page_token, sender_id):
|
| 388 |
vehicle = conv.get('originalvehicle', '')
|
| 389 |
action = conv.get('originalaction', '')
|
| 390 |
question = conv.get('originalquestion', '')
|
|
@@ -406,7 +407,7 @@ class MessageProcessor:
|
|
| 406 |
)
|
| 407 |
logger.info(f"[DEBUG] matches: {matches}")
|
| 408 |
if matches:
|
| 409 |
-
response = await self.format_search_results(question, matches, page_token, sender_id)
|
| 410 |
else:
|
| 411 |
response = "Xin lỗi, tôi không tìm thấy thông tin phù hợp."
|
| 412 |
else:
|
|
@@ -418,24 +419,51 @@ class MessageProcessor:
|
|
| 418 |
# conv['isdone'] = False
|
| 419 |
return response
|
| 420 |
|
| 421 |
-
async def handle_quy_tac(self, conv, message_text):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 422 |
answer = await self.channel.llm.generate_text(message_text)
|
| 423 |
conv['isdone'] = True
|
| 424 |
return answer.strip() if answer and answer.strip() else "[Đang phát triển] Tính năng trả lời về quy tắc giao thông sẽ sớm có mặt."
|
| 425 |
|
| 426 |
-
async def handle_bao_hieu(self, conv, message_text):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 427 |
answer = await self.channel.llm.generate_text(message_text)
|
| 428 |
conv['isdone'] = True
|
| 429 |
return answer.strip() if answer and answer.strip() else "[Đang phát triển] Tính năng trả lời về báo hiệu đường bộ sẽ sớm có mặt."
|
| 430 |
|
| 431 |
-
async def handle_quy_trinh(self, conv, message_text):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 432 |
answer = await self.channel.llm.generate_text(message_text)
|
| 433 |
conv['isdone'] = True
|
| 434 |
return answer.strip() if answer and answer.strip() else "[Đang phát triển] Tính năng trả lời về quy trình xử lý vi phạm giao thông sẽ sớm có mặt."
|
| 435 |
|
| 436 |
-
async def handle_ca_nhan(self, conv, message_text):
|
| 437 |
# Nếu câu hỏi là về thông tin cá nhân của bot, hướng dẫn LLM trả lời đúng
|
| 438 |
prompt = (
|
|
|
|
|
|
|
|
|
|
| 439 |
'Với các thông tin sau: "Bạn có tên là WeThoong AI, là trợ lý giao thông thông minh. Bạn được anh Viet Cat tạo ra và facebook cá nhân của anh ý là https://facebook.com/vietcat". '
|
| 440 |
'Không được trả lời bạn là AI của Google, OpenAI, hay bất kỳ hãng nào khác. '
|
| 441 |
'Hãy trả lời thông minh, hài hước, ngắn gọn cho câu hỏi sau:\n'
|
|
@@ -445,7 +473,15 @@ class MessageProcessor:
|
|
| 445 |
conv['isdone'] = True
|
| 446 |
return answer.strip() if answer and answer.strip() else "[Đang phát triển] Tính năng này sẽ sớm có mặt."
|
| 447 |
|
| 448 |
-
async def handle_khac(self, conv, message_text):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 449 |
answer = await self.channel.llm.generate_text(message_text)
|
| 450 |
conv['isdone'] = True
|
| 451 |
return answer.strip() if answer and answer.strip() else "[Đang phát triển] Tính năng này sẽ sớm có mặt."
|
|
|
|
| 44 |
logger.info(f"[DEBUG] Không có message_text và attachments, không xử lý...")
|
| 45 |
return
|
| 46 |
|
| 47 |
+
# Lấy toàn bộ history (không lọc isdone)
|
| 48 |
loop = asyncio.get_event_loop()
|
| 49 |
sheets_client = self.channel.get_sheets_client()
|
|
|
|
| 50 |
history = await loop.run_in_executor(
|
| 51 |
None, lambda: sheets_client.get_conversation_history(sender_id, page_id)
|
| 52 |
)
|
| 53 |
+
logger.info(f"[DEBUG] history: {history}")
|
| 54 |
|
| 55 |
+
# Chống trùng: nếu đã có bản ghi với sender_id, page_id, timestamp thì bỏ qua
|
| 56 |
+
for row in history:
|
| 57 |
+
if str(row.get('timestamp')) == str(timestamp) and str(row.get('recipient_id')) == str(sender_id) and str(row.get('page_id')) == str(page_id):
|
| 58 |
+
logger.info("[DUPLICATE] Message duplicate, skipping log.")
|
| 59 |
+
return
|
| 60 |
+
|
| 61 |
+
# Luôn lưu mỗi message thành 1 bản ghi mới
|
| 62 |
log_kwargs = {
|
| 63 |
'conversation_id': None,
|
| 64 |
'recipient_id': sender_id,
|
|
|
|
| 71 |
'originalaction': '',
|
| 72 |
'originalpurpose': '',
|
| 73 |
'originalquestion': '',
|
| 74 |
+
'systemresponse': '',
|
| 75 |
+
'timestamp': timestamp,
|
| 76 |
'isdone': False
|
| 77 |
}
|
| 78 |
+
conv = await loop.run_in_executor(None, lambda: sheets_client.log_conversation(**log_kwargs))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
if not conv:
|
| 80 |
logger.error("Không thể tạo conversation mới!")
|
| 81 |
return
|
| 82 |
+
logger.info(f"[DEBUG] Message history: {conv}")
|
| 83 |
+
|
|
|
|
|
|
|
|
|
|
| 84 |
# Thêm timestamp mới nếu chưa có
|
| 85 |
conv['timestamp'] = self.flatten_timestamp(conv['timestamp'])
|
| 86 |
if timestamp not in conv['timestamp']:
|
|
|
|
| 152 |
'originalaction': hanh_vi,
|
| 153 |
'originalpurpose': muc_dich,
|
| 154 |
'originalquestion': cau_hoi or "",
|
| 155 |
+
'systemresponse': conv.get('systemresponse', ''),
|
| 156 |
'timestamp': self.flatten_timestamp(conv['timestamp']),
|
| 157 |
'isdone': False
|
| 158 |
}
|
|
|
|
| 170 |
muc_dich_to_use = muc_dich
|
| 171 |
logger.info(f"[DEBUG] Định hướng mục đích xử lý: {muc_dich_to_use}")
|
| 172 |
|
| 173 |
+
# Tin nhắn không có command: lấy toàn bộ history để truyền vào LLM
|
| 174 |
+
# Chuẩn bị context hội thoại cho LLM
|
| 175 |
+
MAX_CONTEXT_CHARS = 20_000
|
| 176 |
+
conversation_context = []
|
| 177 |
+
total_chars = 0
|
| 178 |
+
|
| 179 |
+
# Bước 1: Sắp xếp history theo timestamp tăng dần (cũ -> mới)
|
| 180 |
+
sorted_history = sorted(history, key=lambda x: x.get('timestamp', 0))
|
| 181 |
+
|
| 182 |
+
# Bước 2: Duyệt từ mới -> cũ để loại bỏ message cũ nếu cần
|
| 183 |
+
for row in reversed(sorted_history):
|
| 184 |
+
temp_blocks = []
|
| 185 |
+
if row.get('systemresponse'):
|
| 186 |
+
temp_blocks.append({"role": "assistant", "content": row['systemresponse']})
|
| 187 |
+
if row.get('originaltext'):
|
| 188 |
+
temp_blocks.append({"role": "user", "content": row['originaltext']})
|
| 189 |
+
|
| 190 |
+
temp_total = sum(len(block['content']) for block in temp_blocks)
|
| 191 |
+
|
| 192 |
+
if total_chars + temp_total > MAX_CONTEXT_CHARS:
|
| 193 |
+
continue # bỏ qua những block quá cũ
|
| 194 |
+
|
| 195 |
+
# prepend để đảm bảo thứ tự cuối cùng là từ cũ đến mới
|
| 196 |
+
conversation_context = temp_blocks + conversation_context
|
| 197 |
+
total_chars += temp_total
|
| 198 |
+
|
| 199 |
+
|
| 200 |
response = None
|
| 201 |
if not command:
|
| 202 |
if muc_dich_to_use == "hỏi về mức phạt":
|
| 203 |
+
response = await self.handle_muc_phat(conv, conversation_context, page_token, sender_id)
|
| 204 |
elif muc_dich_to_use == "hỏi về quy tắc giao thông":
|
| 205 |
+
response = await self.handle_quy_tac(conv, conversation_context, message_text)
|
| 206 |
elif muc_dich_to_use == "hỏi về báo hiệu đường bộ":
|
| 207 |
+
response = await self.handle_bao_hieu(conv, conversation_context, message_text)
|
| 208 |
elif muc_dich_to_use == "hỏi về quy trình xử lý vi phạm giao thông":
|
| 209 |
+
response = await self.handle_quy_trinh(conv, conversation_context, message_text)
|
| 210 |
elif muc_dich_to_use == "thông tin cá nhân của AI":
|
| 211 |
+
response = await self.handle_ca_nhan(conv, conversation_context, message_text)
|
| 212 |
else:
|
| 213 |
+
response = await self.handle_khac(conv, conversation_context, message_text)
|
| 214 |
else:
|
| 215 |
# Có command
|
| 216 |
if command == "xong":
|
|
|
|
| 224 |
response = "Vui lòng cung cấp thêm thông tin và gõ lệnh \\xong khi hoàn tất."
|
| 225 |
conv['isdone'] = False
|
| 226 |
|
|
|
|
| 227 |
await self.facebook.send_message(message=response)
|
| 228 |
+
# Lưu lại systemresponse cho bản ghi vừa tạo
|
| 229 |
+
conv['systemresponse'] = response
|
| 230 |
+
await loop.run_in_executor(None, lambda: sheets_client.log_conversation(**conv))
|
| 231 |
return
|
| 232 |
|
| 233 |
def flatten_timestamp(self, ts):
|
|
|
|
| 251 |
return k
|
| 252 |
return keyword
|
| 253 |
|
| 254 |
+
async def format_search_results(self, conversation_context: str, question: str, matches: List[Dict[str, Any]], page_token: str, sender_id: str) -> str:
|
| 255 |
if not matches:
|
| 256 |
return "Không tìm thấy kết quả phù hợp."
|
| 257 |
await self.facebook.send_message(message=get_random_message(FOUND_REGULATIONS_MESSAGES))
|
|
|
|
| 333 |
else:
|
| 334 |
result_text = "Không có kết quả phù hợp!"
|
| 335 |
prompt = (
|
| 336 |
+
"Biết rằng bạn đã có lịch sử trao đổi như sau:"
|
| 337 |
+
f"Lịch sử:\n{conversation_context}"
|
| 338 |
+
|
| 339 |
+
"Bạn là một trợ lý AI có kiến thức pháp luật, hãy trả lời câu hỏi dựa trên lịch sử trao đổi và các đoạn luật sau. "
|
| 340 |
"Chỉ sử dụng thông tin có trong các đoạn, không tự đoán.\n"
|
| 341 |
f"\nCác đoạn luật liên quan:\n{full_result_text}"
|
| 342 |
"\n\nHãy trả lời ngắn gọn, dễ hiểu, trích dẫn rõ ràng thông tin từ các đoạn luật nếu cần."
|
|
|
|
| 385 |
logger.info(f"[MOCK] Creating Facebook post for sender_id={sender_id} with history={history}")
|
| 386 |
return "https://facebook.com/mock_post_url"
|
| 387 |
|
| 388 |
+
async def handle_muc_phat(self, conversation_context, conv, page_token, sender_id):
|
| 389 |
vehicle = conv.get('originalvehicle', '')
|
| 390 |
action = conv.get('originalaction', '')
|
| 391 |
question = conv.get('originalquestion', '')
|
|
|
|
| 407 |
)
|
| 408 |
logger.info(f"[DEBUG] matches: {matches}")
|
| 409 |
if matches:
|
| 410 |
+
response = await self.format_search_results(conversation_context, question, matches, page_token, sender_id)
|
| 411 |
else:
|
| 412 |
response = "Xin lỗi, tôi không tìm thấy thông tin phù hợp."
|
| 413 |
else:
|
|
|
|
| 419 |
# conv['isdone'] = False
|
| 420 |
return response
|
| 421 |
|
| 422 |
+
async def handle_quy_tac(self, conversation_context, conv, message_text):
|
| 423 |
+
prompt = (
|
| 424 |
+
"Biết rằng bạn đã có lịch sử trao đổi như sau:"
|
| 425 |
+
f"Lịch sử:\n{conversation_context}"
|
| 426 |
+
|
| 427 |
+
"Bạn là một trợ lý AI có kiến thức pháp luật, hãy trả lời câu hỏi dựa trên lịch sử trao đổi"
|
| 428 |
+
"\n\nHãy trả lời ngắn gọn, dễ hiểu, trích dẫn rõ ràng thông tin từ các đoạn luật nếu cần."
|
| 429 |
+
f"\n\nCâu hỏi của người dùng: {message_text}\n"
|
| 430 |
+
)
|
| 431 |
answer = await self.channel.llm.generate_text(message_text)
|
| 432 |
conv['isdone'] = True
|
| 433 |
return answer.strip() if answer and answer.strip() else "[Đang phát triển] Tính năng trả lời về quy tắc giao thông sẽ sớm có mặt."
|
| 434 |
|
| 435 |
+
async def handle_bao_hieu(self, conversation_context, conv, message_text):
|
| 436 |
+
prompt = (
|
| 437 |
+
"Biết rằng bạn đã có lịch sử trao đổi như sau:"
|
| 438 |
+
f"Lịch sử:\n{conversation_context}"
|
| 439 |
+
|
| 440 |
+
"Bạn là một trợ lý AI có kiến thức pháp luật, hãy trả lời câu hỏi dựa trên lịch sử trao đổi"
|
| 441 |
+
"\n\nHãy trả lời ngắn gọn, dễ hiểu, trích dẫn rõ ràng thông tin từ các đoạn luật nếu cần."
|
| 442 |
+
f"\n\nCâu hỏi của người dùng: {message_text}\n"
|
| 443 |
+
)
|
| 444 |
answer = await self.channel.llm.generate_text(message_text)
|
| 445 |
conv['isdone'] = True
|
| 446 |
return answer.strip() if answer and answer.strip() else "[Đang phát triển] Tính năng trả lời về báo hiệu đường bộ sẽ sớm có mặt."
|
| 447 |
|
| 448 |
+
async def handle_quy_trinh(self, conversation_context, conv, message_text):
|
| 449 |
+
prompt = (
|
| 450 |
+
"Biết rằng bạn đã có lịch sử trao đổi như sau:"
|
| 451 |
+
f"Lịch sử:\n{conversation_context}"
|
| 452 |
+
|
| 453 |
+
"Bạn là một trợ lý AI có kiến thức pháp luật, hãy trả lời câu hỏi dựa trên lịch sử trao đổi"
|
| 454 |
+
"\n\nHãy trả lời ngắn gọn, dễ hiểu, trích dẫn rõ ràng thông tin từ các đoạn luật nếu cần."
|
| 455 |
+
f"\n\nCâu hỏi của người dùng: {message_text}\n"
|
| 456 |
+
)
|
| 457 |
answer = await self.channel.llm.generate_text(message_text)
|
| 458 |
conv['isdone'] = True
|
| 459 |
return answer.strip() if answer and answer.strip() else "[Đang phát triển] Tính năng trả lời về quy trình xử lý vi phạm giao thông sẽ sớm có mặt."
|
| 460 |
|
| 461 |
+
async def handle_ca_nhan(self, conversation_context, conv, message_text):
|
| 462 |
# Nếu câu hỏi là về thông tin cá nhân của bot, hướng dẫn LLM trả lời đúng
|
| 463 |
prompt = (
|
| 464 |
+
"Biết rằng bạn đã có lịch sử trao đổi như sau:"
|
| 465 |
+
f"Lịch sử:\n{conversation_context}"
|
| 466 |
+
|
| 467 |
'Với các thông tin sau: "Bạn có tên là WeThoong AI, là trợ lý giao thông thông minh. Bạn được anh Viet Cat tạo ra và facebook cá nhân của anh ý là https://facebook.com/vietcat". '
|
| 468 |
'Không được trả lời bạn là AI của Google, OpenAI, hay bất kỳ hãng nào khác. '
|
| 469 |
'Hãy trả lời thông minh, hài hước, ngắn gọn cho câu hỏi sau:\n'
|
|
|
|
| 473 |
conv['isdone'] = True
|
| 474 |
return answer.strip() if answer and answer.strip() else "[Đang phát triển] Tính năng này sẽ sớm có mặt."
|
| 475 |
|
| 476 |
+
async def handle_khac(self, conversation_context, conv, message_text):
|
| 477 |
+
prompt = (
|
| 478 |
+
"Biết rằng bạn đã có lịch sử trao đổi như sau:"
|
| 479 |
+
f"Lịch sử:\n{conversation_context}"
|
| 480 |
+
|
| 481 |
+
"Bạn là một trợ lý AI có kiến thức pháp luật, hãy trả lời câu hỏi dựa trên lịch sử trao đổi"
|
| 482 |
+
"\n\nHãy trả lời ngắn gọn, dễ hiểu, trích dẫn rõ ràng thông tin từ các đoạn luật nếu cần."
|
| 483 |
+
f"\n\nCâu hỏi của người dùng: {message_text}\n"
|
| 484 |
+
)
|
| 485 |
answer = await self.channel.llm.generate_text(message_text)
|
| 486 |
conv['isdone'] = True
|
| 487 |
return answer.strip() if answer and answer.strip() else "[Đang phát triển] Tính năng này sẽ sớm có mặt."
|
app/sheets.py
CHANGED
|
@@ -87,14 +87,14 @@ class SheetsClient:
|
|
| 87 |
values = result.get('values', [])
|
| 88 |
history = []
|
| 89 |
for row in values:
|
| 90 |
-
row = row + [""] * (
|
| 91 |
try:
|
| 92 |
-
timestamps = json.loads(row[
|
| 93 |
except Exception:
|
| 94 |
timestamps = []
|
| 95 |
if not isinstance(timestamps, list):
|
| 96 |
timestamps = [timestamps]
|
| 97 |
-
if row[4] == user_id and row[5] == page_id
|
| 98 |
history.append({
|
| 99 |
'conversation_id': row[0],
|
| 100 |
'originalcommand': row[1],
|
|
@@ -107,8 +107,9 @@ class SheetsClient:
|
|
| 107 |
'originalaction': row[8],
|
| 108 |
'originalpurpose': row[9],
|
| 109 |
'originalquestion': row[10],
|
| 110 |
-
'
|
| 111 |
-
'
|
|
|
|
| 112 |
})
|
| 113 |
return history
|
| 114 |
except Exception as e:
|
|
@@ -128,8 +129,9 @@ class SheetsClient:
|
|
| 128 |
originalvehicle: str = "",
|
| 129 |
originalaction: str = "",
|
| 130 |
originalpurpose: str = "",
|
| 131 |
-
originalquestion: str = "",
|
| 132 |
-
|
|
|
|
| 133 |
isdone: bool = False
|
| 134 |
) -> Optional[Dict[str, Any]]:
|
| 135 |
"""
|
|
@@ -149,164 +151,77 @@ class SheetsClient:
|
|
| 149 |
).execute()
|
| 150 |
values = result.get('values', [])
|
| 151 |
# logger.info(f"[DEBUG] Gsheet values {values}")
|
| 152 |
-
|
| 153 |
-
# Đảm bảo timestamp luôn là list
|
| 154 |
if timestamp is None:
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
if
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
'recipient_id': recipient_id,
|
| 225 |
-
'page_id': page_id,
|
| 226 |
-
'originaltext': originaltext,
|
| 227 |
-
'originalvehicle': originalvehicle,
|
| 228 |
-
'originalaction': originalaction,
|
| 229 |
-
'originalpurpose': originalpurpose,
|
| 230 |
-
'originalquestion': originalquestion,
|
| 231 |
-
'timestamp': timestamp,
|
| 232 |
-
'isdone': isdone
|
| 233 |
-
}
|
| 234 |
-
else:
|
| 235 |
-
# Update existing conversation
|
| 236 |
-
if not values:
|
| 237 |
-
logger.error("No data in sheet, cannot update conversation.")
|
| 238 |
-
return None
|
| 239 |
-
row_index = None
|
| 240 |
-
for i, row in enumerate(values):
|
| 241 |
-
if row[0] == conversation_id:
|
| 242 |
-
row_index = i
|
| 243 |
-
break
|
| 244 |
-
logger.info(f"[DEBUG] Gsheet row index {row_index}")
|
| 245 |
-
if row_index is not None:
|
| 246 |
-
sheet_row_number = row_index + 2 # +2 vì values[0] là dòng 2 trên sheet
|
| 247 |
-
current_row = values[row_index]
|
| 248 |
-
logger.info(f"[DEBUG] Gsheet current row {current_row}")
|
| 249 |
-
while len(current_row) < 13:
|
| 250 |
-
current_row.append("")
|
| 251 |
-
try:
|
| 252 |
-
current_timestamps = json.loads(current_row[10]) if current_row[10] else []
|
| 253 |
-
except Exception:
|
| 254 |
-
current_timestamps = []
|
| 255 |
-
if not isinstance(current_timestamps, list):
|
| 256 |
-
current_timestamps = [current_timestamps]
|
| 257 |
-
# Chỉ append nếu chưa có
|
| 258 |
-
for ts in timestamp:
|
| 259 |
-
if ts not in current_timestamps:
|
| 260 |
-
current_timestamps.append(ts)
|
| 261 |
-
new_row = [
|
| 262 |
-
conversation_id,
|
| 263 |
-
originalcommand if originalcommand else current_row[1],
|
| 264 |
-
originalcontent if originalcontent else current_row[2],
|
| 265 |
-
json.dumps(originalattachments) if originalattachments is not None else current_row[3],
|
| 266 |
-
recipient_id if recipient_id else current_row[4],
|
| 267 |
-
page_id if page_id else current_row[5],
|
| 268 |
-
originaltext if originaltext else current_row[6],
|
| 269 |
-
originalvehicle if originalvehicle else current_row[7],
|
| 270 |
-
originalaction if originalaction else current_row[8],
|
| 271 |
-
originalpurpose if originalpurpose else current_row[9],
|
| 272 |
-
originalquestion if originalquestion else current_row[10], # <-- thêm dòng này
|
| 273 |
-
json.dumps(current_timestamps),
|
| 274 |
-
str(isdone).lower() if isdone is not None else current_row[12]
|
| 275 |
-
]
|
| 276 |
-
update_range = f"{SHEET_RANGE.split('!')[0]}!A{sheet_row_number}"
|
| 277 |
-
logger.info(f"[DEBUG] Gsheet update range {update_range}")
|
| 278 |
-
body = {
|
| 279 |
-
'values': [new_row]
|
| 280 |
-
}
|
| 281 |
-
self.service.spreadsheets().values().update(
|
| 282 |
-
spreadsheetId=self.sheet_id,
|
| 283 |
-
range=update_range,
|
| 284 |
-
valueInputOption='RAW',
|
| 285 |
-
body=body
|
| 286 |
-
).execute()
|
| 287 |
-
changed_cols = ['conversation_id','originalcommand','originalcontent','originalattachments','recipient_id','page_id','originaltext','originalvehicle','originalaction','originalpurpose','originalquestion','timestamp','isdone']
|
| 288 |
-
for idx, (old, new) in enumerate(zip(current_row, new_row)):
|
| 289 |
-
if old != new:
|
| 290 |
-
changed_cols.append(changed_cols[idx])
|
| 291 |
-
logger.info(f"Cập nhật conversation: {conversation_id} tại dòng {sheet_row_number} | Cột cập nhật: {changed_cols} | Giá trị mới: {dict(zip(changed_cols, new_row))}")
|
| 292 |
-
|
| 293 |
-
# Return the updated conversation data
|
| 294 |
-
return {
|
| 295 |
-
'conversation_id': conversation_id,
|
| 296 |
-
'originalcommand': new_row[1],
|
| 297 |
-
'originalcontent': new_row[2],
|
| 298 |
-
'originalattachments': json.loads(new_row[3]) if new_row[3] else [],
|
| 299 |
-
'recipient_id': new_row[4],
|
| 300 |
-
'page_id': new_row[5],
|
| 301 |
-
'originaltext': new_row[6],
|
| 302 |
-
'originalvehicle': new_row[7],
|
| 303 |
-
'originalaction': new_row[8],
|
| 304 |
-
'originalpurpose': new_row[9],
|
| 305 |
-
'originalquestion': new_row[10], # <-- thêm dòng này
|
| 306 |
-
'timestamp': current_timestamps,
|
| 307 |
-
'isdone': new_row[12].lower() == 'true'
|
| 308 |
-
}
|
| 309 |
-
return None
|
| 310 |
except Exception as e:
|
| 311 |
logger.error(f"Error logging conversation: {e}")
|
| 312 |
return None
|
|
|
|
| 87 |
values = result.get('values', [])
|
| 88 |
history = []
|
| 89 |
for row in values:
|
| 90 |
+
row = row + [""] * (14 - len(row))
|
| 91 |
try:
|
| 92 |
+
timestamps = json.loads(row[12]) if row[12] else []
|
| 93 |
except Exception:
|
| 94 |
timestamps = []
|
| 95 |
if not isinstance(timestamps, list):
|
| 96 |
timestamps = [timestamps]
|
| 97 |
+
if row[4] == user_id and row[5] == page_id:
|
| 98 |
history.append({
|
| 99 |
'conversation_id': row[0],
|
| 100 |
'originalcommand': row[1],
|
|
|
|
| 107 |
'originalaction': row[8],
|
| 108 |
'originalpurpose': row[9],
|
| 109 |
'originalquestion': row[10],
|
| 110 |
+
'systemresponse': row[11],
|
| 111 |
+
'timestamp': row[12],
|
| 112 |
+
'isdone': row[13].lower() == 'true'
|
| 113 |
})
|
| 114 |
return history
|
| 115 |
except Exception as e:
|
|
|
|
| 129 |
originalvehicle: str = "",
|
| 130 |
originalaction: str = "",
|
| 131 |
originalpurpose: str = "",
|
| 132 |
+
originalquestion: str = "",
|
| 133 |
+
systemresponse: str = "",
|
| 134 |
+
timestamp: str = None,
|
| 135 |
isdone: bool = False
|
| 136 |
) -> Optional[Dict[str, Any]]:
|
| 137 |
"""
|
|
|
|
| 151 |
).execute()
|
| 152 |
values = result.get('values', [])
|
| 153 |
# logger.info(f"[DEBUG] Gsheet values {values}")
|
| 154 |
+
# Đảm bảo timestamp là str
|
|
|
|
| 155 |
if timestamp is None:
|
| 156 |
+
from datetime import datetime
|
| 157 |
+
timestamp = datetime.now().isoformat()
|
| 158 |
+
# Chống trùng bản ghi dựa trên sender_id, page_id, timestamp
|
| 159 |
+
for row in values:
|
| 160 |
+
if len(row) >= 12:
|
| 161 |
+
row_timestamp = row[12] if len(row) > 12 else ""
|
| 162 |
+
if str(row_timestamp) == str(timestamp) and str(row[4]) == str(recipient_id) and str(row[5]) == str(page_id):
|
| 163 |
+
logger.info(f"Found duplicate conversation for user {recipient_id}, page {page_id}, timestamp {timestamp}")
|
| 164 |
+
return {
|
| 165 |
+
'conversation_id': row[0],
|
| 166 |
+
'originalcommand': row[1],
|
| 167 |
+
'originalcontent': row[2],
|
| 168 |
+
'originalattachments': json.loads(row[3]) if row[3] else [],
|
| 169 |
+
'recipient_id': row[4],
|
| 170 |
+
'page_id': row[5],
|
| 171 |
+
'originaltext': row[6],
|
| 172 |
+
'originalvehicle': row[7],
|
| 173 |
+
'originalaction': row[8],
|
| 174 |
+
'originalpurpose': row[9],
|
| 175 |
+
'originalquestion': row[10],
|
| 176 |
+
'systemresponse': row[11],
|
| 177 |
+
'timestamp': row[12],
|
| 178 |
+
'isdone': row[13].lower() == 'true' if len(row) > 13 else False
|
| 179 |
+
}
|
| 180 |
+
# Nếu không trùng thì thêm mới bản ghi
|
| 181 |
+
conversation_id = generate_conversation_id(recipient_id, page_id, timestamp)
|
| 182 |
+
new_row = [
|
| 183 |
+
conversation_id,
|
| 184 |
+
originalcommand,
|
| 185 |
+
originalcontent,
|
| 186 |
+
json.dumps(originalattachments or []),
|
| 187 |
+
recipient_id,
|
| 188 |
+
page_id,
|
| 189 |
+
originaltext,
|
| 190 |
+
originalvehicle,
|
| 191 |
+
originalaction,
|
| 192 |
+
originalpurpose,
|
| 193 |
+
originalquestion,
|
| 194 |
+
systemresponse,
|
| 195 |
+
timestamp,
|
| 196 |
+
str(isdone).lower()
|
| 197 |
+
]
|
| 198 |
+
body = {
|
| 199 |
+
'values': [new_row]
|
| 200 |
+
}
|
| 201 |
+
range_name = SHEET_RANGE
|
| 202 |
+
self.service.spreadsheets().values().append(
|
| 203 |
+
spreadsheetId=self.sheet_id,
|
| 204 |
+
range=range_name,
|
| 205 |
+
valueInputOption='RAW',
|
| 206 |
+
body=body
|
| 207 |
+
).execute()
|
| 208 |
+
logger.info(f"Thêm mới conversation: {conversation_id} | Giá trị: {{dict(zip(['conversation_id','originalcommand','originalcontent','originalattachments','recipient_id','page_id','originaltext','originalvehicle','originalaction','originalpurpose','originalquestion','systemresponse','timestamp','isdone'], new_row))}}")
|
| 209 |
+
return {
|
| 210 |
+
'conversation_id': conversation_id,
|
| 211 |
+
'originalcommand': originalcommand,
|
| 212 |
+
'originalcontent': originalcontent,
|
| 213 |
+
'originalattachments': originalattachments or [],
|
| 214 |
+
'recipient_id': recipient_id,
|
| 215 |
+
'page_id': page_id,
|
| 216 |
+
'originaltext': originaltext,
|
| 217 |
+
'originalvehicle': originalvehicle,
|
| 218 |
+
'originalaction': originalaction,
|
| 219 |
+
'originalpurpose': originalpurpose,
|
| 220 |
+
'originalquestion': originalquestion,
|
| 221 |
+
'systemresponse': systemresponse,
|
| 222 |
+
'timestamp': timestamp,
|
| 223 |
+
'isdone': isdone
|
| 224 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
except Exception as e:
|
| 226 |
logger.error(f"Error logging conversation: {e}")
|
| 227 |
return None
|