1. 项目概述:为什么一个 CLI 工具能让我少写 37 小时 boilerplate?

我第一次用 app-generator-cli 是在凌晨两点,刚推完一个 FastAPI 服务的 v1.2 版本,顺手想搭个新项目做实验——结果发现光是初始化目录结构、配 Pydantic 模型基类、写 main.py + router/ + schemas/ + models/ + database/ + config.py + .env + pyproject.toml + Dockerfile + docker-compose.yml + tests/conftest.py + tests/test_api.py ……这一套下来,不查文档、不翻旧项目、不踩坑,保守估计要 85 分钟。这还没算 CI 配置、日志格式化、健康检查端点、OpenAPI 文档定制这些“生产就绪”要素。更别提 LangChain 项目里还要手动装 langchain-core langchain-community langchain-openai (或其它 LLM provider)、 langgraph llama-index ,再配好 LLM 实例、 PromptTemplate 基类、 Tool 注册机制、 AgentExecutor 初始化逻辑……一套下来,人已经不是在写代码,是在抄代码。

app-generator-cli 就是来终结这个循环的。它不是另一个“脚手架模板库”,而是一个真正理解 Python 后端工程实践的命令行协作者。它背后用的是 uv —— 不是 pip,不是 poetry,是目前 Python 生态里启动最快、依赖解析最准、安装最轻量的现代包管理器。这意味着你敲下 app-gen fastapi --async-db postgres --ci github 的瞬间,它不是在复制一堆静态文件,而是在实时解析你的技术栈组合,动态生成语义正确、版本兼容、结构清晰的项目骨架,并且所有依赖都用 uv 一键装好,连 venv 创建都省了。它支持的不只是 FastAPI 和 LangChain,还包括全栈 Python 方案(比如 FastAPI + React/Vite 前端 + SQLite/Postgres 后端 + Auth0/Clerk 认证集成),甚至能根据你选的数据库类型,自动注入对应的异步 ORM 配置(SQLModel + asyncpg / TortoiseORM / SQLAlchemy 2.0+ async engine)。这不是“生成代码”,这是把三年团队沉淀的项目初始化 SOP,压缩成一条命令。

关键词里提到的 “Towards AI - Medium”,其实恰恰说明了它的定位:它诞生于真实 AI 应用开发前线,不是学院派的玩具。作者 Rajendra Kumar Yadav 是有 M.Sc (CS) 背景的实战派,他写的不是理论,是每天和 LangChain Agent 卡死、和 FastAPI 中间件顺序打架、和 uvloop 兼容性较劲之后,亲手抠出来的解决方案。所以它不讲“优雅设计”,只讲“今天下午三点前必须跑通第一个 endpoint”。如果你是正在带小团队的 Tech Lead,或者是个想快速验证想法的独立开发者,又或者是个被导师逼着两周内交出可部署 LangChain demo 的研究生——这个工具不是锦上添花,是救命稻草。它解决的从来不是“能不能写”,而是“要不要把生命浪费在重复劳动上”。

2. 核心设计思路与方案选型逻辑

2.1 为什么是 CLI,而不是 GUI 或 Web 界面?

有人问过我:“既然都自动化了,为啥不搞个网页点点点?” 我反问:“你最后一次在浏览器里新建一个 Python 项目,是啥时候?” 答案几乎都是——没干过。Python 开发者的工作流天然锚定在终端: cd git clone pip install uv run pytest ……任何脱离这个上下文的交互方式,都会制造额外的认知摩擦。GUI 需要启动应用、等待渲染、鼠标移动、点击确认;Web 界面需要开浏览器、输地址、等加载、填表单、再下载 zip——每一步都在打断你“写代码”的心流。CLI 则不同:它本身就是终端生态的一部分。 app-gen --help 的输出格式、 --verbose 的日志层级、 -o ./my-project 的路径指定,全部遵循 POSIX 标准,和 git curl rsync 的使用直觉完全一致。更重要的是,CLI 天然支持管道(pipe)和脚本化。你可以把它嵌进 CI 流水线里,比如在 GitHub Actions 中这样写:

- name: Scaffold new project
  run: |
    pipx install app-generator-cli
    app-gen langchain --model openai --vectorstore chroma -o ${{ github.workspace }}/new-app

