尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Dify 后端代码评审工作流:backend-code-review Skill 的规则路由、严重度分级与四大规则包详解

Dify 后端代码评审工作流:backend-code-review Skill 的规则路由、严重度分级与四大规则包详解 Dify 后端代码评审工作流backend-code-review Skill 的规则路由、严重度分级与四大规则包详解【免费下载链接】difyBuild Agentic workflows, RAG pipelines, with rich AI model and tool support on one collaborative workspace. Deploy on cloud, VPC, or self-hosted, so teams move from prototype to production without rebuilding the stack.项目地址: https://gitcode.com/GitHub_Trending/di/difyDify 在.agents/skills/backend-code-review/目录下内置了一套面向 AI Agent 的后端代码评审技能Skill它定义了评审api/目录下代码的标准工作流先取证、再按 diff 类型路由到四个规则包、最后以 P0P3 严重度输出可复现的缺陷报告。读完本文你将理解这套“证据优先”的评审方法如何落地为分层架构、数据库 Schema、仓储边界与 SQLAlchemy 用法四类具体规则并能结合api/源码看到每条规则对应的真实实现锚点。一、Skill 的定位只在显式评审意图下生效技能入口文件 SKILL.md 的 frontmatter 明确界定了触发条件与排除范围适用用户显式请求对api/下的后端代码做 review 或 audit支持 pending-change待提交变更、file-focused聚焦文件、pasted-diff粘贴 diff三种评审形态。不适用纯实现请求、无评审意图的诊断、前端代码、以及api/之外的后端代码。文件开头还划定了职责边界“The nearestAGENTS.mdowns package facts and commands; this skill owns the review workflow and routes to its bundled rule packs.”最近的AGENTS.md负责包级事实与命令本技能负责评审工作流并将其路由到内置规则包。这与仓库中的分工完全对应api/AGENTS.md 声明了后端命令入口——格式化与 lint 用make lint、类型检查用make type-check、单测用make test、定向测试用make test TARGET_TESTS./api/tests/path且要求通过uv run --project api执行 Python 命令。Makefile 中lint目标实际串联了ruff format、ruff check --fix、响应契约 lintapi/dev/lint_response_contracts.py、lint-imports和dotenv-linter评审时可直接引用这些命令作为“可执行的验证手段”。二、Evidence First四步取证工作流SKILL.md 的第一章要求评审“只报告与可观察失败绑定的问题”具体流程为四步确定评审范围检查相关 diff 或文件精读变更行同时阅读其行为所有者behavior owner、邻近测试以及定义契约的本地 docstring/注释仅在影响正确性时才追踪调用方、持久化边界、鉴权、生成式 schema 或外部 I/O只报告五类发现可观察失败、被违反的契约、安全边界问题、数据完整性风险、或已证实的维护性问题。这里的“本地 docstring 即契约”不是空话。api/AGENTS.md 明确要求“Read surrounding module, class, and function docstrings plus non-obvious comments before changing backend behavior. They are local contracts.”改动后端行为前先读模块/类/函数 docstring 与非显然注释它们是本地契约。也就是说评审的证据链是diff → 行为所有者 → 契约注释 → 调用方/持久化/鉴权边界只有当追踪结果“decide correctness”决定正确性时才扩展调查范围避免把评审发散成全仓库审计。三、Rule Routing按 diff 命中规则包技能核心设计之一是按需加载规则包——“Read only the packs matched by the diff”只读取与 diff 匹配的规则包四个包全部位于 references/ 目录下触发条件规则包涉及模型models或迁移migrationsdb-schema-rule.mdcontroller、service、core/domain、libs 或模型间依赖方向变化architecture-rule.md在既有仓储边界之外访问表repositories-rule.mdSQLAlchemy session、查询、事务、CRUD、并发或原生 SQLsqlalchemy-rule.md当没有任何规则包命中时技能要求直接评审正确性、安全性、行为变化与测试证据并且“Check current official documentation only when local code and contracts do not settle framework or library behavior”只有本地代码与契约无法判定框架/库行为时才查阅当前官方文档——即仓库证据优先于外部文档。四、规则包一Architecture分层与依赖方向architecture-rule.md 覆盖 controller/service/core-domain/libs/model 的分层、依赖方向、职责放置包含三条规则4.1 业务逻辑不得留在 controllerController 只应解析输入、调用 service、返回序列化响应。规则文档给出的反例是在路由处理函数内直接做权限判断、查库、改状态、提交事务bp.post(/apps/app_id/publish) def publish_app(app_id: str): payload request.get_json() or {} if payload.get(force) and current_user.role ! admin: raise ValueError(only admin can force publish) app App.query.get(app_id) app.status published db.session.commit() return {result: ok}修正方向是引入 Pydantic 请求模型并把决策下沉到 service 层bp.post(/apps/app_id/publish) def publish_app(app_id: str): payload PublishRequest.model_validate(request.get_json() or {}) app_service.publish_app(app_idapp_id, forcepayload.force, actor_idcurrent_user.id) return {result: ok}这与 api/AGENTS.md 的边界声明一致“Keep transport parsing and serialization in controllers, orchestration in services, and domain policy incore/or its domain owner.”并规定“Use Pydantic v2 for request and response models. Reuse domain-specific exceptions and translate them at the controller boundary.”用 Pydantic v2 建模请求/响应在 controller 边界翻译领域异常。4.2 保持分层依赖方向依赖方向必须是上层依赖下层controller → service → core/domain 抽象。反例是 core 层反向 import web 上下文# core/policy/publish_policy.py —— 反例 from controllers.console.app import request_context def can_publish() - bool: return request_context.current_user.is_admin正例是让 core 只接受纯领域输入由 service 层把 web/user 上下文适配为领域参数# core/policy/publish_policy.py —— 正例 def can_publish(role: str) - bool: return role admin # service 层适配上下文 allowed can_publish(rolecurrent_user.role)4.3libs/必须保持业务无关规则要求api/libs/下模块保持“可复用、业务无关”不得编码产品/领域规则也不得 import service/controller/领域模块。反例是api/libs/conversation_filter.py中直接实例化ConversationService()判断租户付费计划正例是把通用判断抽成业务无关 helper、把策略留在 service# api/libs/datetime_utils.py业务无关 def older_than_days(idle_days: int, threshold_days: int) - bool: return idle_days threshold_days # services 层保留业务决策 from libs.datetime_utils import older_than_days def should_archive_conversation(conversation, tenant_id: str) - bool: threshold_days 90 if has_paid_plan(tenant_id) else 30 return older_than_days(conversation.idle_days, threshold_days)仓库中 api/libs/datetime_utils.py、api/libs/uuid_utils.py、api/libs/pagination.py 等确实都是这类横切工具符合规则所描述的形态。五、规则包二DB Schema Design模型与迁移db-schema-rule.md 的 Scope 明确覆盖模型基类继承、属性中的 schema 边界、租户感知设计、索引冗余、模型中的方言可移植性、迁移的跨库兼容不覆盖session 生命周期与查询形态那是 sqlalchemy 规则包的事。共五条规则5.1property内禁止查其他表模型属性不得打开 session 或跨表查询否则会把数据访问耦合进 schema 对象并在集合遍历时引发 N1。反例class Conversation(TypeBase): __tablename__ conversations property def app_name(self) - str: with Session(db.engine, expire_on_commitFalse) as session: app session.execute(select(App).where(App.id self.app_id)).scalar_one() return app.name正例是属性只基于已加载字段派生如self.name or Untitled跨表数据由 service/repository 显式批量拉取join/preload/bulk query。5.2 模型定义优先包含tenant_id多租户领域中只要实体属于租户所有数据就应在 schema 中包含tenant_id且相关唯一/索引约束应带租户维度例外是“明确设计为全局元数据的非租户表”且需书面记录该设计决策。仓库现实印证了这一约定api/models/account.py 中workspace.tenant_id: Mapped[str] mapped_column(StringUUID, nullableFalse)api/models/agent.py 同样如此——租户维度以StringUUID落列并参与约束正是规则所要求的形式。5.3 检测并避免前缀冗余索引按最左前缀原则审查索引(a, b, c)已能覆盖大部分(a, b)查找同时保留两者会增加写开销并可能误导优化器。规则要求在模型__table_args__与迁移 DDL 中执行同样标准。5.4 模型中避免 PostgreSQL 专属方言统一封装到models/types业务模型不应直接使用JSONB等单方言类型而应消费 api/models/types.py 中的双方言封装。对照源码可以看到这些封装的真实实现StringUUIDPostgreSQL 用UUID、其余方言用CHAR(36)绑参时统一转为字符串LongTextPostgreSQL 用TEXT、MySQL 用LONGTEXTBinaryDataPostgreSQL 用BYTEA、MySQL 用LONGBLOBAdjustedJSONPostgreSQL 用JSONB支持astext_type、MySQL 用sa.JSON()adjusted_json_index按dify_config.DB_TYPE是否为postgresql决定是否创建 GIN 索引。规则的 Bad/Good 示例即演示“mapped_column(JSONB, ...)→mapped_column(AdjustedJSON(), ...)”的替换路径与上述实现一一对应。5.5 迁移脚本必须做方言分支api/migrations/versions/ 下的迁移必须显式处理 PostgreSQL/MySQL 不兼容对方言敏感的 DDL 或默认值按conn.dialect.name postgresql分支并优先复用models.types的兼容封装。规则给出的反例是硬编码 PG 风格的server_defaultsa.text(database::character varying)正例是def _is_pg(conn) - bool: return conn.dialect.name postgresql conn op.get_bind() default_expr sa.text(database::character varying) if _is_pg(conn) else sa.text(database) with op.batch_alter_table(dataset_keyword_tables) as batch_op: batch_op.add_column( sa.Column(data_source_type, sa.String(255), server_defaultdefault_expr, nullableFalse) )规则强调除非有文档化的刻意兼容例外禁止只针对单一方言的迁移逻辑——这与 Dify 同时支持 PostgreSQL 与 MySQL 部署的现实见 api/models/types.py 中遍布的双方言分支直接相关。六、规则包三Repositories Abstraction仓储边界repositories-rule.md 回答三个问题何时复用既有仓储、何时新建仓储、如何保持 service/core 对基础设施实现的依赖方向。核心规则一条若表/模型已有仓储抽象该表的所有读写查询都必须走它若没有仅当复杂度值得时大/高流量表、重复的复杂查询、可能的存储策略变化才新建并保证 service/core 依赖抽象、infra 提供实现。规则指定的检索路径是api/repositories、api/core/repositories与api/extensions/*/repositories/。仓库中这两处确实存在成体系实现api/repositories/ 下有factory.py与sqlalchemy_api_workflow_run_repository.py、workflow_run_archive_repository.py等按实体拆分的仓储api/core/repositories/ 下有celery_workflow_execution_repository.py、human_input_repository.py等体现“抽象与实现分离”。文档的 Bad 示例是“既有仓储存在却被绕过、service 里写 ad-hoc 查询”class AppService: def archive_app(self, app_id: str, tenant_id: str) - None: app self.session.execute( select(App).where(App.id app_id, App.tenant_id tenant_id) ).scalar_one() app.archived True self.session.commit()正例改为self.app_repo.get_by_id(...) / self.app_repo.save(app)且“缺少的方法应扩展既有抽象而非绕过它”self.app_repo.list_active_for_tenant(tenant_idtenant_id)。对于尚无仓储的大领域Case B正例是定义Protocol抽象 SQLAlchemy 实现 构造函数注入class ConversationRepository(Protocol): def list_recent_for_app(self, app_id: str, tenant_id: str, limit: int) - list[Conversation]: ... class SqlAlchemyConversationRepository: def list_recent_for_app(self, app_id: str, tenant_id: str, limit: int) - list[Conversation]: ... class ConversationService: def __init__(self, conversation_repo: ConversationRepository): self.conversation_repo conversation_repo值得注意的是仓库脚本 scripts/check_no_new_controller_sqlalchemy.py 与 scripts/lint_controller_sqlalchemy.py 的存在说明“禁止 controller 直接进行 SQLAlchemy 访问”这一边界在 CI 层面也有自动检查支撑评审时可将其作为“既有契约”的证据来源。七、规则包四SQLAlchemy Patterns会话、事务与并发sqlalchemy-rule.md 覆盖 session/事务生命周期、查询构造、租户作用域、原生 SQL 边界与写路径并发防护共四条规则。7.1 Session 上下文管理器 显式事务控制写路径上事务必须显式且有界漏commit会静默丢失更新临时性或长事务则放大锁竞争与死锁风险。两种正例# 方式一显式提交 with Session(db.engine, expire_on_commitFalse) as session: run session.get(WorkflowRun, run_id) run.status cancelled session.commit() # 方式二作用域内自动 commit/rollback with Session(db.engine, expire_on_commitFalse) as session, session.begin(): run session.get(WorkflowRun, run_id) run.status cancelled # 非 DB 工作放到事务作用域之外 call_external_api()反例有两类缺 commit 的写操作以及把call_external_api()放进session.begin()事务内部的长事务。后者与 api/AGENTS.md 的硬约束一致“Keep write transactions explicit and bounded. Do not perform external I/O inside an open transaction unless a documented consistency contract requires it.”7.2 共享资源查询强制tenant_id作用域安全类对共享表的读写必须带tenant_id谓词防止跨租户泄漏或污染# 反例无租户作用域 stmt select(Workflow).where(Workflow.id workflow_id) # 正例完整属主链 stmt select(Workflow).where( Workflow.id workflow_id, Workflow.tenant_id tenant_id, )api/AGENTS.md 的对应表述是“Scope tenant-owned reads and writes by the complete owner chain, and propagatetenant_idacross every affected layer. Reconstruct trusted internal references from validated database state after payload or async boundaries.”按完整属主链限定租户读写跨层传播tenant_id跨 payload/异步边界后从已验证的数据库状态重建可信内部引用。7.3 默认优先 SQLAlchemy 表达式而非原生 SQL原生 SQL 只保留给有明确技术约束的场景简单查询应改写为select/update/delete表达式。示例中text(SELECT * FROM workflows WHERE id :id AND tenant_id :tenant_id)对应改写为带租户谓词的select(Workflow)。7.4 写路径并发防护按竞争程度选策略规则给出三种防护手段与适用条件且统一要求“以tenant_id限定作用域、并校验条件写的影响行数”乐观锁竞争通常较低且可重试时。在WHERE中加 version或updated_at守卫rowcount 0即冲突result session.execute( update(WorkflowRun) .where( WorkflowRun.id run_id, WorkflowRun.tenant_id tenant_id, WorkflowRun.version expected_version, ) .values(statuscancelled, versionWorkflowRun.version 1) ) if result.rowcount 0: raise WorkflowStateConflictError(stale version, retry)Redis 分布式锁临界区跨多步/多进程或含非 DB 副作用且需跨 worker 互斥时如redis_client.lock(fworkflow_run_lock:{tenant_id}:{run_id}, timeout20)SELECT ... FOR UPDATE悲观锁同行高竞争且需事务内严格串行化时配.with_for_update()并保持事务短小。反例是“无租户作用域、无冲突检测、无锁”的无条件update(...).values(statuscancelled)commit()会静默覆盖并发更新。八、Severity And Output严重度分级与报告纪律SKILL.md 的最后一章定义了四级严重度与输出纪律这是该技能区别于“泛泛代码风格建议”的关键级别定义P0安全或隐私暴露、数据丢失、或全生产环境级故障P1用户可见回归、鉴权或租户隔离被破坏、公共契约失效、或主流程失败P2具体的正确性、性能、可维护性或测试缺陷很可能导致错误行为P3次要可执行清理项除非用户要求详尽审计否则省略输出要求同样收紧发现项按严重度排序置顶每条必须包含紧凑的文件与行号引用、失败的契约或复现路径、影响、具体修复方向无发现时输出No issues found.并声明重要的验证缺口material verification gap同时禁止三类噪音——夸赞段落、臆测性风险、未经请求的“我可以帮你修”的主动提议。九、小结这套 Skill 在 Dify 仓库中的证据闭环从源码结构看backend-code-review技能的每条规则都能在仓库中找到对应锚点分层声明与命令入口在 api/AGENTS.md双方言类型封装在 api/models/types.py仓储抽象分布在 api/repositories/ 与 api/core/repositories/make lint/make type-check/make test等验证命令在 Makefile 与api/AGENTS.md中可执行。评审时的实际工作流因此是闭环的按 diff 命中规则包 → 以仓库证据契约注释、模型定义、仓储实现、方言封装判定违规 → 用 make 命令验证 → 按 P0P3 输出带文件行号与复现路径的报告。对于在该仓库上进行后端变更的工程师或 Agent这套技能既是评审清单也是一份关于“Dify 后端为什么这样分层、为什么双方言、为什么强制租户作用域”的活文档。【免费下载链接】difyBuild Agentic workflows, RAG pipelines, with rich AI model and tool support on one collaborative workspace. Deploy on cloud, VPC, or self-hosted, so teams move from prototype to production without rebuilding the stack.项目地址: https://gitcode.com/GitHub_Trending/di/dify创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表