跳到正文

动态构建系统提示词

上一课的分段提示词是硬编码的。当只有一个项目、一个沙箱和一个固定工具集时,这没问题。只要这些人动了,提示词就得跟着动。

不同的工作目录。后端沙箱不同。一个只有readgrep的子 Agent。硬编码字符串无法承载这些内容。函数可以。

学习成果

src/system.ts中的buildSystemPrompt(context)函数返回了类型PromptContext的系统提示词字符串。Agent的 instructions 现在是从运行时状态派生的,而不是粘贴进去的。

快速路径

  1. 创建src/system.ts,配备PromptContext接口和buildSystemPrompt(ctx)函数
  2. 从多个章节组成提示词,包括选修部分如gitBranchprojectContext
  3. 调用 buildSystemPrompt(...)index.ts中,并将结果传instructions

动手练习 3.2

把提示词提取到一个类型化的构建器中。

要求:

  1. 定义PromptContext,包含workingDirectorysandboxTypetoolNames、可选gitBranch、可选projectContext
  2. 写入返回分段提示词的buildSystemPrompt(ctx: PromptContext): string
  3. gitBranchprojectContext设为有条件,只有在设置时才包含它们的部分
  4. index.ts中,构建上下文对象,并为instructions字段调用 buildSystemPrompt(ctx)

实现提示:

  • 把部分推入数组,最后join("\n")。纯字符串 concat,无模板引擎
  • 条件段使用简单的if (ctx.foo) sections.push(...)。不要追求更花哨的模式
  • 保持buildSystemPrompt纯净。同样的情境,同样的环境,提示词出,没有副作用。这使得它可以进行单元测试

语境的形态

提示词取决于运行时状态。把这些状态装瓶成一个整体:

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

workingDirectorysandboxType总是适用。toolNames让提示词列出实际接线的工具(这在给子 Agent子集时很重要)。gitBranchprojectContext是可选的,因为它们并不总是可知的。

构建器

ts
export function buildSystemPrompt(ctx: PromptContext): string {
  const sections: string[] = [];

  sections.push(`You are a coding agent working in: ${ctx.workingDirectory}`);
  sections.push(`Sandbox: ${ctx.sandboxType}`);

  sections.push(`
# Agency
- USE your tools. Read files, search code, run commands, then answer.
- Do NOT explain what you WOULD do. Actually do it.
- Available tools: ${ctx.toolNames.join(", ")}`);

  if (ctx.gitBranch) {
    sections.push(`- Current branch: ${ctx.gitBranch}`);
  }

  sections.push(`
# Guardrails
- Prefer simple, minimal changes
- Search before creating, and reuse existing patterns
- No new dependencies without asking`);

  if (ctx.projectContext) {
    sections.push(`
# Project Instructions (from AGENTS.md)
${ctx.projectContext}`);
  }

  return sections.join("\n");
}

这里没有模板引擎。没有什么DSL。有一个阵列,几个push 调用,还有一个join。这是故意的。提示词是一根绳子。构建它应该看起来像搭一根绳子。

接入主流程

index.ts,将内联instructions的字面值替换为建调用:

ts
import { buildSystemPrompt } from "./src/system";

const instructions = buildSystemPrompt({
  workingDirectory: cwd,
  sandboxType: "local",
  toolNames: Object.keys({ read, grep, bash }),
});

const agent = new ToolLoopAgent({
  model: "anthropic/claude-haiku-4-5",
  instructions,
  tools: { read, grep, bash },
  stopWhen: stepCountIs(10),
});

Agent在单一任务上的行为可能看起来和以前一样。这种胜利是结构性的。添加 git 上下文行、交换沙箱类型或剥离子 Agent的部分,现在都需要编辑一个专注于函数的函数,而不是在多行字符串中查找并替换。

**注意:为什么是函数,而不是字符串**

提示词是Harness最重要的配置。把它设为函数意味着它可测试(断言输出在特定上下文下的样子)、可组合(添加部分而不影响其他部分)、可替换(用户可以自己构建构建器)、确定性(每次都相同的上下文、相同的提示词)。费用是一份文件。好处是在你第三次添加某个部分时才显现出来的。

动手试试

运行你以前用过的任何一个提示词:

bash
bun run index.ts . "Find all TODO comments in this project"

输出应该和上一节课一样,因为提示词内容是一样的。这种变化是内心的。通过记录一次该提示词来确认Agent是否还具备所需工具:

ts
console.log(instructions);

你应该能看到完整的Agent和护栏部分,并插值了工作目录和工具名称。

bash
npx tsc --noEmit

提交

bash
git add src/system.ts index.ts
git commit -m "refactor(prompt): extract buildSystemPrompt with runtime context"

完成标准

  • [ ] src/system.ts出口PromptContextbuildSystemPrompt
  • [ ] buildSystemPrompt在相同的上下文下返回与上一课相同的提示词内容
  • [ ] gitBranchprojectContext为可选,仅在提供时才包含
  • [ ] index.ts 调用 buildSystemPrompt(...) 代替使用内联字符串
  • [ ] npx tsc --noEmit

**注意:写个测试给提示词**

添加一个快速断言:用gitBranch: "main"构建提示词,并确认输出中包含“当前分支:主”。不gitBranch地构建,确认这条线路不存在。这是提示词中最小的单元测试,它捕捉到了那种几乎无法通过读取模型输出发现的漏洞。

参考实现

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

export function buildSystemPrompt(ctx: PromptContext): string {
  const sections: string[] = [];

  sections.push(`You are a coding agent working in: ${ctx.workingDirectory}`);
  sections.push(`Sandbox: ${ctx.sandboxType}`);

  sections.push(`
# Agency
- USE your tools. Read files, search code, run commands, then answer.
- Do NOT explain what you WOULD do. Actually do it.
- Available tools: ${ctx.toolNames.join(", ")}`);

  if (ctx.gitBranch) {
    sections.push(`- Current branch: ${ctx.gitBranch}`);
  }

  sections.push(`
# Guardrails
- Prefer simple, minimal changes
- Search before creating, and reuse existing patterns
- No new dependencies without asking`);

  if (ctx.projectContext) {
    sections.push(`
# Project Instructions (from AGENTS.md)
${ctx.projectContext}`);
  }

  return sections.join("\n");
}
ts
import { buildSystemPrompt } from "./src/system";

const tools = { read, grep, bash };

const agent = new ToolLoopAgent({
  model: "anthropic/claude-haiku-4-5",
  instructions: buildSystemPrompt({
    workingDirectory: cwd,
    sandboxType: "local",
    toolNames: Object.keys(tools),
  }),
  tools,
  stopWhen: stepCountIs(10),
});

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