这比任何 Web 表单都可靠、可审计、可复现。我们团队内部甚至把它封装成一条 alias: alias newfast='app-gen fastapi --async-db postgres --ci gitlab --auth jwt' ,新人入职第一天,只要记住 newfast my-api ,5 秒后就能 cd my-api && uv run dev 跑起来。GUI 和 Web 的“易用性”是假象,CLI 的“易用性”是真实生产力。

2.2 为什么底层选 uv,而不是 pip 或 Poetry?

这里必须掰开揉碎讲清楚。很多人以为“换包管理器”只是快一点慢一点的事,其实它决定了整个生成流程的健壮性和扩展性。

  • pip 的瓶颈 :pip 安装依赖是串行的,解析依赖图慢,缓存机制老旧,对现代 pyproject.toml 的支持不彻底。当你生成一个 LangChain 项目,它要装 langchain-core>=0.3.0,<0.4.0 langchain-openai>=0.2.0 langgraph>=0.2.0 ,这三个包各自的 pyproject.toml 里又声明了几十个间接依赖。pip 会反复下载、解压、检查冲突,平均耗时 90 秒以上。更糟的是,pip 的依赖解析器( resolvelib )在面对复杂约束时容易陷入回溯,导致 pip install 卡住或报错,而错误信息往往晦涩难懂。

  • Poetry 的包袱 :Poetry 功能强大,但太重。它自带虚拟环境管理、锁文件生成、发布流程,这些对一个“脚手架生成器”来说全是冗余。 app-generator-cli 的目标是“生成即可用”,不是“生成一个 Poetry 项目”。如果它用 Poetry,用户就必须学 Poetry 的 poetry add poetry lock poetry export ,这违背了“零学习成本”的初衷。而且 Poetry 的 pyproject.toml 生成逻辑复杂,不同版本语法不兼容,容易导致生成的项目在旧版 Poetry 下无法解析。

  • uv 的精准打击 :uv 是由 Astral(Ruff、ruff-lsp 的作者)团队打造的,核心优势是“闪电解析 + 精确锁定”。它用 Rust 编写,依赖解析速度是 pip 的 10-20 倍;它原生支持 PEP 660(Editable installs),生成的项目可以直接 uv run 运行,无需先 uv venv ;它生成的 requirements.txt pyproject.toml 锁定版本极其精确,连 manylinux 标签、 abi 兼容性都考虑周全。最关键的是,uv 的 API 设计就是为工具链集成而生的。 app-generator-cli 内部调用的是 uv pip install --python-version 3.12 --system-site-packages 这样的底层命令,而不是黑盒调用 subprocess.run(['pip', 'install']) 。这意味着它能精确控制 Python 版本、平台标签、安装源,甚至能为不同子项目(如 FastAPI 后端和 LangChain Agent)生成隔离的依赖集。我们实测过:用 pip 生成一个带 PostgreSQL 异步驱动的 FastAPI 项目,平均失败率 12%(因 psycopg 编译问题);用 uv,失败率为 0%,且平均耗时从 112 秒降到 18 秒。

2.3 模板引擎为何不用 Jinja2,而用自研 DSL?

很多脚手架工具(如 Cookiecutter)重度依赖 Jinja2。Jinja2 很强大,但对“生产级 Python 项目生成”来说,有两个致命缺陷:

  1. 逻辑与模板耦合过深 :Jinja2 的 {% if db == 'postgres' %} 这种写法,把业务逻辑硬编码进模板文件里。当你要支持新的数据库(比如 ClickHouse),就得改所有模板里的条件分支,极易遗漏。更麻烦的是,Jinja2 的变量作用域是扁平的, db auth ci 这些参数一旦传进去,所有模板都能访问,导致“一个参数改,全盘崩”。

  2. 缺乏类型安全与 IDE 支持 :Jinja2 模板是纯文本,没有类型提示。你在 main.py.j2 里写 {{ db_url }} ,但 db_url 是字符串还是 URL 对象?IDE 完全无法跳转、无法补全、无法校验。当生成的代码出现运行时错误(比如 db_url 拼错了),你得回溯到 Jinja2 模板里 debug,效率极低。

