VietCat commited on
Commit
d79c9dd
·
1 Parent(s): 01775a7

update analyze flow

Browse files
Files changed (3) hide show
  1. app/constants.py +1 -1
  2. app/llm.py +16 -11
  3. app/message_processor.py +53 -29
app/constants.py CHANGED
@@ -192,4 +192,4 @@ FOUND_REGULATIONS_MESSAGES = [
192
  ]
193
 
194
  SHEET_RANGE = 'chat!A2:N'
195
- VERSION_NUMBER = 123456798
 
192
  ]
193
 
194
  SHEET_RANGE = 'chat!A2:N'
195
+ VERSION_NUMBER = 123456799
app/llm.py CHANGED
@@ -370,17 +370,16 @@ class LLMClient:
370
  Bạn là một chuyên gia phân tích ngôn ngữ tự nhiên (NLP) chuyên xử lý các câu hỏi về luật giao thông Việt Nam. Nhiệm vụ của bạn là đọc kỹ **lịch sử trò chuyện** và **câu hỏi mới nhất** của người dùng để trích xuất thông tin vào một cấu trúc JSON duy nhất. Chỉ trả về đối tượng JSON, không thêm bất kỳ giải thích nào.
371
 
372
  Định dạng JSON bắt buộc:
373
-
374
  {{
375
  "muc_dich": "...",
376
  "phuong_tien": "...",
377
- "tu_khoa": "...",
378
  "cau_hoi": "..."
379
  }}
380
 
381
  Hướng dẫn chi tiết cho từng trường:
382
 
383
- **muc_dich**: Phải là một trong các giá trị sau:
384
  - "hỏi về mức phạt"
385
  - "hỏi về quy tắc giao thông"
386
  - "hỏi về báo hiệu đường bộ"
@@ -388,23 +387,29 @@ class LLMClient:
388
  - "thông tin cá nhân của AI"
389
  - "khác"
390
 
391
- **Phải dựa vào câu hỏi mới nhất để xác định.**
392
-
393
  **phuong_tien**: Tên phương tiện được đề cập trong câu hỏi mới hoặc trong lịch sử gần nhất. Nếu không có, để chuỗi rỗng "".
394
 
395
- **tu_khoa**: Là cụm từ hoặc từ khóa ngắn gọn và phù hợp nhất để **tìm kiếm nội dung liên quan đến câu hỏi**. Có thể là tên hành vi vi phạm, thuật ngữ pháp lý, hoặc khái niệm về quy tắc/báo hiệu/vi phạm. Nếu không thông tin rõ ràng, để chuỗi rỗng "".
 
 
 
396
 
397
  **cau_hoi**: Diễn đạt lại câu hỏi mới nhất của người dùng thành một câu hỏi hoàn chỉnh, kết hợp ngữ cảnh từ lịch sử nếu cần, sử dụng đúng thuật ngữ pháp lý.
398
 
399
  VÍ DỤ MẪU:
400
 
401
- Câu hỏi đầu vào: "ô tô vượt đèn đỏ phạt nhiêu?"
 
 
 
 
 
402
  Kết quả JSON mong muốn:
403
  {{
404
  "muc_dich": "hỏi về mức phạt",
405
- "phuong_tien": "Ô ",
406
- "tu_khoa": "Không chấp hành hiệu lệnh của đèn tín hiệu giao thông",
407
- "cau_hoi": "Mức xử phạt cho hành vi ô không chấp hành hiệu lệnh của đèn tín hiệu giao thông là bao nhiêu?"
408
  }}
409
 
410
  Bây giờ, hãy phân tích lịch sử và câu hỏi sau và chỉ trả về đối tượng JSON.
@@ -412,7 +417,7 @@ class LLMClient:
412
  Lịch sử trò chuyện:
413
  "{conversation_context}"
414
 
415
- Câu hỏi:
416
  "{text}"
