VietCat commited on
Commit
e529ed6
·
1 Parent(s): 28c7707

adjust log

Browse files
app/gemini_client.py CHANGED
@@ -4,6 +4,7 @@ from google.generativeai.generative_models import GenerativeModel
4
  from loguru import logger
5
  from .request_limit_manager import RequestLimitManager
6
  from typing import List, Optional
 
7
 
8
  class GeminiClient:
9
  def __init__(self):
@@ -59,10 +60,10 @@ class GeminiClient:
59
  logger.info(f"[GEMINI][USAGE] Prompt Token Count: {response.usage_metadata.prompt_token_count} - Candidate Token Count: {response.usage_metadata.candidates_token_count} - Total Token Count: {response.usage_metadata.total_token_count}")
60
 
61
  if hasattr(response, 'text'):
62
- logger.info(f"[GEMINI][TEXT_RESPONSE] {response.text}")
63
  return response.text
64
  elif hasattr(response, 'candidates') and response.candidates:
65
- logger.info(f"[GEMINI][CANDIDATES_RESPONSE] {response.candidates[0].content.parts[0].text}")
66
  return response.candidates[0].content.parts[0].text
67
 
68
  logger.info(f"[GEMINI][RAW_RESPONSE] {response}")
 
4
  from loguru import logger
5
  from .request_limit_manager import RequestLimitManager
6
  from typing import List, Optional
7
+ from utils import _safe_truncate
8
 
9
  class GeminiClient:
10
  def __init__(self):
 
60
  logger.info(f"[GEMINI][USAGE] Prompt Token Count: {response.usage_metadata.prompt_token_count} - Candidate Token Count: {response.usage_metadata.candidates_token_count} - Total Token Count: {response.usage_metadata.total_token_count}")
61
 
62
  if hasattr(response, 'text'):
63
+ logger.info(f"[GEMINI][TEXT_RESPONSE] {_safe_truncate(response.text)}")
64
  return response.text
65
  elif hasattr(response, 'candidates') and response.candidates:
66
+ logger.info(f"[GEMINI][CANDIDATES_RESPONSE] {_safe_truncate(response.candidates[0].content.parts[0].text)}")
67
  return response.candidates[0].content.parts[0].text
68
 
69
  logger.info(f"[GEMINI][RAW_RESPONSE] {response}")
app/llm.py CHANGED
@@ -15,16 +15,9 @@ from .utils import (
15
  timing_decorator_async,
16
  timing_decorator_sync, # kept for compatibility even if unused here
17
  call_endpoint_with_retry,
 
18
  )
19
 
20
-
21
- def _safe_truncate(s: str, n: int = 1000) -> str:
22
- """Truncate long strings for logging purposes."""
23
- if not isinstance(s, str):
24
- s = str(s)
25
- return s if len(s) <= n else s[:n] + "... [truncated]"
26
-
27
-
28
  def _parse_json_from_text(text: str) -> Optional[Union[List[Dict[str, Any]], Dict[str, Any]]]:
29
  """Best-effort JSON extractor from LLM free-form responses.
30
 
@@ -136,7 +129,7 @@ class LLMClient:
136
  Tạo text từ prompt sử dụng LLM.
137
  """
138
  logger.info(
139
- f"[LLM] generate_text - provider: {self.provider}\n\t prompt: {_safe_truncate(prompt, 1200)}"
140
  )
141
  try:
142
  if self.provider == "openai":
@@ -154,7 +147,7 @@ class LLMClient:
154
  else:
155
  raise ValueError(f"Unsupported provider: {self.provider}")
156
 
157
- logger.info(f"[LLM] generate_text - provider: {self.provider}\n\t result: {_safe_truncate(result, 1200)}")
158
  return result
159
  except Exception as e:
160
  logger.exception(f"[LLM] Error generating text with {self.provider}: {e}")
@@ -226,7 +219,7 @@ class LLMClient:
226
  self._client, endpoint, payload, 3, 500, headers=headers
227
  )
228
  logger.info(
229
- f"[LLM] generate_text - provider: {self.provider}\n\t response: {_safe_truncate(str(response), 1200)}"
230
  )
