基于规则引擎的AI代码生成:构建可靠后端服务的实践
1. 项目概述:一个不“胡说八道”的AI后端生成器
最近在做一个新项目,需要快速搭建一个用户管理模块的后端。和往常一样,我打开了某个流行的AI代码生成工具,输入了需求:“生成一个基于Node.js和Express的用户注册、登录和JWT验证的REST API。” 几秒钟后,代码生成了,乍一看挺像那么回事。但当我仔细审查时,问题来了:它生成的JWT签名密钥是一个硬编码的简单字符串“secret”,用户密码居然以明文形式存储在数据库的返回结果里,路由中间件的顺序也有问题,认证逻辑放在了一些公开路由之后。这让我不得不停下来,花大量时间去调试、修正这些AI“臆想”出来的错误和安全隐患。
我相信很多开发者都有过类似的经历。AI代码生成工具在带来效率革命的同时,其固有的“幻觉”问题也成了我们信任和采纳它的最大障碍。它可能会生成语法正确但逻辑荒谬的代码,引用不存在的库,或者像我的例子一样,忽视最基本的安全最佳实践。这种不确定性,使得我们无法放心地将生成的代码直接用于生产环境,所谓的“效率提升”最后往往变成了“漏洞排查”的额外负担。
正是被这个问题反复折磨,我决定自己动手,构建一个“不胡说八道”的AI后端生成器,我把它命名为Archon Specs。Archon意为“执政官”,寓意着规则与秩序;Specs即“规格说明书”。这个项目的核心目标不是生成最“聪明”或最“创新”的代码,而是生成最“正确”、最“可靠”、最符合行业既定规范和项目特定约束的代码。它更像一个严格遵循蓝图和建筑规范的工程师,而非一个天马行空的艺术家。接下来,我就详细拆解一下我是如何实现这个目标的,以及在这个过程中积累的一些关键思考。
2. 核心设计思路:用“规则引擎”约束“生成模型”
要让AI不“幻觉”,最直接的想法就是给它戴上“紧箍咒”。传统的代码生成模型(如基于大型语言模型的工具)主要依赖其在海量代码数据上学到的概率分布来生成下一个token。这种方式创造力强,但可控性差,容易偏离具体项目的上下文和技术栈要求。Archon Specs的设计哲学截然不同,它的核心是一个“规则优先,生成为辅”的双层架构。
2.1 架构总览:规范与生成的协同
整个系统可以看作是一个精密的流水线,如下图所示(概念示意):
用户需求输入 ↓ [规范解析与增强层] ↓ [结构化约束生成器] → [规则与知识库] ↓ [代码生成模型 (LLM)] ↓ [静态分析与验证层] ↓ 可部署的、规范的代码输出第一层:规范解析与增强层。这是入口。用户输入的不再是简单的自然语言描述,而是一份结构化的“规格说明书”(Spec)。这份说明书可以通过YAML、JSON或一个引导式UI来生成。它必须明确包含:
- 技术栈:Node.js + Express + PostgreSQL, 还是 Python + FastAPI + MongoDB?版本号是多少?
- 数据模型:每个实体(如User, Product)的字段名、类型、是否必填、默认值、唯一性约束、关联关系。
- API端点:每个端点的路径、HTTP方法、需要的路径/查询/请求体参数、预期的响应格式、需要的身份验证与授权级别。
- 业务规则:密码强度要求、邮件发送逻辑、状态流转条件等。
- 项目规范:代码风格(ESLint/Prettier配置)、目录结构、命名约定(是
camelCase还是snake_case)。
这个层级的目的是将模糊的需求转化为机器和AI都能无歧义理解的精确指令。如果用户输入不够详细,系统会通过预设的问题进行交互式补全,确保没有模糊地带。
第二层:结构化约束生成器。这是系统的“大脑”。它接收解析后的规范,并结合内置的“规则与知识库”,生成一份极度详细的、带有强制约束的“生成任务描述”。这个知识库是我投入大量精力构建的,包括:
- 安全规则:密码必须加盐哈希存储(例如使用bcrypt);JWT密钥必须足够长且建议从环境变量读取;SQL查询必须使用参数化查询或ORM以防止注入;CORS、Helmet等安全中间件必须配置。
- 性能规则:数据库连接需要池化;分页查询必须实现;N+1查询问题必须避免。
- 最佳实践:错误处理中间件必须放在所有路由之后;环境配置必须与代码分离;日志记录需要结构化。
- 框架特定约定:例如,在Express中,路由定义的最佳顺序是什么;在Prisma中,如何正确定义模型关系。
这个生成器输出的不是代码,而是一份给下层AI模型的“不容置疑的指令清单”。例如:“在/api/auth/register的POST处理器中,你必须:1. 从请求体中验证email和password字段。2. 使用bcrypt.hash对密码进行哈希,成本因子为10。3. 使用Prisma Client将用户数据存入User表,确保email字段有唯一约束。4. 绝对不能在响应中返回密码哈希值。5. 返回201状态码和创建的用户ID及邮箱。”
第三层:代码生成模型。这里才会调用大语言模型(如GPT-4、Claude 3或专门微调的代码模型)。但关键区别在于,我们提供给模型的提示词(Prompt)是经过前两层精心构造的。这个Prompt包含了:
- 严格的角色定义:你是一个资深后端工程师,严格遵守所有给定的规范和约束。
- 清晰的上下文:项目技术栈、目录结构、已存在的相关文件(用于生成导入语句和避免冲突)。
- 具体的任务:即第二层生成的“指令清单”。
- 输出格式要求:必须只输出要求的代码块,不能有任何解释性文字。
通过这种方式,我们将AI的创造力引导到一个高度受限的解空间内,极大地降低了它“自由发挥”并产生幻觉的可能性。
第四层:静态分析与验证层。这是安全网。即使在前三层严格控制下,生成的代码仍会经过自动化检查:
- 语法检查:使用语言本身的编译器或解释器(如
node -c)检查语法错误。 - 规则匹配检查:使用简单的AST(抽象语法树)分析器或正则表达式,扫描生成的代码,确保没有出现“硬编码密码”、“
console.log敏感信息”、“未处理的Promise”等明确禁止的模式。 - 依赖检查:确保
package.json中声明的依赖版本与规范一致,并且没有引入不必要或存在已知安全漏洞的包。
只有通过所有检查的代码才会被最终输出。任何一步失败,都会将错误信息和上下文反馈给第二层,尝试调整指令重新生成,或直接向用户报告无法满足的约束。
2.2 为什么选择“约束生成”而非“微调模型”?
一个常见的思路是:为什么不直接微调一个代码大模型,让它学习“正确”的代码模式?我确实考虑过,但最终放弃了,原因如下:
- 成本与敏捷性:收集和清洗一个覆盖所有技术栈、所有最佳实践的高质量代码数据集成本极高。而业务规则、项目规范是千变万化的,每次更新都需要重新微调,不现实。
- 规则的可解释性:基于规则的系统,每一条约束都是清晰、可管理、可追溯的。我可以很容易地添加一条新规则:“所有REST API响应必须包裹在
{ data: ..., message: ... }的结构中”。而在微调模型中,这条规则需要多少训练样本才能被稳定学习?无从得知。 - 混合策略的优越性:当前架构结合了规则引擎的确定性与LLM的灵活性。规则负责“必须怎么做”,LLM负责“如何优雅地实现”。当遇到规则库未覆盖的复杂业务逻辑时,LLM依然能在给定的框架内发挥其理解能力,而基础的安全性、结构性规则已经被牢牢锁死。
注意:这个架构的核心是“规则库”的质量和完备性。它不是一个一蹴而就的东西,而需要随着项目生成、社区反馈和最佳实践的发展而持续迭代。我的做法是,每次手动修复AI生成代码的bug时,都会思考:“这个错误能否被总结成一条规则,加入到Archon Specs的知识库中?” 这使它具备了自我进化的能力。
3. 核心组件深度解析:规则库与提示词工程
要让Archon Specs真正可靠,两个核心组件的设计至关重要:一是静态的规则与知识库,二是动态的提示词构造策略。它们共同决定了生成代码的“底线”和“上限”。
3.1 构建可扩展的规则与知识库
我的规则库不是一堆散乱的if-else语句,而是一个结构化的、可插拔的系统。它主要分为几个维度:
3.1.1 安全规则集这是最高优先级的规则。每条规则都包含“模式”、“严重级别”、“修复建议”和“适用技术栈”。
- rule_id: SEC-001 name: 硬编码密钥检测 pattern: (['\"])(?:[A-Za-z0-9+/=]{20,}|secret|password|key)(['\"]) description: 检测代码中是否存在看似硬编码的密钥、密码或令牌。 severity: CRITICAL action: REJECT # 直接拒绝生成,或强制替换为环境变量读取语句 replacement_template: `process.env.{{ENV_VAR_NAME}}` tech_stack: ["nodejs", "python", "go"] - rule_id: SEC-002 name: SQL注入风险检测 pattern: (?:query|execute|run)\s*\(\s*[`'\"].*?\$\{.*?\}.*?[`'\"] # 简化示例,实际更复杂 description: 检测字符串拼接形式的SQL查询。 severity: HIGH action: SUGGEST_REWRITE suggestion: "使用参数化查询或ORM的安全方法。例如,在Prisma中使用 `prisma.user.findUnique({ where: { email: inputEmail } })`"3.1.2 框架约定规则集这部分确保生成的代码符合特定框架的“味道”。例如,对于Express.js:
- 规则:路由定义应集中在一个
/routes目录下,并通过app.use挂载。 - 规则:错误处理中间件必须定义在所有路由之后,签名应为
(err, req, res, next)。 - 规则:控制器逻辑应尽量精简,复杂业务应抽取到
/services层。
对于数据库ORM(如Prisma):
- 规则:模型定义中,关系字段需使用
@relation装饰器明确标注。 - 规则:查询时应默认排除敏感字段(如
passwordHash),除非显式指定。
3.1.3 项目模板与脚手架规则这是可定制的部分。Archon Specs允许用户定义或选择“项目模板”。一个“企业级REST API模板”可能强制要求:
- 目录结构必须包含
src/controllers,src/services,src/middlewares,src/utils。 - 必须使用Winston或Pino进行结构化日志记录。
- 必须包含统一的响应封装器
ApiResponse和错误类AppError。 - 必须配置Dockerfile和
docker-compose.yml用于本地开发。
当用户选择这个模板后,所有生成的代码都会自动遵循这些结构性和风格性约定。
3.2 动态提示词构造:从规范到精确指令
这是将死板的规则“注入”AI思维的关键环节。我的提示词是一个多部分的模板:
你是一个经验丰富的{{TECH_STACK}}后端开发专家,正在严格遵循“{{PROJECT_NAME}}”项目的开发规范。 **项目上下文:** - 项目根目录:`{{PROJECT_ROOT}}` - 现有主要文件结构:[列出相关文件,如现有的模型定义、工具类等] - 代码风格:{{CODE_STYLE_CONFIG}} **你的任务:** 根据以下精确要求,生成{{FILE_PATH}}文件的代码。你必须严格遵守所有约束。 **技术要求与约束(必须遵守):** 1. **安全要求:** - 所有密码必须使用bcrypt库进行哈希处理,成本因子为10。 - 任何密钥、数据库连接字符串必须从环境变量`process.env`读取,严禁硬编码。 - 所有用户输入在用于数据库操作前必须经过验证。 2. **框架要求:** - 使用Express.js。路由定义在`/routes`目录,控制器逻辑在`/controllers`目录。 - 使用Prisma作为ORM。数据库模型定义见`schema.prisma`文件。 - 使用JWT进行身份验证,令牌在HTTP Only Cookie中发送。 3. **具体实现要求:** - 在`src/routes/auth.js`中,创建一个新的路由`/register` (POST)。 - 该路由的控制器函数应放在`src/controllers/authController.js`中,命名为`register`。 - 控制器逻辑必须: a. 从请求体接收`email`和`password`。 b. 验证邮箱格式和密码长度(>=8位)。 c. 检查邮箱是否已存在。 d. 使用`bcrypt.hash`哈希密码。 e. 使用Prisma Client将用户记录创建到数据库。 f. 生成一个JWT令牌,包含用户ID。 g. 将令牌设置在HTTP Only Cookie中。 h. 返回201状态码和用户基本信息(不含密码)。 - 必须包含完整的错误处理,使用项目约定的`AppError`类和错误处理中间件。 **请只输出要求的代码,不要有任何额外的解释、注释或Markdown格式。从文件的第一行开始输出。**这个提示词的特点在于:
- 角色清晰:将AI定位为“遵循规范的专家”,而非“自由创作者”。
- 上下文具体:提供了文件路径、现有结构,避免了生成孤立、无法融入现有项目的代码。
- 约束显式且优先:将“必须遵守”的条款放在最前面,并用数字列表强调,强化其重要性。
- 任务极度具体:不是“生成注册功能”,而是“在A文件创建B路由,调用C控制器,控制器必须完成步骤a-h”。
- 输出格式严格:要求“只输出代码”,避免了模型附加不必要的自然语言描述,便于后续的自动化处理。
通过这种精细化的提示词工程,我能够将规则的遵守率从传统方式的“可能”提升到“几乎必然”。
4. 实战演练:从Spec到可运行后端的全流程
让我们通过一个具体的例子,看看Archon Specs是如何工作的。假设我们要为一个简单的博客系统生成后端。
4.1 第一步:编写Archon Spec(规格说明书)
用户可以通过YAML文件或Web界面定义Spec。以下是一个简化的YAML示例:
project: name: "simple-blog-api" tech_stack: runtime: "nodejs@18" framework: "express" orm: "prisma" database: "postgresql" auth: "jwt" structure_template: "standard-express-api" models: - name: "User" fields: - name: "id" type: "Int" attributes: ["@id", "@default(autoincrement())"] - name: "email" type: "String" attributes: ["@unique"] - name: "name" type: "String" - name: "passwordHash" type: "String" excludeFromDefaultOutput: true # 关键规则:默认查询不返回此字段 - name: "Post" fields: - name: "id" type: "Int" attributes: ["@id", "@default(autoincrement())"] - name: "title" type: "String" - name: "content" type: "String?" - name: "published" type: "Boolean" default: false - name: "authorId" type: "Int" - name: "author" type: "User" relation: "authorId -> User.id" apis: - endpoint: "/api/auth/register" method: "POST" auth_required: false request_body: - field: "email" type: "string" required: true validation: "email" - field: "password" type: "string" required: true validation: "minLength:8" response: status: 201 body: success: true data: id: "number" email: "string" controller: "authController.register" - endpoint: "/api/posts" method: "GET" auth_required: false query_params: - field: "page" type: "number" default: 1 - field: "limit" type: "number" default: 10 response: status: 200 body: success: true data: "Post[]" pagination: page: "number" limit: "number" total: "number" controller: "postController.getPublishedPosts" rules: security: - "password-hashing:bcrypt" - "jwt-in-http-only-cookie" - "sql-parameterization:prisma" # 使用Prisma本身已免疫注入 validation: - "request-validation:joi" # 指定使用Joi进行请求验证4.2 第二步:系统处理与代码生成
- 解析与增强:系统读取YAML,检查完整性。发现
Post模型关联了User,但/api/posts的响应里可能需要作者信息。系统会提示用户:“在getPublishedPosts的响应中,是否需要包含作者的基本信息(如name)?” 用户确认后,这条信息被补充到规范中。 - 约束生成:规则引擎启动。
- 根据
tech_stack,选择nodejs-express-prisma规则集。 - 根据
security规则,生成指令:“所有涉及User创建的密码字段,必须使用bcrypt.hash处理,成本为10。JWT令牌必须通过res.cookie(‘token’, token, { httpOnly: true, secure: process.env.NODE_ENV === ‘production’ })设置。” - 根据
structure_template,确定目录结构为src/controllers,src/routes,src/middlewares,src/utils,prisma/。 - 根据
validation规则,决定在控制器前使用Joi中间件进行验证。
- 根据
- 调用LLM生成代码:针对每个API端点,构造类似上一节的详细提示词,调用模型生成
authController.js,postController.js,authRoutes.js,postRoutes.js等文件。同时,还会生成核心的prisma/schema.prisma模型文件、src/middlewares/authMiddleware.js、src/utils/AppError.js和src/utils/catchAsync.js(用于包装异步控制器)等基础构件。 - 静态分析与输出:生成的所有代码文件会经过ESLint(根据项目规范配置)、一个简单的安全规则扫描器(检查是否有
console.log(password)之类的明显问题)和Prisma格式检查。全部通过后,系统会输出完整的项目文件夹,并附带一个README.md,说明如何安装依赖、设置环境变量和运行项目。
4.3 第三步:生成代码示例
以下是系统可能生成的src/controllers/authController.js的部分代码。请注意其如何体现规则约束:
const bcrypt = require('bcrypt'); const jwt = require('jsonwebtoken'); const { PrismaClient } = require('@prisma/client'); const { AppError } = require('../utils/AppError'); const catchAsync = require('../utils/catchAsync'); const prisma = new PrismaClient(); const register = catchAsync(async (req, res, next) => { // 1. 数据验证(由Joi中间件已完成,此处直接使用) const { email, password } = req.body; // 2. 检查邮箱是否唯一(规则:业务逻辑检查) const existingUser = await prisma.user.findUnique({ where: { email }, select: { id: true } // 规则:只查询必要字段 }); if (existingUser) { return next(new AppError('Email already in use', 409)); // 规则:使用统一错误类 } // 3. 哈希密码(规则:SEC-001, 使用bcrypt) const passwordHash = await bcrypt.hash(password, 10); // 4. 创建用户(规则:使用Prisma,自动参数化) const newUser = await prisma.user.create({ data: { email, name: req.body.name || null, // 处理可选字段 passwordHash, // 规则:存储哈希值,而非明文 }, select: { // 规则:明确指定返回字段,排除passwordHash id: true, email: true, name: true, createdAt: true, }, }); // 5. 生成JWT令牌(规则:密钥从环境变量读取) const token = jwt.sign( { userId: newUser.id }, process.env.JWT_SECRET, { expiresIn: process.env.JWT_EXPIRES_IN } ); // 6. 设置Cookie(规则:JWT in HTTP Only Cookie) res.cookie('token', token, { httpOnly: true, secure: process.env.NODE_ENV === 'production', maxAge: 24 * 60 * 60 * 1000, // 1 day }); // 7. 发送响应(规则:统一响应格式) res.status(201).json({ success: true, data: newUser, }); }); module.exports = { register };可以看到,生成的代码严格遵循了Spec和规则库中的所有要求:密码哈希、环境变量、Prisma安全查询、字段排除、统一错误处理、JWT Cookie设置和响应格式。几乎没有给“幻觉”留下任何空间。
5. 优势、局限与未来思考
经过一段时间的开发和内部试用,Archon Specs展现出了明显的优势,但也暴露出一些局限性。
5.1 核心优势
- 极高的可靠性:这是最大的卖点。生成的代码可以直接运行,并且符合安全与最佳实践,大大减少了审查和调试时间。对于构建标准化的CRUD API、管理后台接口等场景,效率提升是数量级的。
- 强一致性与可维护性:无论项目由谁发起,或在不同时间生成不同模块,只要使用相同的Spec模板和规则集,代码风格、目录结构、错误处理方式都完全一致。这极大降低了团队协作成本和后续维护成本。
- 知识沉淀与传承:规则库成为了团队或社区最佳实践的载体。一位架构师可以将安全规范、性能要点固化到规则中,确保所有生成的项目都自动继承这些知识,避免了新手重复踩坑。
- 专注业务逻辑:开发者可以将精力从重复的、易错的样板代码(如用户认证、数据验证、错误处理)中解放出来,专注于真正创造价值的复杂业务逻辑。
5.2 当前局限性与挑战
- 复杂业务逻辑的乏力:对于高度复杂、非标准的业务流(例如一个涉及多状态机、复杂事件驱动的工作流引擎),当前的规则+提示词方法可能不够用。模型可能无法深刻理解业务内涵,需要人工编写大量定制化规则,或者最终还是需要开发者手动实现核心部分。
- 规则库的维护负担:构建和维护一个全面、准确、与时俱进的规则库是一项持续的工作。新的框架、新的安全漏洞、新的最佳实践都需要及时更新到规则库中。
- “过度约束”可能抑制创新:在极端追求“不幻觉”的过程中,可能会把代码风格限制得过于死板,有时会排除掉一些虽然不符合旧有规则但更优雅的新解决方案。需要在“规范”和“灵活”之间找到平衡。
- 初始Spec编写成本:要求用户编写结构化的Spec,本身就有一定的学习成本。虽然比直接写代码快,但对于一个非常简单的想法,用户可能会觉得“杀鸡用牛刀”。提供更智能的、交互式的自然语言到Spec的转换工具是未来的方向。
5.3 实践心得与避坑指南
在开发Archon Specs的过程中,我积累了一些关键经验:
- 规则的设计要“语义化”,而非“字面化”:早期我写了一条规则“禁止在代码中出现
password字符串”。结果它错误地拦截了包含password的日志信息(如‘Invalid password provided’)。后来改为检测“在赋值语句右侧或数据库查询中出现的、未经哈希处理的password变量”,准确率才大幅提升。规则要理解代码的意图。 - 分层验证比单次验证更有效:不要指望一次生成就完美。我的流程是:LLM生成 -> 基础语法/规则检查 -> 如有问题,将错误信息作为上下文反馈给LLM进行修正(最多2次)-> 最终输出。这种“生成-反馈-修正”的循环比单纯提高第一次生成的要求更可靠。
- 提供“逃生舱口”:对于某些确实无法用现有规则描述的复杂需求,系统应该允许开发者在生成的代码中手动添加“豁免注释”,例如
// archon-ignore-next-line SEC-005,并记录原因。这既保证了流程的严谨,又提供了必要的灵活性。 - 持续从真实错误中学习:建立一个反馈循环。当用户发现生成的代码仍有问题时,可以提交报告。这个报告应该被分析,并判断是规则缺失、提示词不明确还是LLM自身的问题,然后针对性优化系统。让系统在实际使用中越变越聪明。
构建Archon Specs的过程,让我深刻认识到,现阶段让AI完全替代人类编程是不现实的,但让它成为一个高度自律、永不犯错的高级助手则完全可行。它的价值不在于替代思考,而在于消除那些因粗心、遗忘或知识盲区而导致的低级错误,让我们能把宝贵的创造力集中在真正需要智慧的地方。这个项目还在持续迭代中,但它的核心理念——用确定性的规则约束不确定性的智能——我认为是当前AI辅助开发领域一条非常务实且有效的路径。
