多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

从Harness到AGE:AI辅助编程新范式与Spec-Driven开发实践

从Harness到AGE:AI辅助编程新范式与Spec-Driven开发实践 1. 从Harness Engineering到AGE为什么我们需要新的开发范式如果你最近在关注AI辅助编程或者大型项目的工程化实践大概率会听到两个词Harness Engineering 和 AGE。前者即“Harness Engineering”我们可以理解为“约束工程”或“框架工程”在过去一两年里被不少团队奉为圭臬尤其是在处理复杂、多步骤的AI Agent工作流时。它的核心思想很直接为AI编码任务设计一套严格的、可预测的“约束框架”Harness通过明确的指令、模板和上下文管理来引导和规范AI比如GPT-4、Claude等大模型的输出确保代码生成的质量、一致性和可集成性。听起来很美好对吧我最初接触Harness Engineering时也这么觉得。它确实解决了早期“把需求扔给AI然后祈祷能跑通”的混乱局面。通过精心设计的Prompt模板、严格的输出格式要求比如必须返回JSON Schema、以及分步骤的上下文管理我们确实能产出更结构化的代码。但问题也随之而来。在我和团队将这套方法论应用到几个中大型微服务项目和前端组件库的实践中我们逐渐感受到了它的“重量”和“僵化”。最大的痛点在于Harness本身成了需要大量维护的“元工程”。为了确保AI能理解复杂业务逻辑你需要为每一个细分的任务类型比如“创建RESTful API控制器”、“编写数据库迁移脚本”、“生成React表单组件”编写和维护一套极其详细的Harness。这包括冗长的系统提示词System Prompt动辄上千字定义了角色、规则、输出格式、禁忌。复杂的上下文组装逻辑需要动态拼接项目结构、接口定义、依赖版本等信息这部分逻辑本身就需要代码来实现和维护。脆弱的输出解析依赖AI严格遵循指定的JSON或YAML格式一旦格式稍有偏差整个流程就会中断需要加入复杂的后处理和错误恢复机制。我们团队曾为一个相对标准的CRUD微服务模块维护了超过15个不同的Harness模板。每次技术栈微调比如Spring Boot版本升级、ORM框架更换或者业务规则变化我们不仅要改业务代码还要同步更新对应的Harness模板其维护成本甚至开始抵消AI带来的效率提升。更不用说当面对一些模糊的、探索性的需求时预先定义好的“约束框架”反而成了创新的枷锁。正是在这种背景下我注意到了AGEAttractor-Guided Engineering吸引子引导工程以及与之相关的“Spec-Driven”规范驱动理念。它不像Harness Engineering那样试图用“硬约束”去框定AI的一举一动而是转向一种更灵活、更“引导式”的范式。你可以把它想象成GPS导航和严格按铁轨行驶的火车之间的区别。Harness是铁轨你必须完全按照预设的路径走而AGE更像是GPS它有一个明确的目的地即最终的、可执行的“规范”或“代码”并动态地计算和推荐最佳路径允许你在过程中根据实时路况AI的中间输出、项目上下文进行合理的调整。AGE应用开发模板就是这种新范式的具体落地工具。它不是一个庞大的、中心化的约束框架而是一套轻量的、可组合的“引导器”集合。这些引导器Attractors的核心工作是将高层次的、可能模糊的意图比如“创建一个用户登录服务”通过一系列可预测的、可验证的转换逐步“吸引”到一个具体的、可执行的代码规范Spec上最终再由AI或开发者基于这个清晰的规范去生成代码。这个过程减少了维护庞大静态模板的负担增加了灵活性和对探索性任务的支持。接下来我将结合一个具体的实例拆解AGE模板的核心构成和工作原理。2. AGE应用开发模板的核心组件与运作机制理解AGE模板关键在于理解它的几个核心组件是如何协同工作的。这套模板通常不是一个单一的脚本或配置文件而是一个结构化的目录包含了一系列定义“引导”逻辑的规则和工具。下面我以一个典型的、用于生成后端API服务的AGE模板为例来拆解它的核心部分。2.1 规范定义层从意图到结构化Spec这是AGE的起点和终点。与Harness Engineering中直接要求AI输出代码不同AGE强调先产出一份机器可读、无歧义的“规范”Spec。这个Spec是后续所有步骤的“唯一真相源”。1. 意图解析与上下文增强模板首先会提供一个“意图解析器”。它的输入是用户的自然语言描述例如“需要一个用户管理模块包含注册、登录、查看和更新个人资料的功能使用JWT鉴权数据存到PostgreSQL。” 这个解析器通常由一段精心设计的Prompt比Harness的System Prompt更简洁、更专注于“提问”和“结构化”驱动AI来完成。它的输出不是代码而是一个初步的结构化对象可能包括实体Entities识别出的核心业务对象如User。操作Operations针对每个实体的操作如createUser,loginUser,getUserProfile,updateUserProfile。约束Constraints非功能性要求如“使用JWT”、“PostgreSQL”、“RESTful API”。2. 规范迭代与精炼初步的Spec往往不够完善。AGE模板会引入“规范精炼器”。这是一个迭代过程模板会引导AI或开发者基于现有项目上下文比如已有的package.json、数据库Schema、API网关配置去补充细节。例如检查是否已存在User实体定义如果有则复用其字段。根据项目约定的目录结构确定Controller、Service、Repository应该放在哪个路径下。确认JWT的库和配置方式是否与项目现有的鉴权模块一致。这个过程可能通过多轮简单的QA式交互完成最终产出一个详细的、项目上下文相关的Spec文件例如一个user_management.spec.yaml。这个文件会明确到service: UserManagement entities: - name: User fields: - name: id type: uuid primary: true - name: email type: string unique: true validation: email - name: password_hash type: string hidden: true operations: - name: register endpoint: POST /api/v1/auth/register request: { email: string, password: string } response: { user: User, token: string } dependencies: [PasswordHashing, JWTGenerator] - name: login endpoint: POST /api/v1/auth/login request: { email: string, password: string } response: { user: User, token: string } techStack: framework: NestJS orm: TypeORM database: PostgreSQL auth: JWT directoryStructure: controller: src/modules/user/user.controller.ts service: src/modules/user/user.service.ts entity: src/entities/user.entity.ts这个YAML文件就是被“吸引”到的稳定状态——一份清晰的蓝图。2.2 引导器动态的路径规划与决策引导器是AGE的“大脑”。它们是一系列小的、单一职责的函数或规则负责在生成Spec和代码的每一步做出决策。与Harness的静态模板不同引导器是动态的、可条件触发的。常见的引导器类型包括技术栈匹配器分析项目现有的package.json、pom.xml或go.mod自动将Spec中的techStack部分与现有配置对齐。如果项目用TypeORM它就不会生成Prisma的实体代码。模式检测器识别重复模式。例如当发现Spec中定义了多个具有created_at和updated_at字段的实体时引导器可以建议并自动应用一个“时间戳审计”基类或装饰器。依赖关系解析器分析operations中的dependencies确保在生成代码时相关的服务或工具类已被创建或引入。例如看到JWTGenerator依赖它会检查src/common/auth/目录下是否存在该工具若不存在则触发生成该工具的Sub-Spec。冲突解决器当生成的代码与现有代码冲突时例如要创建的user.service.ts文件已存在引导器会提供选项覆盖、合并交互式选择差异、或重命名新文件。这些引导器不像Harness那样写死在庞大的Prompt里而是作为独立的、可配置的规则存在。在一个Python AGE模板中你可能看到类似下面的配置片段概念性代码# attractors.py def attractor_tech_stack_match(spec, project_context): 引导器技术栈匹配 if spec.tech_stack.framework is None: # 从项目上下文推断框架 if project_context.has_file(nest-cli.json): spec.tech_stack.framework NestJS elif project_context.has_file(src/main/java): spec.tech_stack.framework SpringBoot return spec def attractor_crud_pattern(spec): 引导器CRUD模式检测与增强 for entity in spec.entities: ops [create, read, update, delete] # 如果实体操作包含了这些模式自动补全标准的CRUD端点 if all(op in [op.name for op in spec.operations if op.entity entity.name] for op in ops): # 自动为实体添加标准的CRUD服务方法模板标记 entity.metadata[hasStandardCrud] True return spec这种设计使得引导逻辑模块化、可测试、且易于扩展。你可以为你的团队定制特殊的引导器比如“自动添加公司标准的API响应包装器”或“强制遵循内部代码风格规范”。2.3 代码生成器从确定性的Spec到可运行代码一旦一份稳定、详细的Spec被生成和确认代码生成就变成了一个相对确定性的任务。AGE模板中的代码生成器部分反而可能比Harness更“简单”因为它不需要处理模糊的需求只需要将结构化的Spec翻译成特定技术栈的代码。生成器的工作流通常是模板渲染使用如Jinja2、Handlebars或EJS这样的模板引擎将Spec数据填充到预定义的代码模板中。每个技术栈NestJS, Spring Boot, Express等都有一套对应的模板。代码格式化生成原始代码后立即调用项目配置的格式化工具如Prettier、Black、gofmt进行标准化。静态分析对生成的代码运行简单的lint检查如ESLint、pylint确保没有明显的语法或风格问题。集成验证尝试将生成的文件放入项目结构中运行依赖安装命令如npm install或pip install -r requirements.txt中新增的依赖并可能执行一个快速的构建测试如npm run build或mvn compile来验证集成是否成功。这个阶段的关键在于由于Spec是精确的所以生成代码的差异化和试错成本极低。如果对生成的代码不满意开发者通常不需要去修改复杂的Prompt而是回头调整那份更易读、更易修改的YAML Spec文件然后重新运行生成过程。这实现了关注点的分离AI和开发者专注于“要什么”Spec而模板负责“怎么实现”Code。3. 实战使用AGE模板快速搭建一个用户管理微服务理论说得再多不如亲手操作一遍。假设我们有一个基于NestJS和TypeORM的Node.js后端项目现在需要快速增加一个完整的用户管理模块。我们将使用一个假设的AGE CLI工具和对应的NestJS模板来完成。注意以下示例基于AGE的核心概念和常见工具链设计并非某个特定开源工具的文档。实际工具的命令和文件结构可能有所不同但工作流是相通的。3.1 环境初始化与项目扫描首先确保你位于项目的根目录下。AGE工具需要扫描你的项目以建立上下文。# 假设AGE CLI工具名为 age-cli age-cli context scan这个命令会分析你的项目识别出框架和版本如NestJS 10.x数据库ORM如TypeORM现有目录结构已存在的实体、模块、服务依赖管理文件package.json扫描结果会被保存在一个本地缓存中供后续的引导器使用。这一步替代了Harness Engineering中需要手动在Prompt里描述项目上下文的繁琐工作。3.2 启动AGE交互式会话定义Spec接下来我们启动一个交互式会话让AGE引导我们定义需求。age-cli generate service工具会进入一个交互模式并开始提问。这些问题是由模板中的“意图解析”引导器动态生成的? 请输入新服务的名称或描述 用户管理模块包含注册、登录、个人资料CRUDJWT鉴权。 [AGE] 正在分析您的需求... [AGE] 识别到核心实体: User。 [AGE] 识别到操作: 注册, 登录, 获取资料, 更新资料。 [AGE] 检测到项目使用 NestJS TypeORM PostgreSQL。是否沿用此技术栈 (Y/n) Y [AGE] 检测到项目已存在 src/entities 目录。是否将 User 实体放置于此 (Y/n) Y [AGE] 检测到项目已有一个 AuthModule其中包含 JwtService。是否将登录/注册的鉴权逻辑关联至此模块 (Y/n) Y [AGE] 请确认 User 实体字段 初始建议字段id(uuid), email(string), password_hash(string), name(string?), created_at(timestamp) 是否需要添加其他字段 (y/N) y ? 请输入字段名完成后直接回车 avatar_url ? 字段类型 string ? 是否可为空 (Y/n) Y 字段 avatar_url 已添加。继续添加 (y/N) N [AGE] 正在为您生成规范预览...此时工具会在内存中构建一个初步的Spec。它会展示一个基于YAML的预览并允许你直接编辑。# 预览内容大致如下 service: UserService module: UserModule entity: name: User path: src/entities/user.entity.ts fields: [...] operations: - name: register method: POST path: /auth/register ... - name: login ... - name: getProfile ... - name: updateProfile ... dependencies: - nestjs/jwt - bcrypt你可以在这个预览界面直接修改YAML。确认无误后输入:wq或点击确认。3.3 引导器介入精炼与冲突解决当你确认预览后AGE并不会立即生成代码。一系列引导器会开始工作依赖检查引导器发现Spec中需要bcrypt但package.json中未声明。它会提示“检测到需要bcrypt包用于密码哈希。是否自动添加到package.json的dependencies中(Y/n)”目录结构引导器根据NestJS最佳实践和项目现有模式建议将Controller、Service、Module文件分别放在src/modules/user/目录下。冲突检测引导器如果src/entities/user.entity.ts已经存在它会提示“目标文件已存在。请选择[1] 查看差异并合并 [2] 覆盖 [3] 另存为新文件 [4] 中止”。在这个过程中你作为开发者始终拥有控制权。引导器只是提出建议和发现潜在问题最终的决策由你做出。这比Harness遇到错误直接失败要友好和强大得多。3.4 代码生成与后处理所有引导步骤确认完成后AGE工具开始执行生成[AGE] 开始生成代码... Created: src/entities/user.entity.ts Created: src/modules/user/dto/create-user.dto.ts Created: src/modules/user/dto/update-user.dto.ts Created: src/modules/user/user.controller.ts Created: src/modules/user/user.service.ts Created: src/modules/user/user.module.ts [AGE] 正在运行代码格式化 (prettier)... [AGE] 正在更新 package.json... [AGE] 正在运行 ESLint 检查... [AGE] 生成成功 [AGE] 建议下一步运行 npm install 安装新增依赖然后将 UserModule 导入到您的 AppModule 中。打开生成的user.service.ts你会发现它已经是一个结构完整、包含了方法骨架、JWT依赖注入和密码哈希逻辑的模板代码几乎可以直接运行。更重要的是因为Spec是精确的生成的代码风格与项目现有代码高度一致。4. AGE vs. Harness Engineering核心理念与适用场景对比通过上面的实战你应该能感受到AGE与Harness Engineering在哲学和实践上的显著差异。下面我用一个表格来系统性地对比两者这有助于你决定在什么情况下选择哪种范式。对比维度Harness Engineering (约束工程)AGE (吸引子引导工程)核心隐喻火车与铁轨AI必须严格沿着预设的、详细的轨道Harness行驶。GPS导航设定清晰目的地Spec系统动态规划路径引导允许合理绕行和调整。控制粒度细粒度、过程控制严格控制AI思考、输出的每一步格式和内容。粗粒度、目标控制关注最终产出Spec的质量对中间过程干预较少更灵活。维护重心维护“轨道”需要持续维护大量针对具体任务类型的、复杂的Prompt模板和上下文管理逻辑。维护“目的地地图”和“交通规则”维护核心的Spec定义标准、可复用的引导器Attractors和代码模板。灵活性较低对模糊、探索性需求或偏离预设轨道的任务处理能力弱。较高通过迭代精炼Spec来处理模糊需求引导器可适应不同上下文。上手复杂度初期较低后期高对于简单、重复任务写一个Harness见效快。但复杂系统Harness会变得臃肿难维护。初期较高后期平滑需要先理解Spec和引导器概念但一旦搭建好模板扩展和维护成本低。产出物直接是代码或接近代码的片段。首先是结构化规范Spec然后是基于Spec的代码。调试与迭代困难如果输出不符合预期需要反复调整冗长的Prompt过程像“黑盒调参”。相对容易问题通常出现在Spec层面。调试一份YAML比调试一段复杂的Prompt要直观得多。迭代只需修改Spec并重新生成。团队协作容易产生“Prompt孤岛”不同成员编写的Harness风格不一难以复用和统一管理。便于标准化和复用Spec可以作为团队共识的“蓝图”引导器和模板可以共享确保输出一致性。最佳适用场景1. 高度标准化、重复性极强的代码生成如根据Swagger文档生成API Client根据数据库表生成CRUD界面。2. 输出格式严格固定的任务如生成特定格式的配置文件、报告。1. 中大型、需要长期维护的项目开发。2. 需求有一定模糊性或探索性的任务。3. 需要与现有复杂项目深度集成的场景。4. 追求团队协作和工程化规范的项目。我的个人体会是Harness Engineering像是一门精准的“手工艺”适合解决点状问题。而AGE更像是在引入一套“工业化”的生产线它可能前期投入更大但对于追求可持续性、可维护性和团队规模化的工程团队来说AGE带来的长期收益是Harness难以比拟的。它尤其适合作为团队内部的一个“平台工程”基建将AI能力以一种更可控、更可管理的方式赋能给所有开发者。5. 构建你自己的AGE模板关键步骤与避坑指南如果你被AGE的理念吸引想为自己的团队或常用技术栈构建一个定制化的AGE模板可以参考以下步骤。我将以“为FastAPI项目构建一个生成数据库模型和Pydantic Schema的AGE模板”为例说明关键环节。5.1 第一步定义你的规范Spec标准这是最重要的一步。Spec是你的“合同”必须清晰无歧义。确定范围你的模板主要生成什么是完整的CRUD端点还是仅仅数据库模型本例中我们聚焦于“根据简单的描述生成SQLAlchemy模型类和对应的Pydantic Schema类”。设计Spec格式创建一个JSON Schema或TypeScript Interface来定义你的Spec结构。例如// spec.d.ts (概念) interface EntitySpec { name: string; tableName?: string; fields: Array{ name: string; type: string | integer | boolean | datetime | float; primary_key?: boolean; nullable?: boolean; default?: any; foreign_key?: { entity: string; field: string }; }; indexes?: Array{ fields: string[]; unique?: boolean }; } interface SchemaSpec { // 可能区分CreateSchema, UpdateSchema, ResponseSchema name: string; base: Create | Update | Response; fields: Array{...}; // 可能只包含EntitySpec字段的子集 }保持简洁一开始不要追求大而全。从最核心的字段名称、类型开始后续可以通过引导器逐步补充。5.2 第二步开发核心引导器引导器是模板的智能所在。从最简单的开始名称格式化引导器确保用户输入的user profile这样的实体名被规范化为UserProfilePascalCase用于类名和user_profilesnake_case用于表名。类型推断引导器当用户描述字段为“邮箱”时自动将类型设置为string并添加email验证标记。描述为“创建时间”时自动设置为datetime并可能建议添加defaultdatetime.utcnow。关系检测引导器当发现字段名类似author_id、user_id时自动提示用户“检测到字段author_id是否希望将其设置为指向User实体的外键”如果用户确认则在Spec中自动补全foreign_key信息。避坑指南引导器的边界引导器不应过于“自作聪明”。一个常见的坑是引导器基于不完整的上下文做出了错误的、且难以撤销的假设。例如看到一个category_id就默认关联到Category表但实际项目中这个表可能叫ProductCategory。好的做法是引导器发现模式后总是以确认或选择的方式与用户交互而不是静默修改Spec。即“建议并询问”而非“假定并执行”。5.3 第三步创建代码模板使用模板引擎如Jinja2为每个输出文件创建模板。模板应清晰、可读并留有合理的“缺口”供引导器或用户后续填充。模型模板 (model_template.j2)# {{ spec.entity.name }}.py from sqlalchemy import Column, Integer, String, DateTime, ForeignKey from sqlalchemy.orm import relationship from .database import Base class {{ spec.entity.name }}(Base): __tablename__ {{ spec.entity.table_name or spec.entity.name|snakecase }} {% for field in spec.entity.fields %} {{ field.name }} Column({{ field.type|map_sa_type }}{% if field.primary_key %}, primary_keyTrue{% endif %}{% if not field.nullable %}, nullableFalse{% endif %}) {% endfor %} {% if spec.entity.relationships %} # Relationships {% for rel in spec.entity.relationships %} {{ rel.name }} relationship({{ rel.target_entity }}, back_populates{{ rel.back_populates }}) {% endfor %} {% endif %}Schema模板 (schema_template.j2)类似地为Pydantic模型创建模板。避坑指南模板的抽象层次不要试图在一个模板里处理所有情况。否则模板会变得极其复杂。正确的做法是利用引导器在生成Spec阶段就将差异处理好。例如通过引导器判断是否需要created_at字段并在Spec中明确标记。这样模板里只需要简单的条件判断{% if field.name created_at %}逻辑清晰得多。5.4 第四步集成与测试将解析器、引导器、模板和生成器串联起来形成一个命令行工具或IDE插件。测试驱动为你的模板创建测试用例。输入一段自然语言描述验证最终生成的代码是否符合预期。这能有效防止“魔法”失效。处理边缘情况用户输入了无效的字段类型怎么办实体名和Python关键字冲突怎么办在引导器中加入足够的验证和纠错逻辑。提供良好的错误信息当生成失败时错误信息应明确指出是哪个引导器、哪个模板、哪行Spec出了问题而不是抛出一堆堆栈跟踪。构建一个成熟的AGE模板确实需要前期投入但一旦建成它就会成为团队生产力的倍增器。你可以从一个小而美的模板开始比如专门生成“数据模型”的模板然后逐步扩展其能力。6. 未来展望Spec-Driven 开发与AI的深度融合AGE模板所代表的“Spec-Driven”思想其意义可能远超出一个代码生成工具。它指向了一种新的软件构建范式将“定义规范”作为开发流程的核心首要环节并且这个规范是机器可读、可执行、可验证的。想象一下这个工作流产品/架构师使用自然语言或图形化工具描述一个微服务的高层需求。AGE系统通过与AI交互将其转化为一份详细的API SpecOpenAPI、数据模型Spec如Prisma Schema和组件依赖Spec。开发者审查并精炼这份Spec添加具体的业务逻辑约束和性能要求。AGE模板根据这份最终的、达成共识的Spec一键生成符合项目规范的、可运行的后端代码、前端API Client、甚至基础部署配置Dockerfile, k8s YAML。开发者专注于在生成的代码骨架上填充核心业务逻辑这些逻辑可能也由AI辅助完成。任何对需求的修改都首先反映在Spec的变更上然后通过AGE工具链自动同步到代码、测试和文档中。在这个范式中Spec成为了连接产品、架构、开发、测试的“活文档”和“单一真相源”。AI的作用在初期是帮助人类从模糊意图生成精确Spec即AGE中的“吸引”过程在后期则是基于精确的Spec生成高质量的、可维护的代码。当然我们离这个理想状态还有距离。当前AGE模板和Spec-Driven实践面临的挑战包括复杂业务逻辑的Spec化如何将非结构化的业务规则清晰地定义到Spec中Spec的版本管理与协作当多人同时修改一份Spec时如何管理合并冲突现有系统的反向工程如何将庞大的遗留代码库反向生成可用的Spec但这些挑战也正是机遇。随着AI理解能力和代码分析能力的持续进步以及像AGE这样的工程化实践不断成熟我们有理由相信“Spec-Driven, AI-Assisted”的开发模式将成为构建未来复杂软件系统的主流方式。它不会取代开发者而是将开发者从重复的、机械的编码劳动中解放出来更专注于架构设计、复杂问题解决和创造性的工作。而今天开始探索和应用AGE模板正是为迎接那个未来所做的必要准备。
返回列表