Skip to content

Commit 101ee7c

Browse files
committed
fix: surface MinerU credential errors in renovation
1 parent 483a14c commit 101ee7c

4 files changed

Lines changed: 390 additions & 5 deletions

File tree

backend/services/file_parser_service.py

Lines changed: 143 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,116 @@
1717

1818
logger = 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

21131
def _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

backend/services/task_manager.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def _append_extra_fields(desc_text: str, desc_content: dict) -> str:
4444
parts.append(f"\n{name}{value}")
4545
return ''.join(parts)
4646
from pathlib import Path
47+
from services.file_parser_service import is_mineru_auth_error_message
4748
from services.pdf_service import split_pdf_to_pages
4849

4950
logger = logging.getLogger(__name__)
@@ -1383,6 +1384,8 @@ def process_single_page(idx, page_pdf_path):
13831384
_batch_id, md_text, extract_id, error_msg, _failed = file_parser_service.parse_file(page_pdf_path, filename)
13841385
if error_msg:
13851386
logger.warning(f"Page {idx} parse warning: {error_msg}")
1387+
if is_mineru_auth_error_message(error_msg):
1388+
raise ValueError(error_msg)
13861389
md_text = md_text or ''
13871390

13881391
# Supplement with header/footer from layout.json

0 commit comments

Comments
 (0)