417
  """.strip()
418
 
 
370
  Bạn là một chuyên gia phân tích ngôn ngữ tự nhiên (NLP) chuyên xử lý các câu hỏi về luật giao thông Việt Nam. Nhiệm vụ của bạn là đọc kỹ **lịch sử trò chuyện** và **câu hỏi mới nhất** của người dùng để trích xuất thông tin vào một cấu trúc JSON duy nhất. Chỉ trả về đối tượng JSON, không thêm bất kỳ giải thích nào.
371
 
372
  Định dạng JSON bắt buộc:
 
373
  {{
374
  "muc_dich": "...",
375
  "phuong_tien": "...",
376
+ "tu_khoa": [...],
377
  "cau_hoi": "..."
378
  }}
379
 
380
  Hướng dẫn chi tiết cho từng trường:
381
 
382
+ **muc_dich**: Phải là một trong các giá trị sau, dựa vào **câu hỏi mới nhất**:
383
  - "hỏi về mức phạt"
384
  - "hỏi về quy tắc giao thông"
385
  - "hỏi về báo hiệu đường bộ"
 
387
  - "thông tin cá nhân của AI"
388
  - "khác"
389
 
 
 
390
  **phuong_tien**: Tên phương tiện được đề cập trong câu hỏi mới hoặc trong lịch sử gần nhất. Nếu không có, để chuỗi rỗng "".
391
 
392
+ **tu_khoa**: **MỘT DANH SÁCH (LIST) các thuật ngữ pháp lý** ngắn gọn, chính xác nhất để tìm kiếm trong sở dữ liệu luật.
393
+ - **Chuyển đổi ngôn ngữ**: Chuyển đổi ngôn ngữ đời thường của người dùng (ví dụ: "vượt đèn đỏ") thành thuật ngữ pháp lý chính xác (ví dụ: "Không chấp hành hiệu lệnh của đèn tín hiệu giao thông").
394
+ - **Trích xuất nhiều từ khóa**: Nếu câu hỏi phức tạp, hãy trích xuất nhiều từ khóa liên quan. Ví dụ: "vượt đèn đỏ khi đang say rượu" -> ["Không chấp hành hiệu lệnh của đèn tín hiệu giao thông", "Điều khiển xe trên đường mà trong máu hoặc hơi thở có nồng độ cồn"].
395
+ - **Xử lý ngữ cảnh không hài lòng**: Đọc kỹ lịch sử. Nếu người dùng hỏi lại hoặc thể hiện không hài lòng (ví dụ: "không phải", "ý tôi là..."), hãy tạo ra một bộ từ khóa **MỚI** và **KHÁC** với các từ khóa đã dùng trong lượt hỏi trước (được ghi trong `(từ khóa đã dùng: ...)` của lịch sử) để tìm kiếm thông tin chính xác hơn.
396
 
397
  **cau_hoi**: Diễn đạt lại câu hỏi mới nhất của người dùng thành một câu hỏi hoàn chỉnh, kết hợp ngữ cảnh từ lịch sử nếu cần, sử dụng đúng thuật ngữ pháp lý.
398
 
399
  VÍ DỤ MẪU:
400
 
401
+ Lịch sử trò chuyện:
402
+ "Người dùng: xe máy đi vào đường cấm thì sao? (từ khóa đã dùng: đi vào khu vực cấm)
403
+ Trợ lý: Mức phạt cho hành vi đi vào khu vực cấm là..."
404
+
405
+ Câu hỏi mới nhất: "không phải, ý tôi là đi vào đường cao tốc cơ"
406
+
407
  Kết quả JSON mong muốn:
408
  {{
409
  "muc_dich": "hỏi về mức phạt",
410
+ "phuong_tien": "Xe máy",
411
+ "tu_khoa": ["Điều khiển xe đi vào đường cao tốc"],
412
+ "cau_hoi": "Mức xử phạt cho hành vi xe máy đi vào đường cao tốc là bao nhiêu?"
413
  }}
414
 
415
  Bây giờ, hãy phân tích lịch sử và câu hỏi sau và chỉ trả về đối tượng JSON.
 
417
  Lịch sử trò chuyện:
418
  "{conversation_context}"
419
 
420
+ Câu hỏi mới nhất:
421
  "{text}"
422
  """.strip()
423
 
app/message_processor.py CHANGED
@@ -102,29 +102,44 @@ class MessageProcessor:
102
  logger.info(f"[LLM][RAW] Kết quả trả về từ analyze: {llm_analysis}")
103
 
104
  muc_dich = None
105
- tu_khoa = None
106
  cau_hoi = None
