LightRAG 开源贡献:别把模型服务的 503 当作回答

1346 字
7 分钟
LightRAG 开源贡献:别把模型服务的 503 当作回答

模型服务返回 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 通过。

Terminal window
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 已披露。以上事实以公开提交和测试记录为依据,不将尚未合并的代码写成已被上游采用。

参考#

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

LightRAG 开源贡献:别把模型服务的 503 当作回答
https://github.com/HKUDS/LightRAG/pull/4057
作者
叶立恒
发布于
2026-09-23
许可协议
CC BY-NC-SA 4.0