创建时间: 2026-08-28最后更新: 2026-08-28

1. 配置层

uv 在项目中会同时读取标准 Python 配置和自己的扩展配置:

pyproject.toml
01
[project]
02
name = "fastapi-langchain"
03
version = "0.1.0"
04
requires-python = ">=3.13"
05
dependencies = ["fastapi[standard]"]
06
07
[dependency-groups]
08
dev = ["ruff", "pytest"]
09
10
[tool.uv]
11
default-groups = ["dev"]

这三部分不能混为一谈:

配置性质其他工具是否可能理解
[project]Python 标准项目元数据是
[dependency-groups]标准开发依赖分组是,但安装命令由工具决定
[tool.uv]uv 专属设置通常不会

uv 也支持独立的 uv.toml。在 uv.toml 中要去掉 tool.uv 前缀:

uv.toml
1
default-groups = ["dev"]

如果同一目录同时存在 uv.toml 和 pyproject.toml,uv 会采用 uv.toml,并忽略该 pyproject.toml 中的 [tool.uv],两处设置不会合并。因此,把配置拆到 uv.toml 后,应将需要保留的 uv 设置一起迁移过去。

对于当前单体 FastAPI 项目,常用 uv 配置较短,放在 pyproject.toml 更容易统一查看。只有 uv 设置很多、需要单独分发,或不希望修改项目元数据时,才有必要拆成 uv.toml。

命令行参数、环境变量和配置文件都可能影响 uv。对于同一项设置,通常按照「命令行参数 > 环境变量 > 持久配置」的优先级生效;持久配置中又是项目级高于用户级,用户级高于系统级。排查「为什么本机与 CI 行为不同」时,应同时检查这些来源,不能只看项目中的 TOML。

如果是第一次接触 uv,可以先掌握配置层、依赖来源和 uv run。后面的环境约束与 workspace 属于进阶能力,当前单体 FastAPI 项目暂时用不到。

2. 依赖组

[dependency-groups] 负责定义组中有哪些包,[tool.uv] 可以决定 uv 默认启用哪些组:

default-groups.toml
01
[dependency-groups]
02
lint = ["ruff", "mypy"]
03
test = ["pytest", "pytest-asyncio", "coverage", "httpx"]
04
dev = [
05
{ include-group = "lint" },
06
{ include-group = "test" },
07
]
08
09
[tool.uv]
10
default-groups = ["dev"]

dev 本身就是 uv 默认会包含的特殊组,因此普通项目不写 default-groups 也可以。显式声明的价值在于让团队清楚默认策略,或者改成其他组合。

每个依赖组默认都要与项目的 Python 版本范围兼容。如果某个开发工具只支持更高版本,可以为组单独声明限制:

group-python.toml
1
[project]
2
requires-python = ">=3.11"
3
4
[dependency-groups]
5
dev = ["example-tool"]
6
7
[tool.uv.dependency-groups]
8
dev = { requires-python = ">=3.13" }

这意味着项目本身仍可运行在 Python 3.11,但使用该开发组需要 Python 3.13。只有团队确实接受这种差异时才这样配置,否则开发工具和生产代码使用同一最低版本更简单。

uv 默认会把所有依赖组一起解析进同一个锁文件,即使命令实际只安装其中一组。因此,各组通常也必须彼此兼容。如果 CPU 和 GPU 两套机器学习依赖本来就不能同时使用,可以通过 uv 的 conflicts 配置明确声明互斥关系:

conflicts.toml
01
[dependency-groups]
02
cpu = ["example-model[cpu]"]
03
gpu = ["example-model[gpu]"]
04
05
[tool.uv]
06
conflicts = [
07
[
08
{ group = "cpu" },
09
{ group = "gpu" },
10
],
11
]

声明以后,uv 可以把两套结果记录在同一个锁文件中,但同时选择 cpu 和 gpu 仍会报错。互斥配置不是用来掩盖随意的版本冲突,而是表达两套环境本来就不应同时安装。

3. 来源

正常依赖从 Python 包索引中解析。如果项目需要 Git 仓库、本地目录或指定索引,可以在 [tool.uv.sources] 中补充来源。

Git 来源

git-source.toml
1
[project]
2
dependencies = ["internal-agent-tools"]
3
4
[tool.uv.sources.internal-agent-tools]
5
git = "https://github.com/example/internal-agent-tools.git"
6
tag = "v1.2.0"

