从早期版本到 1.5.11:`past_releases` 里的演进轨迹

2026-08-10

判断一个开源项目现在处在什么阶段,看 star 数没什么用。更能说明问题的是它把版本说明存成了什么样子:有几份、按什么粒度切、旧的那些有没有被删掉。DeepTutor 这个仓库恰好把这条线完整留在了 assets/releases/ 下,可以一个文件一个文件地数。

下面所有数字都对应我们采集的仓库快照:HEAD 456f9c2,采集日 2026-08-10,deeptutor/__version__.py 第 9 行的版本号是 1.5.11。我们只读了仓库文本,没有安装、部署或运行过这个项目。

一、版本号只有一个来源,且 CI 会拦

先把”当前是哪一版”这件事定死。deeptutor/__version__.py 的模块 docstring(第 1-7 行)写明了发布动作的顺序:在这里 bump __version__、commit、然后给这个 commit 打 v<__version__> 形式的 tag(原文举的例子是 v1.4.0),CI 会校验 tag 与该值一致后才发布到 PyPI。同一段还说明了两个下游消费者——Web 侧边栏徽章和 CLI banner——都直接从这个文件读版本号。

打包侧是同一套:pyproject.toml:246 用 setuptools 的动态取值 version = {attr = "deeptutor.__version__.__version__"},CLI-only 子包在 packaging/deeptutor-cli/pyproject.toml:60 同源。

这条约定不是靠自觉维持的。.github/workflows/pypi-release.yml 第 72-99 行有一个名为 “Verify release tag matches deeptutor/__version__.py” 的步骤,用正则从文件里读出 __version__,与 tag 归一化后比较,不一致就报错 Bump __version__ before tagging.。第 43-51 行还有一道前置检查:tag 必须落在 main 上,否则 refusing to publish to PyPI。

所以”版本号”在这个仓库里有三个位置需要对得上:文件里的字符串、git tag、发布产物。你要核这一条,不用装任何东西:

cat deeptutor/__version__.py
grep -n "Verify release tag" .github/workflows/pypi-release.yml

顺带一个和版本演进直接相关的细节:发布工作流只产出 wheel、不产 sdist,注释给的理由是完整 wheel 里已内含打包好的 Next.js 资源(.github/workflows/pypi-release.yml:115-125);而校验清单里只要求 deeptutor-<version>-py3-none-any.whl 这一个产物,没有包含 deeptutor-cli(同文件 139-141 行)。

二、65 份版本说明的形状

版本说明分两处放:assets/releases/ 下是当前版的三份——ver1-5-9.mdver1-5-10.mdver1-5-11.md;再往下一层 assets/releases/past_releases/ 下是 62 份。合起来仓库内共 65 份。

按文件名归到大版本上,分布是这样:

版本段past_releases 内份数覆盖的版本
0.x80.2.0、0.3.0、0.4.0、0.4.1、0.5.0、0.5.1、0.5.2、0.6.0
1.071.0.0-beta1、1.0.0-beta-2/-3/-4、1.0.1、1.0.2、1.0.3
1.141.1.0-beta、1.1.0、1.1.1、1.1.2
1.261.2.0 ~ 1.2.5
1.3111.3.0 ~ 1.3.10
1.4171.4.0-beta、1.4.0 ~ 1.4.15
1.591.5.0 ~ 1.5.8

再加上 assets/releases/ 里的 1.5.9 / 1.5.10 / 1.5.11,就是 65。

这张表要怎么读?它给的是发布节奏,不是功能量。1.3 段 11 份、1.4 段 17 份、1.5 段到目前为止 12 份(含当前的三份),而 0.x 全段只有 8 份——补丁位号在 1.3 之后明显走得更长。至于每一份说明里到底写了什么,我们没有逐份读,只按文件名统计(这一点后面还会再说一次)。

时间锚点上能对得住的有三条:README 的发布板块记 2026.8.10 的 v1.5.11、2026.8.7 的 v1.5.10、2026.8.4 的 v1.5.9(README.md:51-55);assets/releases/ver1-5-11.md 第 3-5 行标注 **Release Date:** 2026.08.10,并写了一句 “Drop-in — no migrations, nothing to re-index.”;README 第 166 行把 v1.0.0-beta.1(2026.4.4)记为 “Agent-native architecture rewrite (~200k lines)“,第 164 行记 v1.0.0-beta.2(2026.4.7)起 “Python 3.11+ minimum”。要说明的是,那个 ~200k lines 是 README 自称的量级,我们没有核对过代码行数,本文引用它只是为了标出这条时间线上的位置。

beta 也是数得清的。README 记了六个:v1.0.0-beta.1 / beta.2 / beta.3 / beta.4、v1.1.0-beta、v1.4.0-beta;past_releases/ 下对应存在六个 beta 文件(ver-1-0-0-beta1ver1-0-0-beta-2/-3/-4ver1-1-0-betaver1-4-0-beta)。这两处对得上。

数这些不需要工具链:

ls assets/releases
ls assets/releases/past_releases | wc -l

一个会绊人的坑:ls 给的是字典序,不是语义序。同一个大版本里,补丁位一旦进到两位数,字典序就会把 10 以上的那些排到个位数前面去。想按版本先后看,得自己做自然排序,别把 ls 的输出直接当时间线读。另外这批文件的命名本身也不完全统一——同是 1.0.0 的 beta,既有 ver-1-0-0-beta1 这种写法,也有 ver1-0-0-beta-2 这种,靠模式外推文件名容易落空。

三、反直觉的那一处:1.4.5 至今还在参与依赖解析

前面两节都是”历史归历史”。真正值得单拎出来的是:这条演进线里的一个旧版本号,今天仍然写在 pyproject.toml 的注释里,并且它描述的是一个现在就会发生的行为。

