Skip to content

Commit 7ca5993

Browse files
author
MARSYAS-PC\llxxy
committed
feat: improve support for reasoning models
1 parent 4de8206 commit 7ca5993

3 files changed

Lines changed: 103 additions & 22 deletions

File tree

service/glossary/ai_generator.py

Lines changed: 99 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
279354
def 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

308386
def extract_term_context(text: str, term: str, context_window: int = 50) -> str:

service/log/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,6 @@ def get_logger(name: str = "LightVT") -> logging.Logger:
8080
logger = loggerDict[name] = setup_logger(name)
8181
return logger
8282

83+
logging.getLogger("llama_cpp").setLevel(logging.ERROR)
84+
8385
__all__ = ["get_logger"]

service/translator/llm_helper/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ def create_llm(model_path: str, n_gpu_layers: int = 0, n_ctx: int = 4096) -> Any
88
return Llama(
99
model_path=model_path,
1010
n_gpu_layers=n_gpu_layers,
11-
n_ctx=n_ctx
11+
n_ctx=n_ctx,
12+
verbose=False
1213
)

0 commit comments

Comments
 (0)