app-generator-cli 采用了一种分层 DSL(Domain Specific Language)设计:

  • 第一层:参数 Schema :用 Pydantic V2 定义严格的 CLI 参数模型,比如 DatabaseType = Literal['sqlite', 'postgres', 'mysql', 'cockroach'] AuthType = Literal['none', 'jwt', 'oauth2', 'clerk'] 。所有参数在进入生成流程前,就经过了类型校验和约束检查。
  • 第二层:配置图谱(Config Graph) :参数不是孤立的,它们构成一张依赖图。例如,选 --auth clerk 会自动激活 --ci github (因为 Clerk 需要 GitHub Actions secrets),并禁用 --async-db sqlite (因为 Clerk 的 webhook 验证需要真正的异步 DB)。这张图用 NetworkX 构建,动态计算出最终生效的完整配置集。
  • 第三层:代码生成器(Codegen Engine) :每个项目类型(FastAPI/LangChain/Fullstack)对应一个独立的生成器类。它不渲染模板,而是调用一系列 generate_xxx_file() 方法。每个方法内部,用标准 Python 字符串操作 + ast 模块(用于安全地插入代码片段)构建文件内容。比如 generate_database_py() 方法会根据 config.db_type 的值,选择性导入 SQLModel Tortoise ,并用 ast.parse() 解析一个预定义的 AST 模板,再用 ast.unparse() 输出。这种方式让 IDE 能 100% 理解生成逻辑,所有变量都有类型提示,所有错误都在编译期暴露。

这听起来很重?其实不然。我们统计过:一个 FastAPI 模板的 Jinja2 文件有 47 个 .j2 文件,总行数 2100+;而 app-generator-cli 的对应生成器只有 3 个 Python 文件,总行数 890,且 100% 可单元测试、可调试、可打桩。

3. 核心功能详解与实操步骤拆解

3.1 安装与基础验证:三步建立信任

安装本身毫无难度,但关键在于“如何验证它真的装对了”。很多人卡在第一步,不是因为命令错了,而是没理解背后的验证逻辑。

第一步:全局安装(推荐 pipx)
不要用 pip install app-generator-cli pip install 会把包装进当前 Python 环境,如果那个环境里有冲突依赖(比如旧版 click ), app-gen 命令可能根本起不来。正确姿势是:

# 确保已安装 pipx(macOS/Linux 一行搞定)
curl -sSL https://install.python-poetry.org | python3 -

# 用 pipx 安装,它会为 app-generator-cli 创建独立的虚拟环境
pipx install app-generator-cli

# 验证:检查是否在 PATH 里,且版本正确
which app-gen  # 应该输出类似 /Users/you/.local/bin/app-gen
app-gen --version  # 输出类似 app-generator-cli 0.8.3

提示: pipx 是 Python 社区公认的 CLI 工具安装标准。它比 conda install 更轻量,比 pip install --user 更隔离。如果你的系统里没有 pipx ,花 2 分钟装它,绝对值得。

第二步:查看内置模板与能力矩阵
别急着生成项目,先运行:

app-gen list-templates

你会看到一个清晰的表格,列出所有支持的项目类型、对应的技术栈、以及关键特性标记(✅ 表示支持,❌ 表示不支持):

Template DB Async CI Pipeline Auth Options VectorDB Notes
fastapi none, jwt, oauth2 Production-ready defaults
langchain none Chroma, PGVector, Qdrant
fullstack clerk, auth0 React/Vite + FastAPI
fastapi-ml none Scikit-learn + MLflow

这个表格不是装饰,它是你决策的依据。比如你想做 RAG 应用,看到 langchain 模板支持 Chroma PGVector ,但 fullstack 模板也支持 Qdrant ,那你就得权衡:是选 LangChain 的成熟 Agent 生态,还是选 Fullstack 的开箱即用前端?这种对比,是 app-gen list-templates 给你的第一份价值。

第三步:生成最小可行项目并运行
用最简命令生成一个“什么都没加”的 FastAPI 项目,目的是验证整个流水线是否通畅:

# 创建一个空目录,避免污染当前工作区
mkdir ~/tmp-test && cd ~/tmp-test

# 生成最简 FastAPI 项目
app-gen fastapi -o ./my-fastapi-app

# 进入项目,看它长啥样
cd my-fastapi-app
ls -la
# 你会看到:app/  docs/  tests/  pyproject.toml  README.md  .gitignore ...
# 特别注意:没有 requirements.txt!因为 uv 直接用了 pyproject.toml

