1717
1818logger = logging .getLogger (__name__ )
1919
20+ MINERU_AUTH_ERROR_MESSAGE = "MinerU Token 无效或已过期,请在设置页更新 MinerU Token 后重试。"
21+ _MINERU_RESPONSE_TEXT_LIMIT = 500
22+
23+ _MINERU_AUTH_TERMS = (
24+ "unauthorized" ,
25+ "authorization" ,
26+ "auth" ,
27+ "token" ,
28+ "api key" ,
29+ "apikey" ,
30+ "forbidden" ,
31+ "permission" ,
32+ "credential" ,
33+ "鉴权" ,
34+ "认证" ,
35+ "授权" ,
36+ "令牌" ,
37+ "密钥" ,
38+ "权限" ,
39+ )
40+
41+ _MINERU_AUTH_FAILURE_TERMS = (
42+ "expired" ,
43+ "invalid" ,
44+ "unauthorized" ,
45+ "forbidden" ,
46+ "denied" ,
47+ "expire" ,
48+ "failed" ,
49+ "fail" ,
50+ "failure" ,
51+ "过期" ,
52+ "无效" ,
53+ "失败" ,
54+ "拒绝" ,
55+ )
56+
57+
58+ def is_mineru_auth_error_message (message : str | None ) -> bool :
59+ """Return True when a user-facing parse error is a MinerU credential error."""
60+ return bool (message and MINERU_AUTH_ERROR_MESSAGE in message )
61+
62+
63+ def _truncate_mineru_error_text (text : str ) -> str :
64+ if len (text ) <= _MINERU_RESPONSE_TEXT_LIMIT :
65+ return text
66+ return text [:_MINERU_RESPONSE_TEXT_LIMIT ] + "..."
67+
68+
69+ def _extract_response_error_text (response ) -> str :
70+ if response is None :
71+ return ""
72+
73+ parts = []
74+ status_code = getattr (response , "status_code" , None )
75+ if status_code :
76+ parts .append (f"HTTP { status_code } " )
77+
78+ try :
79+ body = response .json ()
80+ except Exception :
81+ try :
82+ body_text = _truncate_mineru_error_text (str (getattr (response , "text" , "" ) or "" ).strip ())
83+ if body_text :
84+ parts .append (body_text )
85+ except Exception :
86+ pass
87+ else :
88+ if isinstance (body , dict ):
89+ for key in ("msg" , "message" , "error" , "detail" ):
90+ value = body .get (key )
91+ if isinstance (value , str ) and value .strip ():
92+ parts .append (value .strip ())
93+ elif isinstance (value , dict ):
94+ nested = value .get ("message" ) or value .get ("msg" ) or value .get ("error" )
95+ if isinstance (nested , str ) and nested .strip ():
96+ parts .append (nested .strip ())
97+ elif isinstance (value , list ):
98+ for item in value :
99+ if isinstance (item , str ) and item .strip ():
100+ parts .append (item .strip ())
101+ elif isinstance (item , dict ):
102+ nested = item .get ("message" ) or item .get ("msg" ) or item .get ("error" )
103+ if isinstance (nested , str ) and nested .strip ():
104+ parts .append (nested .strip ())
105+ elif body is not None :
106+ parts .append (str (body ))
107+
108+ return " " .join (parts )
109+
110+
111+ def _looks_like_mineru_auth_error (text : str | None , status_code : int | None = None ) -> bool :
112+ if status_code in (401 , 403 ):
113+ return True
114+
115+ normalized = str (text or "" ).lower ()
116+ if not normalized :
117+ return False
118+
119+ has_auth_term = any (term in normalized for term in _MINERU_AUTH_TERMS )
120+ has_failure_term = any (term in normalized for term in _MINERU_AUTH_FAILURE_TERMS )
121+ return has_auth_term and has_failure_term
122+
123+
124+ def _mineru_auth_error_with_detail (detail : str | None = None ) -> str :
125+ detail = (detail or "" ).strip ()
126+ if not detail :
127+ return MINERU_AUTH_ERROR_MESSAGE
128+ return f"{ MINERU_AUTH_ERROR_MESSAGE } MinerU 返回:{ detail } "
129+
20130
21131def _get_ai_provider_format (provider_format : str = None ) -> str :
22132 """Get the configured AI provider format
@@ -280,7 +390,11 @@ def _get_upload_url(self, filename: str) -> tuple[Optional[str], Optional[str],
280390 result = response .json ()
281391
282392 if result .get ("code" ) != 0 :
283- error_msg = f"Failed to get upload URL: { result .get ('msg' )} "
393+ result_msg = result .get ('msg' )
394+ if _looks_like_mineru_auth_error (result_msg ):
395+ error_msg = _mineru_auth_error_with_detail (result_msg )
396+ else :
397+ error_msg = f"Failed to get upload URL: { result_msg } "
284398 logger .error (error_msg )
285399 return None , None , error_msg
286400
@@ -289,7 +403,15 @@ def _get_upload_url(self, filename: str) -> tuple[Optional[str], Optional[str],
289403 return batch_id , upload_url , None
290404
291405 except requests .exceptions .RequestException as e :
292- error_msg = f"Network error while requesting upload URL: { str (e )} "
406+ response = getattr (e , "response" , None )
407+ response_text = _extract_response_error_text (response )
408+ if response is not None and _looks_like_mineru_auth_error (
409+ f"{ response_text } { str (e )} " ,
410+ getattr (response , "status_code" , None ),
411+ ):
412+ error_msg = _mineru_auth_error_with_detail (response_text or str (e ))
413+ else :
414+ error_msg = f"Network error while requesting upload URL: { str (e )} "
293415 logger .error (error_msg )
294416 return None , None , error_msg
295417
@@ -341,7 +463,11 @@ def _poll_result(self, batch_id: str, max_wait_time: int = 600) -> tuple[Optiona
341463 task_info = response .json ()
342464
343465 if task_info .get ("code" ) != 0 :
344- error_msg = f"Failed to query task status: { task_info .get ('msg' )} "
466+ task_msg = task_info .get ('msg' )
467+ if _looks_like_mineru_auth_error (task_msg ):
468+ error_msg = _mineru_auth_error_with_detail (task_msg )
469+ else :
470+ error_msg = f"Failed to query task status: { task_msg } "
345471 logger .error (error_msg )
346472 return None , None , error_msg
347473
@@ -354,14 +480,27 @@ def _poll_result(self, batch_id: str, max_wait_time: int = 600) -> tuple[Optiona
354480 return self ._download_markdown (full_zip_url )
355481 elif task_status == "failed" :
356482 err_msg = task_info ["data" ]["extract_result" ][0 ].get ("err_msg" , "Unknown error" )
357- error_msg = f"File parsing failed: { err_msg } "
483+ if _looks_like_mineru_auth_error (err_msg ):
484+ error_msg = _mineru_auth_error_with_detail (err_msg )
485+ else :
486+ error_msg = f"File parsing failed: { err_msg } "
358487 logger .error (error_msg )
359488 return None , None , error_msg
360489 else :
361490 logger .debug (f"Current task status: { task_status } , waiting..." )
362491 time .sleep (2 ) # Wait 2 seconds before next poll
363492
364493 except requests .exceptions .RequestException as e :
494+ response = getattr (e , "response" , None )
495+ response_text = _extract_response_error_text (response )
496+ if response is not None and _looks_like_mineru_auth_error (
497+ f"{ response_text } { str (e )} " ,
498+ getattr (response , "status_code" , None ),
499+ ):
500+ error_msg = _mineru_auth_error_with_detail (response_text or str (e ))
501+ logger .error (error_msg )
502+ return None , None , error_msg
503+
365504 logger .warning (f"Network error while polling result: { str (e )} , retrying..." )
366505 time .sleep (2 )
367506
0 commit comments