@@ -77,14 +77,16 @@ def _normalize_level(level: Optional[str]) -> str:
7777
7878
7979def _get_user_level (user_id : str ) -> str :
80+ t0 = time .time ()
8081 with OrmSession (engine ) as s :
8182 row = s .query (UserProfile ).filter_by (user_id = user_id ).first ()
82- if row and row .cefr_level :
83- return _normalize_level ( row . cefr_level )
84- return ""
83+ level = _normalize_level ( row . cefr_level ) if row and row .cefr_level else ""
84+ logger . debug ( "user_level 查询 user=%s level=%r 耗时=%.3fs" , user_id [: 8 ], level , time . time () - t0 )
85+ return level
8586
8687
8788def _cache_get (text : str , kind : str , cefr_level : str ) -> Optional [Dict ]:
89+ t0 = time .time ()
8890 h = _hash_text (text )
8991 with OrmSession (engine ) as s :
9092 row = (
@@ -96,13 +98,24 @@ def _cache_get(text: str, kind: str, cefr_level: str) -> Optional[Dict]:
9698 row .hit_count = (row .hit_count or 0 ) + 1
9799 s .commit ()
98100 try :
99- return json .loads (row .explanation )
101+ data = json .loads (row .explanation )
102+ logger .info (
103+ "cache_get HIT kind=%s level=%s hit_count=%d 耗时=%.3fs" ,
104+ kind , cefr_level , row .hit_count , time .time () - t0 ,
105+ )
106+ return data
100107 except json .JSONDecodeError :
108+ logger .warning (
109+ "cache_get 命中但 JSON 解析失败 kind=%s level=%s 耗时=%.3fs" ,
110+ kind , cefr_level , time .time () - t0 ,
111+ )
101112 return None
113+ logger .info ("cache_get MISS kind=%s level=%s 耗时=%.3fs" , kind , cefr_level , time .time () - t0 )
102114 return None
103115
104116
105117def _cache_set (text : str , kind : str , cefr_level : str , explanation : Dict , overwrite : bool = False ) -> None :
118+ t0 = time .time ()
106119 h = _hash_text (text )
107120 with OrmSession (engine ) as s :
108121 existing = (
@@ -116,6 +129,9 @@ def _cache_set(text: str, kind: str, cefr_level: str, explanation: Dict, overwri
116129 existing .source_text = text
117130 existing .hit_count = 1 # 重生成清零计数
118131 s .commit ()
132+ logger .debug ("cache_set OVERWRITE kind=%s level=%s 耗时=%.3fs" , kind , cefr_level , time .time () - t0 )
133+ else :
134+ logger .debug ("cache_set SKIP(已存在)kind=%s level=%s 耗时=%.3fs" , kind , cefr_level , time .time () - t0 )
119135 return
120136 s .add (
121137 ExplanationCache (
@@ -128,8 +144,10 @@ def _cache_set(text: str, kind: str, cefr_level: str, explanation: Dict, overwri
128144 )
129145 try :
130146 s .commit ()
147+ logger .debug ("cache_set INSERT kind=%s level=%s 耗时=%.3fs" , kind , cefr_level , time .time () - t0 )
131148 except IntegrityError :
132149 s .rollback ()
150+ logger .warning ("cache_set IntegrityError kind=%s level=%s 耗时=%.3fs" , kind , cefr_level , time .time () - t0 )
133151
134152
135153def _strip_code_fences (raw : str ) -> str :
@@ -190,6 +208,7 @@ async def explain_text(
190208 :param context: word 模式下提供所在句子,sentence 模式下忽略
191209 :param force: True 时跳过缓存,重新生成并覆盖;带 3s debounce
192210 """
211+ t_start = time .time ()
193212 if not text or not text .strip ():
194213 raise ValueError ("解读内容不能为空" )
195214 text = text .strip ()
@@ -198,6 +217,12 @@ async def explain_text(
198217 if kind not in VALID_KINDS :
199218 raise ValueError (f"无效的解读类型: { kind } ,可选值: sentence / word" )
200219
220+ preview = text if len (text ) <= 30 else text [:30 ] + "…"
221+ logger .info (
222+ "explain_text 入口 kind=%s text_len=%d preview=%r user=%s force=%s" ,
223+ kind , len (text ), preview , user_id [:8 ] if user_id else "-" , force ,
224+ )
225+
201226 cefr_level = _get_user_level (user_id )
202227 cache_level = cefr_level or _DEFAULT_LEVEL
203228
@@ -207,30 +232,58 @@ async def explain_text(
207232 else :
208233 cached = _cache_get (text , kind , cache_level )
209234 if cached is not None :
235+ logger .info (
236+ "explain_text 完成 kind=%s 来源=cache 总耗时=%.3fs" ,
237+ kind , time .time () - t_start ,
238+ )
210239 return {"explanation" : cached , "cefr_level" : cache_level , "cached" : True }
211240
212241 prompt_level = cefr_level or _DEFAULT_LEVEL
242+ t_prompt = time .time ()
213243 if kind == "sentence" :
244+ liaison = liaison_prompt_block ()
214245 system_prompt = EXPLAIN_SENTENCE_PROMPT .format (
215246 cefr_level = prompt_level ,
216- liaison_kb = liaison_prompt_block (),
247+ liaison_kb = liaison ,
248+ )
249+ logger .debug (
250+ "prompt 拼装 kind=sentence system_len=%d liaison_len=%d 耗时=%.3fs" ,
251+ len (system_prompt ), len (liaison ), time .time () - t_prompt ,
217252 )
218253 else :
219254 system_prompt = EXPLAIN_WORD_PROMPT .format (
220255 cefr_level = prompt_level ,
221256 text = text .replace ('"' , '\\ "' ),
222257 context = (context or "" ).replace ('"' , '\\ "' ),
223258 )
259+ logger .debug (
260+ "prompt 拼装 kind=word system_len=%d context_len=%d 耗时=%.3fs" ,
261+ len (system_prompt ), len (context or "" ), time .time () - t_prompt ,
262+ )
224263
225264 messages = [
226265 {"role" : "system" , "content" : system_prompt },
227266 {"role" : "user" , "content" : text },
228267 ]
229268 client = get_client ()
269+ t_llm = time .time ()
230270 raw = await client .complete (messages , max_tokens = 2000 , scene = "explain" )
271+ llm_elapsed = time .time () - t_llm
272+ logger .info (
273+ "explain_text LLM 完成 kind=%s output_len=%d 耗时=%.2fs" ,
274+ kind , len (raw or "" ), llm_elapsed ,
275+ )
276+
277+ t_parse = time .time ()
231278 explanation = _parse_explanation (raw )
279+ logger .debug ("explain_text JSON 解析 耗时=%.3fs fields=%d" , time .time () - t_parse , len (explanation ))
232280
233281 _cache_set (text , kind , cache_level , explanation , overwrite = force )
282+ total = time .time () - t_start
283+ logger .info (
284+ "explain_text 完成 kind=%s 来源=llm 总耗时=%.2fs (LLM 占比=%.0f%%)" ,
285+ kind , total , (llm_elapsed / total * 100 ) if total > 0 else 0 ,
286+ )
234287 return {"explanation" : explanation , "cefr_level" : cache_level , "cached" : False , "regenerated" : force }
235288
236289
@@ -266,12 +319,19 @@ async def stream_sentence_explanation(
266319 - 结束后额外 yield 一个 {"_done": True} 事件
267320 :param force: True 时跳过缓存重新生成(带 3s debounce)
268321 """
322+ t_start = time .time ()
269323 if not text or not text .strip ():
270324 raise ValueError ("解读内容不能为空" )
271325 text = text .strip ()
272326 if len (text ) > MAX_TEXT_LEN :
273327 raise ValueError (f"解读内容不能超过 { MAX_TEXT_LEN } 字符" )
274328
329+ preview = text if len (text ) <= 30 else text [:30 ] + "…"
330+ logger .info (
331+ "stream_sentence 入口 text_len=%d preview=%r user=%s force=%s" ,
332+ len (text ), preview , user_id [:8 ] if user_id else "-" , force ,
333+ )
334+
275335 cefr_level = _get_user_level (user_id )
276336 cache_level = cefr_level or _DEFAULT_LEVEL
277337
@@ -282,23 +342,31 @@ async def stream_sentence_explanation(
282342 else :
283343 cached = _cache_get (text , "sentence" , cache_level )
284344 if cached is not None :
345+ logger .info ("stream_sentence 来源=cache 总耗时=%.3fs" , time .time () - t_start )
285346 yield json .dumps ({"_cached" : True , "cefr_level" : cache_level , "explanation" : cached }, ensure_ascii = False )
286347 return
287348
288349 prompt_level = cefr_level or _DEFAULT_LEVEL
350+ t_prompt = time .time ()
351+ liaison = liaison_prompt_block ()
289352 system_prompt = (
290353 EXPLAIN_SENTENCE_PROMPT .format (
291354 cefr_level = prompt_level ,
292- liaison_kb = liaison_prompt_block () ,
355+ liaison_kb = liaison ,
293356 )
294357 + _STREAM_FORMAT_HINT
295358 )
359+ logger .debug (
360+ "stream prompt 拼装 system_len=%d liaison_len=%d 耗时=%.3fs" ,
361+ len (system_prompt ), len (liaison ), time .time () - t_prompt ,
362+ )
296363 messages = [
297364 {"role" : "system" , "content" : system_prompt },
298365 {"role" : "user" , "content" : text },
299366 ]
300367
301368 client = get_client ()
369+ t_llm = time .time ()
302370 buffer = ""
303371 collected : Dict [str , object ] = {}
304372 async for chunk in client .chat_stream_messages (messages , scene = "explain" ):
@@ -332,6 +400,12 @@ async def stream_sentence_explanation(
332400 except json .JSONDecodeError :
333401 pass
334402
403+ llm_elapsed = time .time () - t_llm
335404 if collected :
336405 _cache_set (text , "sentence" , cache_level , collected , overwrite = force )
406+ total = time .time () - t_start
407+ logger .info (
408+ "stream_sentence 完成 来源=llm fields=%d LLM=%.2fs 总耗时=%.2fs" ,
409+ len (collected ), llm_elapsed , total ,
410+ )
337411 yield json .dumps ({"_done" : True , "cefr_level" : cache_level , "regenerated" : force }, ensure_ascii = False )
0 commit comments