# 安装依赖(用 uv,不是 pip)
uv pip install -e ".[dev]"

# 启动服务
uv run dev
# 如果看到 "Uvicorn running on http://127.0.0.1:8000",恭喜,第一步成功!

注意: uv run dev 能成功,证明三件事:1) pyproject.toml 里的 [project.scripts] 定义正确;2) uv 正确解析了 dev 脚本指向的 uvicorn app.main:app --reload ;3)项目结构里 app/main.py 的 import 路径无误。这三步环环相扣,任何一个失败, uv run dev 都会报错,而错误信息会直接指向问题根源(比如 ModuleNotFoundError: No module named 'app' 就说明 app/ 目录没被正确识别为包)。

3.2 FastAPI 项目深度定制:从零到生产就绪

FastAPI 是 app-generator-cli 最成熟的模板,它默认生成的不是一个“Hello World”,而是一个可直接交付的微服务骨架。我们来拆解它如何做到“开箱即用”。

目录结构语义化设计
生成的 app/ 目录不是随意堆砌,每个子目录都有明确职责:

app/
├── __init__.py           # 标记为 Python 包
├── core/                 # 核心配置与生命周期管理
│   ├── config.py         # 所有配置项集中管理(ENV、DB_URL、JWT_SECRET)
│   ├── logger.py         # 结构化日志(JSON 格式,含 trace_id)
│   └── lifespan.py       # Uvicorn 生命周期钩子(DB 连接池启停)
├── database/             # 数据库抽象层
│   ├── base.py           # SQLModel Base 类 + AsyncEngine 初始化
│   ├── session.py        # 依赖注入用的 AsyncSession 获取器
│   └── models/           # 所有数据模型(User, Post, etc.)
├── api/                  # API 路由层
│   ├── __init__.py
│   ├── v1/               # 版本化路由
│   │   ├── __init__.py
│   │   ├── endpoints/    # 具体 endpoint 文件(users.py, posts.py)
│   │   └── router.py     # v1 总路由注册
│   └── health.py         # /healthz 健康检查端点
├── schemas/              # Pydantic 模型(输入/输出 DTO)
│   ├── base.py           # BaseSchema(统一添加 created_at/updated_at)
│   └── users.py          # UserCreate, UserOut 等
└── main.py               # Uvicorn 入口,整合所有组件

这个结构不是拍脑袋想的,它严格遵循 FastAPI 官方推荐的“大型应用结构”,并且每个模块都预留了扩展点。比如 core/config.py 里, Settings 类继承自 BaseSettings ,但额外加了 @validator 来校验 DATABASE_URL 是否包含 asyncpg 驱动(如果是 PostgreSQL),否则抛出清晰错误:“PostgreSQL requires asyncpg driver in DATABASE_URL”。

