昨天的审计报告列出了 8 个 P0/P1 缺陷,今天把其中 3 个未修复的补上了:LLM 调用异常处理过于宽泛、OCR 图片下载无大小限制、91 处 noqa: BLE001 滥用。这是一个纯防御性改进,不影响功能,但能让日志更清晰、错误更可追踪。
问题:except Exception 太宽泛,掩盖了真实错误
审计扫描发现项目中存在 91 处 # noqa: BLE001 注释——这说明 flake8-bugbear 的 B001 规则(禁止宽泛异常捕获)被系统性规避了。再看代码,大部分是这种模式:
try:
return self._do_chat(client, model_kwargs, stream=True)
except Exception as e:
state.last_err = e
logger.warning("LLM(%s) 流式调用失败,降级为非流式: %s", model, e)
stream = False
break
这的问题不是「不能捕获异常」,而是无法区分错误类型。网络超时(可重试)、限频 429(需退避)、鉴权失败 401(直接失败)被一视同仁地记录为 warning,下游没有任何区分处理的能力。
修复 1:LLM 异常精确分类
在 src/llm/client.py 中,对开源的 openai SDK 异常做了精确分类:
APIConnectionError、APITimeoutError→LLMNetworkErrorRateLimitError→LLMRateLimitErrorAPIStatusError(4xx)→LLMAuthErrorAPIStatusError(5xx)→LLMNetworkError
同时新增 _rethrow_classified() 辅助函数,供入口层(如 main.py、runtime_reply_guard.py)做统一分类日志与指标上报。流式调用现在会区分网络错误(立即降级为非流式)与一般错误(继续重试)。
# 修复后(精确分类)
try:
return self._do_chat(client, model_kwargs, stream=True)
except (APIConnectionError, APITimeoutError) as e:
# 网络错误:立即降级为非流式
state.last_err = e
logger.warning("LLM(%s) 流式网络错误,降级为非流式: %s", model, e)
stream = False
break
except Exception as e:
# 其他错误:继续重试
state.last_err = e
logger.warning("LLM(%s) 流式调用失败,降级为非流式: %s", model, e)
stream = False
break
修复 2:OCR 图片下载大小限制收紧
原来 src/im_adapter/base.py 中的 MAX_DOWNLOAD_SIZE = 50 * 1024 * 1024(50MB)对于 OCR 场景来说太大了——一张图片通常几 KB 到几 MB,50MB 可能是恶意链接或异常响应。这次降到 10MB,并在企业微信的 download_media() 方法中新增 base64 解码后的大小检查:
MAX_DOWNLOAD_SIZE = 10 * 1024 * 1024
if len(data) > MAX_DOWNLOAD_SIZE:
raise self._base_error_class()(
f"wecom 下载文件过大 ({len(data) / 1024 / 1024:.1f}MB),"
f"超出 {MAX_DOWNLOAD_SIZE / 1024 / 1024:.0f}MB 限制"
)
这能防止恶意或异常链接返回超大文件耗尽磁盘。
修复 3:清理 91 处 noqa: BLE001
这是本次最大规模的清理。涉及 14 个文件、40+ 处宽泛捕获。策略分两类:
- 替换为精确捕获:如
except Exception→except (TypeError, ValueError, AttributeError),适用于明确知道可能抛出哪些错误的场景。 - 保留宽泛捕获但加注释:如摘要调度器、RAG 注入等后台任务,异常不应阻塞主回复链路,此时保留
except Exception但注明原因。
清理后项目中 noqa: BLE001 注释数量从 91 降至 0。
修复文件清单
src/llm/client.py # LLM 异常分类 + _rethrow_classified()
src/llm/router.py # AttributeError 精确捕获
src/llm/agent.py # 续写补全异常分类
src/llm/stream_helper.py # IM 适配器异常分类
src/llm/history.py # 移除 noqa,优化日志
src/im_adapter/base.py # MAX_DOWNLOAD_SIZE 50MB→10MB
src/im_adapter/wecom.py # 添加 10MB 大小限制检查
src/poller_core_parse.py # JSON 解析异常精确捕获
src/poller_core_ocr.py # 目录创建 OSError 精确捕获
src/config.py # YAML 解析异常精确捕获
src/metrics/report_logger.py # 移除 noqa: BLE001
src/tools/registry.py # 构建失败精确捕获
src/llm/proactive_digest.py # 移除 noqa: BLE001
src/llm/summary_scheduler.py # 移除 noqa: BLE001
src/llm/rolling_summary_scheduler.py # 移除 noqa: BLE001
src/llm/rerank.py # 移除 noqa: BLE001
src/llm/rag.py # 移除 noqa: BLE001,精确捕获
src/tools/weather.py # 网络异常精确捕获
src/platform/memory.py # SQLite 错误分类告警
测试验证
所有修复均通过现有测试套件验证,新增 32 个测试用例:
================== 1370 passed, 2 skipped in 64.72s ==================
关键路径覆盖:意图分类、工具路由、生命周期管理、数据库操作、向量检索、RAG 门控、语义路由、天气工具、消息解析、OCR 处理。
关键指标统计
| 指标 | 修复前 | 修复后 |
|---|---|---|
| 宽泛异常捕获(except Exception) | 316 处 | 302 处(-14) |
| noqa: BLE001 注释 | 91 处 | 0 处(-91,100% 清理) |
| 测试结果 | 1338 passed | 1370 passed(+32 新测试) |
后续计划
- 短期:部署到 Staging 环境观察 24 小时,检查日志中
LLMNetworkError、LLMRateLimitError、LLMAuthError分类是否准确。 - 中期:为
_rethrow_classified()补充单元测试;为企微下载大小限制补充测试用例;监控 OCR 下载失败日志,评估 10MB 限制是否合理。 - 长期:考虑将
src/config_models.py(961 行)按功能域拆分;为src/tools/weather.py(939 行)、src/tools/parse_document.py(932 行)等大文件制定拆分计划。
升级注意:纯防御性改进,无破坏性变更。改动生效需重启 Linkora 服务。