LightRAG 开源贡献:别把模型服务的 503 当作回答
- 1WeKnora 开源贡献:让 OCR 切片在文档视图中可见
- 2WeKnora Swagger 空白页排查:HTTP 200 不代表访问到了正确服务
- 3从 201 个项目只返回 100 个说起:修复 WeKnora GitLab 分页遗漏
- 4LightRAG 开源贡献:别把模型服务的 503 当作回答本文
- 5多读一个字节:修复 WeKnora RSS 超限响应的静默截断
- 6Notion 接口返回 401,为什么知识库里的文档被删了?
- 7Notion 正文没读到,为什么下一次同步不重试?
- 8Notion 数据库删了一行,为什么知识库还记得它?
- 9RSS 全文恢复了,为什么知识库仍然只有摘要?
- 10Notion 正文清空了,知识库为什么还留着旧答案?
- 11工具调用成功了,为什么结果是空的?一次 AgentScope MCP 贡献复盘
- 12SQL 执行成功却查错了数据:修复 WeKnora Agent 的列名纠正
- 13GitLab 分支回退了,知识库为什么还停在新版本?
- 14RSS 正文没变,为什么知识库里的标题和来源链接过期了?
- 15两次同步都成功,知识库却退回旧版本:修复 WeKnora 的并发同步
- 16源端没变,失败文档却再也同步不回来:修复 WeKnora 的入库确认
模型服务返回 HTTP 503,正文是 upstream unavailable。如果应用直接读取正文并返回,它看起来就像模型给出了一句回答。流式接口也可能把这段错误文本逐块发给用户。
这次在 AI 辅助源码扫描中,我定位到 LightRAG 的 LoLLMs 适配器没有检查 HTTP 状态,提交了 Issue #4056 与 PR #4057。本文记录修复,也记录首次 CI 暴露的验证遗漏。
截至 2026-09-23,PR 尚未合并。维护者曾给出 LGTM/Approved;补推测试修正后,该条审核当前显示 Dismissed,新提交仍待复审和 CI 运行。 状态以 PR 页面 为准。
错误发生在模型协议边界
LoLLMs 适配器使用 aiohttp.ClientSession 访问生成与向量接口。默认情况下,读取 response.text() 并不自动拒绝 4xx/5xx 响应。
原代码的普通生成路径直接返回文本,流式路径直接消费 response.content。因此,401、429、503 都可能变成“正常输出”。
向量路径则先解析 JSON,再取 vector。错误 JSON 可能变成 KeyError: 'vector',错误 HTML 可能变成内容类型异常;这些报错掩盖了更直接的 HTTP 状态。如果失败响应碰巧包含 vector,原实现甚至会接受它。
先写失败测试,再动生产代码
为了不依赖真实模型、API Key 或外部配额,回归测试用 aiohttp.test_utils.TestServer 启动本地服务,仍由真实的 aiohttp 客户端发请求。
覆盖的场景包括:
- 普通和流式生成分别遇到 401、429、503。
- 流式失败不得先输出错误正文。
- 向量请求必须先检查状态,再尝试解析正文。
- HTTP 200 下,普通回答、流式回答与向量结果正常返回。
新增 12 个测试在修改生产代码前得到 9 项失败、3 项成功控制通过。其中一个细节是:ContentTypeError 本身属于 ClientResponseError 子类,只检查宽泛的异常父类会误以为正确。因此向量错误测试还区分 HTTP 状态错误与内容解析错误。
修复只有三个配置点
最终在普通生成、流式生成和向量调用的 HTTP 会话中开启:
async with aiohttp.ClientSession( timeout=timeout, headers=headers, raise_for_status=True,) as session: ...这样失败状态在消费正文前被转换为 ClientResponseError,调用方能获得 HTTP 状态;流式调用会在开始迭代时遇到异常,而不是先收到错误文本。
初次修复后,LoLLMs 目录的 19 项测试通过,全仓 pre-commit 通过。补丁没有增加新重试策略,也没有修改其他模型供应商。
CI 为什么仍失败了一项
维护者批准运行 CI 后,Python 3.12 的离线测试出现 7734 项通过、1 项失败,Python 3.14 任务随后被取消。失败来自另一个目录中的主机配置测试:
FakeSession.__init__() got an unexpected keyword argument 'raise_for_status'真实 aiohttp 支持这个参数,但 tests/api/config/test_api_config_lollms_host.py 中手写的模拟对象只接收 timeout 和 headers。我此前只运行适配器目录,漏掉了这个跨目录调用方。
在本地补齐锁定依赖后,先复现同一个 TypeError,再只修改模拟对象的一行签名:
def __init__(self, timeout=None, headers=None, *, raise_for_status=False): pass该测试仍负责验证配置的主机地址到达真实调用路径;HTTP 错误行为由本地服务器回归测试负责。补修后扩大到 API 配置目录和 LoLLMs 目录,162 项通过,全仓 pre-commit 通过。
python -m pytest tests/api/config tests/llm/lollms_impl -m offline -q --tb=short修正提交为 43078bbe。本机没有跑完整离线矩阵,新远端工作流等待维护者批准,不能把本地 162 项通过写成远端全量通过。
维护者建议的下一步:有边界的重试
维护者认可本次状态检查,并提出非阻塞的后续建议:暂时性 429/503 可以重试,永久性 401/403 应快速失败;优先尊重 Retry-After,支持秒数和 HTTP 日期,并校验无效值、限制最大等待,再考虑带抖动的有上限指数退避。
流式重试尤其需要边界:一旦已经向调用方输出内容,就不能简单重新请求并把新结果接到旧结果后面。 否则可能重复文字,甚至让下游工具消费到两段互相矛盾的输出。
这些建议没有混入本次小修。先正确暴露失败,再单独设计哪些失败可恢复,能让每个补丁都有清楚的责任。
对 Agent 应用的价值
这项贡献没有修改 Agent 规划算法,但处理的是同样重要的模型调用可靠性:失败不能伪装成成功,流式输出必须有明确异常语义,测试范围需要沿调用链寻找。
这也是一次验证范围不足的实际教训。局部目录通过不等于所有调用方兼容;在修改第三方客户端构造参数时,应同时搜索全仓的模拟对象和集成入口。
源码扫描、实现、测试与文章整理使用了 AI 辅助,PR 已披露。以上事实以公开提交和测试记录为依据,不将尚未合并的代码写成已被上游采用。
参考
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!












