refactor message flow on purposes
Browse files- app/facebook.py +56 -16
- app/message_processor.py +54 -57
app/facebook.py
CHANGED
@@ -63,27 +63,67 @@ class FacebookClient:
|
|
63 |
|
64 |
return hmac.compare_digest(signature[7:], expected)
|
65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
66 |
@timing_decorator_async
|
67 |
-
async def send_message(self, page_access_token: Optional[str] = None, recipient_id: Optional[str] = None, message: str = "") ->
|
68 |
page_access_token = page_access_token or self.page_token
|
69 |
recipient_id = recipient_id or self.sender_id
|
70 |
if not page_access_token or not recipient_id:
|
71 |
raise ValueError("FacebookClient: page_access_token and recipient_id must not be None when sending a message.")
|
72 |
-
#
|
73 |
-
response_to_send = message.replace('**', '*') if isinstance(message, str) else message
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
87 |
|
88 |
@timing_decorator_sync
|
89 |
def parse_message(self, body: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
|
63 |
|
64 |
return hmac.compare_digest(signature[7:], expected)
|
65 |
|
66 |
+
def format_message(self, text: str) -> str:
|
67 |
+
# 1. Thay bullet markdown bằng ký hiệu khác
|
68 |
+
text = text.replace('\n* ', '\n- ')
|
69 |
+
text = text.replace('\n * ', '\n + ')
|
70 |
+
text = text.replace('\n* ', '\n- ')
|
71 |
+
text = text.replace('\n * ', '\n + ')
|
72 |
+
# 2. Chuyển **text** hoặc __text__ thành *text*
|
73 |
+
import re
|
74 |
+
text = re.sub(r'\*\*([^\*]+)\*\*', r'*\1*', text)
|
75 |
+
text = re.sub(r'__([^_]+)__', r'*\1*', text)
|
76 |
+
# 3. Loại bỏ các tiêu đề markdown kiểu #, ##, ###, ...
|
77 |
+
text = re.sub(r'^#+\s+', '', text, flags=re.MULTILINE)
|
78 |
+
# 4. Rút gọn nhiều dòng trống liên tiếp thành 1 dòng trống
|
79 |
+
text = re.sub(r'\n{3,}', '\n\n', text)
|
80 |
+
# 5. Loại bỏ các markdown không hỗ trợ khác nếu cần
|
81 |
+
return text
|
82 |
+
|
83 |
+
def split_message(self, text: str, max_length: int = 2000) -> list:
|
84 |
+
"""
|
85 |
+
Chia message thành các đoạn <= max_length ký tự, ưu tiên chia theo dòng.
|
86 |
+
"""
|
87 |
+
lines = text.split('\n')
|
88 |
+
messages = []
|
89 |
+
current = ""
|
90 |
+
for line in lines:
|
91 |
+
# +1 cho ký tự xuống dòng
|
92 |
+
if len(current) + len(line) + 1 > max_length:
|
93 |
+
messages.append(current.rstrip())
|
94 |
+
current = ""
|
95 |
+
current += (line + '\n')
|
96 |
+
if current.strip():
|
97 |
+
messages.append(current.rstrip())
|
98 |
+
return messages
|
99 |
+
|
100 |
@timing_decorator_async
|
101 |
+
async def send_message(self, page_access_token: Optional[str] = None, recipient_id: Optional[str] = None, message: str = "") -> dict:
|
102 |
page_access_token = page_access_token or self.page_token
|
103 |
recipient_id = recipient_id or self.sender_id
|
104 |
if not page_access_token or not recipient_id:
|
105 |
raise ValueError("FacebookClient: page_access_token and recipient_id must not be None when sending a message.")
|
106 |
+
# Format message
|
107 |
+
response_to_send = self.format_message(message.replace('**', '*')) if isinstance(message, str) else message
|
108 |
+
# Chia nhỏ nếu quá dài
|
109 |
+
messages = self.split_message(response_to_send)
|
110 |
+
results = []
|
111 |
+
for msg in messages:
|
112 |
+
if len(msg) > 2000:
|
113 |
+
msg = msg[:2000] # fallback cắt cứng
|
114 |
+
url = f"https://graph.facebook.com/v18.0/me/messages?access_token={page_access_token}"
|
115 |
+
payload = {
|
116 |
+
"recipient": {"id": recipient_id},
|
117 |
+
"message": {"text": msg}
|
118 |
+
}
|
119 |
+
try:
|
120 |
+
response = await self._client.post(url, json=payload)
|
121 |
+
response.raise_for_status()
|
122 |
+
results.append(response.json())
|
123 |
+
except httpx.HTTPError as e:
|
124 |
+
logger.error(f"Error sending message to Facebook: {e}")
|
125 |
+
raise HTTPException(status_code=500, detail="Failed to send message to Facebook")
|
126 |
+
return results[0] if results else {}
|
127 |
|
128 |
@timing_decorator_sync
|
129 |
def parse_message(self, body: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
app/message_processor.py
CHANGED
@@ -196,52 +196,15 @@ class MessageProcessor:
|
|
196 |
response = None
|
197 |
if not command:
|
198 |
if muc_dich_to_use == "hỏi về mức phạt":
|
199 |
-
|
200 |
-
vehicle = conv.get('originalvehicle', '')
|
201 |
-
action = conv.get('originalaction', '')
|
202 |
-
keywords = [kw.strip() for kw in vehicle.split(',') if kw.strip()]
|
203 |
-
if keywords:
|
204 |
-
if action:
|
205 |
-
logger.info(f"[DEBUG] tạo embedding: {action}")
|
206 |
-
embedding = await self.channel.embedder.create_embedding(action)
|
207 |
-
logger.info(f"[DEBUG] embedding: {embedding[:5]} ... (total {len(embedding)})")
|
208 |
-
matches = self.channel.supabase.match_documents(
|
209 |
-
embedding,
|
210 |
-
vehicle_keywords=keywords,
|
211 |
-
user_question=action
|
212 |
-
)
|
213 |
-
logger.info(f"[DEBUG] matches: {matches}")
|
214 |
-
if matches:
|
215 |
-
response = await self.format_search_results(message_text, matches, page_token, sender_id)
|
216 |
-
else:
|
217 |
-
response = "Xin lỗi, tôi không tìm thấy thông tin phù hợp."
|
218 |
-
else:
|
219 |
-
logger.info(f"[DEBUG] Không có hành vi vi phạm: {message_text}")
|
220 |
-
response = "Xin lỗi, tôi không tìm thấy thông tin về hành vi vi phạm trong câu hỏi của bạn."
|
221 |
-
conv['isdone'] = True
|
222 |
-
else:
|
223 |
-
response = "Vui lòng cho biết loại phương tiện bạn cần tìm (xe máy, ô tô...)"
|
224 |
-
conv['isdone'] = False
|
225 |
elif muc_dich_to_use == "hỏi về quy tắc giao thông":
|
226 |
-
|
227 |
-
answer = await self.channel.llm.generate_text(message_text)
|
228 |
-
response = 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."
|
229 |
-
conv['isdone'] = True
|
230 |
elif muc_dich_to_use == "hỏi về báo hiệu đường bộ":
|
231 |
-
|
232 |
-
answer = await self.channel.llm.generate_text(message_text)
|
233 |
-
response = 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."
|
234 |
-
conv['isdone'] = True
|
235 |
elif muc_dich_to_use == "hỏi về quy trình xử lý vi phạm giao thông":
|
236 |
-
|
237 |
-
answer = await self.channel.llm.generate_text(message_text)
|
238 |
-
response = 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."
|
239 |
-
conv['isdone'] = True
|
240 |
else:
|
241 |
-
|
242 |
-
answer = await self.channel.llm.generate_text(message_text)
|
243 |
-
response = 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."
|
244 |
-
conv['isdone'] = True
|
245 |
else:
|
246 |
# Có command
|
247 |
if command == "xong":
|
@@ -256,11 +219,9 @@ class MessageProcessor:
|
|
256 |
conv['isdone'] = False
|
257 |
|
258 |
# 6. Gửi response và cập nhật final state
|
259 |
-
|
260 |
-
response_to_send = format_for_facebook(response.replace('**', '*')) if isinstance(response, str) else response
|
261 |
-
await self.facebook.send_message(message=response_to_send)
|
262 |
if hasattr(self.channel, 'sheets'):
|
263 |
-
await loop.run_in_executor(None, lambda:
|
264 |
return
|
265 |
|
266 |
def flatten_timestamp(self, ts):
|
@@ -454,14 +415,50 @@ class MessageProcessor:
|
|
454 |
logger.info(f"[MOCK] Creating Facebook post for sender_id={sender_id} with history={history}")
|
455 |
return "https://facebook.com/mock_post_url"
|
456 |
|
457 |
-
def
|
458 |
-
|
459 |
-
|
460 |
-
|
461 |
-
|
462 |
-
|
463 |
-
|
464 |
-
|
465 |
-
|
466 |
-
|
467 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
196 |
response = None
|
197 |
if not command:
|
198 |
if muc_dich_to_use == "hỏi về mức phạt":
|
199 |
+
response = await self.handle_muc_phat(conv, message_text, page_token, sender_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
200 |
elif muc_dich_to_use == "hỏi về quy tắc giao thông":
|
201 |
+
response = await self.handle_quy_tac(conv, message_text)
|
|
|
|
|
|
|
202 |
elif muc_dich_to_use == "hỏi về báo hiệu đường bộ":
|
203 |
+
response = await self.handle_bao_hieu(conv, message_text)
|
|
|
|
|
|
|
204 |
elif muc_dich_to_use == "hỏi về quy trình xử lý vi phạm giao thông":
|
205 |
+
response = await self.handle_quy_trinh(conv, message_text)
|
|
|
|
|
|
|
206 |
else:
|
207 |
+
response = await self.handle_khac(conv, message_text)
|
|
|
|
|
|
|
208 |
else:
|
209 |
# Có command
|
210 |
if command == "xong":
|
|
|
219 |
conv['isdone'] = False
|
220 |
|
221 |
# 6. Gửi response và cập nhật final state
|
222 |
+
await self.facebook.send_message(message=response)
|
|
|
|
|
223 |
if hasattr(self.channel, 'sheets'):
|
224 |
+
await loop.run_in_executor(None, lambda: sheets_client.log_conversation(**conv))
|
225 |
return
|
226 |
|
227 |
def flatten_timestamp(self, ts):
|
|
|
415 |
logger.info(f"[MOCK] Creating Facebook post for sender_id={sender_id} with history={history}")
|
416 |
return "https://facebook.com/mock_post_url"
|
417 |
|
418 |
+
async def handle_muc_phat(self, conv, message_text, page_token, sender_id):
|
419 |
+
vehicle = conv.get('originalvehicle', '')
|
420 |
+
action = conv.get('originalaction', '')
|
421 |
+
keywords = [kw.strip() for kw in vehicle.split(',') if kw.strip()]
|
422 |
+
if keywords:
|
423 |
+
if action:
|
424 |
+
logger.info(f"[DEBUG] tạo embedding: {action}")
|
425 |
+
embedding = await self.channel.embedder.create_embedding(action)
|
426 |
+
logger.info(f"[DEBUG] embedding: {embedding[:5]} ... (total {len(embedding)})")
|
427 |
+
matches = self.channel.supabase.match_documents(
|
428 |
+
embedding,
|
429 |
+
vehicle_keywords=keywords,
|
430 |
+
user_question=action
|
431 |
+
)
|
432 |
+
logger.info(f"[DEBUG] matches: {matches}")
|
433 |
+
if matches:
|
434 |
+
response = await self.format_search_results(message_text, matches, page_token, sender_id)
|
435 |
+
else:
|
436 |
+
response = "Xin lỗi, tôi không tìm thấy thông tin phù hợp."
|
437 |
+
else:
|
438 |
+
logger.info(f"[DEBUG] Không có hành vi vi phạm: {message_text}")
|
439 |
+
response = "Xin lỗi, tôi không tìm thấy thông tin về hành vi vi phạm trong câu hỏi của bạn."
|
440 |
+
conv['isdone'] = True
|
441 |
+
else:
|
442 |
+
response = "Vui lòng cho biết loại phương tiện bạn cần tìm (xe máy, ô tô...)"
|
443 |
+
conv['isdone'] = False
|
444 |
+
return response
|
445 |
+
|
446 |
+
async def handle_quy_tac(self, conv, message_text):
|
447 |
+
answer = await self.channel.llm.generate_text(message_text)
|
448 |
+
conv['isdone'] = True
|
449 |
+
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."
|
450 |
+
|
451 |
+
async def handle_bao_hieu(self, conv, message_text):
|
452 |
+
answer = await self.channel.llm.generate_text(message_text)
|
453 |
+
conv['isdone'] = True
|
454 |
+
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."
|
455 |
+
|
456 |
+
async def handle_quy_trinh(self, conv, message_text):
|
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_khac(self, conv, message_text):
|
462 |
+
answer = await self.channel.llm.generate_text(message_text)
|
463 |
+
conv['isdone'] = True
|
464 |
+
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."
|