Massimiliano Pippi Massimiliano Pippi • 2025-05-21

当我们谈论 LlamaIndex 时,实际上指的是一个由超过 650 个 Python 包组成的生态系统,其中大部分是 IntegrationsPacks。所有这些包共享一个 GitHub 仓库,工程师们亲切地称之为 "monorepo"。在本文中,我们将介绍 LlamaDev,我们用于大规模管理 monorepo 的新工具,并解释我们在现有工具中遇到的挑战,从而促使我们开发了这个新工具。

挑战:650 多个依赖树

monorepo 中的每个 Python 包都发布在 PyPI 上,并带有自己的 pyproject.toml 文件。对于那些不熟悉这个生态系统的人来说,现代 Python 包的 pyproject.toml 文件是一个一站式商店,定义了包生命周期的多个方面:它的依赖项、发布版本号、支持的 Python 和操作系统版本,以及像 linters 和类型检查器这样的工具应该如何处理这个特定的包。大多数包都有测试,有些还有额外的开发依赖项列表。

Integrations 和 Packs 有一些共同的代码,这些代码通过一个名为 llama-index-core 的基础包发布,生态系统中的几乎每个包都依赖于它。此外,它们还可以相互依赖,例如 llama-index-llms-azure-openai 需要 llama-index-llms-openai 才能工作。正如你可以想象的那样,这对于测试策略有一些影响,例如:

开发很容易(还算容易)

在这些包中的一个中进行开发的体验各不相同。有时,我们处理的是一个独立的 Integration,除了 llama-index-core 之外没有其他的 LlamaIndex 依赖项。这是最理想的情况,我们针对特定版本的 core 包测试我们的代码,如果它有效,那就没问题了。

有时,我们处理的是一个在 monorepo 中有依赖项的包。在这种情况下,我们需要确保我们的更改对包本身有效,而不会破坏其他包。有时,我们处理的是 core 包,在这种情况下,我们需要格外小心,不要引入可能影响整个 monorepo 的破坏性更改。

最后但并非最不重要的一点是,我们会尽最大努力在我们的包中支持多个 Python 和操作系统版本,对于绝大多数情况来说,这意味着在三个操作系统(Windows、Mac 和 Linux)上,支持 Python 3.9 一直到 3.12+。

维护很难

在 monorepo 中,你首先想要的是一致性。即使测试执行方式或包的构建方式略有差异,也可能会使跨平台支持复杂化,并将跨 monorepo 的批量操作变成一场噩梦。最终得到 650 多个_略有_不同的包的风险非常高,特别是在 Python 生态系统中,因此我们引入的第一个工具是 Python 项目管理器。

Python 项目管理器通常依赖于 pyproject.toml 文件来提供诸如构建和发布包、管理依赖项和 lockfile、定义脚本和自动化任务、准备虚拟环境以便在不同的 Python 版本下隔离地工作等功能。

第一次迭代:Poetry 用于单个包

我们选择的工具是 Poetry:功能完整,在 Python 社区中非常成熟,它为我们服务了 一年多。在单个包上使用 Poetry 非常轻松:凭借其富有表现力的命令行界面,它可以设置特定的 Python 版本并构建虚拟环境,因此在迭代代码时运行测试变得非常容易。但不要太兴奋:我们处理的是一个 monorepo,即使是最容易的事情也会变得复杂。

虽然 Poetry 非常适合在单个包上独立工作,但它并不真正了解包依赖关系。例如,它无法知道依赖于我们的某个包实际上位于同一个 git 仓库的兄弟目录中。由于位于同一个仓库中,我们可以轻松地触发测试来验证我们的上游更改是否破坏了依赖项,但是 Poetry 只是没有信息来执行此操作。

第一次迭代:Pants 用于构建管理

因此,我们不得不引入另一个工具:我们需要一个构建系统,最好是专门设计用于在 monorepo 中工作的。构建系统是一种软件,它可以自动将源代码构建成 artifacts,并处理所有中间步骤。对于 Python 代码库,这意味着为特定 Python 版本设置虚拟环境,获取和安装依赖项,运行测试,构建 wheel 包,并选择性地将它们发布到像 PyPI 这样的全局注册表中。

我们选择了 Pants 来完成这项工作,而且它也为我们服务了 一年多。当上游发生更改时,Pants 显式支持 Python 项目,并且在检测要在 monorepo 中的多个包中触发哪些测试方面特别智能。它还带有一个强大的缓存系统,可以显着加快每个构建环境的设置速度。

1.0 版本运行了一年

回顾一下,在一年多的时间里,这是 LlamaIndex monorepo 中使用的技术栈:

我们的设置运行良好,但不可否认的是,存在一些小缺陷。小缺陷的问题在于,随着系统规模的扩大,它们往往会变得越来越不小。对单个包的单秒操作似乎并不多,但当你必须重复它 650 次时,现在自动化作业需要 10 分钟。

规模问题:构建速度和缓存服务器

让我们从 Poetry 开始。该工具运行良好,我们可以表达复杂的依赖关系需求,但是依赖关系解析器有时可能会非常慢。再加上 pip 是唯一可用的安装程序这一事实,在包上工作的开销可以用秒来衡量。同样,你可能不会注意到在单个包上工作,但是如果没有 Pants 缓存虚拟环境,则在 monorepo 上广泛运行单元测试可能需要很长时间。