231
  try:
232
  logger.info(
@@ -323,7 +316,7 @@ class LLMClient:
323
  return {
324
  "category": "unknown",
325
  "confidence": 0.0,
326
- "reasoning": f"Cannot parse JSON from response: {_safe_truncate(response, 500)}",
327
  }
328
 
329
  @timing_decorator_async
@@ -353,7 +346,7 @@ class LLMClient:
353
 
354
  try:
355
  logger.info(
356
- f"[LLM][RAW_RESPONSE][extract_entities] {_safe_truncate(response, 2000)}"
357
  )
358
  parsed = _parse_json_from_text(response or "")
359
  if isinstance(parsed, list):
@@ -380,7 +373,7 @@ class LLMClient:
380
  {{
381
  "muc_dich": "...",
382
  "phuong_tien": "...",
383
- "hanh_vi": "...",
384
  "cau_hoi": "..."
385
  }}
386
 
@@ -398,7 +391,7 @@ class LLMClient:
398
 
399
  **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 "".
400
 
401
- **hanh_vi**: 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 có thông tin rõ ràng, để chuỗi rỗng "".
402
 
403
  **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ý.
404
 
@@ -409,7 +402,7 @@ class LLMClient:
409
  {{
410
  "muc_dich": "hỏi về mức phạt",
411
  "phuong_tien": "Ô tô",
412
- "hanh_vi": "Không chấp hành hiệu lệnh của đèn tín hiệu giao thông",
413
  "cau_hoi": "Mức xử phạt cho hành vi ô tô không chấp hành hiệu lệnh của đèn tín hiệu giao thông là bao nhiêu?"
414
  }}
415
 
@@ -423,7 +416,7 @@ class LLMClient:
423
  """.strip()
424
 
425
  response = await self.generate_text(prompt, **kwargs)
426
- logger.info(f"[LLM][RAW][analyze] Kết quả trả về từ generate_text: {_safe_truncate(response, 2000)}")
427
 
428
  try:
429
  parsed = _parse_json_from_text(response or "")
 
15
  timing_decorator_async,
16
  timing_decorator_sync, # kept for compatibility even if unused here
17
  call_endpoint_with_retry,
18
+ _safe_truncate
19
  )
20
 
 
 
 
 
 
 
 
 
21
  def _parse_json_from_text(text: str) -> Optional[Union[List[Dict[str, Any]], Dict[str, Any]]]:
22
  """Best-effort JSON extractor from LLM free-form responses.
23
 
 
129
  Tạo text từ prompt sử dụng LLM.
130
  """
131
  logger.info(
132
+ f"[LLM] generate_text - provider: {self.provider}\n\t prompt: {_safe_truncate(prompt)}"
133
  )
134
  try:
135
  if self.provider == "openai":
 
147
  else:
148
  raise ValueError(f"Unsupported provider: {self.provider}")
149
 
150
+ logger.info(f"[LLM] generate_text - provider: {self.provider}\n\t result: {_safe_truncate(result)}")
151
  return result
152
  except Exception as e:
153
  logger.exception(f"[LLM] Error generating text with {self.provider}: {e}")
 
219
  self._client, endpoint, payload, 3, 500, headers=headers
220
  )
221
  logger.info(
222
+ f"[LLM] generate_text - provider: {self.provider}\n\t response: {_safe_truncate(str(response))}"
223
  )
224
  try:
225
  logger.info(
 
316
  return {
317
  "category": "unknown",
318
  "confidence": 0.0,
319
+ "reasoning": f"Cannot parse JSON from response: {_safe_truncate(response)}",
320
  }
321
 
322
  @timing_decorator_async
 
346
 
347
  try:
348
  logger.info(
349
+ f"[LLM][RAW_RESPONSE][extract_entities] {_safe_truncate(response)}"
350
  )
351
  parsed = _parse_json_from_text(response or "")
352
  if isinstance(parsed, list):
 
373
  {{
374
  "muc_dich": "...",
375
  "phuong_tien": "...",
376
+ "tu_khoa": "...",
377
  "cau_hoi": "..."
378
  }}
379
 
 
391
 
392
  **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 "".
393
 
394
+ **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 có thông tin rõ ràng, để chuỗi rỗng "".
395
 
396
  **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ý.
397
 
 
402
  {{
403
  "muc_dich": "hỏi về mức phạt",
404
  "phuong_tien": "Ô tô",
405
+ "tu_khoa": "Không chấp hành hiệu lệnh của đèn tín hiệu giao thông",
406
  "cau_hoi": "Mức xử phạt cho hành vi ô tô không chấp hành hiệu lệnh của đèn tín hiệu giao thông là bao nhiêu?"
407
  }}
408
 
 
416
  """.strip()
417
 
418
  response = await self.generate_text(prompt, **kwargs)
419
+ logger.info(f"[LLM][RAW][analyze] Kết quả trả về từ generate_text: {_safe_truncate(response)}")
420
 
421
  try:
422
  parsed = _parse_json_from_text(response or "")
app/message_processor.py CHANGED
@@ -116,17 +116,17 @@ class MessageProcessor:
116
  logger.info(f"[LLM][RAW] Kết quả trả về từ analyze: {llm_analysis}")
117
 
118
  muc_dich = None
119
- hanh_vi = None
120
  cau_hoi = None
121
  if isinstance(llm_analysis, dict):
122
  keywords = [self.normalize_vehicle_keyword(llm_analysis.get('phuong_tien', ''))]
123
  muc_dich = llm_analysis.get('muc_dich')
124
- hanh_vi = llm_analysis.get('hanh_vi')
125
  cau_hoi = llm_analysis.get('cau_hoi')
126
  elif isinstance(llm_analysis, list) and len(llm_analysis) > 0:
127
  keywords = [self.normalize_vehicle_keyword(llm_analysis[0].get('phuong_tien', ''))]
128
  muc_dich = llm_analysis[0].get('muc_dich')
129
- hanh_vi = llm_analysis[0].get('hanh_vi')
130
  cau_hoi = llm_analysis[0].get('cau_hoi')
131
  else:
132
  keywords = extract_keywords(message_text, VEHICLE_KEYWORDS)
@@ -135,13 +135,13 @@ class MessageProcessor:
135
  cau_hoi = cau_hoi.replace(kw, "")
136
  cau_hoi = cau_hoi.strip()
137
 
138
- logger.info(f"[DEBUG] Phương tiện: {keywords} - Hành vi: {hanh_vi} - Mục đích: {muc_dich} - Câu hỏi: {cau_hoi}")
139
 
140
  # Hợp nhất dữ liệu đã phân tích vào `conv`
141
  conv['originalcommand'] = command
142
  conv['originalcontent'] = remaining_text
143
  conv['originalvehicle'] = ','.join(keywords)
144
- conv['originalaction'] = hanh_vi
145
  conv['originalpurpose'] = muc_dich
146
  conv['originalquestion'] = cau_hoi or ""
147
 
@@ -328,7 +328,7 @@ class MessageProcessor:
328
  match_count=match_count,
329
  user_question=search_query
330
  )