这里使用普通子表把多个字段分行书写。字段较少时,也可以使用更紧凑的内联表:

git-source-inline.toml
1
[tool.uv.sources]
2
internal-agent-tools = { git = "https://github.com/example/internal-agent-tools.git", tag = "v1.2.0" }

TOML 1.1 已允许多行内联表,但当前 Python tomllib 仍按照 TOML 1.0 解析,许多 pyproject.toml 工具也需要兼容旧规则。为了让配置能被更多工具读取,内联表仍建议保持在同一行;字段较多时改用上面的普通子表。

可以使用 tag、branch 或 rev 选择版本。生产依赖更适合固定标签或提交,而不是长期跟随一个持续变化的分支。

本地来源

path-source.toml
1
[project]
2
dependencies = ["shared-agent-core"]
3
4
[tool.uv.sources]
5
shared-agent-core = { path = "../shared-agent-core", editable = true }

editable = true 表示本地包代码变化后无需重新安装即可生效,适合联调。部署时必须保证对应目录存在,或者改用可访问的包索引/Git 来源。

索引来源

private-index.toml
1
[[tool.uv.index]]
2
name = "company"
3
url = "https://packages.example.com/simple"
4
explicit = true
5
6
[tool.uv.sources]
7
internal-agent-tools = { index = "company" }

explicit = true 表示这个索引只服务于明确绑定的包,避免普通公共依赖意外从私有索引解析。对于内部包,这是比「让所有依赖都在所有索引中搜索」更容易控制的方式。

用户名、密码和 Token 不应直接写进 url 后提交。对于名为 company 的索引,可以通过 UV_INDEX_COMPANY_USERNAME 和 UV_INDEX_COMPANY_PASSWORD 提供凭据,并由 CI Secret 注入真实值。uv 不会把索引凭据写进 uv.lock。

添加来源时,优先使用 uv 命令生成正确结构:

source-commands.bash
1
uv add git+https://github.com/example/internal-agent-tools.git
2
uv add --editable ../shared-agent-core

然后再检查产生的 [project].dependencies 与 [tool.uv.sources],理解声明和来源之间的分工。

还要注意,[tool.uv.sources] 只会被 uv 使用,也不会成为构建后的标准依赖元数据。pip 或其他工具只能看到 [project].dependencies 中的包名,不知道对应的 Git 地址、本地路径或 workspace 成员。准备发布包时,可以执行 uv build --no-sources,检查项目脱离这些开发来源后是否仍能正常构建。

4. 环境(进阶)

uv 会为 requires-python 覆盖的环境解析依赖。如果项目只部署在有限平台,可以使用环境标记限制求解范围:

resolution-environments.toml
1
[tool.uv]
2
environments = [
3
"sys_platform == 'darwin'",
4
"sys_platform == 'linux'",
5
]

这表示项目不打算为 Windows 解析一套可用依赖。它不会在运行时检测操作系统并阻止 FastAPI 启动,而是在依赖解析阶段收窄目标环境。

某些包只有预编译 wheel,没有源码包。项目必须保证特定平台可解析时,可以声明 required environments:

required-environments.toml
1
[tool.uv]
2
required-environments = [
3
"sys_platform == 'linux' and platform_machine == 'x86_64'",
4
]

这类设置通常出现在 PyTorch 等平台差异明显的项目。普通 FastAPI + LangChain 服务没有遇到真实解析问题时,不需要提前添加。

两个设置解决的问题不同:

设置作用
environments限制锁文件需要为哪些环境解析依赖
required-environments要求只有 wheel 的包必须覆盖哪些环境

环境约束还应与容器和生产服务器一致。如果开发机是 macOS、生产环境是 Linux,锁文件至少要覆盖 Linux。不能为了让本地解析更快而把生产平台排除掉。

5. 工作区(进阶)

当一个仓库包含多个彼此关联的 Python 包时,uv workspace 可以统一管理它们:

workspace.toml
1
[tool.uv.workspace]
2
members = [
3
"packages/*",
4
"services/*",
5
]
6
exclude = [
7
"services/legacy-api",
8
]

工作区成员仍然各自拥有 pyproject.toml,根项目负责发现成员并共享同一个锁文件。成员之间可以使用 workspace 来源:

