课程0基础Agent开发课 / Python基础 / Python包管理与项目结构-AI项目的工程化实践
— 22 min read

Python包管理与项目结构-AI项目的工程化实践

> **本文适合谁**:想把 Python AI 项目管理得像 Java Maven 项目一样规范的工程师。读完本篇,你能建立一个依赖锁定、环境隔离、配置安全的 AI 项目骨架。

Python 包管理与项目结构:AI 项目的工程化实践

本文适合谁:想把 Python AI 项目管理得像 Java Maven 项目一样规范的工程师。读完本篇,你能建立一个依赖锁定、环境隔离、配置安全的 AI 项目骨架。

AI 项目的包管理问题比普通 Python 项目更突出。LangChain 的 API 在 0.1 版本大幅调整,旧代码在新版本里无法运行。如果 requirements.txt(记录项目所有依赖包及版本的文本文件)里没有锁定版本号,不同机器、不同时间安装出来的环境就会产生差异,排查问题代价很高。

Java 项目没有这个问题——pom.xml 里每个依赖都有明确的版本号,Maven 锁定完整的依赖树。Python 项目需要主动建立同等的规范。

1.1 Python 包管理工具完整对比

pip
标准工具 内置Python
慢/无lock文件
基础场景

poetry
依赖管理+打包一体
中速/lock文件
库开发推荐

uv
Rust实现 极速安装
10-100x更快
AI项目首选

AI项目推荐:uv(速度)或 poetry(库发布)

pip、Poetry、uv 三种包管理工具的速度、功能与适用场景对比

市面上有几种主流方案,各有适用场景。下面的对比帮你快速选型:

工具 锁文件 虚拟环境管理 Python 版本管理 速度 推荐程度
pip + requirements.txt 手动(不完整) 不含 不含 基准 不推荐新项目
pip-tools 自动生成 不含 不含 老项目维护
poetry 自动(完整) 不含 中等 推荐(稳定)
uv 自动(完整) 极快(10-100x,以官方测试为准) 强烈推荐新项目
conda 有限 数据科学/ML 场景

1.1.1 pip + requirements.txt:最基础,不推荐新项目

code
langchain==0.3.7
openai==1.55.3
fastapi==0.115.4

手动在每个包名后面加 ==版本号。问题是这只锁定了直接依赖,没有锁定依赖的依赖(传递依赖)。langchain==0.3.7 依赖 langchain-core,但 requirements.txt 里没有记录 langchain-core 的版本。不同环境装出来的 langchain-core 版本可能不一样,导致行为差异。

这个方案的根本缺陷是没有完整的锁文件,不同机器装出来的环境不保证一致。

1.1.2 pip-tools:生成锁文件

pip-tools 解决了传递依赖(你安装的包自身又依赖的其他包)问题。在 requirements.in 里写直接依赖,pip-compile 生成 requirements.txt(相当于 lock 文件,完整记录所有包的精确版本),锁定所有传递依赖:

bash
pip install pip-tools
pip-compile requirements.in          # 生成 requirements.txt
pip-sync requirements.txt            # 安装精确版本

requirements.txt 锁定完整依赖树,不同机器装出来的环境完全一致。但 pip-tools 不含虚拟环境管理,需要搭配 venv 使用,工具链碎片化。

1.1.3 poetry:现代化方案(稳定首选)

poetry 是成熟的现代包管理工具,用 pyproject.toml(Python 项目的统一配置文件格式,PEP 517/518 标准)管理项目配置,自动管理虚拟环境,把开发依赖(只在开发阶段需要,如测试工具)和生产依赖(线上运行必须的包)分开:

bash
# 安装 poetry
curl -sSL https://install.python-poetry.org | python3 -

# 创建新项目
poetry new my-ai-app
cd my-ai-app

# 添加依赖
poetry add langchain openai fastapi
poetry add --group dev pytest mypy black

