快速结论:当使用 CrewAI 的 LanceDBStorage.list_records() 读取记忆记录时,返回结果可能不是最新的记录,而是最早的一批记录。优先排查该方法是否在排序前就按 limit + offset 截断了候选行:[BUG] LanceDBStorage.list_records() returns oldest records instead of newest first due to premature query truncation。
适用环境:Issue 已确认的环境为 macOS Sonoma、Python 3.12、crewAI 1.15.21、crewAI Tools 1.15.21、Venv 虚拟环境;存储后端为 LanceDB。
最快修复方案:暂无已合并并正式发布的一步修复方案。Issue 讨论中提出的修复已由 PR #7418 提交,但该 PR 的合并状态与对应正式版本号在 Issue 中没有明确证据,可优先尝试升级到包含该 PR 的 crewAI 版本,或参考 PR 改动调整 list_records()。
注意事项:Issue 中的建议修复方案是不带 limit 参数调用 _scan_rows(scope_prefix),依赖默认 _SCAN_ROWS_LIMIT(50,000)限制扫描量;当同一 scope 内记录数接近或超过该上限时,仍可能影响结果完整性。此外,PR 最终实现采用了批量流式 top-k 堆,与 Issue 正文中的简单排序补丁并不完全一致,修改源码时应以实际 PR 内容为准。
问题场景
用户在 CrewAI 中使用 LanceDB 作为记忆存储后端,先向 LanceDBStorage 写入多条 MemoryRecord,再调用 storage.list_records(limit=3) 期望拿到最新 3 条记录。按文档,list_records() 应返回“newest first”,按 created_at 降序排列,但实际返回的是最早写入的几条记录。
报错原文
Returned IDs: ['rec_2', 'rec_1', 'rec_0']
Expected: ['rec_9', 'rec_8', 'rec_7']
Actual: ['rec_2', 'rec_1', 'rec_0']
[BUG] LanceDBStorage.list_records() returns oldest records instead of newest first due to premature query truncation
原因分析
问题出在 lib/crewai/src/crewai/memory/storage/lancedb_storage.py 的 list_records() 实现。当前逻辑先执行 self._scan_rows(scope_prefix, limit=limit + offset),即先只取前 limit + offset 行,然后才按 created_at 降序排序并切片。这样扫描阶段拿到的是表中较早的行,最新记录在排序之前就被截断,永远不会进入最终结果。
Issue 正文给出的复现结果中,期望返回 ['rec_9', 'rec_8', 'rec_7'],实际返回 ['rec_2', 'rec_1', 'rec_0'],正是这种“先截断、后排序”顺序导致的表现。
环境排查
- 确认 crewAI 版本是否为 1.15.21 或存在相同
list_records()截断逻辑的版本。 - 确认 Python 版本为 3.12(Issue 环境),并检查是否在 Venv 虚拟环境中运行。
- 确认安装的 crewAI Tools 版本与 crewAI 版本一致(Issue 中均为 1.15.21)。
- 确认使用的是
LanceDBStorage,而非QdrantEdgeStorage等其他存储实现。 - 检查本地
lancedb_storage.py中list_records()是否存在_scan_rows(scope_prefix, limit=limit + offset)这类排序前截断。 - 确认测试或业务代码中的
created_at确实按预期递增,以排除数据层时间戳本身乱序。
解决步骤
- 先运行 Issue 中的最小复现脚本:向临时目录中的
LanceDBStorage写入 10 条created_at依次递增的记录,再调用list_records(limit=3),确认返回是否为最早 3 条而非最新 3 条。 - 检查当前 crewAI 版本是否已包含 PR #7418 的修复;如已包含,直接升级或安装对应版本后重新验证。
- 如仍需本地修改,参考 Issue 建议:在
list_records()中不要传入limit=limit + offset,而是在 scope 内扫描候选行(使用默认_SCAN_ROWS_LIMIT),按created_at降序排序后再执行records[offset : offset + limit]。 - 如需与 PR #7418 最终实现保持一致,应参考该 PR:将 scope 过滤和仅含元数据(排除向量 embedding)的投影下推到 LanceDB,再用以
created_at为键、容量为offset + limit的有界 top-k 最小堆流式筛选,最后返回limit/offset切片。 - 修改后补充回归测试,至少覆盖:默认查询返回最新记录、带
offset的分页顺序、以及 scope 过滤下的排序正确性。
验证方法
重新执行复现脚本,确认 list_records(limit=3) 返回 ['rec_9', 'rec_8', 'rec_7'],并且返回顺序为 created_at 降序。再补充带 offset 的分页用例,确认分页切片发生在正确排序之后,而不是在扫描阶段提前截断。
参考来源
关联修复 PR:crewAIInc/crewAI #7418
AI 工具推荐
想把多个 AI 模型放在一个入口?
GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。
推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。
这个方案解决了吗?
可以继续搜索完整报错,或查看同一工具的其他排查指南。
![typing.Dict[str, X] return annotation is wrapped in {"result": ...}, unlike dict[str, X]](https://www.chat-gpts.plus/wp-content/uploads/2026/10/3745-7e2d6b6c-768x403.jpg)
![[Bug]: decisions (typesafe): missing usage becomes zero cost, a 200 with an invalid body becomes a 500, and the error body is only in the me](https://www.chat-gpts.plus/wp-content/uploads/2026/10/48147-5054fca8-768x403.jpg)
