首页
/ GraphRAG 依赖升级实战手册:pandas 3.0 / numpy 2.x 迁移陷阱与已验证修复模式

GraphRAG 依赖升级实战手册:pandas 3.0 / numpy 2.x 迁移陷阱与已验证修复模式

2026-09-05 12:50:31作者:平淮齐Percy

本文基于 GraphRAG 仓库中的迁移陷阱参考文档(.agents/skills/update-deps/references/migration-gotchas.md),完整拆解本仓库在依赖版本升级后实际踩到过、并已修复的库 API 变更模式:np.array_split 在 pandas 3.0 下退化为裸 ndarray、copy= 参数移除与 Copy-on-Write 语义变化、numpy 2.x 类型别名移除,以及 ruff preview 模式下的 RUF069 / ASYNC119 规则、pyright 类型桩同步问题。读完本文,你能在 uv run poe check / uv run poe test_unit 因依赖升级而失败时,按图索骥地定位并做最小化、与仓库既有代码风格一致的修复,而不必再从零排查。

一、适用场景:何时需要这份手册

文档开篇明确了加载时机:当一次依赖版本提升(dependency bump)导致测试失败,或 pyright / ruff 因某个库的 API 变化而报错时,应查阅这份参考,并对每一条失败套用「最小修复、与兄弟代码保持一致」的原则。文档中每一条记录都是「在本代码库中真实命中并修复过的模式」,而非泛泛的兼容性笔记。

在本仓库的工程实践中,这份文档由 update-deps 技能文件(SKILL.md)按流程第 6 步「Repair breakages」按需加载:先完成基线确认、版本编辑、uv lock / uv sync --all-packages 重新锁定,再跑静态检查与单元测试,最后针对库 API 变化带来的测试/类型失败加载本文档中记录的模式。仓库当前的技术栈基线可以从各成员的 pyproject.toml 中直接确认:

也就是说,pandas 已经走在 3.0 线上、numpy 走在 2.x 线上,这两个大版本的 API 变化正是每次 bump 之后破坏面的主要来源——这也是文档把它们放在最前面讲解的原因。

二、pandas 3.0:DataFrame.swapaxes 移除引发的静默退化

这是文档中最具隐蔽性的一条。表面上代码没有任何变化,但 np.array_split(df, n) 的返回类型悄悄从 DataFrame 变成了裸 numpy 数组。

机制链条np.array_split 内部调用 np.swapaxes,而后者过去会委托给 DataFrame.swapaxes,从而返回列名(column names)完好的 DataFrame;pandas 2.1 起 swapaxes 被弃用,3.0 中直接移除,于是 np.array_split(df, n) 现在返回的是普通 ndarray。此时如果按旧写法用 pd.DataFrame(fold) 重建,得到的是整数 RangeIndex 列(0, 1, 2, ...),后续任何 df["some_column"] 都会抛出 KeyError

典型症状KeyError: '<column>',且 traceback 末端落在 pandas/core/indexes/range.py ... get_loc——看到这个特征组合,基本可以断定是列名在某次拆分中丢失了。

修复模式:不要切分 DataFrame 本体,改为切分「位置索引」,再用 iloc 选取行。文档给出的对照代码:

# Broken under pandas 3.0
return [pd.DataFrame(fold) for fold in np.array_split(reports, n)]

# Fixed — preserves columns, dtypes, and even fold sizes
return [
    reports.iloc[indices]
    for indices in np.array_split(np.arange(len(reports)), n)
]

这个修复之所以优于直接重建 DataFrame,是因为 iloc 选取天然保留了原帧的列、dtype 以及分片(fold)大小语义。

仓库中的真实落地:主包查询模块 drift search 的 primer 拆分逻辑正是按修复后的写法实现的——packages/graphrag/graphrag/query/structured_search/drift_search/primer.py#L212-L229 中的 split_reports 方法,将社区报告 DataFrame 拆成 primer_folds 份以支持并行处理,核心两行即为:

return [
    reports.iloc[indices]
    for indices in np.array_split(np.arange(len(reports)), primer_folds)
]

