跳到正文

验证契约

你在模块3的提示词中增加了验证部分。那是开始。这是完整版。

Agent应合理地运行项目实际拥有的门,并报告结果,区分其导致的失败和已存在的失败。最后一点比人们想象的更重要。一个在三个人已经失败时说“考试通过”的Agent,比一个在没有测试运行但略微失败时说“所有测试都通过”更有用。两人都在撒谎。 真实的说法是“三个已有的失败,我的变更没有带来新的失败。”

学习成果

Agent发现项目package.json(以及AGENTS.md(如果存在)的验证门,按已知顺序运行,并报告区分失败与既有失败的范围声明。

快速路径

  1. 发现package.json脚本中的可用门
  2. 按顺序运行:typecheck、lint、测试、构建
  3. 报告精确命令和输出
  4. 区分“我导致了这次失败”和“这本来就已经失败了”

动手练习 9.3

通过项目感知的门序列和范围化的权利要求契约扩展系统提示词。

要求:

  1. index.ts(或辅助工具)中,阅读package.json脚本并建立可用的验证命令列表
  2. 将列表作为新的上下文字段传递给buildSystemPrompt,然后verificationCommands: string[]
  3. 更新# Verification部分,列出项目的实际门,而不是通用列表
  4. 添加明确的范围索赔规则:区分你的失败与已有的失败

实现提示:

  • 检查scripts.typecheckscripts["type-check"]scripts.lintscripts.testscripts.build。不同项目使用不同名称
  • 当没有typecheck脚本且TypeScript处于依赖状态时,可以回退到npx tsc --noEmit
  • 顺序很重要。typecheck第一个,因为它失败得最快。最后建,因为最慢
  • 范围索赔规则对Agent诚信影响最大。状态明确地说

探索package.json的门

ts
import type { Sandbox } from "./sandbox";

export async function discoverGates(sandbox: Sandbox): Promise<string[]> {
  try {
    const raw = await sandbox.readFile("package.json");
    const pkg = JSON.parse(raw);
    const scripts = pkg.scripts ?? {};
    const gates: string[] = [];

    if (scripts.typecheck || scripts["type-check"]) {
      gates.push("npm run typecheck");
    } else if (pkg.devDependencies?.typescript || pkg.dependencies?.typescript) {
      gates.push("npx tsc --noEmit");
    }

    if (scripts.lint) gates.push("npm run lint");
    if (scripts.test) gates.push("npm test");
    if (scripts.build) gates.push("npm run build");

    return gates;
  } catch {
    return [];
  }
}

该函数返回的是Agent实际可以执行的命令数组。如果缺少package.json或无法读取,则数组为空,Agent不运行门。这总比不存在的门要好。

传到提示词

ts
export interface PromptContext {
  workingDirectory: string;
  sandboxType: string;
  toolNames: string[];
  gitBranch?: string;
  projectContext?: string;
  verificationCommands?: string[];
}

// In buildSystemPrompt, replace the existing Verification section:
const gates = ctx.verificationCommands?.length
  ? ctx.verificationCommands.map((c, i) => `${i + 1}. \`${c}\``).join("\n")
  : "(no verification commands discovered for this project)";

sections.push(`
# Verification
After making changes, verify your work by running these gates in order:
${gates}

Run each gate, capture the output, and report what passed and what didn't.

Distinguish failures you caused from failures that were already there:
- "Ran tsc: passed."
- "Ran npm test: 47 passed, 3 failed. The 3 failures are pre-existing in user.test.ts and unrelated to my changes."

Do NOT claim "tests pass" without running them. Do NOT inflate partial
verification into a blanket success claim.`);

Agent现在看到的是项目的实际门,而不是通用的占位列表。

接入主流程

ts
import { discoverGates } from "./src/verification";

const verificationCommands = await discoverGates(sandbox);

const agent = new ToolLoopAgent({
  // ...
  instructions: buildSystemPrompt({
    workingDirectory: cwd,
    sandboxType: sandbox.type,
    toolNames: Object.keys(tools),
    projectContext,
    verificationCommands,
  }),
});

对于一个有tsc和测试但没有构建脚本的项目,Agent现在知道它有两个门。对于一个完全没有脚本的项目,Agent确实知道范围需要验证。

范围化的索赔,并排呈现

Agent会说什么你想要什么
“所有测试都通过。”npm test:47分通过,3分不及格。这些失败其实user.test.ts就存在,和我的改动无关。”
“这套建筑有效。”“跑npm run build:4.2秒成功,未收到警告。”
“看起来不错。”“跑了,TSC:通过了。lint没有配置。测试套件通过(12项测试)。”

左侧列是模型在提示词没有反击时的默认声音。右侧的柱子是契约的制造目标。

**注意:最难的门槛是Agent的诚实**

你可以布线完美门发现,Agent仍然会显示“所有测试通过”,即使它没有运行测试。保护部队是系统提示词部分,不是发现代码。花时间在措辞上。“区分你造成的失败和已经存在的失败”是这句话的重担。

动手试试

做一个小改动,让Agent确认:

bash
bun run index.ts . "Rename the cwd variable in src/sandbox-local.ts to workingDir, then verify"

Agent应当:

  1. 重新命名
  2. 按顺序运行发现的门
  3. 报告每个门的结果,并附上具体命令和结果
  4. 如果有任何门故障,请区分是重命名导致的还是本来就存在的故障
bash
npx tsc --noEmit

提交

bash
git add src/verification.ts src/system.ts index.ts
git commit -m "feat(verify): discover project gates and require scoped claims"

完成标准

  • [ ] discoverGates返回当前项目存在的门
  • [ ] 系统提示词的验证部分列出了发现的门
  • [ ] Agent按顺序运行门,并报告准确结果
  • [ ] Agent区分了其失败与已有失败
  • [ ] 在没有脚本的项目中,Agent报告的验证是有限的
  • [ ] npx tsc --noEmit

**注意:快速失败,顺序正确**

目前星门是固定顺序运行的。试着在项目中对每个项目做基准测试。typecheck可能只有三秒钟。测试可能要三十个。构建可能要九十。按典型时长排序,先跑最快,这样故障能更早暴露。然后注意:有些门依赖于其他门。如果失败了,构建tsc就毫无意义。如何在不失去失效性质的情况下表达这种感觉?

参考实现

ts
import type { Sandbox } from "./sandbox";

export async function discoverGates(sandbox: Sandbox): Promise<string[]> {
  try {
    const raw = await sandbox.readFile("package.json");
    const pkg = JSON.parse(raw);
    const scripts = pkg.scripts ?? {};
    const gates: string[] = [];

    if (scripts.typecheck || scripts["type-check"]) {
      gates.push("npm run typecheck");
    } else if (pkg.devDependencies?.typescript || pkg.dependencies?.typescript) {
      gates.push("npx tsc --noEmit");
    }

    if (scripts.lint) gates.push("npm run lint");
    if (scripts.test) gates.push("npm test");
    if (scripts.build) gates.push("npm run build");

    return gates;
  } catch {
    return [];
  }
}

非官方简体中文翻译 · 原课程来自 Vercel Academy