昨天的审计报告列出了 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 异常做了精确分类:

同时新增 _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+ 处宽泛捕获。策略分两类:

  1. 替换为精确捕获:如 except Exception → except (TypeError, ValueError, AttributeError),适用于明确知道可能抛出哪些错误的场景。
  2. 保留宽泛捕获但加注释:如摘要调度器、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 passed1370 passed(+32 新测试)

后续计划

升级注意:纯防御性改进,无破坏性变更。改动生效需重启 Linkora 服务。

完整变更见 CHANGELOG,代码在 GitHub。