快速结论:当 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/annotations(api/controllers/service_api/app/annotation.py) - Console
GET /apps/{app_id}/annotations(api/controllers/console/app/annotation.py) - Console
GET /apps/{app_id}/annotations/{annotation_id}/hit-histories(api/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_id与get_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=true;limit > 100 且有剩余行时误报 has_more=false。此外返回体中回显的 limit 也是未裁剪的原始值,而不是实际使用的 100。
环境排查
- 确认 Dify 版本为 main 或 >= 1.17.0,且为 Self Hosted (Source) 部署。
- 确认调用的是上述三个标注相关路由,而非数据集 / 文档 / 段落列表接口。
- 确认是否已包含 #41780 / #41775 / #41776 对 dataset / document / segment 列表的同类修复;这些修复不覆盖 annotation 端点。
- 检查
api/libs/pagination.py中max_per_page是否仍为 100。 - 检查控制器中
has_more是否仍写作len(...) == limit,以及返回的limit是否仍是原始入参。
解决步骤
- 在三个受影响控制器中,将原始
limit替换为裁剪后的有效分页大小,可优先尝试 Issue 建议的写法:effective_limit = min(limit, 100)。 - 将
has_more改为基于有效分页大小与总数比较:has_more = page * effective_limit < total。 - 响应中回显的
limit改为effective_limit,避免客户端拿到与查询不一致的页大小。 - 对三个端点分别补充回归测试,至少覆盖“最后一页恰好填满”和“limit > 100 且还有剩余行”两种场景(Issue 称回归测试在修复前失败、修复后通过)。
- 如需彻底消除同类问题,可继续排查 Issue 中点名的 console datasets 控制器(
controllers/console/datasets/datasets.py、datasets_document.py、external.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=true,limit为 100,后续页能继续取到剩余 50 条。 - 对
GET /apps/{app_id}/annotations与GET /apps/{app_id}/annotations/{annotation_id}/hit-histories重复以上两类用例。 - 观察前端不再出现无意义的“加载更多”请求,也不会在 100 条后提前停止分页。
参考来源
AI 工具推荐
想把多个 AI 模型放在一个入口?
GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。
推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。
这个方案解决了吗?
可以继续搜索完整报错,或查看同一工具的其他排查指南。