workspace-source.toml
1
[project]
2
dependencies = ["agent-core"]
3
4
[tool.uv.sources]
5
agent-core = { workspace = true }

uv run 和 uv sync 默认针对 workspace 根项目。需要操作指定成员时,可以使用 --package:

workspace-command.bash
1
uv run --package agent-core pytest
2
uv sync --package agent-core

当前 fastapi-langchain 是独立仓库,没有多个 Python 包,不需要为了「更工程化」而创建 workspace。工作区解决的是多包协作问题,不是普通目录分层问题。app/api、app/core、app/services 都属于同一个 Python 项目,不应被拆成 workspace 成员。如果多个包需要互相冲突的依赖或各自独立的虚拟环境,也更适合作为独立项目并通过路径依赖联调,而不是放进同一个 workspace。

6. 命令

用户很容易把 uv run 与前端的 npm run 混淆。两者名称相似,但语义不同:

run-meaning.txt
1
npm run dev:在 package.json 的 scripts 中查找 dev 字符串并执行
2
uv run dev:准备好 Python 项目环境,然后执行名为 dev 的命令

因此,仅仅写下 [tool.fastapi] 不会生成 dev 命令:

fastapi-config.toml
1
[tool.fastapi]
2
entrypoint = "app.main:app"

这份配置支持:

valid-fastapi-command.bash
1
uv run fastapi dev

但不支持:

missing-dev-command.bash
1
uv run dev

uv run 不会查询任务名称。它会先创建或同步项目环境,再执行后面的命令或 Python 脚本。要让 uv run dev 成立,dev 必须能从进程的 PATH 中找到;它通常来自项目虚拟环境中的命令入口,也可能是系统已经提供的可执行文件。常见做法有三种:

  1. 继续使用清晰的完整命令 uv run --env-file .env fastapi dev;
  2. 使用 Makefile、Just 或 Poe the Poet 等任务工具;
  3. 在 [project.scripts] 中注册一个 Python 函数入口,此时项目还需要声明构建系统。

例如安装并使用 Poe the Poet:

poe-tasks.toml
1
[dependency-groups]
2
dev = ["poethepoet"]
3
4
[tool.poe.tasks]
5
dev = "fastapi dev"
6
test = "pytest"
7
lint = "ruff check ."

执行方式是:

poe-run.bash
1
uv run poe dev

任务工具适合项目确实有多条组合命令时使用。只有一个启动命令时,直接写完整命令更透明,也减少一层依赖。

uv run --env-file .env fastapi dev 中的 --env-file 是 uv 的参数,它先把文件内容加入子进程环境,再运行 FastAPI CLI。FastAPI 和应用最终读取的是环境变量,不是 uv 把 .env 转成了 TOML。同名变量已经存在于进程环境时,环境变量优先于 .env 文件;包含密钥的 .env 还应加入 .gitignore,不能提交到仓库。

维护

uv 配置越复杂,越需要控制边界:

  • 包版本范围属于 [project] 或依赖组;
  • Git、本地路径、索引绑定属于 [tool.uv.sources];
  • 索引地址属于 [[tool.uv.index]];
  • 索引凭据属于环境或 Secret 管理;
  • FastAPI 应用入口属于 [tool.fastapi];
  • 应用业务参数属于 Settings,而不是 [tool.uv]。

排查 uv 行为时,可以使用下面的顺序:

uv-diagnostics.bash
1
uv --version
2
uv python find
3
uv lock --check
4
uv tree --locked
5
uv sync --locked

uv lock --check 只检查锁文件是否与项目声明一致。uv tree 默认可能先更新锁文件,因此这里加上 --locked,让锁文件过期时直接失败;uv sync --locked 会在不改写锁文件的前提下检查并同步环境。CI 使用这些命令,可以发现只改了 pyproject.toml 却忘记提交 uv.lock 的问题。

--frozen 会跳过锁文件的新旧检查,直接信任现有 uv.lock。它适合 Docker 分层构建等明确需要忽略项目元数据变化的场景,不适合用作常规 CI 一致性检查。

不要在不了解后果时复制大量 [tool.uv] 字段。默认行为已经适合大多数单体服务,配置应该用来表达真实约束,而不是展示 uv 支持多少能力。

正在验证登录状态
请稍候,验证完成后将继续显示文章内容