首页
/ Dify Agent(dify-agent)包开发规范:标准命令、Docstring 本地契约与测试边界实践

Dify Agent(dify-agent)包开发规范:标准命令、Docstring 本地契约与测试边界实践

2026-09-06 11:40:37作者:董灵辛Dennis

Dify 仓库中的 dify-agent 包承载了 Dify Agent 运行时——在 FastAPI 服务背后托管由 Agenton 组合的 Pydantic AI 运行实例。本文基于该包的开发者指南 dify-agent/AGENTS.md,完整解读其文档字符串(docstring)本地契约规则、四个标准包命令的底层实现、uv 环境与 Pydantic v2 的依赖约束,以及"测试稳定行为、守护真实依赖边界"的测试组织原则,并结合 Makefilepyproject.toml 与导入边界测试源码给出可验证的实现证据,帮助你在修改 dify-agent 包时严格遵循其既定边界而不会破坏客户端安全导入等核心不变量。

dify-agent 包与其开发者指南的定位

dify-agent 包同时面向两类使用者:作为库被 dify_agent.Client 客户端直接导入的调用方,以及以 dify_agent.server.app:app 形式运行的 Agent 后端服务。按 dify-agent/README.md 的说明,框架无关的 Agenton 代码位于 src/agentonsrc/agenton_collections,而 Dify 特化的运行时源码位于 src/dify_agent,两者文档分别见 docs/agenton/guide/index.mddocs/dify-agent/index.md

dify-agent/AGENTS.md 是这个包面向 AI 编码助手与人类开发者的行为守则,全文虽短,但规定了四层约束:docstring 契约、标准命令、环境要求、测试与边界原则。以下逐一展开。

Docstring 与非显式注释是"本地契约"

AGENTS.md 开篇给出的规则是:在修改行为之前,先阅读周边的 docstring 和非显而易见的注释;它们是本地契约,只应在其所拥有的行为(owned behavior)真正变化时才更新,并且必须与当前代码保持对齐;当改动触及公共运行时契约时,还应阅读 docs/dify-agent/index.md

这条规则在源码中有直接体现。以 src/dify_agent/init.py 为例,其模块 docstring 声明了"客户端安全顶层导出"契约:

"""Client-safe top-level exports for the Dify Agent package.

Default installs must be able to import ``dify_agent`` without pulling in server
runtime adapters or their optional dependencies. Server-only adapter entry points
remain under ``dify_agent.adapters.llm``.
"""

该契约的含义是:默认安装下导入 dify_agent 不得拖入服务端运行时适配器及其可选依赖(如 FastAPI、Redis、各 LLM 供应商 SDK)。docstring 不只是说明文字,而是与导入边界测试共同构成可执行的约定——一旦行为变更,docstring 必须同步更新,否则契约与实际代码脱节。

四个标准包命令及其底层实现

AGENTS.md 要求所有包命令在 dify-agent/ 目录下执行,并给出四个核心命令。对照 dify-agent/Makefile 可以看到每个目标的确切动作:

命令 用途 Makefile 中的实际执行
make check Lint 检查 uv run --project . python -m ruff check .
make fix 格式化并自动修复 lint ruff format .,再 ruff check --fix .
make typecheck 类型检查 basedpyright --level error src examples tests
make test 运行本地测试与文档/示例测试 uv run --project . --extra server python -m pytest tests

几个值得注意的实现细节:

  • 所有命令都通过 uv --directory "$(PROJECT_DIR)" run --project . 执行,即强制使用包自带的 uv 虚拟环境,避免系统解释器混入;
  • typecheck 使用 basedpyright 且级别为 --level error,检查范围精确限定为 srcexamplestests 三个目录,与 pyproject.toml[tool.pyright]include = ["src", "examples", "tests"] 一致;
  • make test 显式带 --extra server 安装服务端可选依赖,因为 tests 目录中既有 tests/local/ 本地测试,也有 tests/docs/ 下校验文档示例输出的测试(test_examples.pytest_snippets.py);
  • Makefile 还提供 serve/dev(uvicorn 启动 dify_agent.server.app:app,8000 端口)、docs/docs-serve(MkDocs 构建,见 mkdocs.yml)等运维与文档目标,但 AGENTS.md 列出的四个命令才是日常开发闭环的最小集合。

另外,pyproject.toml[tool.pytest.ini_options] 配置了 addopts = ["--import-mode=importlib"],注释解释了原因:多个测试模块在不同目录共享同名文件名(如 test_layer.pytest_client.py),默认 prepend 导入模式按 basename 推导模块名会导致导入文件不匹配。因此运行 pytest 时若手动执行,也应带上 --import-mode=importlib(Makefile 的 make test 已通过 ini 选项覆盖)。

uv 环境与 Pydantic v2 的硬性约束

