[Bug]: annotation list / hit-history has_more uses raw limit while query caps at 100 (missed sibling of #41780/#41776)

当 Dify 自托管实例调用标注列表或命中历史分页接口时,若传入的 limit 恰好等于总数、或大于 100,分页返回值 has_more 会算错,导致前端出现“幽灵加载更多”或提前停止分页。优先排查这三个接口的 has_more 计算是否使用了未经 100 上限裁剪的原始 limit 。

快速结论:当 Dify 自托管实例调用标注列表或命中历史分页接口时,若传入的 limit 恰好等于总数、或大于 100,分页返回值 has_more 会算错,导致前端出现“幽灵加载更多”或提前停止分页。优先排查这三个接口的 has_more 计算是否使用了未经 100 上限裁剪的原始 limit

适用环境:Dify 版本 main(>= 1.17.0),Self Hosted (Source)。Issue 未提供 Python、CUDA、显卡或具体依赖版本信息。

最快修复方案:暂无确认的一步修复方案(Issue 中提出建议补丁,但截至关闭时未见已合并的针对这三个端点的主线 PR)。

注意事项:建议修复代码只有在控制器层同步 effective_limit = min(limit, 100) 才能生效;同一 len(...) == limit 模式的 console datasets 控制器与 explore/trial.py 属于同类问题,Issue 明确列为后续跟进项,未包含在本次修复范围内。

问题场景

在 Dify 自托管源码部署(main 分支,>= 1.17.0)中,调用以下三个标注相关分页接口时会触发:

  • Service API GET /apps/annotationsapi/controllers/service_api/app/annotation.py
  • Console GET /apps/{app_id}/annotationsapi/controllers/console/app/annotation.py
  • Console GET /apps/{app_id}/annotations/{annotation_id}/hit-historiesapi/controllers/console/app/annotation.py

当请求 limit=20 且总数恰好为 20,或请求 limit=200 且总数超过 100 时,分页行为异常:客户端要么多发起一次无用请求、显示“幽灵加载更多”,要么直接停止分页并静默隐藏第 100 条之后的记录。

报错原文

[Bug]: annotation list / hit-history has_more uses raw limit while query caps at 100 (missed sibling of #41780/#41776)

Current Behavior

- Last page exactly full: GET /apps/annotations?page=1&limit=20 with exactly 20 annotations returns has_more=true, even though total=20.
  Clients issue a useless extra request / show a phantom "loading more".
- limit above the 100 cap with remaining rows: GET /apps/annotations?page=1&limit=200 with total=150 returns has_more=false
  (because the query returns 100 items but 100 == 200 is false), silently hiding the remaining 50 annotations and stopping pagination.

原因分析

最可能的原因是 has_more 的计算与查询实际使用的分页大小不一致:

  • 三个端点的控制器分别用 has_more = len(items) == limit 判断,limit 是客户端原始传入值。
  • 底层 AppAnnotationService.get_annotation_list_by_app_idget_annotation_hit_histories 都调用 paginate_query(stmt, session=session, page=page, per_page=limit, max_per_page=100)
  • paginate_query 内部通过 per_page = min(per_page, max_per_page) 把页大小钳制到 100。

因此返回条数反映的是被裁剪后的 100,而比较用的 limit 仍是原始值,两个场景都会算错:limit 恰好等于 total 时误报 has_more=truelimit > 100 且有剩余行时误报 has_more=false。此外返回体中回显的 limit 也是未裁剪的原始值,而不是实际使用的 100。

环境排查

  • 确认 Dify 版本为 main 或 >= 1.17.0,且为 Self Hosted (Source) 部署。
  • 确认调用的是上述三个标注相关路由,而非数据集 / 文档 / 段落列表接口。
  • 确认是否已包含 #41780 / #41775 / #41776 对 dataset / document / segment 列表的同类修复;这些修复不覆盖 annotation 端点。
  • 检查 api/libs/pagination.pymax_per_page 是否仍为 100。
  • 检查控制器中 has_more 是否仍写作 len(...) == limit,以及返回的 limit 是否仍是原始入参。

解决步骤

  1. 在三个受影响控制器中,将原始 limit 替换为裁剪后的有效分页大小,可优先尝试 Issue 建议的写法:effective_limit = min(limit, 100)
  2. has_more 改为基于有效分页大小与总数比较:has_more = page * effective_limit < total
  3. 响应中回显的 limit 改为 effective_limit,避免客户端拿到与查询不一致的页大小。
  4. 对三个端点分别补充回归测试,至少覆盖“最后一页恰好填满”和“limit > 100 且还有剩余行”两种场景(Issue 称回归测试在修复前失败、修复后通过)。
  5. 如需彻底消除同类问题,可继续排查 Issue 中点名的 console datasets 控制器(controllers/console/datasets/datasets.pydatasets_document.pyexternal.py)与 controllers/console/explore/trial.py 中同样的 len(...) == limit 写法。

验证方法

  • GET /apps/annotations?page=1&limit=20 且总数为 20 的场景验证:返回 has_more=false,且 limit 字段与实际分页大小一致。
  • GET /apps/annotations?page=1&limit=200 且总数为 150 的场景验证:返回 has_more=truelimit 为 100,后续页能继续取到剩余 50 条。
  • GET /apps/{app_id}/annotationsGET /apps/{app_id}/annotations/{annotation_id}/hit-histories 重复以上两类用例。
  • 观察前端不再出现无意义的“加载更多”请求,也不会在 100 条后提前停止分页。

参考来源

langgenius/dify #41875

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 23324

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注