@@ -90,7 +90,8 @@ def set_progress(msg: str, value: float):
9090 llm = Llama (
9191 model_path = model_path ,
9292 n_gpu_layers = n_gpu_layers ,
93- n_ctx = 4096
93+ n_ctx = 4096 ,
94+ verbose = False
9495 )
9596 # 步骤2: 从每个切片提取术语(包含上下文)
9697 term_contexts = extract_terms_with_context (chunks , llm , config , stop_event , add_progress )
@@ -258,13 +259,13 @@ def extract_terms_from_chunk(
258259- 最多提取{ config .max_terms_per_chunk } 个最重要的术语
259260
260261【输出格式】
261- 请以JSON数组格式输出 :
262- ["term1", "term2","term3", ... ]
262+ 严格只输出 JSON 数组,不要输出任何思考过程、分析说明或 markdown 代码围栏。示例 :
263+ ["term1", "term2", "term3"]
263264
264265【文本内容】
265266{ chunk }
266267
267- 请开始提取 :
268+ 请直接输出 JSON 数组 :
268269"""
269270 try :
270271 # 🔥 使用内部翻译函数
@@ -276,33 +277,110 @@ def extract_terms_from_chunk(
276277 logger .error (f"片段 { chunk_index } 术语提取失败: { e } " )
277278 return []
278279
280+ def _strip_thinking (response : str ) -> str :
281+ """剥除 LLM 思考过程(Reasoning/Thinking 标签及内容),保留最终答案"""
282+ # 1. 剥除 <think>...</think> 标签(DeepSeek-R1 等)
283+ response = re .sub (r'<think>.*?</think>' , '' , response , flags = re .DOTALL | re .IGNORECASE )
284+ # 2. 剥除 Thinking Process: ... 到 "Output:" / "Final Answer:" 或第一个 JSON 数组之前的段落
285+ response = re .sub (
286+ r'(?:Thinking\s*Process|Reasoning|思考过程|分析过程)[::]\s*.*?'
287+ r'(?:Output[::]|Final\s*Answer[::]|答案[::]|请开始提取:|请输出|JSON\s*输出|输出格式[::])\s*' ,
288+ '' ,
289+ response ,
290+ flags = re .DOTALL | re .IGNORECASE
291+ )
292+ # 3. 如果仍有明显的思考段落(多级列表且不含 JSON 数组),尝试截断到第一个 '['
293+ first_bracket = response .find ('[' )
294+ if first_bracket > 0 :
295+ prefix = response [:first_bracket ]
296+ if len (prefix ) > 300 and re .search (r'(?:\*\*.*\*\*|\d+\.\s+|Analyze|Step|Task|Categories|注意|请)' , prefix ):
297+ response = response [first_bracket :]
298+ return response .strip ()
299+
300+ def _extract_json_array (response : str ) -> Optional [str ]:
301+ """从 LLM 响应中提取 JSON 数组文本"""
302+ # 优先匹配 markdown 代码围栏内的 JSON 数组
303+ code_block_match = re .search (r'```(?:json)?\s*(\[.*?\])\s*```' , response , re .DOTALL )
304+ if code_block_match :
305+ return code_block_match .group (1 )
306+ # 使用括号计数器寻找最外层合法 JSON 数组起点
307+ start = response .find ('[' )
308+ if start == - 1 :
309+ return None
310+ depth = 0
311+ in_string = False
312+ escape = False
313+ for i , ch in enumerate (response [start :], start ):
314+ if escape :
315+ escape = False
316+ continue
317+ if ch == '\\ ' :
318+ escape = True
319+ continue
320+ if ch == '"' :
321+ in_string = not in_string
322+ continue
323+ if in_string :
324+ continue
325+ if ch == '[' :
326+ depth += 1
327+ elif ch == ']' :
328+ depth -= 1
329+ if depth == 0 :
330+ return response [start :i + 1 ]
331+ return None
332+
333+ def _repair_json_array (json_text : str ) -> str :
334+ """修复 LLM 输出的常见 JSON 数组问题"""
335+ # 移除单行注释
336+ json_text = re .sub (r'//.*?$' , '' , json_text , flags = re .MULTILINE )
337+ # 移除多行注释
338+ json_text = re .sub (r'/\*.*?\*/' , '' , json_text , flags = re .DOTALL )
339+ # 移除尾逗号(}, ])前的逗号)
340+ json_text = re .sub (r',\s*([}\]])' , r'\1' , json_text )
341+ return json_text
342+
343+ def _fallback_line_parse (response : str ) -> List [str ]:
344+ """逐行提取双引号包围的字符串作为术语"""
345+ terms = []
346+ for line in response .split ('\n ' ):
347+ line = line .strip ()
348+ if line .startswith ('"' ) and line .endswith ('"' ):
349+ term = line .strip ('"' ).strip ().lower ()
350+ if 2 <= len (term ) <= 50 :
351+ terms .append (term )
352+ return terms [:15 ]
353+
279354def parse_term_extraction_response (response : str ) -> List [str ]:
280- """解析术语提取响应"""
355+ """解析术语提取响应(多层容错) """
281356 try :
282- # 提取JSON数组
283- json_match = re .search (r'\[.*?\]' , response , re .DOTALL )
284- if json_match :
285- json_text = json_match .group ()
286- terms_list = json .loads (json_text )
287-
357+ # 第 1 层:剥除思考过程后提取 JSON
358+ cleaned = _strip_thinking (response )
359+ json_text = _extract_json_array (cleaned )
360+ if json_text :
361+ repaired = _repair_json_array (json_text )
362+ try :
363+ terms_list = json .loads (repaired )
364+ except json .JSONDecodeError :
365+ terms_list = json .loads (json_text )
288366 if isinstance (terms_list , list ):
289367 return [term .strip ().lower () for term in terms_list if isinstance (term , str ) and term .strip ()]
290368
291- # 备用解析:逐行提取
292- terms = []
293- lines = response .split ('\n ' )
369+ # 第 2 层:剥除思考过程后备用逐行解析
370+ terms = _fallback_line_parse (cleaned )
371+ if terms :
372+ return terms
294373
295- for line in lines :
296- line = line .strip ()
297- if line .startswith ('"' ) and line .endswith ('"' ):
298- term = line .strip ('"' ).strip ().lower ()
299- if 2 <= len (term ) <= 50 :
300- terms .append (term )
374+ # 第 3 层:原始响应逐行解析(最终兜底)
375+ terms = _fallback_line_parse (response )
376+ if terms :
377+ return terms
301378
302- return terms [: 15 ] # 限制数量
379+ return []
303380
304381 except Exception as e :
305382 logger .error (f"解析术语提取响应失败: { e } " )
383+ logger .error (f"原始响应内容(前2000字符): { response [:2000 ]} " )
306384 return []
307385
308386def extract_term_context (text : str , term : str , context_window : int = 50 ) -> str :
0 commit comments