位置在 pyproject.toml:197-215,是 optional-dependencies 里的两个可选依赖分组 graphragrag-lightrag,它们都带着 python_version < '3.14' 这个 environment marker。

这个 marker 不是随手加的。同处注释写明的理由是:否则 pip 会静默把 deeptutor 回退到最后一个不带 extra 的版本 1.4.5。 也就是说,marker 挡下来的不是某个依赖装不上的报错,而是一次没有报错的降级。

这处之所以反直觉,是因为大部分人读版本演进史时的默认假设是”旧版本只影响过去”。但依赖解析器不这么看:它眼里所有历史版本都是候选项,只要当前版本的约束解不开,它就会往回走,一直走到某个能解开的版本为止——而那个版本恰好是 1.4.5,一个 2026 年上半段的号码。

顺着这条线还能看到第二个方向的约束:pyproject.toml:12-18requires-python 定为 >=3.11,<3.14,注释解释上限的原因是 3.14 上 pip 找不到部分编译依赖(例如 faiss-cpu)的 wheel,会退化成注定失败的源码构建,并引用了 issue #664。CLI-only 子包在 packaging/deeptutor-cli/pyproject.toml:9-12 用的是同一个区间。

也就是说:3.11 这个下限的由来写在 README 的 v1.0.0-beta.2 条目里,3.14 这个上限的由来写在 pyproject 注释里,而”越界会发生什么”写在 extra 的 marker 注释里——三段解释分散在三个文件,但讲的是同一条演进线。

要自己核这一处,两条命令就够:

grep -n "1.4.5" pyproject.toml
grep -n "requires-python" -A 6 pyproject.toml

那么怎么判断自己是不是正踩在这条回退上?判定动作有三步,都不需要真的把项目跑起来。

第一步,看解释器。先确认当前环境的 Python 是哪一版:只要落在 3.14 及以上,就已经在 pyproject.toml:12-18 那条 >=3.11,<3.14 之外了;README 的 Option 1(PyPI 安装)给出的前置条件同样是 Python 3.11–3.13。

第二步,看装出来的到底是哪一版。把 pip 实际解析出的 deeptutor 版本号,和你打算装的版本对一下。我们这份快照里 deeptutor/__version__.py 第 9 行写的是 1.5.11;如果你手上拿到的是 1.4.5,那正好是注释里点名的那个回退终点。版本号在这个仓库里只有一个来源,CLI banner 与 Web 侧边栏徽章都从该文件直接读,所以这个号码可以当作判定依据来用。

第三步,看这次装的是不是带 extra。这条回退的因果链挂在 graphragrag-lightrag 这两个分组的 marker 上;不涉及这两个分组的安装,不在本文讨论的范围内。

什么情况说明不是这个原因:如果你的解释器本来就落在 3.11–3.13 区间内,requires-python 与这两个 extra 的 marker 都不会构成排除条件,那版本不对就得往别处找原因了——那部分我们没有依据,也不做推测。

同一类”版本史留在代码里”的残留还有一处:pyproject.toml:176-177tutorbot extra 被注明为 “Legacy alias kept for one release”。它是个别名,注释自己给了保留期限。

四、README 与译版之间的一处口径差

README.md 有一个「📦 Releases」板块。10 个译版 README(assets/README/ 下的 AR/CN/ES/FR/HI/JA/PL/PT/RU/TH)都不含这个板块,译版从「新闻 / News」开始(assets/README/README_CN.md:49)。

核查动作是一条命令,输出全是 0:

grep -c '📦' assets/README/*.md

差异就到这里为止。两处内容不同是事实,我们不推断原因,也不据此评价什么。对读者的实际影响只有一条:如果你是从中文或其它语言的 README 进来的,想找版本演进线,得回到根 README 或者直接去 assets/releases/ 数文件。

五、这条线上我们没有核实的部分

写版本史最容易滑向的错误,是把文件名当成内容。下面几条是我们这次明确没有做的,写出来免得被当成结论:

  • 65 份版本说明中,除 ver1-5-11.md 开头几行外,其余内容我们都没读。上面那张分布表是按文件名统计的,没有核对每份文件内部标注的版本号与日期是否与文件名一致。
  • 我们没有核对 git tag 列表,也没有核对 PyPI 上实际发布过哪些版本。CI 里那两道校验是工作流文件里写着的规则,不等于历史上每一次发布都走过这条规则。
  • 我们没有联网,README 里的 star 数、下载量、GHCR 镜像是否真的存在,全部未核实。截至 2026-08-10,本文引用的仓库内数字只代表 HEAD 456f9c2 这一个快照。
  • pyproject.tomldependencies 确切条目数我们没有逐行计数,只知道是 40 余项。
  • 我们没有在任何一个 3.14 解释器上试过 pip 的解析过程。第三节那段”回退到 1.4.5”是 pyproject.toml 注释自己写下的口径,不是我们观察到的结果,也不代表你那边一定会复现出同一个版本号。

最后一句边界:本文只讲版本说明文件怎么组织、版本号怎么落地,不涉及任何一个版本带来的功能变化或使用效果——那些我们既没有运行过,也不在这次核对的范围内。


本文依据 DeepTutor 官方仓库(github.com/HKUDS/DeepTutor)的 README、AGENTS.mdpyproject.tomldeeptutor/ 下的源码整理,核对日 2026-08-10,对应仓库快照 456f9c2(版本 1.5.11)。 本文内容为仓库源码与文档口径,我们没有安装、部署或运行过该项目,也没有调用过其中任何一个模型 API, 因此不涉及生成质量、响应速度与教学效果的任何描述。 参数与默认值随版本变动,请以仓库最新代码与 --help 的实际输出为准。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。