Deep Agents Monorepo 开发实战:基于 uv 与 make 的编辑-测试-lint 循环、Pre-commit 钩子与基准测试工程规范
【免费下载链接】deepagentsThe batteries-included agent harness.项目地址: https://gitcode.com/GitHub_Trending/de/deepagents
本指南基于仓库 libs/DEVELOPMENT.md 展开,它既是 Deep Agents monorepo 所有贡献者与开发者的第一入口文档,也是理解libs/下多个独立版本包如何协同开发的路线图。读完你将掌握:用uv+make完成从环境准备、依赖安装到单测/集成测试/lint/类型检查的完整开发闭环,理解 pre-commit 钩子与分支命名校验的工作方式,并能按仓库规范写出符合 Google 风格 docstring、正确抑制 ruff 规则、通过 warnings-as-errors 测试门槛的代码。
这份文档解决什么问题
libs/DEVELOPMENT.md是 Deep Agents monorepo 的开发起点文档,面向两类读者:需要改动某个包的贡献者,以及想搞清楚"每个包如何独立测试、发布、做基准测试"的架构读者。它与 libs/ARCHITECTURE.md 分工明确:后者讲运行时代码如何组织(SDK 的三层结构、create_deep_agent()的组装与执行、中间件栈),前者讲开发时如何动手改代码、跑测试、过 CI 门槛。仓库根目录的 AGENTS.md 则把这份文档作为全局开发准则的权威引用来源,并在其中明确了"Warnings are errors"、per-file-ignores 使用策略、基准测试调用方式等细节的归属。
文档的核心立场可以概括为三句话:
- 工具链只有两样:
uv管解释器、虚拟环境与依赖;make管任务编排,每个包的Makefile是命令的唯一事实来源。 - 你只在正在改的那个包里工作,不在根目录维护统一工程配置——monorepo 下没有根
pyproject.toml。 - 本地能过的检查要尽量覆盖 CI 会跑的检查:format、lint、lockfile 新鲜度、Conventional Commit 消息、warnings-as-errors 测试。
前置条件:uv 与 make
文档明确要求两个工具,且态度很坚决:
uv:负责解释器、虚拟环境和依赖管理。不要使用pip、poetry或conda。uv会自动按各包pyproject.toml的requires-python拉取合适的 Python 解释器,因此"没有需要全局安装或固定的 Python 版本"。make:任务运行器。每个包的Makefile是其命令的事实来源,进入任意包目录运行make help即可列出全部可用目标。
从源码看,仓库在libs/Makefile中确实按包映射了 Python 版本(libs/Makefile):
# Map package dirs to their required Python version # acp requires 3.14, everything else uses 3.12 python_version = $(if $(filter acp,$1),3.14,3.12)也就是说仓库级命令会为libs/acp使用 Python 3.14,其余包统一使用 3.12。而 SDK 包本身的requires-python范围更宽:以 libs/deepagents/pyproject.toml 为例是>=3.11,<4.0。两条规则叠加即可理解"不固定全局 Python 版本、以包的声明为准"的含义。
仓库布局:libs/ 下的独立版本包
这是一个由独立版本管理的包组成的 monorepo,所有包都在libs/下:
libs/ ├── deepagents/ # Core SDK — create_deep_agent, middleware, backends ├── acp/ # Agent Client Protocol integration ├── evals/ # Evaluation suite and Harbor integration ├── code/ # Prebuilt coding agent for interactive and headless use ├── talon/ # Local runtime host for long-running agents └── partners/ # Provider/sandbox integrations ├── daytona/ ├── modal/ ├── vercel/ ├── runloop/ └── quickjs/关键约定:
- 每个包都有自己的
pyproject.toml、Makefile和README.md;仓库根目录没有pyproject.toml。 - 本地包依赖是**可编辑安装(editable)**的,因此改了一个包,依赖它的兄弟包在开发时立即可见新改动。
- 包与包之间的横切约定(Conventional Commits、分支命名、测试要求、公共接口稳定性)统一收口在根目录 AGENTS.md,并在文档中按子系统给出了精确的"搜索路由"(例如 SDK 源码与测试在
libs/deepagents/deepagents与libs/deepagents/tests),避免在 monorepo 里做宽泛搜索。
快速开始与 Setup
文档给出的标准初始化流程(在你要改的包内执行):
uv tool install pre-commit pre-commit install --install-hooks cd libs/deepagents uv sync --all-groups make test make lintuv sync --all-groups会安装包本身加全部依赖组(test、lint 等)。文档强调uv自动创建并管理虚拟环境,不需要手动activate。
monorepo 的四条铁律:
- 显式安装依赖:始终用
uv sync(按需加--group <name>或--all-groups),绝不允许隐式安装。 - 不要在包目录之外创建虚拟环境。
- 不要在同一个会话里混用多个环境。
- 每个包在
pyproject.toml里声明自己的 Python 支持范围,不固定全局 Python 版本,以包的requires-python为准。
作为补充,libs/code包还提供了一个一键引导目标make bootstrap(libs/code/Makefile),内部等价于"uv sync --group test+ 回到仓库根执行uvx pre-commit install --install-hooks",适合把整条开发链一次性配好。
常用命令速查表
在包目录(如libs/deepagents)内运行,核心 SDK 包(deepagents、code)的命令一致,其他包以make help输出为准:
| 命令 | 作用 |
|---|---|
make help | 列出该包可用的全部目标 |
make test | 跑单元测试(断网;启用覆盖率的包会输出 coverage) |
make test TEST_FILE=tests/unit_tests/test_foo.py | 只跑单个测试文件 |
make integration_test | 跑集成测试(允许联网) |
make lint | 跑ruff检查 +ty类型检查 |
make format | 自动格式化并应用安全的ruff修复 |
make type | 只跑ty类型检查器 |
make coverage | 跑该包显式定义的覆盖率目标,通常含 XML 输出 |
也可以绕过 make 直接运行单个测试:
uv run --group test pytest tests/unit_tests/test_specific.py从 libs/deepagents/Makefile 可以看到这些目标的真实实现细节,值得展开几点:
make test实际执行的是uv run --group test pytest -n auto -vvv --disable-socket --allow-unix-socket $(TEST_FILE) --benchmark-disable $(COV_ARGS)。-n auto来自 pytest-xdist 并行,--disable-socket --allow-unix-socket来自 pytest-socket,从机制上保证单测不能碰网络(只允许 Unix socket)。TEST_FILE ?= tests/unit_tests/是默认值,integration_test通过前置变量赋值把TEST_FILE切到tests/integration_tests/,并加上--timeout 30防止联网测试挂死。lint目标 =ruff check+ruff format --diff+make type;type目标跑ty check deepagents。libs/code的lint还会额外校验命令目录(generate_commands_catalog.py --check)和工作目录约束(check_process_cwd.py)。
仓库级命令:从 libs/ 扇出
在libs/目录下运行,会扇出到所有包:
| 命令 | 作用 |
|---|---|
make lint | 逐个包执行 lint |
make format | 逐个包执行格式化 |
make lock | 更新所有 lockfile(追加no-cache可绕过 uv 缓存) |
make lock-check | 校验所有 lockfile 是否最新 |
make lock-bump DEP=<pkg> | 在所有 lockfile 中升级某个依赖 |
以 libs/Makefile 的实现为证,make lock会对所有发现到Makefile/pyproject.toml的包目录逐一执行uv lock --directory <pkg> --python <version>;make lock-bump DEP=requests则对每个包执行uv lock -P 'requests'。这意味着"升级一个跨包依赖"在仓库里是一条命令的事,且始终带上与包对应的 Python 版本参数。
Docstrings:Google 风格 + Args 段
仓库要求每个公共函数都写 Google 风格 docstring,规则定义在根目录 AGENTS.md,libs/deepagents/pyproject.toml 中的[tool.ruff.lint.pydocstyle]配置convention = "google"从工具层面强制了这一约定。文档给出了标准模板:
def send_email(to: str, msg: str, *, priority: str = "normal") -> bool: """Send an email to a recipient with specified priority. Any additional context about the function can go here. Args: to: The email address of the recipient. msg: The message body to send. priority: Email priority level. Returns: `True` if email was sent successfully, `False` otherwise. Raises: InvalidEmailError: If the email address format is invalid. SMTPConnectionError: If unable to connect to email server. """结合 AGENTS.md 的补充约定:类型写在签名里而非 docstring 里;不要重复默认值(除非后处理或条件行为会改变它);公共参数、返回值和异常要精炼描述,重点讲"为什么"而不是复述代码;函数尽量控制在 20 行以内。此外仓库强制ruff开启"ALL"全部规则(见 pyproject.toml),这意味着 pydocstyle 相关规则(D 系列)会在本地 lint 阶段直接拦截缺失或格式错误的 docstring。
抑制 ruff 规则的正确姿势
文档对这一主题的态度非常明确:per-file-ignores是对整个文件生效的"粗粒度开关",只应为覆盖某一整类文件的原则性策略保留;单条例外应该用行内# noqa精确到行、自文档化,并在注释里说明理由——如果理由说不出来,"那大概率是代码本身有问题"。
文档给出的正反示例(与 libs/deepagents/pyproject.toml 的真实配置相互印证):
# GOOD - categorical policy in pyproject.toml [tool.ruff.lint.per-file-ignores] "tests/**" = ["D1", "S101"] # BAD - single-line exception buried in pyproject.toml "deepagents_code/agent.py" = ["PLR2004"]# GOOD - precise, self-documenting inline suppression timeout = 30 # noqa: PLR2004 # default HTTP timeout, not arbitrary仓库实践中的两个典型类别策略:"tests/**"放宽注解、docstring、魔法值、安全断言类规则(D1、S101、PLR2004等,见 pyproject.toml);"scripts/**"允许独立脚本使用盲捕获与print。这与 AGENTS.md 的全局约定完全一致:不要把单条违规藏进文件级忽略里。
Pre-commit 钩子与分支命名校验
仓库使用pre-commit统一管理格式化、lint、lockfile 校验与 Conventional Commit 消息校验。安装方式:
uv tool install pre-commit # or: pipx install pre-commit pre-commit install --install-hooks从 .pre-commit-config.yaml 可以看到钩子的真实组成:
- commit-msg 阶段:
conventional-pre-commit校验提交消息类型(feat/fix/docs/chore等 13 种,见 配置文件)。 - pre-commit 阶段:
no-commit-to-branch禁止直接提交到main;check-yaml、check-toml、end-of-file-fixer、trailing-whitespace等基础检查;以及一组language: system的本地钩子,按变更文件路径扇出到各包的make format lint(如deepagents、deepagents-code、evals、acp,见 .pre-commit-config.yaml)。 - lockfile 与版本一致性钩子:
lock-check(校验pyproject.toml/uv.lock是否同步)、extras-sync(校验 extras 与必需依赖一致)、version-equality(校验pyproject.toml与_version.py版本一致)、branch-scopes-sync(校验分支规则在钩子、CI、pr_lint 三处一致,见 .pre-commit-config.yaml)。 - pre-push 阶段:
branch-name钩子(.pre-commit-config.yaml),执行.githooks/pre-push。
注意minimum_pre_commit_version: '3.2.0'(.pre-commit-config.yaml):3.2.0 是第一个接受 git-hook 命名阶段(pre-commit/pre-push/pre-merge-commit)的版本,更老的 pre-commit 会在 schema 校验阶段直接拒绝整个配置文件。
分支命名 pre-push 钩子细节
文档对分支命名校验的机制讲得很细,核心事实:
- pre-push 阶段拒绝不符合
<github-用户名>/<scope>/<短描述>约定的分支,例如mdrxy/cli/startup-cmd-flag。 - 它通过 pre-commit 运行,所以
pre-commit install --install-hooks就会启用,不需要单独配置core.hooksPath(那会遮蔽其他已安装的钩子)。 - 如果你在钩子加入之前就装过 pre-commit,必须重跑安装命令。pre-commit 在安装时按类型各写一个钩子文件,老 checkout 的
.git/hooks/pre-push不存在,直到重装才会生效。 - 钩子解析 GitHub 登录名的顺序是:
git config github.user→gh api user→user.email的本地部分。如果你的提交邮箱是users.noreply.github.com或first.last@这类与登录名不匹配的地址,显式配置是最可靠的做法:
git config github.user <your-github-login>- 永远放行的分支:受保护分支(
main、master、vX.Y)、自动化分支(release-please--*、dependabot/*、copilot/*)和发布分支(alpha/*、beta/*、rc/*、dev/*)。推这些分支时钩子根本不需要解析登录名。 - 作为本地便利设施,可用
git push --no-verify或SKIP=branch-name git push跳过。 - 两个盲区(经由 pre-commit 而非裸 git 钩子运行的固有后果):一次推送多个 ref 只校验其中一个;推送没有新提交的分支不触发任何钩子。
.github/workflows/branch_name_check.yml以 PR 头分支上的非阻塞警告覆盖这两点,且 CI 故意不校验用户名段与 PR 作者是否一致——这一点上它比本地钩子更宽松。
测试规范
测试文件镜像源码布局:deepagents/middleware/foo.py的测试放在tests/unit_tests/middleware/test_foo.py(libs/deepagents/tests/unit_tests/middleware 目录真实存在,实测还覆盖了 backends、_api 等目录)。测试原则(见 AGENTS.md):优先测真实行为、尽量少用 mock;断网测试放tests/unit_tests/、联网测试放tests/integration_tests/;不写只是复述实现结构的"change-detector"测试;不手动加@pytest.mark.asyncio,因为各包都开了asyncio_mode = "auto"。
警告即失败(Warnings fail the suite)
这是本仓库最值得注意的工程约束之一:每个包都把"error"放在 pytestfilterwarnings的第一位,任何仓库未显式接受的警告都会导致测试运行失败。规则出处是根目录 AGENTS.md,实现见 libs/deepagents/pyproject.toml:
[tool.pytest.ini_options] filterwarnings = [ "error", "ignore:Passing `model=None` to `create_deep_agent`:DeprecationWarning", "ignore:The feature `forked subagents` is in beta:langchain_core._api.LangChainBetaWarning", # ... ]"error"之后是按条目评审过的白名单。一个游离警告会以什么方式暴露,取决于它何时被触发(文档原话):
- 在测试内部触发:该测试失败。
- 在模块导入时触发:该文件的收集(collection)失败。
- 在 pytest 仍在配置阶段触发(通常来自插件):整个运行以
INTERNALERROR中止——这是 CI 输出里最难读的一种。而且 pytest 加载插件期间、ini 过滤器生效之前发出的警告根本不会被捕获,因此"一次干净的运行"并不能证明某个依赖是无警告的。
应对策略:先修可处理的警告,把白名单条目当作最后手段。条目写法规则(来自 AGENTS.md):用@pytest.mark.filterwarnings把预期警告限定到单个测试;对PytestUnhandledThreadExceptionWarning之类倾向default::而非ignore::以保留可见性;ini 里 message 字段是未转义的正则,要转义字面元字符;过滤器可以按版本限定(如只在 Python 3.14 触发)。
bypass-warnings-check标签
维护者可以对 PR 打上bypass-warnings-check标签并重跑失败任务,从而把警告从"错误"降级。文档明确这是在时间压力下合入修复的逃生舱,不是永久修复:merge-queue 的运行会再次强制执行该策略,警告最终仍必须被处理或加入白名单。它的两个边界:
- 只作用于走
_test.yml的任务;ci.yml里的test-quickjs-sdk-smoke任务直接调用 pytest,没有绕过路径。 - 发布运行(
release.yml)始终强制执行,因此只对构建出的 wheel 出现的警告无法靠标签放行。
基准测试:bench 与 bench-memory
三个包承载基准测试:libs/deepagents、libs/code、libs/partners/quickjs。每个定义bench(墙钟时间)与bench-memory(堆内存)两个 Make 目标;其他包没有bench目标,所以make -C libs/evals bench会直接失败。
这些目标被视为基准测试调用的唯一事实来源:本地运行和可复用 CI 工作流(.github/workflows/_benchmark.yml)都调用它们。要改基准测试的跑法,改 Makefile 即可,CI 自动继承。
# 单包(与 CI 调用的目标一致): make -C libs/deepagents bench # deepagents 与 code 一次跑完(BENCH_PACKAGES 定义在 libs/Makefile; # 注意不含 quickjs): make -C libs bench-all # 不带 CodSpeed 插桩的纯 pytest-benchmark,更快,适合本地临时调优: make -C libs/deepagents benchmark以上命令与仓库实现完全对应:BENCH_PACKAGES := deepagents code定义在 libs/Makefile,bench-all目标在 libs/Makefile 扇出到这两个包;libs/deepagents的 Makefile 中:
benchmark: ## Run benchmark tests uv run --group test pytest ./tests/benchmarks -m benchmark bench: ## Run benchmarks under CodSpeed instrumentation uv run --group test pytest ./tests/benchmarks -m benchmark --codspeed bench-memory: ## Run memory benchmarks under CodSpeed instrumentation uv run --group test pytest ./tests/benchmarks -m memory_benchmark --codspeed配套事实:
bench-memory只跑memory_benchmark标记的子集;在 CI 中它由_benchmark.yml的has-memory-benchmarks输入门控(默认false),目前没有调用方开启,所以内存基准实际上只在本地跑;如果要在 sweep 中加一个,就把这个 flag 接上。- 结果会上传到 CodSpeed 仪表盘,每个包一个独立视图(左上角选择器切换)。回归阈值在仪表盘上管理,不在仓库里,文档明确提醒"写在这里的数值会漂移"(写作时为全局 10%),并建议对噪声底远低于该阈值的基准收紧逐基准阈值,因为过宽的阈值会掩盖紧致代码里的真实回归。
.github/workflows/_benchmark_nightly.yml是_benchmark.yml的唯一调用方,没有 per-PR 基准任务。它按每日 cron 跑清单里的每个包,保证未改动包的基线不漂移;覆盖范围只有libs/deepagents和libs/code——libs/partners/quickjs虽然定义了基准目标但不在 sweep 里。在升级pytest-codspeed或CodSpeedHQ/action的 SHA 之前,可用workflow_dispatch对该工作流做一次临时运行。
贡献约定:Conventional Commits 与 CI 门槛
约定收口在根目录 AGENTS.md,核心包括:Conventional Commits 且必须带 scope、分支命名、测试要求与公共接口稳定性。规范的标题示例:
feat(sdk): add new chat completion feature fix(sdk): resolve type hinting issue chore(evals): update infrastructure dependencies test(code): missing unit tests for `_git` feat(code): `--startup-cmd` flag style(code): strip trailing annotations from `ask_user` questions补充约定(来自 AGENTS.md):type(scope):后以小写字母开头(专有名词或命名代码实体除外);类/函数/参数名用反引号包裹;标题里不放 issue 关闭标记(放 PR 正文);版本分支同步用chore(repo): sync main into vX.Y(release是类型不是 scope);每个值得 bump 的 PR 只包含一个可发布组件,跨包依赖或 lockfile 变动单独开chore(deps):PR。
外部 PR 必须关联维护者已批准的 issue 或 discussion,且贡献者需先被指派才能开 PR。CI 在测试之外还跑多项门槛:Conventional Commit lint、lockfile 新鲜度、版本/extras 一致性、SDK-pin 检查等。在改动的包内跑make format lint、从libs/跑make lock-check,即可清掉最常见的几项。
与架构文档的衔接
如果你要改的不是某个命令的配置,而是 SDK 的运行机制,文档建议先读 libs/ARCHITECTURE.md。它把系统切成三层来理解(libs/ARCHITECTURE.md):LangGraph 是运行时(state、checkpoint、streaming、interrupt);LangChain 的create_agent()是模型 + 工具 + 中间件组成 agent 循环的抽象;Deep Agents 则是叠加其上的opinionated harness——通过create_deep_agent()组装默认中间件栈,并配置 backends、subagents、skills、memory 与 profiles。开发流程上,构造(construction,组装图)与执行(execution,LangGraph 驱动循环)是两个阶段,而 harness 行为主要通过中间件注入。
小结
libs/DEVELOPMENT.md是进入 Deep Agents monorepo 的工程总纲,通篇贯彻三条主线:工具收敛(uv+make,拒绝 pip/poetry/conda 与根级工程配置)、质量门槛前置(warnings-as-errors、分支命名 pre-push 钩子、Conventional Commits、per-file-ignores 最小化)、单一事实来源(每个包的 Makefile 同时服务本地开发与 CI/基准工作流)。按文档的顺序走一遍uv sync --all-groups→make test→make lint→pre-commit install --install-hooks,你就能在一个可预测、可复现、与 CI 对齐的本地环境里完成对任意包的改动。
进一步阅读:运行时结构与 SDK 起点见 libs/ARCHITECTURE.md;全局开发准则、测试要求与公共接口稳定性见 AGENTS.md;仓库级扇出命令的实现见 libs/Makefile;SDK 包内 lint/测试/基准的精确实现见 libs/deepagents/Makefile 与 libs/deepagents/pyproject.toml。
【免费下载链接】deepagentsThe batteries-included agent harness.项目地址: https://gitcode.com/GitHub_Trending/de/deepagents
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考