GraphRAG monorepo 依赖更新实战指南:从 uv 工作区重新锁定到 pandas 3.0 迁移问题修复
本文基于 GraphRAG 仓库中官方维护的依赖更新技能文档 SKILL.md,完整拆解这个 uv workspace monorepo 的依赖升级标准流程:哪些版本说明符可以改、哪些版本行绝不可手改、如何正确执行 uv lock/uv sync 重新锁定,以及升级 pandas/numpy 大版本后如何用仓库验证过的迁移模式修复测试与类型检查失败。读完后你可以独立完成一次「依赖清扫(dependency sweep)」,并让 uv run poe check 与 uv run poe test_unit 双双转绿。
一、先认清仓库结构:这是一个 uv workspace monorepo
依赖更新的所有操作都建立在对仓库布局的准确理解之上。根据根目录 pyproject.toml,GraphRAG 的依赖分布在三个层次:
- 根
pyproject.toml的 dev 依赖组:[dependency-groups]下的dev组集中了开发工具链,包括coverage~=7.6、deptry~=0.21、mkdocs-material~=9.5、pandas-stubs~=3.0、poethepoet~=0.31、pyright~=1.1、pytest~=9.1、ruff~=0.8、semversioner~=3.0等(见 pyproject.toml#L26-L48)。 - 各成员包的运行时依赖:每个
packages/*/pyproject.toml的[project] dependencies中。例如 packages/graphrag/pyproject.toml 声明了pandas~=3.0、numpy~=2.1、pydantic~=2.10、networkx~=3.4、spacy~=3.8等。 - 工作区成员之间的互相引用:通过
[tool.uv.sources]中的{ workspace = true }声明(pyproject.toml#L60-L67 列出了graphrag-chunking、graphrag-common、graphrag-input、graphrag-storage、graphrag-cache、graphrag-vectors、graphrag-llm七个成员),并以graphrag-*==X.Y.Z的精确版本行互相锁定。例如 packages/graphrag-llm/pyproject.toml#L38-L39 中的graphrag-cache==3.1.1、graphrag-common==3.1.1。
工作区成员范围由 [tool.uv.workspace] members = ["packages/*"] 定义(pyproject.toml#L57-L58)。此外还有一个关键点:包解析不走公共 PyPI,而是走 Microsoft 内部 feed 代理:
[[tool.uv.index]]
url="https://packagefeedproxy.microsoft.io/pypi/simple"
default=true
(pyproject.toml#L53-L55)任务编排则统一由 poethepoet 承担,[tool.poe.tasks] 定义了 check、fix、format、test_unit、test_verbs、test_integration 等命令(pyproject.toml#L70-L148)。
二、两条不可协商的硬性规则
官方技能文档把两条规则列为 non-negotiable,违反它们会直接导致解析或同步失败:
规则 1:只能使用 Microsoft feed 代理索引
所有 resolve 和 sync 必须走 [[tool.uv.index]] 配置的 https://packagefeedproxy.microsoft.io/pypi/simple。严禁针对公共 PyPI 解析、严禁添加 --index/--default-index/--index-url 覆盖参数、严禁把 UV_INDEX_URL 或 PIP_INDEX_URL 设为 pypi.org,也不允许禁用或重排已配置的索引。
规则 2:目标版本必须发布满 7 天以上
该代理不提供服务最近一周内发布的版本——同步到一个「太新」的版本必然失败。因此:
- 若要精确钉住某个版本,先核实其发布日期,选取「超过 7 天的最新 release」;
- 否则就让版本说明符保持浮动(如
~=X.Y),交给uv lock自行选择——代理上本来就只看得见合格版本。
这个规则解释了后文一个高频故障模式:如果 uv sync 找不到你刚钉住的版本,几乎可以断定该版本在代理上的「年龄」不足一周,此时应退一档到超过一周的最新 release,而不是怀疑锁文件损坏。
三、三类「发布流程持有」的版本行:禁止手改
这是本仓库依赖更新中最容易踩错的地方。以下三类内容不属于依赖更新者的编辑范围,因为它们由发布(release)流程统一管理:
- 跨包精确钉住:任何包中的
graphrag-cache==...、graphrag-llm==...等行。它们由脚本 scripts/update_workspace_dependency_versions.py 自动重写。从源码看,该脚本调用uv run semversioner current-version取当前版本,再遍历packages/*,用正则{包名}\s*==\s*\d+\.\d+\.\d+把所有跨包钉住统一替换为当前版本(scripts/update_workspace_dependency_versions.py#L26-L54)。手工编辑这些行只会造成版本漂移(drift)。 [project] version字段:由 semversioner 管理。packages/graphrag/pyproject.toml#L3-L4 中甚至有显式注释:# Maintainers: do not change the version here manually。graspologic-native>=1.2,<1.3保留钉住:packages/graphrag/pyproject.toml#L46-L49 中的注释说明了原因——1.3.x 会改变 Leiden 聚类输出(社区数量/层级),从而破坏固定的回归测试与黄金社区数据。只有在有意的黄金数据刷新时才可升级,并且必须明确声明这一动作。
发布流程如何驱动上述自动化的完整链条,可以在根 pyproject.toml 的 release 任务序列中看到:先 semversioner release 与生成 changelog,再逐个执行 _semversioner_update_*_toml_version(用 update-toml 把各包 project.version 写为 semversioner current-version 的值),最后运行 _semversioner_update_workspace_dependency_versions(即上面的脚本)和 _sync(pyproject.toml#L116-L132)。理解这条链后就能明白:依赖更新者只需要动「第三方库的说明符」,其余交给发布流水线。
四、完整更新流程:八步走
以下是技能文档规定的标准流程,每一步都给出了可直接执行的命令。
1. 先建立基线(Baseline first)
在改动任何版本之前,确认工作树干净且检查/测试本来就通过,这样后续失败才能归因于本次升级。推荐在独立分支(如 dep-sweep)上操作:
uv run poe check
uv run poe test_unit
2. 确定升级范围并编辑说明符
范围可以是用户点名的目标包集合,也可以是一次完整清扫。编辑对象是相关 packages/*/pyproject.toml 的 [project] dependencies 中的 ~=/>=/< 说明符,以及根 dev 组。上节列出的「发布持有」行保持不动。编辑完成后务必复核:版本编辑会触碰多个 pyproject.toml,确认自己没有在改相邻说明符时误伤 graphrag-*== 钉住或 version 字段。
3. 解析与锁定(全部走已配置的 feed 代理,不传任何索引覆盖)
# 完整「取到允许的最新版本」清扫
uv lock --upgrade
# 编辑说明符后的定向升级
uv lock
# 安装所有工作区成员(注意是 --all-packages,不是裸 uv sync)
uv sync --all-packages
失败处理原则:
- 若解析失败,阅读冲突信息,放宽或调整有问题的说明符后重新锁定,不要删除
uv.lock来强制通过; - 若 sync 找不到刚钉住的版本,按规则 2 退到超过一周的最新 release。
4. 静态检查
uv run poe check # = ruff format --check + ruff check + pyright
uv run poe fix # 应用安全的 ruff 自动修复
uv run poe format # ruff format 格式化
从根 pyproject.toml#L142-L144 可以看到 check 任务的确切序列为 ['check_format', '_ruff_check', '_pyright']。剩余的 lint/类型错误需手工修复——参见下一节的迁移模式。
5. 测试
uv run poe test_unit # 快速反馈循环(pytest ./tests/unit)
uv run poe test_verbs # 变更范围广或触及 indexing 时运行(pytest ./tests/verbs)
uv run poe test_integration # 同上(pytest ./tests/integration)
注意不要用 uv run poe test——它会跑全部用例并附 coverage 报告(_test_all + coverage_report,pyproject.toml#L146-L148),速度很慢。每一个新增失败都要调查清楚。
6. 修复破坏
对因库 API 变化导致的测试/类型失败,加载参考文档 .agents/skills/update-deps/references/migration-gotchas.md,套用其中经过本仓库验证的修复模式。原则:修复保持最小、与同包内的兄弟代码风格一致;有真实修复可用时,优先真实修复而不是 # noqa。
7. 记录变更
uv run semversioner add-change -t patch -d "<简短描述>"
只有用户意图确实需要时才用 minor/major。
8. 最终验证
重新运行 uv run poe check 与 uv run poe test_unit,两者都必须转绿;在下结论前先看下一节的「已知 flake」说明,避免把偶发失败误判为回归。
五、升级后破坏修复:仓库验证过的迁移模式
当升级(尤其是 pandas / numpy 大版本)引发测试或 pyright 失败时,migration-gotchas.md 收录了「本仓库实际命中并修复过」的模式,以下逐一展开其原理。
pandas 3.0:np.array_split(df, n) 不再返回 DataFrame
np.array_split 内部调用 np.swapaxes,该函数在 pandas 2.1 弃用、3.0 移除,过去它会委托给 DataFrame.swapaxes 并保留列名返回 DataFrame;现在则退化为返回纯 numpy 数组。用 pd.DataFrame(fold) 重建后列名变成整数 RangeIndex,后续 df["some_column"] 直接抛 KeyError(traceback 终点在 pandas/core/indexes/range.py ... get_loc)。
修复模式是对位置索引做切分,再用 iloc 选行,从而保留列、dtype 乃至各 fold 的规模:
# 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)
]
pandas 3.0:copy= 关键字被移除
pandas 3.0 默认启用 Copy-on-Write(CoW)并删除了 copy= 参数,df.merge(other, copy=False)、pd.concat([...], copy=False) 等调用会抛 TypeError。修复方式直接删除 copy= 参数——CoW 本身已避免不必要的拷贝。
pandas 3.0:CoW 下的链式赋值
在 CoW 下,对切片做原地修改(df[mask]["col"] = x)不再写回,可能告警或报错。修复:通过 .loc 赋值(df.loc[mask, "col"] = x),并把 inplace=True 式操作的结果重新赋回变量,而不是依赖对视图的原地修改。
numpy 2.x 常见破坏点
- 移除的别名(
np.float_、np.int0、np.bool8、np.object0等)——改用内置类型或显式带大小 dtype(np.float64、np.bool_); - 对 DataFrame 使用
np.array_split不再保留 frame(即上文的 pandas 条目); - 部分函数移出顶层命名空间,需从文档指定的子模块导入。
ruff:本仓库启用 preview 模式
根 pyproject.toml#L151-L161 中 [tool.ruff] 设了 target-version = "py310",且 format 与 lint 均开启 preview = true。这意味着某些 preview-only 规则在本仓库会被触发(在其他仓库未必),典型两条:
- RUF069(浮点相等比较):
x == 0.0/!= 0.0会被标记。语义允许时改用非相等保护(如非正除零保护写x <= 0.0),容差判断用math.isclose(...); - ASYNC119(异步生成器中持有上下文管理器 yield):不要在
with/async with块内直接yield,应先在块内物化数据、块关闭后再 yield:
with Path.open(path, "r", encoding=enc) as f:
rows = list(csv.DictReader(f))
for row in rows:
yield transform(row)
pyright:类型桩随大版本走
dev 组钉了 pandas-stubs~=3.0(pyproject.toml#L37)。升级 pandas 时必须同步升级匹配的 stubs,让 pyright 反映新 API;依赖变更后 pyright 可能暴露来自新 stub 的 optional/overload 错误,应在调用点修复而不是抑制(除非能证明 stub 本身错了)。
通用排查方法
参考文档给出的三步法:1) 把 traceback/诊断读到叶子帧——出问题的库调用和被改动的符号通常就在最里面;2) 看同包内兄弟模块如何处理同一模式,保持写法一致;3) 每次修复后重跑 uv run poe check 与 uv run poe test_unit 确认。
六、仓库级 Gotchas 与完成前自检清单
容易混淆的仓库级细节
test_unit而非test:poe test全量跑 coverage,慢;快速反馈循环用test_unit(pytest ./tests/unit)。- Ruff 运行于 preview 模式(
preview = true、target-version = "py310"),preview-only 规则(如 RUF069、ASYNC119)在本仓库会触发。 - 已知存量 flake:
tests/unit/indexing/test_profiling.py::TestWorkflowProfiler::test_handles_exception_in_context对时序敏感、可能偶发失败——它不是依赖回归。从源码看(tests/unit/indexing/test_profiling.py#L70-L84),该用例在 profiler 上下文中抛异常后断言metrics.overall > 0等指标确实被采集,属于典型的耗时敏感断言,判读失败结论前应先排除这一干扰。 uv sync --all-packages:安装全部工作区成员必须带--all-packages,裸uv sync会漏装成员包。- pandas 处于 3.0 线、numpy 处于 2.x:它们的大版本 API 变化是本仓库升级后破坏的常规来源。
- 版本编辑涉及多个
pyproject.toml,务必确认没有误改graphrag-*==钉住或version字段。
完成前自检清单(Completion checklist)
一次依赖更新只有全部满足以下条件才算完成:
| 检查项 | 要求 |
|---|---|
| 索引合规 | 所有 resolve/sync 均使用 packagefeedproxy.microsoft.io 索引;未引入公共 PyPI 或任何索引覆盖 |
| 版本年龄 | 没有任何依赖被升级到最近 7 天内发布的版本 |
| 编辑范围 | 只有预期的说明符被改动;未触碰 graphrag-*== 钉住或 version 字段 |
| 锁文件 | uv.lock 由 uv lock/uv lock --upgrade 重新生成,未被手工编辑或删除 |
| 静态检查 | uv run poe check 通过(ruff format、ruff lint、pyright) |
| 单元测试 | uv run poe test_unit 通过(仅可忽略已知的 profiling flake) |
| 更广套件 | 若变更范围广,已运行 test_verbs/test_integration |
| 变更日志 | 已通过 semversioner 添加 changelog 条目 |
| 保留钉住 | 高风险/被有意压住的钉住(如 graspologic-native)保持原状,除非明确升级 |
七、小结
GraphRAG 的依赖更新流程本质上是一次「受约束的自动化协作」:更新者负责第三方库说明符的编辑与失败修复,[tool.uv.sources] 工作区解析、scripts/update_workspace_dependency_versions.py 的跨包钉住重写、semversioner 的版本管理则分别接管了自己领域内的版本行。把两条硬规则(只用 feed 代理、版本年龄 ≥ 7 天)与「三不碰」原则(跨包钉住、version 字段、graspologic-native 压住行)作为前置约束,再配合 migration-gotchas.md 中针对 pandas 3.0 / numpy 2.x / ruff preview 的验证过修复模式,即可把一次大版本依赖清扫的失败面收敛到可预测、可归因、可复验的范围。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00