# 安装所有依赖
poetry install
toml
# pyproject.toml(poetry 管理)
[tool.poetry.dependencies]
python = "^3.11"
langchain = "^0.3.7"
fastapi = "^0.115"
openai = "^1.55"

[tool.poetry.group.dev.dependencies]
pytest = "^8.0"
mypy = "^1.8"
black = "^24.0"

poetry.lock 锁定完整依赖树,poetry install 装出来的环境和 CI、其他开发者完全一致。

poetry 的优缺点:

  • 优点:成熟稳定,社区支持好,有完整的发包流程(poetry publish)
  • 缺点:安装速度比 uv 慢很多,不含 Python 版本管理(需要 pyenv 补充)

1.1.4 uv:2024 年发布、目前首选

uv 是 Astral 公司用 Rust 编写的包管理工具,2024 年发布,速度是 pip 的 10-100 倍(以官方测试为准)。

uv 是包管理、虚拟环境、Python 版本管理三合一的工具:可以直接替换 pip、内置 venv 管理(uv venv)、还能安装和管理多个 Python 版本(uv python install 3.11)。新项目推荐直接用 uv:

bash
# 安装 uv
curl -LsSf https://astral.sh/uv/install.sh | sh

# 创建项目(类比 mvn archetype:generate)
uv init my-ai-app
cd my-ai-app

# 添加依赖(类比 mvn dependency:resolve,但快 10 倍以上,以官方测试为准)
uv add langchain langchain-openai openai fastapi
uv add --dev pytest mypy black ruff

# 运行代码(自动激活虚拟环境)
uv run python main.py

# 安装所有依赖(根据 uv.lock,类比 mvn install)
uv sync

uv 与 poetry 的功能对比:

功能 uv poetry
安装速度 极快(Rust 实现) 中等(Python 实现)
锁文件 uv.lock poetry.lock
Python 版本管理 含(uv python install 不含(需要 pyenv)
发包到 PyPI 含(uv publish 含(poetry publish
成熟度 较新(2024) 成熟(2018)
社区生态 快速增长 非常成熟

选型建议:新项目用 uv,老项目继续用 poetry。两者功能等价,uv 只是更快。

1.2 虚拟环境:必须用,没有例外

虚拟环境是 Python 工程化的基础。直接在系统 Python 里 pip install,所有项目共享同一个包目录,迟早出现版本冲突。

Java 没有这个问题,因为依赖在项目的 ~/.m2/repository 目录里,按 groupId/artifactId/version 隔离。Python 的 pip install 默认装到系统目录,没有项目隔离。

三种虚拟环境方案:

bash
# venv:Python 内置,最基础
python -m venv .venv
source .venv/bin/activate       # macOS/Linux
.venv\Scripts\activate          # Windows

# conda:适合数据科学,可以管理 Python 版本
conda create -n myproject python=3.11
conda activate myproject

# uv:最快,推荐新项目用
uv venv                          # 创建 .venv
uv venv --python 3.11            # 指定 Python 版本
source .venv/bin/activate

核心规则:每个项目一个虚拟环境,激活虚拟环境后再 pip install。

用 uv 时不需要手动激活虚拟环境,uv run 自动处理:

bash
uv run python main.py          # 自动使用 .venv
uv run pytest tests/           # 自动使用 .venv

1.3 AI 项目的标准目录结构

以下目录结构适合中等规模的 AI 应用(一个 Agent 服务 + FastAPI 接口,FastAPI 是 Python 中最流行的 Web 框架之一,原生支持异步和自动生成 API 文档):

code
my-ai-app/
├── src/
│   ├── __init__.py
│   ├── agents/           # Agent 逻辑
│   │   ├── __init__.py
│   │   ├── research_agent.py
│   │   └── coding_agent.py
│   ├── tools/            # 工具定义
│   │   ├── __init__.py
│   │   ├── search.py
│   │   └── calculator.py
│   ├── chains/           # LangChain 链
│   │   ├── __init__.py
│   │   └── rag_chain.py
│   ├── api/              # FastAPI 路由
│   │   ├── __init__.py
│   │   ├── main.py
│   │   └── routes/
│   └── config.py         # 配置管理(统一读取环境变量)
├── tests/
│   ├── unit/
│   ├── integration/
│   └── conftest.py       # pytest 共享 fixture
├── docs/
├── scripts/              # 一次性脚本,数据处理等
├── .env.example          # 环境变量模板(提交到 git)
├── .env                  # 真实环境变量(加入 .gitignore!)
├── .gitignore
├── pyproject.toml        # 项目配置
├── Makefile              # 常用命令快捷方式
└── docker-compose.yml

这个结构的核心原则:

  • src/ 布局:把所有代码放在 src/ 目录下,避免导入路径混乱。Java 项目的 src/main/java 是同样的思路
  • 按功能分模块:agents、tools、chains 分开,不要全塞进一个文件
  • tests 和 src 平级:不要把测试代码混进业务代码目录
  • .env 永远不进 git:API Key 泄露是真实风险,参见下一节

1.4 环境变量管理:API Key 的安全处理

AI 项目依赖大量 API Key 和配置项。这些值不应写死在代码里。这是安全基础,不是可选项——API Key 一旦进入 git 历史,即使后来删除也可能已经被爬虫抓走。

1.4.1 .env 文件的最佳实践

项目根目录准备两个文件:

.env.example(提交到 git,作为环境变量的文档和模板):

bash
# .env.example
# 复制此文件为 .env 并填入真实值

# ===== LLM API Keys =====
OPENAI_API_KEY=your_openai_api_key_here
ANTHROPIC_API_KEY=your_anthropic_api_key_here   # 可选,用于 Claude 模型
DEEPSEEK_API_KEY=your_deepseek_api_key_here      # 可选,备用模型

# ===== 可观测性 =====
LANGSMITH_API_KEY=your_langsmith_api_key_here    # LangChain 链路追踪
LANGSMITH_PROJECT=my-ai-app                       # 项目名称

# ===== 向量数据库 =====
PINECONE_API_KEY=your_pinecone_api_key_here       # 可选
CHROMA_HOST=localhost                             # ChromaDB 地址

# ===== 数据库 =====
DATABASE_URL=postgresql://localhost:5432/mydb

# ===== 应用配置 =====
ENVIRONMENT=development    # development / staging / production
LOG_LEVEL=INFO
MAX_TOKENS=2000
TEMPERATURE=0.7

.env(本地填入真实值,永远不提交到 git):

bash
OPENAI_API_KEY=sk-proj-真实的key
ENVIRONMENT=development
LOG_LEVEL=DEBUG

.gitignore 必须包含:

code
.env
.env.local
.env.*.local
*.env

1.4.2 用 pydantic-settings 读取配置(推荐)

python-dotenv 更好,支持类型验证,能在启动时自动检测配置缺失:

python
# src/config.py
# 目的:统一管理所有配置,在启动时验证配置完整性
from pydantic_settings import BaseSettings
from pydantic import Field
from typing import Literal

class Settings(BaseSettings):
    # ===== API Keys =====
    openai_api_key: str = Field(description="OpenAI API Key,必填")
    anthropic_api_key: str | None = None  # 可选,None 表示不使用 Claude
    deepseek_api_key: str | None = None

    # ===== 可观测性 =====
    langsmith_api_key: str | None = None
    langsmith_project: str = "default"

    # ===== 应用配置 =====
    environment: Literal["development", "staging", "production"] = "development"
    log_level: str = "INFO"
    max_tokens: int = 2000
    temperature: float = Field(default=0.7, ge=0.0, le=2.0)

    # pydantic-settings 的配置
    model_config = {
        "env_file": ".env",           # 从 .env 文件加载
        "env_file_encoding": "utf-8",
        "case_sensitive": False,       # OPENAI_API_KEY 和 openai_api_key 都能识别
    }

# 全局单例:应用启动时创建一次,整个应用共用
settings = Settings()

# 预期输出(如果 .env 文件配置正确,没有输出;如果缺失必填项,报错):
# pydantic_settings.main.SettingsError: openai_api_key
#   Field required [type=missing, input_url=...]

用起来直接导入:

python
# 在其他模块中使用
from src.config import settings

print(settings.openai_api_key[:10] + "...")  # 打印前10位,不泄露完整 Key
print(settings.environment)  # "development"
print(settings.temperature)  # 0.7(是 float,不是字符串)

优势:IDE 有完整的类型提示,环境变量缺失会在启动时抛出 ValidationError,不会等到实际调用时才报错。比 Java 的 @Value("${...}") 在错误发现时机上更友好。

1.4.3 python-dotenv 简单方案(较旧项目)

python
# src/config_simple.py
# 目的:简单场景下用 python-dotenv 读取 .env 文件
from dotenv import load_dotenv
import os

load_dotenv()  # 自动查找并加载 .env 文件

# 读取配置
OPENAI_API_KEY = os.environ["OPENAI_API_KEY"]    # 不存在则抛 KeyError
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")       # 有默认值,不报错
MAX_TOKENS = int(os.getenv("MAX_TOKENS", "2000")) # 需要手动类型转换

缺点:类型不安全(全是字符串),缺失配置在运行时才发现,IDE 没有提示。新项目用 pydantic-settings。

1.4.4 多环境配置管理

bash
# 开发环境(.env)
ENVIRONMENT=development
LOG_LEVEL=DEBUG
DATABASE_URL=postgresql://localhost:5432/dev_db
OPENAI_API_KEY=sk-dev-...

# 生产环境(不用 .env 文件,由 K8s/Docker 注入环境变量)
# ENVIRONMENT=production
# LOG_LEVEL=WARNING
# DATABASE_URL=postgresql://prod-server:5432/prod_db
# OPENAI_API_KEY=sk-prod-...

CI/CD(持续集成/持续部署,即代码提交后自动触发构建、测试、发布的流水线)里通过 Secret 注入,不用 .env 文件。GitHub Actions 示例:

yaml
# .github/workflows/ci.yml
env:
  OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
  ENVIRONMENT: testing

1.5 依赖分层

pyproject.toml 的标准写法(uv 管理):

toml
[project]
name = "my-ai-app"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
    # LLM 框架
    "langchain>=0.3.0",
    "langchain-openai>=0.2.0",
    "langgraph>=0.2.0",
    # LLM API
    "openai>=1.50.0",
    # Web 框架
    "fastapi>=0.115.0",
    "uvicorn[standard]>=0.32.0",
    # 数据校验与配置
    "pydantic>=2.9.0",
    "pydantic-settings>=2.6.0",
    # 向量数据库(按需选择)
    "chromadb>=0.5.0",
]

[project.optional-dependencies]
# 开发依赖:只在开发时需要,生产不安装
dev = [
    "pytest>=8.0.0",
    "pytest-asyncio>=0.24.0",
    "mypy>=1.8.0",
    "black>=24.0.0",
    "ruff>=0.8.0",
]

[tool.black]
line-length = 88
target-version = ["py311"]

[tool.ruff]
line-length = 88
select = ["E", "F", "I"]   # pycodestyle + pyflakes + isort

[tool.mypy]
python_version = "3.11"
ignore_missing_imports = true

[tool.pytest.ini_options]
asyncio_mode = "auto"        # pytest-asyncio:自动处理 async 测试

生产依赖装必须的包,开发依赖只在开发环境装:

bash
# 生产环境
uv pip install .

# 开发环境(含 dev 依赖)
uv pip install ".[dev]"

# 或者用 uv sync
uv sync            # 生产依赖
uv sync --dev      # 含开发依赖

1.6 代码格式化:black + ruff

Java 有 Checkstyle 和代码格式化规范,Python 用 black + ruff 组合。

black:代码格式化工具,不可配置(这是设计哲学,减少团队争议)。装上以后直接跑 black src/,代码格式统一。

ruff:lint 工具(静态代码分析工具,自动检查代码中的格式问题和潜在错误),替代 flake8 + isort + 一堆插件,Rust 写的,极快。

Makefile 里把常用命令都收进去:

makefile
.PHONY: fmt lint test typecheck

fmt:
	black src/ tests/
	ruff check --fix src/ tests/

lint:
	ruff check src/ tests/

test:
	pytest tests/ -v

typecheck:
	mypy src/

# 一键做所有检查(CI 用)
check: fmt lint typecheck test

dev:
	uvicorn src.api.main:app --reload --port 8000

1.7 完整示例:用 uv 初始化一个 AI 项目

bash
# 1. 安装 uv(如果没有)
curl -LsSf https://astral.sh/uv/install.sh | sh

# 2. 初始化项目
uv init my-ai-app
cd my-ai-app

# 3. 指定 Python 版本并创建虚拟环境
uv venv --python 3.11
source .venv/bin/activate  # Windows: .venv\Scripts\activate

# 4. 添加依赖(uv 自动更新 pyproject.toml 和 uv.lock)
uv add langchain langchain-openai langgraph fastapi uvicorn pydantic pydantic-settings
uv add --dev pytest mypy black ruff pytest-asyncio

# 5. 创建目录结构
mkdir -p src/{agents,tools,chains,api/routes} tests/{unit,integration} docs scripts

# 6. 初始化包(__init__.py 让目录变成 Python 包)
touch src/__init__.py src/agents/__init__.py src/tools/__init__.py
touch src/chains/__init__.py src/api/__init__.py

# 7. 创建配置文件
cat > src/config.py << 'EOF'
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    openai_api_key: str
    environment: str = "development"
    log_level: str = "INFO"

    model_config = {"env_file": ".env", "case_sensitive": False}

settings = Settings()
EOF

# 8. 创建 .env.example
cat > .env.example << 'EOF'
OPENAI_API_KEY=your_openai_api_key_here
ENVIRONMENT=development
LOG_LEVEL=INFO
EOF

# 9. 创建 .gitignore(API Key 安全的基础)
cat > .gitignore << 'EOF'
.env
.env.local
.venv/
__pycache__/
*.pyc
.mypy_cache/
.ruff_cache/
dist/
*.egg-info/
EOF

# 10. 验证环境
uv run python -c "import langchain; print('langchain:', langchain.__version__)"

项目初始化完,其他人只需要四步就能跑起来:

bash
uv venv --python 3.11
source .venv/bin/activate
uv sync --dev    # 根据 uv.lock 安装精确版本
cp .env.example .env    # 填入真实的 API Key

1.8 与 Java Maven 的工作流对比

操作 Java Maven Python uv
创建项目 mvn archetype:generate uv init my-project
添加依赖 编辑 pom.xml + mvn install uv add langchain
锁定版本 Maven Dependency Tree(隐式) uv.lock(显式)
安装依赖 mvn install uv sync
运行代码 java -jar target/... uv run python main.py
区分开发依赖 <scope>test</scope> uv add --dev pytest
发布包 mvn deploy uv publish

1.9 小结

工程化是 AI 项目可维护性的基础,不是可选项。AI 项目的依赖关系复杂(LangChain 自身就依赖几十个包),版本更新频繁(LangChain 几乎每个月都有较大的 API 变化),多人协作也很普遍。

工具/实践 作用 优先级
uv 包管理、虚拟环境、Python 版本管理三合一 立刻用
pyproject.toml 统一项目配置,类比 pom.xml 立刻用
.env + .gitignore API Key 安全管理 立刻用(安全红线)
pydantic-settings 类型安全的配置读取 强烈推荐
black + ruff 代码格式统一 团队项目必备
mypy 类型检查 推荐
Makefile 统一命令入口 推荐

规范一次建好,后续节省的排查时间远超建规范的成本。

本页目录