AGENTS.md 明确要求:使用包自己的 uv 环境,并使用 Pydantic v2 API。这一约束在 dify-agent/pyproject.toml 中有对应的硬边界:

  • requires-python = ">=3.12,<4.0",且 ruff 配置 target-version = "py312",即代码只允许使用 Python 3.12 语法特性;
  • 核心依赖锁定为 pydantic>=2.12.5,<2.13(v2 系)、pydantic-ai-slim>=2.30.0,<3.0.0httpx==0.28.1 等;
  • 服务端可选依赖([server] extra)包含 fastapi==0.136.0redisuvicorn[standard]e2blogfire 等——这正是导入边界测试要隔离的对象;
  • 开发依赖组(dev)含 ruffbasedpyrightpytestpytest-mockpytest-examples,文档组(docs)含 mkdocs-material 等。

AGENTS.md 还有一条容易被忽视的要求:在集成、实现或 mock 某个运行时契约尚未在本仓建立的 API 之前,先检查该依赖的当前源码或官方文档。这意味着不允许凭记忆假设第三方库行为,尤其在 mock 边界上——mock 的字段、方法签名必须对照依赖的真实契约核实。

测试组织:镜像 src 结构,只测稳定行为

AGENTS.md 的 "Tests And Boundaries" 一节给出四条原则,每条都能在仓库中找到对应的落地机制:

1. 本地测试放在 tests/local/ 下,并镜像 src/ 包结构。 实际目录 dify-agent/tests/local/ 严格对应源码分层:tests/local/agenton/tests/local/agenton_collections/tests/local/dify_agent/(下含 client/layers/runtime/server/storage/agent_stub/ 等子包)、tests/local/shellctl/,与 src/ 下的顶层包一一对应。

2. 测试稳定行为和真实依赖边界,不得用本地 mock 声称覆盖了真实网络、框架接线、序列化或第三方运行时。 这一原则通过 pytest marker 在机制上得到区分:pyproject.toml 定义了 markers = ["integration: requires a real external service or exercises multiple concrete adapters"],需要真实服务的测试统一放在 tests/integration/ 目录(如 run_local_integration.shtest_working_environment.pytest_e2b_transport_soak.py),与 make test 默认运行的 tests 路径中的本地测试隔离。

3. 测试、公共文档与本地契约随行为变更保持一致。 文档侧的示例输出由 tests/docs/test_examples.py 校验,make update-examples 目标(pytest --update-examples tests/docs/test_examples.py)用于重写文档中的示例输出,保证 docs/dify-agent/ 下文档与行为不脱节。

4. 保留现有运行时与层(layer)的所有者,不要为绕过它们而添加通用工具或兼容边界。 这条"架构防腐"原则由 tests/local/dify_agent/test_import_boundaries.py 以自动化方式强制。该文件实现了 _run_import_check 助手:在子进程中用 guarded_import 包装 builtins.__import__,对一组"被封禁"的模块(命中即抛 ModuleNotFoundError)设置拦截,然后在干净命名空间里导入待测模块并执行断言。以 test_dify_agent_root_import_is_client_safe 为例,封禁列表包括 fastapiredisopenaianthropicgooglepydantic_settingsdify_agent.serverdify_agent.runtime 等服务端模块,随后断言 from dify_agent import Client 成立且 'Client' in dify_agent.__all__——这正是前文 docstring 契约的可执行验证。类似地,test_protocol_and_dify_plugin_exports_do_not_import_server_only_modules 验证各 layer 包(dify_agent.protocoldify_agent.layers.*)的导出配置类在封禁服务端模块的前提下仍可导入;test_agenton_collection_roots_do_not_eagerly_import_pydantic_ai_implementations 则验证 agenton_collections 根不会急加载 Pydantic AI 实现。这些边界测试就是"不得添加通用工具或兼容边界绕过既有 owner"的守门员:任何试图在 dify_agent 顶层或 protocol/layer 包中悄悄引入服务端依赖的重构,都会在 make test 中直接失败。

小结

dify-agent/AGENTS.md 虽然篇幅精炼,但它定义了 dify-agent 包完整的质量闭环:docstring 作为本地契约约束"改代码先读契约";make check / make fix / make typecheck / make test 四个命令(dify-agent/Makefile)覆盖 lint、格式化、basedpyright 类型检查与测试;uv 环境与 Pydantic v2、Python 3.12 的锁定约束(dify-agent/pyproject.toml)划清依赖边界;tests/local/ 镜像 src/ 的结构、真实边界测试外置 tests/integration/、以及 test_import_boundaries.py 的导入封禁机制,共同保证客户端安全导出、运行时与 layer 的所有权不被侵蚀。遵循这些约定,对 dify-agent 的修改才能既通过全部本地检查,又不破坏其作为库与作为服务双重身份之间的依赖隔离。

登录后查看全文
热门项目推荐
相关项目推荐