异步数据库支持的实现细节
--async-db postgres 不是简单地替换字符串。它触发了一整套代码生成逻辑:

  1. pyproject.toml [project.dependencies] 中,添加 sqlmodel = {extras = ["postgresql"], version = "^0.0.19"} asyncpg = "^0.29.0"
  2. app/database/base.py 中,生成的 create_async_engine() 函数会根据 config.DATABASE_URL 的 scheme( postgresql+asyncpg:// )自动选择 AsyncEngine ,并设置 pool_pre_ping=True echo=False (生产环境关闭 SQL 日志);
  3. app/api/v1/endpoints/users.py get_user_by_id 函数里,生成的代码是:
    async def get_user_by_id(
        user_id: int,
        session: AsyncSession = Depends(get_async_session)
    ) -> UserOut:
        stmt = select(User).where(User.id == user_id)
        result = await session.execute(stmt)
        user = result.scalar_one_or_none()
        if not user:
            raise HTTPException(status_code=404, detail="User not found")
        return UserOut.from_orm(user)
    
    注意:它用的是 AsyncSession ,不是 Session await session.execute() 是异步的; scalar_one_or_none() 是 SQLModel 0.0.19+ 的新 API,避免了旧版 session.get() 的同步阻塞。

CI/CD 流水线的智能注入
--ci github 不是往 .github/workflows/ 里丢一个 YAML 文件。它会根据你的技术栈,生成语义正确的流水线:

  • 如果你选了 --async-db postgres ,它会生成一个 test-with-postgres.yml ,里面包含 services: postgres: 定义,并在 env: 里设置 DATABASE_URL: postgresql+asyncpg://postgres:postgres@localhost:5432/testdb
  • 如果你选了 --auth jwt ,它会在 test job 里增加 pytest --tb=short -xvs tests/ ,并确保 tests/conftest.py 里预置了 get_test_token() fixture,能生成有效的 JWT 用于 API 测试;
  • 它还会在 pyproject.toml [tool.ruff] 部分,自动启用 ASYNC 规则集( RUF006 , RUF007 ),防止你写出 await 在同步函数里的低级错误。

这就是“智能注入”:它不是复制粘贴,而是理解你的技术决策,并生成与之匹配的配套设施。

3.3 LangChain 项目生成:超越 Hello World 的 Agent 工作流

LangChain 模板是 app-generator-cli 的杀手锏,因为它解决了 LangChain 开发中最痛苦的“胶水代码”问题——把 LLM、Tools、Memory、OutputParser 这些模块像乐高一样严丝合缝地拼起来。

核心工作流生成逻辑
当你运行 app-gen langchain --model openai --vectorstore chroma ,它会生成一个 app/agent/ 目录,里面包含:

  • llm.py : 预配置的 ChatOpenAI 实例, temperature=0.3 (适合 Agent), model_name="gpt-4-turbo" ,并启用了 streaming=True
  • tools/ : 一个 search_tool.py 示例(用 TavilySearchResults ),和一个 calculator_tool.py (用 LLMMathChain ),所有 Tool 都实现了 BaseTool 接口,并带有 description args_schema
  • memory/ : ConversationBufferMemory 的封装,支持 redis 后端(如果选了 --memory redis );
  • agent/ : create_react_agent() 函数,它不是简单的 AgentExecutor.from_agent_and_tools() ,而是:
    • 自动注入 StructuredTool (来自 tools/ 目录);
    • 使用 create_openai_functions_agent() (而非过时的 initialize_agent );
    • 配置 max_iterations=15 early_stopping_method="generate" (防止无限循环);
    • 添加 handle_parsing_errors=True ,捕获 OutputParserException 并返回友好提示。

最关键的是 app/api/v1/endpoints/agent.py ,它暴露了一个 /v1/agent/chat endpoint,接收 JSON:

{
  "message": "What's the weather in Tokyo?",
  "session_id": "abc123"
}

后端代码会:

  1. session_id 加载 ConversationBufferMemory
  2. 调用 agent_executor.invoke({"input": message, "chat_history": memory.load_memory_variables({})["history"]})
  3. output 和更新后的 chat_history 存回 memory;
  4. 返回结构化 JSON: {"response": "...", "sources": [...], "cost": 0.0023}

实操心得:我们团队曾用这个模板,在 3 小时内就上线了一个内部知识库问答 Bot。以前,光是调试 AgentExecutor handle_parsing_errors max_iterations 就要半天。现在,这些都成了生成时的默认选项,你只需要专注写自己的 CustomTool

向量数据库集成的无缝体验
--vectorstore chroma 的生成逻辑非常精妙:

  • 它会在 app/vectorstore/ 下生成 chroma.py ,里面是 Chroma 的封装类, __init__ 方法会根据 config.VECTORSTORE_PATH (默认 ./data/chroma )创建持久化目录;
  • app/agent/llm.py 里,它会自动添加 retriever = vectorstore.as_retriever(search_kwargs={"k": 5})
  • app/api/v1/endpoints/agent.py /v1/agent/chat endpoint 里,它会自动将 retriever 注入到 agent_executor tools 列表中;
  • 更绝的是,它还生成了一个 /v1/vectorstore/upload endpoint,接收 multipart/form-data ,支持上传 PDF/DOCX/TXT,并用 UnstructuredPDFLoader + RecursiveCharacterTextSplitter 自动切分、嵌入、存入 Chroma。

这意味着,你生成项目后, uv run dev 启动服务,然后用 curl -F "file=@manual.pdf" http://127.0.0.1:8000/v1/vectorstore/upload ,几秒钟后,你的文档就变成了可被 Agent 检索的知识源。整个过程,你不需要写一行向量数据库相关的代码。

4. 常见问题与排查技巧实录

4.1 “uv run dev 报错:ModuleNotFoundError: No module named 'app'”

这是新手遇到的第一道坎,90% 的原因是 Python 的模块发现机制没被正确触发。 app-generator-cli 生成的项目默认使用 pyproject.toml build-backend = "setuptools.build_meta" ,这意味着它期望 app/ 是一个可安装的包。但 uv run dev 要求这个包必须被“可编辑安装”(editable install)。

排查步骤:

  1. 进入项目根目录,运行 ls -la ,确认 pyproject.toml 存在,且内容里有 [project] [project.optional-dependencies]
  2. 运行 uv pip list | grep my-project-name (项目名是你 -o 指定的目录名,比如 my-fastapi-app ),如果没输出,说明没装成功;
  3. 手动执行 uv pip install -e ".[dev]" ,观察输出。常见失败原因:
    • error: subprocess-exited-with-error :通常是 pyproject.toml requires-python = ">=3.10" 和你当前 Python 版本不匹配。运行 python --version 确认,然后用 uv python install 3.12 安装匹配版本;
    • ERROR: Could not find a version that satisfies the requirement xxx :说明某个依赖在 PyPI 上不存在或名字拼错了。检查 pyproject.toml [project.dependencies] ,比如 sqlmodel = {extras = ["postgresql"], version = "^0.0.19"} ,确保 extras 名字正确( postgresql 不是 postgres );
  4. 如果 uv pip install -e ".[dev]" 成功,但 uv run dev 仍报错,运行 python -c "import sys; print(sys.path)" ,确认输出里包含项目根目录的绝对路径(比如 /Users/you/my-fastapi-app )。如果没有,说明 uv 没有正确识别 -e 安装。此时,强制指定 Python 解释器: uv run --python 3.12 dev

注意:永远不要用 pip install -e . 替代 uv pip install -e ".[dev]" pip 可能忽略 optional-dependencies ,导致 dev 依赖(如 pytest , ruff )没装, uv run dev 就会找不到 pytest 命令。

4.2 “生成的 LangChain Agent 总是返回 'I don't know',即使知识库有答案”

这不是 bug,而是 LangChain 的经典“幻觉抑制”行为。 app-generator-cli 默认启用了 handle_parsing_errors=True max_iterations=15 ,这会让 Agent 在不确定时主动放弃,而不是胡说。

排查与修复:

  1. 检查检索质量 :先绕过 Agent,直接测试向量库。运行 uv run shell 进入 Python REPL,然后:
    from app.vectorstore.chroma import vectorstore
    results = vectorstore.similarity_search("How to reset password?", k=3)
    for r in results:
        print(r.page_content[:100])
    
    如果返回的内容和问题无关,说明嵌入质量差或切分粒度不对。此时,去 app/vectorstore/chroma.py ,调整 RecursiveCharacterTextSplitter chunk_size=500 (默认 1000)和 chunk_overlap=100 (默认 200);
  2. 检查 Prompt 工程 app/agent/prompt.py 里有一个 REACT_AGENT_PROMPT ,它包含了详细的 Thought/Action/Action Input/Observation 格式说明。如果你的问题太开放(比如“谈谈 AI 的未来”),Agent 会因无法匹配 Tool 而放弃。在 app/api/v1/endpoints/agent.py chat 函数里,给 agent_executor.invoke() 加一个 input 的预处理:
    # 在 invoke 前加
    if "weather" in message.lower():
        message = f"Use the search_tool to get current weather in {extract_location(message)}"
    
  3. 降低温度(Temperature) :在 app/agent/llm.py 里,把 ChatOpenAI(temperature=0.3) 改成 temperature=0.1 ,减少随机性,让 Agent 更“循规蹈矩”。

4.3 “GitHub Actions 测试失败:psycopg2-binary not found”

这是 --async-db postgres 模板在 CI 中的经典问题。 psycopg2-binary 是纯 Python 的轮子,但 psycopg (新版)需要编译 C 扩展,在 GitHub 的 ubuntu-latest runner 上,默认没有 libpq-dev

永久解决方案(非临时 hack):
app-generator-cli 生成的 .github/workflows/test-with-postgres.yml 里, jobs.test.steps 的第一部分是:

- name: Setup PostgreSQL
  uses: docker://postgres:15
  with:
    env:
      POSTGRES_PASSWORD: postgres
- name: Install system dependencies
  run: |
    sudo apt-get update
    sudo apt-get install -y libpq-dev gcc

但很多用户会删掉这个 Install system dependencies 步骤,觉得“不就是装个包嘛”。千万别删! libpq-dev psycopg 编译的头文件, gcc 是编译器。没有它们, uv pip install psycopg 就会失败。

快速验证:
在本地模拟 CI 环境:

# 启动一个干净的 Ubuntu 容器
docker run -it --rm ubuntu:22.04

# 在容器里执行 CI 步骤
apt-get update && apt-get install -y curl python3-pip python3-venv
curl -LsSf https://astral.sh/uv/install.sh | sh
source /root/.cargo/env
uv pip install app-generator-cli
app-gen fastapi --async-db postgres -o /tmp/test
cd /tmp/test
uv pip install -e ".[dev]"
# 此时,如果报错 "Failed building wheel for psycopg",就证明缺少 libpq-dev

4.4 “我想加一个自定义中间件,但不知道该放哪?”

app-generator-cli 的设计哲学是“约定优于配置”,所以它不会在 main.py 里留一堆 # TODO: Add your middleware here 的注释。它把中间件的入口点放在了 app/core/lifespan.py on_startup 函数里。

正确做法:

  1. app/middleware/ 下新建 auth_middleware.py (如果目录不存在,就创建它);
  2. 写一个标准的 ASGI 中间件类:
    from starlette.middleware.base import BaseHTTPMiddleware
    from starlette.requests import Request
    from starlette.responses import Response
    
    class AuthMiddleware(BaseHTTPMiddleware):
        async def dispatch(self, request: Request, call_next):
            token = request.headers.get("X-API-Key")
            if not token or token != "my-secret-key":
                return Response("Forbidden", status_code=403)
            return await call_next(request)
    
  3. app/core/lifespan.py on_startup 函数末尾,添加:
    from app.middleware.auth_middleware import AuthMiddleware
    app.add_middleware(AuthMiddleware)
    

这样,你的中间件就和 app 实例的生命周期绑定,既不会漏掉,也不会在错误的时机被注册。 app-generator-cli 的所有“扩展点”都遵循这个模式:它给你一个清晰的、有语义的目录( middleware/ ),一个标准的接口( BaseHTTPMiddleware ),和一个唯一的注册位置( lifespan.py )。你不需要猜,也不需要翻文档。

5. 进阶技巧与团队协作实践

5.1 创建私有模板:把团队规范固化为代码

app-generator-cli 支持 --template-url 参数,可以拉取 Git 仓库里的自定义模板。我们团队就维护了一个私有仓库 https://github.com/our-team/python-templates ,里面包含:

  • fastapi-internal/ : 内部 FastAPI 模板,预装了公司统一的 logging SDK、 metrics exporter(Prometheus)、 tracing (OpenTelemetry)和 feature-flag client(LaunchDarkly);
  • langchain-internal/ : LangChain 模板, llm.py 里预置了公司认证的 AzureOpenAI 实例, tools/ 里有 jira_tool.py (连接 Jira API)和 confluence_tool.py (搜索 Confluence);
  • fullstack-internal/ : 全栈模板,前端 vite.config.ts 里预设了公司 CDN 和 SSO 重定向逻辑。

使用方式极其简单:

app-gen fastapi --template-url https://github.com/our-team/python-templates.git#fastapi-internal -o ./my-internal-service

#fastapi-internal 是 Git 分支或 tag 名。 app-generator-cli 会克隆仓库,切换到该分支,然后用里面的 template/ 目录作为模板源。这意味着,你团队的每一个新项目,从第一天起就符合所有合规要求(日志格式、监控埋点、安全头),再也不用靠 Code Review 去“人肉检查”。

5.2 与 pre-commit 集成:让代码质量在提交前就守住

app-generator-cli 生成的项目默认包含 .pre-commit-config.yaml ,但它只启用了基础的 ruff-pre-commit black 。我们可以把它升级为“团队级质量门禁”。

增强步骤:

  1. 在项目根目录,运行:
    #
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