说到 Pants,它也有自己的一系列缺陷。我们遇到的第一个问题是设置:虽然定义 targets 和依赖项是一项非常艰巨的任务,但至少是你做一次然后就可以受益的事情,但是缓存系统要求更高。我们不得不在 AWS 上托管一个服务,该服务位于公共地址之后,以便可以从 Github workflow 中使用它。并且缓存服务器不是你部署一次就忘记的东西。我们不得不对其进行几次扩容,在负载均衡器后面添加两个冗余实例,并多次调整存储大小。有时,Github workflow 会出现奇怪的错误而失败,直到后来才发现缓存服务器无响应或行为异常。所有这些都可以理解,但是考虑到我们团队的规模,在资源和金钱方面维护它非常昂贵。

控制问题:日志记录、不一致等等

这还不是全部:Pants 复杂的缓存带来了一个额外的代价。在构建期间使用的 Python 虚拟环境由 Pants 在内部使用 pip 管理。你对它的控制不多,环境不容易访问,这有一些影响。

为什么我们需要 2.0 版本

回顾一下,随着 monorepo 大小的增加,我们面临着以下问题:

回顾我们过去的 Slack 对话,自从我们开始摆弄用 uv 替换 pip 以便利用其在安装时带来的巨大加速的想法以来,已经有一段时间了,但是我们直到最近才有时间进行认真的尝试。以下是发生的事情。

将项目从 Poetry 迁移到 uv

最初的任务非常模糊,它基本上是这样说的:“在所有地方使用 uv,以便我们可以缩短 CI 反馈循环”。我们不想摆脱现有的工具,只是想引入一个看起来很有希望的新工具。

Pants 在文档中有一个部分 提到了 uv,因此我们乐观地认为有可能使两者共存。这种乐观情绪持续了大约一个小时,这是浏览文档、浏览 Pants Discord 服务器中的一些线程并意识到不行的必要时间,Pants 无法在内部使用 uv 代替 pip。有一个第三方 plugin 可以做到这一点,但是我们不想在已经几乎无法控制的东西上添加另一层复杂性,因此我们放弃了该想法。

B 计划:我们仍然可以在 Poetry 项目中调用 uv,所以至少我们可以加快本地开发速度,不是吗?不是。Poetry 不支持 uv,并且在可预见的将来也不会。幸运的是,uv 不仅仅是 pip 的替代品,它实际上是一个非常强大的 Python 项目管理器。因此,我们尝试了一下,并迁移了一些包,以查看需要进行哪些更改。这有了一个非常好的开端。如果你安装了 uv,则迁移包实际上是在包含 pyproject.toml 文件的文件夹上运行的一个命令:

uvx migrate-to-uv

我们不会说我们对安装包的依赖关系的速度感到印象深刻,因为那是意料之中的。我们爱上的是其他一切:uv 简单、整洁、富有表现力。uv run -- 非常强大,你再也不需要在你的生活中 activate 虚拟环境了。我们中的一些 Homebrew 核心用户有史以来第一次让 Python 项目管理器工具安装了 Python 发行版。我们很快就决定告别 Poetry。如果开发人员体验对我们来说如此之好,那么贡献者肯定会喜欢它。

如何在 2.0 中管理构建?

不过我们仍然不满意。CI 仍然无法从 uv 中受益,并且贡献者仍然必须在 pull request 检查中与神秘的失败作斗争。有些事情在困扰着我们,如果你不做出改变,就不会在像 LlamaIndex 这样的东西上工作。所以我们做出了改变。

我们还没有疯狂到认为我们可以重写像 Pants 这样的东西并替换它,但是我们只使用了大约 20% 的 Pants 功能,而且我们绝对比 20% 疯狂。因此,我们重写了检测 monorepo 中所有包的依赖关系图的代码,仅依赖于 pyproject.toml 文件。最重要的是,我们添加了一些代码来检测在每个 pull request 中应该测试哪些包,考虑到已更改的文件。然后是一些调用 pytest 并仅获取重要日志的代码。然后是一些避免因无法测试的包或不相关的失败而导致 CI 作业失败的逻辑。然后我们决定所有这些代码 都可以变成一个定制工具

介绍 LlamaDev

我们将我们的新工具称为 LlamaDev,任何贡献者都可以使用 uv run llama-dev 从 monorepo 的克隆版本中运行它。

自从 我们从 Poetry+Pants 切换到 uv+LlamaDev 以来,我们引入的改进列表很长:

显然,这建立在 uv 为改善在任何 Python 项目上工作的日常工作所做的一切之上。

现在你可以轻松地在 LlamaIndex 和 LlamaDev 上工作

自从我们切换到 uv+LlamaDev 以来,我们引入了一些优化,例如 使用 uv 缓存,并向 LlamaDev 添加了几个 新功能,但是通过你的帮助,我们可以做得更多。如果你一直想为 LlamaIndex 做出贡献,那么现在是开始的正确时机,因为 入门从未如此简单。如果你是一位经验丰富的贡献者,我们需要你的帮助来使 LlamaDev 成为你一直想要的工具:我们将很乐意讨论功能请求并审核代码贡献。

相关文章

Introducing LlamaIndex 0.11 2024-08-22

How to train a custom GPT on your data with EmbedAI + LlamaIndex 2023-12-14

Becoming Proficient in Document Extraction 2023-11-20

Announcing LlamaIndex 0.9 2023-11-15