1414
1515import json
1616import os
17+ import re
1718import time
1819
1920# 复用现有模型
@@ -74,6 +75,31 @@ def _get_llm_config() -> dict:
7475 return {"api_key" : api_key , "base_url" : base_url , "model_name" : model_name }
7576
7677
78+ def _is_retryable_error (e : Exception ) -> bool :
79+ """判断异常是否值得重试(5xx/超时/上游错误)。
80+
81+ W3 修复:精确判定,避免 "5" in msg 误匹配 "500 tokens"/"line 5"。
82+ 优先级:status_code 属性(5xx) → TimeoutError → 异常名含 timeout →
83+ msg 含 upstream → msg 精确匹配 5xx 状态码。
84+ """
85+ status = getattr (e , "status_code" , None )
86+ if status is not None :
87+ try :
88+ return 500 <= int (status ) < 600
89+ except (TypeError , ValueError ):
90+ pass
91+ if isinstance (e , TimeoutError ):
92+ return True
93+ name = type (e ).__name__ .lower ()
94+ if "timeout" in name :
95+ return True
96+ msg = str (e ).lower ()
97+ if "upstream" in msg :
98+ return True
99+ # 精确匹配 5xx(前后非数字),避免 "500 tokens"/"line 5" 误命中
100+ return bool (re .search (r'(?:^|\D)5\d{2}(?:\D|$)' , msg ))
101+
102+
77103def _findings_to_json (findings : list [Finding ]) -> str :
78104 """将 findings 转换为 JSON 格式供 LLM 分析"""
79105 findings_data = []
@@ -160,7 +186,10 @@ def _call_llm_with_openai_client(findings: list[Finding]) -> list[dict]:
160186 temperature = 0.1 ,
161187 max_tokens = 2000 ) # 降低到2000减少400错误
162188
163- response_text = response .choices [0 ].message .content
189+ # W2 修复: 空响应防护,避免 IndexError 导致全部裁决丢失
190+ if not response .choices :
191+ raise Exception ("LLM 返回空 choices(限流/内容过滤)" )
192+ response_text = response .choices [0 ].message .content or ""
164193 verdicts = _parse_llm_response (response_text )
165194 all_verdicts .extend (verdicts )
166195 break # 成功,跳出重试循环
@@ -171,8 +200,8 @@ def _call_llm_with_openai_client(findings: list[Finding]) -> list[dict]:
171200 if "400" in error_msg or "bad request" in error_msg :
172201 print ("[LLM Layer] 400错误(prompt太大),不重试" )
173202 raise Exception (f"OpenAI API 400错误: { str (e )} " ) from e
174- # 超时/5xx/Upstream错误指数退避重试
175- elif attempt < max_retries and ( "timeout" in error_msg or "5" in error_msg or "upstream" in error_msg ):
203+ # 超时/5xx/Upstream错误指数退避重试(W3 修复: 精确判定,避免 "5"/"timeout" 子串误匹配)
204+ elif attempt < max_retries and _is_retryable_error ( e ):
176205 wait_time = 2 ** attempt # 指数退避:1s, 2s, 4s
177206 print (f"[LLM Layer] 调用失败,{ wait_time } 秒后重试 { attempt + 1 } /{ max_retries } : { str (e )} " )
178207 time .sleep (wait_time )
@@ -357,7 +386,10 @@ def _call_llm_with_openai_client_supplementary(existing_findings: list[Finding],
357386 temperature = 0.1 ,
358387 max_tokens = 1500 ) # 进一步降低到1500减少400错误
359388
360- response_text = response .choices [0 ].message .content
389+ # W2 修复: 空响应防护
390+ if not response .choices :
391+ raise Exception ("LLM 补召回返回空 choices(限流/内容过滤)" )
392+ response_text = response .choices [0 ].message .content or ""
361393 new_findings = _parse_supplementary_findings (response_text )
362394 return new_findings
363395
@@ -367,8 +399,8 @@ def _call_llm_with_openai_client_supplementary(existing_findings: list[Finding],
367399 if "400" in error_msg or "bad request" in error_msg :
368400 print ("[LLM Layer] 补召回400错误(prompt太大),不重试" )
369401 raise Exception (f"OpenAI 补召回400错误: { str (e )} " ) from e
370- # 超时/5xx/Upstream错误指数退避重试
371- elif attempt < max_retries and ( "timeout" in error_msg or "5" in error_msg or "upstream" in error_msg ):
402+ # 超时/5xx/Upstream错误指数退避重试(W3 修复: 精确判定)
403+ elif attempt < max_retries and _is_retryable_error ( e ):
372404 wait_time = 2 ** attempt # 指数退避:1s, 2s, 4s
373405 print (f"[LLM Layer] 补召回失败,{ wait_time } 秒后重试 { attempt + 1 } /{ max_retries } : { str (e )} " )
374406 time .sleep (wait_time )
@@ -495,20 +527,20 @@ def enhance(
495527 verdict_list = []
496528 for key , verdict_data in recorded_verdicts .items ():
497529 # 解析 key: "rule_id:file:line"
498- parts = key . split ( ":" )
499- if len ( parts ) == 3 :
500- rule_id , file_path , line_str = parts
501- try :
502- line = int (line_str )
503- verdict_list .append ({
504- "rule_id" : rule_id ,
505- "file" : file_path ,
506- "line" : line ,
507- "verdict" : verdict_data ["verdict" ],
508- "reason" : verdict_data ["reason" ],
509- })
510- except ValueError :
511- continue
530+ # S2 修复: rpartition/partition 切分,避免文件路径含 ":"(Windows 盘符)时切错
531+ key_path , _ , line_str = key . rpartition ( ":" )
532+ rule_id , _ , file_path = key_path . partition ( ":" )
533+ try :
534+ line = int (line_str )
535+ verdict_list .append ({
536+ "rule_id" : rule_id ,
537+ "file" : file_path ,
538+ "line" : line ,
539+ "verdict" : verdict_data ["verdict" ],
540+ "reason" : verdict_data ["reason" ],
541+ })
542+ except ValueError :
543+ continue
512544
513545 # 应用降噪裁决
514546 enhanced_findings = _apply_verdicts (findings , verdict_list )
0 commit comments