107
- if isinstance(llm_analysis, dict):
108
- keywords = [self.normalize_vehicle_keyword(llm_analysis.get('phuong_tien', ''))]
109
- muc_dich = llm_analysis.get('muc_dich')
110
- tu_khoa = llm_analysis.get('tu_khoa')
111
- cau_hoi = llm_analysis.get('cau_hoi')
112
- elif isinstance(llm_analysis, list) and len(llm_analysis) > 0:
113
- keywords = [self.normalize_vehicle_keyword(llm_analysis[0].get('phuong_tien', ''))]
114
- muc_dich = llm_analysis[0].get('muc_dich')
115
- tu_khoa = llm_analysis[0].get('tu_khoa')
116
- cau_hoi = llm_analysis[0].get('cau_hoi')
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  else:
 
118
  keywords = extract_keywords(message_text, VEHICLE_KEYWORDS)
119
  cau_hoi = message_text
120
  for kw in keywords: cau_hoi = cau_hoi.replace(kw, "")
121
  cau_hoi = cau_hoi.strip()
122
 
123
- logger.info(f"[DEBUG] Phương tiện: {keywords} - Hành vi: {tu_khoa} - Mục đích: {muc_dich} - Câu hỏi: {cau_hoi}")
 
124
 
125
  conv.update({
126
  'originalcommand': command, 'originalcontent': remaining_text, 'originalvehicle': ','.join(keywords),
127
- 'originalaction': tu_khoa, 'originalpurpose': muc_dich, 'originalquestion': cau_hoi or ""
128
  })
129
 
130
  muc_dich_to_use = muc_dich or conv.get('originalpurpose')
@@ -174,22 +189,29 @@ class MessageProcessor:
174
  return max([self.get_latest_timestamp(item) for item in ts_value]) if ts_value else 0
175
  return 0
176
 
177
- def get_llm_history(self, history: List):
 
 
 
 
178
  sorted_history = sorted(history, key=lambda row: self.get_latest_timestamp(row.get('timestamp', 0)))
179
- total_chars = 0
180
- MAX_CONTEXT_CHARS = 20_000
181
- conversation_context = []
182
- for row in reversed(sorted_history):
183
- temp_blocks = []
184
- if row.get('originaltext'):
185
- temp_blocks.append({"role": "user", "content": row['originaltext']})
186
- if row.get('systemresponse'):
187
- temp_blocks.append({"role": "assistant", "content": row['systemresponse']})
188
- temp_total = sum(len(block['content']) for block in temp_blocks)
189
- if total_chars + temp_total > MAX_CONTEXT_CHARS: continue
190
- conversation_context = temp_blocks + conversation_context
191
- total_chars += temp_total
192
- return conversation_context
 
 
 
193
 
194
  def flatten_timestamp(self, ts):
195
  flat = []
@@ -272,6 +294,7 @@ class MessageProcessor:
272
  vehicle = conv.get('originalvehicle', '')
273
  action = conv.get('originalaction', '')
274
  question = conv.get('originalquestion', '')
 
275
 
276
  if not action and not question:
277
  return "Để tra cứu mức phạt, bạn vui lòng cung cấp hành vi vi phạm nhé."
@@ -285,6 +308,7 @@ class MessageProcessor:
285
  loop = asyncio.get_event_loop()
286
  match_count = get_settings().match_count