可以把它视为这条「已验证修复」在本仓库查询主链路中的活体证据:分片只作用于 np.arange(len(reports)) 产生的位置索引,DataFrame 本体从不经过 np.array_split

三、pandas 3.0:copy= 参数移除与 Copy-on-Write

pandas 3.0 将 Copy-on-Write(CoW)设为默认行为,并同步删除了 copy= 参数。文档指出:形如 df.merge(other, copy=False)pd.concat([...], copy=False) 的调用会直接抛出 TypeError。修复方式非常干脆——删掉 copy= 实参即可,因为 CoW 默认行为本身已经避免了那次不必要的拷贝,保留该参数反而失去了语义。

同一大版本下还有一条写入语义的变化:在 CoW 生效之后,通过切片做链式赋值(df[mask]["col"] = x)不再回写到原帧,可能告警或报错。文档给出的正确姿势是统一走 .locdf.loc[mask, "col"] = x;对于 inplace=True 风格的操作,应改为「重新赋值操作结果」,而不是依赖对视图的原地突变。

配套地,仓库在 ruff 配置里把 PD002(阻止 inplace=True 的规则)列入 ignore 并标注了 TODO,见 pyproject.toml[tool.ruff.lint] ignore 段——从配置结构看,团队对 inplace 写法的治理是渐进的,因此遇到 CoW 报错时按文档要求「改走 .loc」而不是全局替换 inplace,是更稳妥的最小改动。

四、numpy 2.x:别名移除与命名空间迁移

文档对 numpy 2.x 归纳了三条迁移要点:

  1. 移除的类型别名np.float_np.int0np.bool8np.object0 等已不存在。应改用 Python 内建类型(floatintboolobject)或显式定长 dtype(np.float64np.bool_)。对 GraphRAG 这类以 pandas DataFrame 为数据主干的项目,影响点通常集中在 astype(...)np.array(..., dtype=...) 以及 embedding 数组构造等位置。
  2. np.array_split 对 DataFrame 不再保帧:与第二节是同一条陷阱,numpy 视角的入口,修复方式相同。
  3. 顶层命名空间收缩:部分函数移出了顶层命名空间,需要从文档标注的子模块导入。遇到 AttributeError: module 'numpy' has no attribute '...' 时,先查对应函数的子模块归属,而不是回退旧写法。

五、ruff preview 模式:RUF069 与 ASYNC119

本仓库的 ruff 显式开启了 preview 模式(根 pyproject.toml[tool.ruff.lint] preview = truetarget-version = "py310",且 [tool.ruff.format] preview = true),因此 RUF069、ASYNC119 这类 preview-only 规则在本仓库会生效,而在其他仓库可能不会——这是文档专门提醒的仓库特定事实。

RUF069(float-equality-comparison)

x == 0.0 / != 0.0 这类浮点等值比较会被标记。文档给出的两条语义化替代路径:

  • 当比较用于「防非正数除法」这类守卫时,改用非等值判断,例如用 x <= 0.0 表达非正数守卫;
  • 当确实需要容差比较时,使用 math.isclose(...)

文档同时强调:只要存在真实可行的修复,就不要用一刀切的 # noqa 压制——「prefer a real, minimal fix over suppression」是贯穿全文的总原则。

ASYNC119(async generator 中持有上下文管理器时 yield)

不要在 async generator 里「持有 with / async with 的同时 yield」;正确做法是在块内把数据物化(materialize),块关闭之后再逐条 yield。文档示例:

with Path.open(path, "r", encoding=enc) as f:
    rows = list(csv.DictReader(f))
for row in rows:
    yield transform(row)

仓库中大量 CSV 读取代码正体现了这种「先物化、后消费」的结构,可以作为对齐参照:

从源码结构看,团队对文件读取类 generator 的共识写法就是「有限资源块内完成物化,yield 阶段只处理内存中的数据」,修复 ASYNC119 时照抄兄弟模块即可保持风格一致。

六、pyright:类型桩必须与主版本同步升级

