AI 写代码已经不是"要不要用"的问题,而是"怎么管"的问题。电商搜索团队在 H1 周期里踩过足够的坑后得出一个结论:裸奔式 AI-Coding——让模型自由生成、人工事后 review——上线事故率不可接受。真正可控的做法是在输入、生成、输出三个环节各加一道硬约束,同时把 QA 从"接单测试"推到"标准制定"的前端,和 RD 形成闭环。这篇文章把这套实践拆开讲,并给出可直接改造的 CI 流水线配置。
三层约束框架:输入 / 生成 / 输出
把 AI-Coding 的质量管控分成三层,不是概念游戏,而是因为每一层的失效模式不同,拦截手段也不同。
输入层——给模型什么,它就生成什么。 输入约束的核心是规范 Prompt 和上下文。具体做法包括:
- 为每类任务维护一份 Prompt 模板库,模板里写清楚函数签名、入参校验规则、禁止使用的依赖;
- 上下文窗口只注入与当前任务相关的代码片段和接口文档,而不是把整个仓库丢进去——无关上下文越多,模型"幻觉"越严重;
- 对输入做静态校验:如果 Prompt 里缺少返回值约定或异常处理要求,直接拒绝提交给模型。
生成层——模型在推理过程中可以被引导。 生成约束不是改模型权重(大多数团队做不到),而是在推理参数和后处理规则上设卡:
- 限制
temperature和top_p,生产代码场景建议temperature ≤ 0.2,降低随机性; - 在 system prompt 中硬编码"禁止生成网络请求、文件写入、动态执行"等安全红线;
- 对生成结果做结构化抽取:只取代码块,丢弃模型附带的自然语言解释,避免解释片段被误当代码执行。
输出层——生成结果必须过机器门才能进仓库。 输出约束是最硬的一道闸:
- lint + type check 必须通过,零 warning 才允许合并;
- 单测覆盖率阈值硬卡(例如新增代码行覆盖率 ≥ 80%);
- 安全扫描(依赖漏洞、硬编码密钥、SQL 注入模式)作为必须通过的 gate;
- 人工 review 不取消,但 scope 收窄:只审查业务逻辑正确性,格式和风格问题交给机器。
三层叠加的效果是:输入层减少模型犯错概率,生成层限制犯错幅度,输出层拦截漏网之鱼。任何一层单独用都不够,三层一起才能把 AI 代码的上线风险压到可接受区间。
Harness 工程基础设施:让约束自动执行
约束写在文档里没人看,写在 CI pipeline 里没人能绕过。电商搜索团队用 Harness 搭了一套工程基础设施,把三层约束全部落地为流水线 stage:
- Stage 1 — Input Guard:解析提交的 Prompt 模板 ID 和上下文清单,校验必填字段,缺失则 pipeline 立即 fail;
- Stage 2 — Generate & Extract:调用模型服务,抽取代码块,做结构化格式校验(语法树是否完整、import 是否在白名单内);
- Stage 3 — Output Gate:跑 lint、type check、单测、安全扫描,任何一项不达标则阻断合并。
Harness 的优势在于每个 stage 可以独立配置超时、重试和通知策略,RD 和 QA 各自维护自己负责的 stage 规则,不需要互相等。这比"一条大脚本跑到底"的 Jenkins 旧模式更可控,也更容易迭代。
RD 与 QA:从线性交接到闭环共建
旧模式下,RD 写完代码扔给 QA 测,QA 测完扔回 RD 改,循环往复。AI-Coding 时代这个模式有两个致命问题:一是 AI 生成代码的缺陷密度和类型分布跟人写的不同,QA 用老套路覆盖不到;二是交接周期长,AI 代码迭代快,等 QA 测完可能已经落后两个版本。
新协作模式的核心变化:
- 目标对齐:RD 和 QA 的共同目标不是"代码功能正确",而是"AI 代码安全上线"。安全上线 = 功能正确 + 风险可控 + 可观测;
- QA 左移:QA 在 Prompt 模板设计阶段就介入,定义"可测试性标准"——比如要求 AI 生成的每个函数必须有纯逻辑入口(不依赖外部状态),否则单测写不了;
- 闭环共建:QA 发现的缺陷模式被提炼成规则,回注到输入层 Prompt 模板或输出层扫描规则里,下次同类问题在生成阶段就被拦截,而不是等 QA 再测一遍。
实际操作中,QA 每周把高频缺陷做分类,输出一份"缺陷→约束映射表",RD 据此更新 Prompt 模板和 CI 规则。这张表是闭环的关键载体——没有它,QA 左移就只是"更早介入"而不是"更早拦截"。
实践:搭建三层约束的 CI 流水线
下面给出一个 GitHub Actions workflow,把三层约束落地为可运行的 stage。电商搜索团队用的是 Harness,但逻辑完全一致——读者可以根据自己团队的 CI 平台做对应改造。
name: ai-code-quality-gate
on:
pull_request:
paths:
- 'src/**' # 只对业务代码目录触发
- 'ai-prompts/**' # Prompt 模板变更也触发,确保输入约束同步更新
jobs:
# ── Stage 1: 输入约束校验 ──
input-guard:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 校验 Prompt 模板必填字段
run: |
# 检查每个 Prompt 模板是否包含必填段落
MISSING=0
for f in ai-prompts/*.md; do
for field in "函数签名" "入参校验" "异常处理" "禁止依赖"; do
if ! grep -q "$field" "$f"; then
echo "❌ $f 缺少必填字段: $field"
MISSING=1
fi
done
done
if [ "$MISSING" -eq 1 ]; then
echo "::error::Prompt 模板校验失败,缺少必填字段"
exit 1
fi
echo "✅ Prompt 模板校验通过"
- name: 校验上下文注入范围
run: |
# 上下文文件清单不得超过 20 个,避免窗口膨胀
COUNT=$(wc -l < ai-prompts/context-manifest.txt)
if [ "$COUNT" -gt 20 ]; then
echo "::error::上下文文件数 $COUNT 超过阈值 20"
exit 1
fi
# ── Stage 2: 生成结果抽取与结构校验 ──
generate-extract:
needs: input-guard
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 抽取代码块 & 校验语法完整性
run: |
python3 scripts/extract_code_blocks.py \
--input src/ \
--output .extracted/ \
--whitelist-deps "utils,models,api" \
--blocklist-deps "os.system,subprocess,eval,exec"
# 用 Python AST 检查抽取结果是否语法完整
python3 scripts/ast_check.py --dir .extracted/ || {
echo "::error::生成代码语法树不完整"
exit 1
}
# ── Stage 3: 输出质量门 ──
output-gate:
needs: generate-extract
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Lint & Type Check
run: |
pip install ruff mypy
ruff check src/ --strict
mypy src/ --strict --ignore-missing-imports
- name: 单测覆盖率卡点
run: |
pip install pytest pytest-cov
pytest src/ --cov=src/ --cov-fail-under=80
- name: 安全扫描
uses: zizmorcore/zizmor-action@v0 # 扫描硬编码密钥等
with:
severity: high
- name: 依赖漏洞检查
run: |
pip install pip-audit
pip-audit --strict
改造要点:
ai-prompts/目录和src/目录并列管理,Prompt 模板跟代码一起版本控制;extract_code_blocks.py和ast_check.py是团队自维护的脚本,核心逻辑是:从 AI 输出中只取 ```python 块,白名单校验 import,AST 解析确认语法完整;- 覆盖率阈值
--cov-fail-under=80根据团队实际情况调整,但必须有一个硬数字,不能是"尽量高"; - 安全扫描用了
zizmor+pip-audit,读者可替换为自己团队的工具链(Semgrep、Trivy 等),逻辑不变。
下面是 extract_code_blocks.py 的最小可运行版本,可以直接放到 scripts/ 目录:
"""从 AI 生成文件中抽取代码块,过滤禁止依赖"""
import ast, re, sys, pathlib
WHITELIST = {"utils", "models", "api"}
BLOCKLIST = {"os.system", "subprocess", "eval", "exec"}
def extract_blocks(text: str) -> list[str]:
"""抽取 ```python ... ``` 代码块"""
return re.findall(r"```python\n(.*?)```", text, re.DOTALL)
def check_imports(code: str) -> list[str]:
"""返回违规 import 列表"""
violations = []
tree = ast.parse(code)
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for alias in node.names:
root = alias.name.split(".")[0]
if root not in WHITELIST:
violations.append(alias.name)
if isinstance(node, ast.ImportFrom):
if node.module and node.module.split(".")[0] not in WHITELIST:
violations.append(node.module)
return violations
def check_blocklist(code: str) -> list[str]:
"""返回出现的禁止调用"""
found = [b for b in BLOCKLIST if b in code]
return found
def main(src_dir: str, out_dir: str):
violations_total = []
out_path = pathlib.Path(out_dir)
out_path.mkdir(exist_ok=True)
for f in pathlib.Path(src_dir).rglob("*.py"):
blocks = extract_blocks(f.read_text())
for i, block in enumerate(blocks):
imp_violations = check_imports(block)
blk_violations = check_blocklist(block)
if imp_violations or blk_violations:
violations_total.append(
f"{f.name} block#{i}: imports={imp_violations} blocklist={blk_violations}"
)
continue
# 语法完整性校验
try:
ast.parse(block)
except SyntaxError as e:
violations_total.append(f"{f.name} block#{i}: syntax error {e}")
continue
# 写出合法代码块
(out_path / f"{f.stem}_block{i}.py").write_text(block)
if violations_total:
for v in violations_total:
print(f"❌ {v}")
sys.exit(1)
print("✅ 所有代码块校验通过")
if __name__ == "__main__":
main(sys.argv[1], sys.argv[2])
上线前的取舍与检查清单
三层约束不是零成本。几个实际取舍需要团队提前对齐:
| 取舍点 | 建议 | 理由 |
|---|---|---|
temperature 设多低 |
生产代码 0.1–0.2 | 太低会牺牲多样性(比如模型总生成同一套解法),太高则不可控 |
| 覆盖率阈值定多少 | 新增代码 ≥ 80%,存量逐步提升 | 一刀切 80% 对老代码不现实,分阶段更可执行 |
| QA 左移到什么程度 | 至少覆盖 Prompt 模板评审 | 再往前推到模型选型阶段,多数 QA 团队能力暂不具备 |
| 人工 review 取消吗 | 不取消,scope 收窄 | 机器能拦格式和安全问题,业务逻辑正确性仍需人判断 |
上线检查清单(每次 AI 代码合并前逐项确认):
- [ ] Prompt 模板包含四项必填字段(函数签名、入参校验、异常处理、禁止依赖)
- [ ] 上下文文件数 ≤ 阈值,无无关注入
- [ ] 生成代码 import 全部在白名单内,无
eval/exec/subprocess - [ ] lint + type check 零 warning
- [ ] 新增代码单测覆盖率 ≥ 团队阈值
- [ ] 安全扫描无 high severity 发现
- [ ] QA 已评审 Prompt 模板的可测试性
- [ ] 本周缺陷→约束映射表已更新到 CI 规则
最后一项容易被忽略,但它才是闭环运转的证据。如果映射表连续两周没更新,要么是缺陷已经彻底被前置拦截了(好事),要么是 QA 没在做提炼(坏事)——后者意味着闭环断了,QA 左移退化为"更早介入但没反馈"。
三层约束 + 闭环共建,本质上是把"AI 代码质量"从一个模糊的信任问题,变成一组可度量、可自动执行、可迭代的质量工程问题。约束越硬,AI-Coding 的上线信心越足。