331
- logger.info(f"[DEBUG] matches: {matches}")
332
  if matches:
333
  response = await self.format_search_results(conversation_context, question or action, matches, page_token, sender_id)
334
  else:
 
116
  logger.info(f"[LLM][RAW] Kết quả trả về từ analyze: {llm_analysis}")
117
 
118
  muc_dich = None
119
+ tu_khoa = None
120
  cau_hoi = None
121
  if isinstance(llm_analysis, dict):
122
  keywords = [self.normalize_vehicle_keyword(llm_analysis.get('phuong_tien', ''))]
123
  muc_dich = llm_analysis.get('muc_dich')
124
+ tu_khoa = llm_analysis.get('tu_khoa')
125
  cau_hoi = llm_analysis.get('cau_hoi')
126
  elif isinstance(llm_analysis, list) and len(llm_analysis) > 0:
127
  keywords = [self.normalize_vehicle_keyword(llm_analysis[0].get('phuong_tien', ''))]
128
  muc_dich = llm_analysis[0].get('muc_dich')
129
+ tu_khoa = llm_analysis[0].get('tu_khoa')
130
  cau_hoi = llm_analysis[0].get('cau_hoi')
131
  else:
132
  keywords = extract_keywords(message_text, VEHICLE_KEYWORDS)
 