文档在 pyright 一节给出两条可操作规则:

  1. 类型桩跟着主版本走dev 组中固定了 pandas-stubs~=3.0(见根 pyproject.toml[dependency-groups] dev)。升级 pandas 时必须同步升级匹配版本的 stubs,否则 pyright 仍按旧 API 面做检查,既可能漏报新 API,也可能对新写法误报。
  2. 新报错优先在调用点修复:依赖变更后,pyright 可能因为更新的 stub 暴露新的 optional / overload 错误。文档要求「在调用点修复而不是压制,除非能证明 stub 本身有误」。

配合的验证命令是根 pyproject.toml[tool.poe.tasks] 定义的 check 序列:ruff format --check + ruff check + pyright 三步,即 uv run poe check;另有 uv run poe fix(安全 autofix)与 uv run poe format 可用于批量预处理,剩余问题再手工处理。

七、通用排查方法论:从 traceback 到最小修复

文档收尾部分给出三步方法论,适合作为每次 bump 后的固定动作序列:

  1. 把 traceback / 诊断读到叶子帧:出错的库调用和发生变化的符号名通常就写在最后几帧里——比如第二节的 pandas/core/indexes/range.py get_loc 特征;
  2. 对齐兄弟模块:检查同包内处理相同模式的兄弟模块已经怎么做,修复要与既有代码风格保持一致,避免引入「第二个风格」;
  3. 优先真实最小修复,拒绝压制:修完立即重跑 uv run poe checkuv run poe test_unit 确认。这里要特别注意 SKILL 文档强调的一个易错点:快速反馈循环应跑 test_unit 而不是 test(后者是全量覆盖套件,非常慢);若变更面较广或触及 indexing / query,还应追加 uv run poe test_verbsuv run poe test_integration

另有一条来自上游技能文档、与本手册直接配套的仓库特定提示:tests/unit/indexing/test_profiling.py::TestWorkflowProfiler::test_handles_exception_in_context 是已知的时序敏感 flake,偶发失败时不应直接归因为依赖回归——在把失败判定为回归之前,先排除这条已知不稳定用例。

八、升级过程中的「不可触碰区」(与迁移修复联动)

虽然修复 API 破坏是本文主线,但有两个与版本编辑强相关、且会直接影响「是否需要同步 stub / 是否值得修」的仓库约束,值得在动手前对照:

  1. graspologic-native>=1.2,<1.3 是刻意压住的packages/graphrag/pyproject.toml#L46-L49 的注释写明 1.3.x 会改变 Leiden 聚类输出,从而破坏黄金回归数据,除非有意做 golden-data 刷新,否则不应升级。若一次 bump 恰好触到这条 pin,正确的响应是保持 pin 不变,而不是改代码去迁就新聚类结果。
  2. 跨包 pin 与 version 字段由发布流程托管graphrag-cache==... 等跨包 pin 由 scripts/update_workspace_dependency_versions.py 从 semversioner 版本自动改写,[project] version 字段同样由 semversioner 管理,手工编辑会造成漂移。修改 ~=/>=/< 规格符时务必确认没有顺手改到这些受管行。

完成修复后的收尾动作是补一条 changelog:uv run semversioner add-change -t patch -d "<short description>"(仅当用户意图确属 minor/major 时才升级语义级别),然后复跑 uv run poe checkuv run poe test_unit,双绿才算闭环。

小结

这份参考文档的价值在于「可执行的最小修复模式 + 本仓库真实验证」:pandas 3.0 的 np.array_split 退化用「切位置索引 + iloc」解决(drift search primer 已按此落地)、copy= 参数直接删除、链式赋值守卫迁移到 .loc;numpy 2.x 的类型别名换内建或定长 dtype;ruff preview 下 RUF069 用 <= 0.0 / math.isclose 语义化改写、ASYNC119 用「块内物化、块外 yield」对齐兄弟模块;pyright 侧保持 pandas-stubs 与 pandas 主版本同步。整套流程以 uv run poe check + uv run poe test_unit 作为最终验收门槛,配合已知 flake 的排除规则与受管 pin 的禁改约束,构成了一套可重复执行的依赖升级修复工作流。

登录后查看全文
热门项目推荐
相关项目推荐