1. 依赖
依赖是项目运行或开发时需要安装的其他 Python 包。当前 FastAPI 服务会导入这些模块:
1from fastapi import FastAPI2from langchain.agents import create_agent3from langchain_openai import ChatOpenAI4from pydantic_settings import BaseSettings
这些导入能否成功,取决于对应的包是否已经安装在当前 Python 环境中。pyproject.toml 负责声明项目依赖,uv 负责解析并安装它们:
1[project]2dependencies = [3"fastapi[standard]>=0.141.1",4"langchain>=1.3.16",5"langchain-openai>=1.6.0",6"pydantic-settings>=2.10.1",7]
依赖名称与导入名称可能不同:
| 安装名称 | 导入名称 |
|---|---|
langchain-openai | langchain_openai |
pydantic-settings | pydantic_settings |
python-dotenv | dotenv |
安装名称由包发布到 Python 包索引时决定;导入名称由包内部模块结构决定。遇到 ModuleNotFoundError 时,不要仅凭导入名猜安装命令,应先查看该库的官方安装说明。
依赖还分为直接依赖和传递依赖。我们主动声明 fastapi,它内部还需要 Starlette、Pydantic 等包,这些就是传递依赖。项目应把代码直接使用的包声明为直接依赖,不必把依赖树中的每个包都手工复制到 [project].dependencies。反过来,如果代码直接导入了一个传递依赖,也应把它提升为直接依赖,避免上游调整依赖树后项目突然失效。
2. 运行依赖
[project].dependencies 保存项目正常运行必须存在的包。uv 会把它们安装到项目虚拟环境:
1[project]2dependencies = [3"fastapi[standard]>=0.141.1",4"langchain>=1.3.16",5"langchain-openai>=1.6.0",6"pydantic-settings>=2.10.1",7]
如果生产代码会导入某个包,它通常就应该是运行依赖。不能因为开发电脑已经全局安装,就省略声明。
当前项目通过 pydantic-settings 读取 .env,而 pydantic-settings 已经把 python-dotenv 声明为自己的依赖,因此不需要重复添加。只有项目直接导入 dotenv,或者确实需要独立约束它的版本时,才应把 python-dotenv 提升为直接依赖。
fastapi[standard] 中的方括号表示安装 FastAPI 提供的 standard extra。extra 是一个包公开的可选功能集合。FastAPI 的标准 extra 会带上 CLI、Uvicorn 及常见标准依赖,因此当前项目可以直接运行:
1uv run fastapi dev
当前版本的 standard extra 还会带上 FastAPI Cloud CLI。如果只需要常用标准依赖而不需要这个云部署命令,可以改用 fastapi[standard-no-fastapi-cloud-cli]。
extra 不是版本范围。下面三个片段分别表示包名、可选功能和版本约束:
1fastapi [standard] >=0.141.12包名 extra 版本约束
有些依赖看起来功能相似,但并不重复。例如:
fastapi[standard]提供 Web 框架和常用服务端依赖;langchain提供 Agent、消息和 Runnable 等核心抽象;langchain-openai提供 OpenAI 与 OpenAI 兼容接口的模型实现;pydantic-settings负责把环境变量和配置文件转换成类型安全的 Settings。
判断一个包是否应该保留,应该检查代码是否直接依赖它以及 extra 是否已经覆盖需求,不能只看包名是否相似。
3. 版本
依赖字符串可以包含版本约束:
| 写法 | 含义 |
|---|---|
fastapi | 允许解析器选择任意兼容版本 |
fastapi>=0.141.1 | 不低于指定版本 |
fastapi>=0.141.1,<1 | 位于指定区间 |
fastapi==0.141.1 | 只允许一个精确版本 |
fastapi~=0.141.1 | 等价于 >=0.141.1,<0.142.0,允许补丁升级 |
fastapi!=0.142.0 | 排除某个已知问题版本 |
应用项目通常不需要把 [project].dependencies 全部锁成 ==。可以声明项目愿意支持的最低版本和必要上限,再由 uv.lock 锁定当前环境的精确结果。
需要注意,>=0.141.1 是一项兼容性承诺,不表示 uv 已经替你测试过 0.141.1。uv 默认倾向选择范围内的较新版本,锁文件只能记录并复现这次选中的组合。如果项目必须保证最低版本可用,还需要通过最低版本解析或单独的测试矩阵进行验证。
1pyproject.toml:这个项目允许使用哪些版本?2uv.lock:这一次实际安装哪些精确版本?
如果只写宽泛范围却不提交锁文件,不同机器可能在不同时间解析出不同结果。如果把每个传递依赖都写死在 pyproject.toml,升级和维护又会变得困难。
环境标记
依赖可以根据 Python 版本或操作系统条件生效:
1[project]2dependencies = [3"uvloop>=0.22; sys_platform != 'win32'",4"colorama>=0.4; sys_platform == 'win32'",5]
分号右侧是环境标记。只有条件成立时才安装对应依赖。平台差异确实存在时再使用,普通纯 Python 依赖不需要额外添加条件。
直接来源
标准依赖字符串主要面向包索引。Git 仓库、本地目录和私有索引等来源,uv 会通过 [tool.uv.sources] 等配置补充。它们会在下一篇 uv 配置中单独讲解。
4. 分组
并非所有依赖都应该随生产应用安装。测试、格式化、类型检查只在开发或 CI 中使用,可以放进标准的 [dependency-groups]:
01[dependency-groups]02dev = [03{ include-group = "lint" },04{ include-group = "test" },05]06lint = [07"ruff>=0.12",08"mypy>=1.17",09]10test = [11"pytest>=8.4",12"pytest-asyncio>=1.1",13"coverage>=7.10",14"httpx>=0.28",15]
这里有三个组:lint、test 和 dev。dev 通过 include-group 包含另外两个组,因此本地开发可以一次安装完整工具链,CI 也可以只安装自己需要的组。
uv 默认会包含 dev 组。常用命令包括:
1uv sync # 同步项目及默认依赖组2uv sync --no-dev # 不包含 dev 组3uv sync --no-default-groups --group test # 项目、运行依赖和 test 组4uv sync --only-group lint # 只同步 lint,不安装项目和运行依赖5uv sync --all-groups # 同步全部组
--group test 会在项目和默认依赖组之外追加 test。但当前示例中的默认 dev 已经包含 test,单独追加不会改变结果,因此上面先用 --no-default-groups 关闭默认组。--only-group lint 的含义更严格:只安装这个组,当前项目及其运行依赖也会被省略。
--no-dev 只是关闭名为 dev 的组。如果 [tool.uv] 还配置了其他默认组,它们仍会安装;生产环境需要排除全部默认组时,应使用 --no-default-groups。
还要区分依赖组与可选依赖:
| 配置 | 面向谁 | 是否进入发布元数据 |
|---|---|---|
[project].dependencies | 所有运行用户 | 是 |
[project.optional-dependencies] | 安装项目并选择额外功能的用户 | 是 |
[dependency-groups] | 当前项目开发、测试和文档流程 | 否 |
例如,一个可发布的库可以提供 PostgreSQL extra:
1[project.optional-dependencies]2postgres = [3"psycopg[binary]>=3.2",4]
当前项目可以通过 uv sync --extra postgres 启用这个 extra;其他项目依赖它时,可以执行 uv add "your-package[postgres]"。但当前 FastAPI 应用的 pytest、Ruff 不属于对外功能,应放进依赖组而不是 optional dependencies。
旧版 uv 曾使用 [tool.uv].dev-dependencies。当前项目应优先采用已经标准化的 [dependency-groups],不要在新文章和新项目中继续复制旧写法。
5. uv 命令
虽然可以手动编辑依赖数组,但日常增删依赖更适合使用 uv 命令。默认情况下,uv 会更新 pyproject.toml 和 uv.lock,并同步项目虚拟环境;只有显式使用 --no-sync 时才跳过环境同步:
1uv add sqlalchemy2uv add "psycopg[binary]>=3.2"3uv add --dev pytest4uv add --group lint ruff5uv remove sqlalchemy
命令中的引号由 Shell 使用,防止 >、<、方括号等字符被错误解释。写入 TOML 后,依赖仍然是普通字符串。
几个常用命令的职责如下:
| 命令 | 作用 |
|---|---|
uv add | 声明依赖,更新锁文件并同步环境 |
uv remove | 移除直接依赖,重新解析并同步环境 |
uv lock | 创建或更新锁文件,不同步环境 |
uv lock --check | 检查锁文件是否与项目声明一致 |
uv sync | 让虚拟环境与项目及锁文件一致 |
uv run | 在项目环境中执行命令,必要时先同步 |
uv tree | 查看直接和传递依赖关系 |
uv run 不是简单地使用系统 Python。它会找到项目、虚拟环境和锁文件,然后在对应环境中执行命令:
1uv run python -c "import fastapi; print(fastapi.__version__)"
使用 python main.py 能运行,不代表依赖声明正确;它可能意外使用系统环境。项目统一使用 uv run,可以减少「我的电脑能运行,别人电脑不能运行」的问题
6. 锁文件
uv.lock 是 uv 自动维护的跨平台锁文件。它包含精确版本、包来源、哈希值和依赖关系。通常应该提交到应用项目仓库,让开发、CI 和部署基于同一解析结果。
不要手工编辑 uv.lock。需要升级依赖时,应修改声明或使用 uv 命令:
1uv lock --upgrade-package fastapi2uv lock --upgrade3uv lock --check4uv sync --locked --no-default-groups
--upgrade-package fastapi 只把 FastAPI 作为升级目标,并尽量保留其他已锁定版本;如果新版 FastAPI 对传递依赖提出了新要求,相关包仍可能随之变化。--upgrade 会尝试升级整个依赖图,适合计划明确的集中升级。
uv lock --check 只检查锁文件,uv sync --locked 则会在同步环境前检查。如果 pyproject.toml 中的依赖修改使锁文件过期,它们会失败,而不是自动改写锁文件。示例中的 --no-default-groups 进一步排除了 dev 等默认组,适合只安装生产运行依赖。
--frozen 的含义不同:它会跳过新旧检查,直接把现有锁文件当作事实来源。如果 pyproject.toml 新增了依赖而锁文件尚未更新,新依赖可能不会被安装。这个选项适合明确需要信任现有锁文件的场景,例如 Docker 分层构建时尚未复制全部项目文件,但不应把它当作 CI 的一致性检查开关。
升级后至少需要检查:
- FastAPI 服务能否启动;
- 请求和响应校验是否一致;
- LangChain 模型调用和流式事件是否变化;
- LangGraph checkpointer 和数据库驱动是否兼容;
- 测试、类型检查和格式化是否通过。
锁文件能固定版本,不能证明版本一定正确。验证仍然要由测试和运行检查完成。
常见排查
声明了依赖仍无法导入
先确认命令是否通过 uv run 执行,再检查安装名称和导入名称是否混淆:
1uv run python -c "import langchain_openai; print(langchain_openai.__file__)"2uv tree
开发依赖进入生产环境
检查包是否误放进 [project].dependencies。生产同步时可以使用 uv sync --locked --no-default-groups,而不是在构建机器上复用开发虚拟环境。
依赖解析冲突
先阅读 uv 输出,确认是哪两个范围无法同时成立。不要立刻删除所有上限或执行强制安装。冲突通常来自:项目支持范围过宽、某个集成包版本过旧,或不同依赖组确实不兼容。
只更新了 pyproject.toml
手工改完依赖后运行 uv lock 或 uv sync,并同时提交锁文件。代码审查时,依赖声明与锁文件应出现在同一次变更中。
把模型名当作 Python 依赖
deepseek-v4-flash 是远程模型标识,不是需要 uv add 的 Python 包。模型调用需要 langchain-openai 等客户端包,而模型名应进入应用配置。