135
  cau_hoi = cau_hoi.replace(kw, "")
136
  cau_hoi = cau_hoi.strip()
137
 
138
+ 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}")
139
 
140
  # Hợp nhất dữ liệu đã phân tích vào `conv`
141
  conv['originalcommand'] = command
142
  conv['originalcontent'] = remaining_text
143
  conv['originalvehicle'] = ','.join(keywords)
144
+ conv['originalaction'] = tu_khoa
145
  conv['originalpurpose'] = muc_dich
146
  conv['originalquestion'] = cau_hoi or ""
147
 
 
328
  match_count=match_count,
329
  user_question=search_query
330
  )
331
+ logger.info(f"[DEBUG] matches: {matches[:2]}...{matches[-2:]}")
332
  if matches:
333
  response = await self.format_search_results(conversation_context, question or action, matches, page_token, sender_id)
334
  else:
app/reranker.py CHANGED
@@ -249,5 +249,5 @@ class Reranker:
249
  # Cache kết quả với system mới
250
  self._set_cached_result(cache_key, scored)
251
 
252
- logger.info(f"[RERANK] Top reranked docs: {result}")
253
  return result
 
249
  # Cache kết quả với system mới
250
  self._set_cached_result(cache_key, scored)
251
 
252
+ logger.info(f"[RERANK] Top reranked docs: {result[:2]}...{result[-2:]}")
253
  return result
app/sheets.py CHANGED
@@ -198,7 +198,7 @@ class SheetsClient:
198
  if len(row) > id_col_idx:
199
  sheet_conv_id = str(row[id_col_idx]).strip()
200
  is_match = sheet_conv_id == target_conv_id
201
- logger.trace(f"Dòng {i}: So sánh ID: '{sheet_conv_id}' == '{target_conv_id}' -> {is_match}")
202
  if is_match:
203
  found_row_index = i
204
  found_row_data = dict(zip(header, row))
@@ -215,13 +215,13 @@ class SheetsClient:
215
  sheet_page_id = str(row[page_col_idx]).strip()
216
 
217
  id_match = (sheet_recipient_id == recipient_id) and (sheet_page_id == page_id)
218
- logger.trace(f"Dòng {i}: So sánh (user, page): ('{sheet_recipient_id}' == '{recipient_id}') AND ('{sheet_page_id}' == '{page_id}') -> {id_match}")
219
 
220
  if id_match:
221
  try:
222
  sheet_timestamps = [str(ts).strip() for ts in _flatten_and_unique_timestamps(json.loads(row[timestamp_col_idx]))]
223
  ts_match = event_timestamp and event_timestamp in sheet_timestamps
224
- logger.trace(f"Dòng {i}: So sánh timestamp: '{event_timestamp}' in {sheet_timestamps} -> {ts_match}")
225
  if ts_match:
226
  found_row_index = i
227
  found_row_data = dict(zip(header, row))
 
198
  if len(row) > id_col_idx:
199
  sheet_conv_id = str(row[id_col_idx]).strip()
200
  is_match = sheet_conv_id == target_conv_id
201
+ # logger.trace(f"Dòng {i}: So sánh ID: '{sheet_conv_id}' == '{target_conv_id}' -> {is_match}")
202
  if is_match:
203
  found_row_index = i
204
  found_row_data = dict(zip(header, row))
 
215
  sheet_page_id = str(row[page_col_idx]).strip()
216
 
217
  id_match = (sheet_recipient_id == recipient_id) and (sheet_page_id == page_id)
218
+ # logger.trace(f"Dòng {i}: So sánh (user, page): ('{sheet_recipient_id}' == '{recipient_id}') AND ('{sheet_page_id}' == '{page_id}') -> {id_match}")
219
 
220
  if id_match:
221
  try:
222
  sheet_timestamps = [str(ts).strip() for ts in _flatten_and_unique_timestamps(json.loads(row[timestamp_col_idx]))]
223
  ts_match = event_timestamp and event_timestamp in sheet_timestamps
224
+ # logger.trace(f"Dòng {i}: So sánh timestamp: '{event_timestamp}' in {sheet_timestamps} -> {ts_match}")
225
  if ts_match:
226
  found_row_index = i
227
  found_row_data = dict(zip(header, row))
app/supabase_db.py CHANGED
@@ -71,7 +71,7 @@ class SupabaseClient:
71
  words = cleaned_text.split()
72
  or_query_tsquery = " ".join([word for word in words if word not in VIETNAMESE_STOP_WORDS])
73
  logger.info(f"[DEBUG][RPC]: or_query_tsquery: {or_query_tsquery}")
74
- logger.info(f"[DEBUG][RPC]: embedding: {embedding}")
75
 
76
  try:
77
  payload = {
 
71
  words = cleaned_text.split()
72
  or_query_tsquery = " ".join([word for word in words if word not in VIETNAMESE_STOP_WORDS])
73
  logger.info(f"[DEBUG][RPC]: or_query_tsquery: {or_query_tsquery}")
74
+ logger.info(f"[DEBUG][RPC]: embedding: {embedding[:5]}...{embedding[-5:]}")
75
 
76
  try:
77
  payload = {
app/utils.py CHANGED
@@ -157,4 +157,47 @@ def get_random_message(message_list: List[str]) -> str:
157
  if not message_list:
158
  return "Đang xử lý..."
159
 
160
- return random.choice(message_list)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  if not message_list:
158
  return "Đang xử lý..."
159
 
160
+ return random.choice(message_list)
161
+
162
+ def _safe_truncate(
163
+ s: str,
164
+ nguong_a: int = 100,
165
+ do_dai_x: int = 50,
166
+ do_dai_y: int = 100
167
+ ) -> str:
168
+ """
169
+ Cắt chuỗi một cách thông minh dựa trên độ dài của nó.
170
+
171
+ - Nếu độ dài chuỗi < nguong_a: chỉ hiển thị `do_dai_x` ký tự đầu tiên.
172
+ - Nếu độ dài chuỗi >= nguong_a: hiển thị `do_dai_y` ký tự đầu và `do_dai_y` ký tự cuối.
173
+
174
+ Args:
175
+ s: Chuỗi đầu vào cần xử lý.
176
+ nguong_a (A): Ngưỡng độ dài để quyết định logic cắt chuỗi.
177
+ do_dai_x (X): Số ký tự đầu tiên cần hiển thị cho chuỗi ngắn.
178
+ do_dai_y (Y): Số ký tự đầu/cuối cần hiển thị cho chuỗi dài.
179
+
180
+ Returns:
181
+ Chuỗi đã được cắt ngắn theo quy tắc.
182
+ """
183
+ if not isinstance(s, str):
184
+ s = str(s)
185
+
186
+ s_len = len(s)
187
+
188
+ # --- Trường hợp 1: Độ dài chuỗi NGẮN HƠN ngưỡng A ---
189
+ if s_len < nguong_a:
190
+ # Nếu chuỗi đã ngắn hơn hoặc bằng X, trả về nguyên bản
191
+ if s_len <= do_dai_x:
192
+ return s
193
+ # Nếu không, cắt lấy X ký tự đầu
194
+ return f"{s[:do_dai_x]}... [đã cắt]"
195
+
196
+ # --- Trường hợp 2: Độ dài chuỗi DÀI HƠN hoặc BẰNG ngưỡng A ---
197
+ else:
198
+ # Nếu việc lấy Y ký tự đầu và Y cuối sẽ bao trọn cả chuỗi (2*Y >= s_len)
199
+ # thì không cần cắt để tránh hiển thị trùng lặp.
200
+ if s_len <= do_dai_y * 2:
201
+ return s
202
+ # Ngược lại, lấy Y ký tự đầu và Y ký tự cuối
203
+ return f"{s[:do_dai_y]}... [đã cắt] ...{s[-do_dai_y:]}"