287
 
 
288
  matches = await loop.run_in_executor(
289
  None,
290
  lambda: self.channel.supabase.match_documents(
@@ -300,7 +324,7 @@ class MessageProcessor:
300
  else:
301
  response = "Xin lỗi, tôi không tìm thấy thông tin phù hợp với hành vi bạn mô tả."
302
  except Exception as e:
303
- logger.error(f"Lỗi khi tra cứu mức phạt: {e}")
304
  response = "Đã có lỗi xảy ra trong quá trình tra cứu. Vui lòng thử lại sau."
305
 
306
  conv['isdone'] = True
 
102
  logger.info(f"[LLM][RAW] Kết quả trả về từ analyze: {llm_analysis}")
103
 
104
  muc_dich = None
105
+ tu_khoa_list = [] # Sửa: đổi tên thành tu_khoa_list và khởi tạo là list rỗng
106
  cau_hoi = None
107
+
108
+ # Sửa: Đơn giản hóa logic, vì LLM giờ luôn trả về 1 dict
109
+ analysis_data = None
110
+ if isinstance(llm_analysis, list) and llm_analysis:
111
+ analysis_data = llm_analysis[0]
112
+ elif isinstance(llm_analysis, dict):
113
+ analysis_data = llm_analysis
114
+
115
+ if analysis_data:
116
+ # Lấy phương tiện và chuẩn hóa
117
+ phuong_tien = self.normalize_vehicle_keyword(analysis_data.get('phuong_tien', ''))
118
+ keywords = [phuong_tien] if phuong_tien else []
119
+
120
+ muc_dich = analysis_data.get('muc_dich')
121
+
122
+ # Lấy danh sách từ khóa, đảm bảo nó là list
123
+ raw_tu_khoa = analysis_data.get('tu_khoa', [])
124
+ if isinstance(raw_tu_khoa, list):
125
+ tu_khoa_list = raw_tu_khoa
126
+ elif isinstance(raw_tu_khoa, str) and raw_tu_khoa:
127
+ tu_khoa_list = [raw_tu_khoa] # Chuyển string thành list 1 phần tử
128
+
129
+ cau_hoi = analysis_data.get('cau_hoi')
130
  else:
131
+ # Fallback logic cũ nếu LLM không phân tích được
132
  keywords = extract_keywords(message_text, VEHICLE_KEYWORDS)
133
  cau_hoi = message_text
134
  for kw in keywords: cau_hoi = cau_hoi.replace(kw, "")
135
  cau_hoi = cau_hoi.strip()
136
 
137
+ # Sửa: Log danh sách từ khóa
138
+ logger.info(f"[DEBUG] Phương tiện: {keywords} - Từ khóa pháp lý: {tu_khoa_list} - Mục đích: {muc_dich} - Câu hỏi: {cau_hoi}")
139
 
140
  conv.update({
141
  'originalcommand': command, 'originalcontent': remaining_text, 'originalvehicle': ','.join(keywords),
142
+ 'originalaction': ' '.join(tu_khoa_list), 'originalpurpose': muc_dich, 'originalquestion': cau_hoi or ""
143
  })
144
 
145
  muc_dich_to_use = muc_dich or conv.get('originalpurpose')
 
189
  return max([self.get_latest_timestamp(item) for item in ts_value]) if ts_value else 0
190
  return 0
191
 
192
+ def get_llm_history(self, history: List[Dict[str, Any]]) -> str:
193
+ """
194
+ Định dạng lịch sử hội thoại thành một chuỗi văn bản duy nhất,
195
+ bao gồm cả các từ khóa đã sử dụng để cung cấp ngữ cảnh cho LLM.
196
+ """
197
  sorted_history = sorted(history, key=lambda row: self.get_latest_timestamp(row.get('timestamp', 0)))
198
+
199
+ # Lấy 5 lượt hội thoại gần nhất để tránh context quá dài
200
+ recent_history = sorted_history[-5:]
201
+
202
+ context_lines = []
203
+ for row in recent_history:
204
+ user_text = row.get('originaltext', '').strip()
205
+ assistant_text = row.get('systemresponse', '').strip()
206
+ keywords_used = row.get('originalaction', '').strip()
207
+
208
+ if user_text:
209
+ context_lines.append(f"Người dùng: {user_text} (từ khóa đã dùng: {keywords_used})")
210
+
211
+ if assistant_text:
212
+ context_lines.append(f"Trợ lý: {assistant_text}")
213
+
214
+ return "\n".join(context_lines)
215
 
216
  def flatten_timestamp(self, ts):
217
  flat = []
 
294
  vehicle = conv.get('originalvehicle', '')
295
  action = conv.get('originalaction', '')
296
  question = conv.get('originalquestion', '')
297
+
298
 
299
  if not action and not question:
300
  return "Để tra cứu mức phạt, bạn vui lòng cung cấp hành vi vi phạm nhé."
 
308
  loop = asyncio.get_event_loop()
309
  match_count = get_settings().match_count
310
 
311
+
312
  matches = await loop.run_in_executor(
313
  None,
314
  lambda: self.channel.supabase.match_documents(
 
324
  else:
325
  response = "Xin lỗi, tôi không tìm thấy thông tin phù hợp với hành vi bạn mô tả."
326
  except Exception as e:
327
+ logger.error(f"Lỗi khi tra cứu mức phạt: {e}\n{traceback.format_exc()}")
328
  response = "Đã có lỗi xảy ra trong quá trình tra cứu. Vui lòng thử lại sau."
329
 
330
  conv['isdone'] = True