Skip to content

课 2 · Prompt 分层与模板

本课目标

理解 Meta Prompt(元提示词)、System Prompt(系统提示词)、User Prompt(用户提示词)的分层关系,掌握 Prompt 模板的工程化写法。课后你会拿到一个可复用的 Prompt 模板引擎。

课 1 我们学了怎么写好一段 System Prompt。但实际开发中,你很快会遇到两个问题:

  1. System Prompt 越写越长,改一处怕影响别处——能不能用 AI 来写 Prompt
  2. 不同用户、不同场景需要不同的 Prompt——能不能做成模板动态拼装?

这一课解决这两个问题。

三种 Prompt 的定位

在一个 AI 应用中,"提示词"其实不是一个东西,而是三层:

名称谁写的什么时候用干什么
Meta Prompt(元提示词)开发者开发阶段用 AI 生成 / 优化 System Prompt
System Prompt(系统提示词)开发者运行时,整个会话定义角色、规则、输出格式
User Prompt(用户提示词)终端用户运行时,单轮提出问题或给出指令

三层的关系是:Meta Prompt 产出 System Prompt,System Prompt 约束 User Prompt 的处理方式

先看关系图:

System Prompt:产品说明书

课 1 已经讲了 System Prompt 的 XML 结构化写法。这里补充一个认知:System Prompt 本质上是产品说明书——它定义了这个 AI 产品"是什么、能做什么、不能做什么、怎么做"。

它和用户输入的根本区别:

  • System Prompt 是开发者写的,用户看不到,整个会话只设一次
  • User Message 是终端用户的输入,每轮都不同

这意味着 System Prompt 的质量直接决定产品表现。写得好,模型就是一个称职的专家;写得差,模型就是一个什么都答、什么都不确定的全能但无用的 AI。

User Prompt:不只是用户随便打字

在很多 AI 产品中,User Prompt 看起来是用户"自由输入"的,但产品侧可以做很多引导:

typescript
// 搜索框的 Placeholder 引导
const placeholder = '试试问:React 和 Vue 的区别是什么?'

// 预设问题建议
const suggestions = [
  '帮我审查这段代码',
  '解释一下 useEffect 的依赖数组',
  '对比 REST 和 GraphQL',
]

// 在用户输入前自动拼接上下文
function buildUserMessage(userInput: string, context: { fileName?: string }) {
  if (context.fileName) {
    return `我正在编辑文件 ${context.fileName},我的问题是:${userInput}`
  }
  return userInput
}

这些都属于 User Prompt 层面的工程——让用户更容易问出好问题

Meta Prompt:用 AI 写 Prompt

Meta Prompt 是最上层的 Prompt——它的输出不是回答用户问题,而是生成另一个 Prompt

什么时候需要 Meta Prompt:

  • 你要为一个新场景写 System Prompt,但不知道从哪下手
  • 你写了一版 System Prompt,但效果不好,想让 AI 帮你优化
  • 你有一批测试用例,想让 AI 根据失败 case 自动调整 Prompt

Meta Prompt 实战

用 AI 生成 System Prompt

最直接的用法:告诉 AI 你的业务需求,让它帮你生成一个结构化的 System Prompt。

typescript
import { generateText, Output } from 'ai'
import { createOpenAI } from '@ai-sdk/openai'
import { z } from 'zod'

const model = createOpenAI({
  apiKey: process.env.CHAT_API_KEY,
  baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
}).chat('qwen3.6-plus')

// 定义 System Prompt 的结构
const SystemPromptSchema = z.object({
  role: z.string().describe('角色定义,一句话说明这个 AI 是谁'),
  capabilities: z.array(z.string()).describe('能力列表,3-5 项'),
  rules: z.array(z.string()).describe('行为规则,5-8 条'),
  outputFormat: z.string().describe('输出格式要求'),
  refusalTemplate: z.string().describe('无法回答时的回复模板'),
})

const metaPrompt = `你是一个 Prompt 工程专家。
根据用户描述的业务场景,生成一个结构化的 System Prompt。
要求:
- 角色定义要具体,不要泛泛的"你是一个 AI 助手"
- 规则要可执行,不要模糊的"尽量做好"
- 输出格式要明确到具体的结构
- 必须包含拒答模板`

async function generateSystemPrompt(businessRequirement: string) {
  const { output } = await generateText({
    model,
    output: Output.object({ schema: SystemPromptSchema }),
    system: metaPrompt,
    prompt: businessRequirement,
  })

  // 把结构化结果拼成 XML 格式的 System Prompt
  return `
<role>
${output.role}
</role>

<capabilities>
${output.capabilities.map(c => `- ${c}`).join('\n')}
</capabilities>

<rules>
${output.rules.map(r => `- ${r}`).join('\n')}
</rules>

<output_format>
${output.outputFormat}
</output_format>

<refusal>
${output.refusalTemplate}
</refusal>
`.trim()
}

用法:

typescript
const prompt = await generateSystemPrompt(
  '我在做一个前端组件库的文档站,需要一个 AI 助手帮用户查组件用法、属性说明、代码示例。'
)
console.log(prompt)

Meta Prompt 迭代:用失败 case 改进

生成一版 System Prompt 后,跑几个测试用例。把失败的结果喂回去让 AI 改进:

typescript
const iteratePrompt = `你是一个 Prompt 优化专家。

当前 System Prompt:
<current_prompt>
{currentPrompt}
</current_prompt>

以下是用这个 Prompt 跑出来的失败用例:
<failed_cases>
{failedCases}
</failed_cases>

请分析失败原因,输出改进后的 System Prompt。
改进要求:
- 只修改导致失败的部分,不要重写整个 Prompt
- 每处修改说明改了什么、为什么改`

这就是 Meta Prompt 的核心用法:把 Prompt 开发从手工试错变成有反馈的迭代循环

Prompt 模板

当产品有多种场景、多类用户时,写一堆独立的 System Prompt 既难维护又容易不一致。解决方案是模板化——把可复用的结构固定下来,变化的部分用变量填充。

变量插值

最基本的模板能力——把运行时信息插入 Prompt:

typescript
interface PromptVariables {
  userName: string
  currentDate: string
  language: string
  productName: string
}

function buildSystemPrompt(vars: PromptVariables): string {
  return `
<role>
你是「${vars.productName}」的智能助手,正在为 ${vars.userName} 提供服务。
</role>

<rules>
- 使用${vars.language}回答
- 今天是 ${vars.currentDate},涉及时间相关的问题以此为准
- 称呼用户为"${vars.userName}"
</rules>

<output_format>
- 先给结论,再展开
- 代码示例使用 TypeScript
</output_format>
`.trim()
}

条件段落

有些 Prompt 段落只在特定条件下才需要——用条件拼接取代写多份完整 Prompt:

typescript
interface PromptConfig {
  role: 'user' | 'admin' | 'developer'
  features: {
    codeReview: boolean
    docSearch: boolean
    debugging: boolean
  }
  language: string
}

function buildSystemPrompt(config: PromptConfig): string {
  const sections: string[] = []

  // 基础角色(所有人都有)
  sections.push(`
<role>
你是一个前端技术助手。
</role>`)

  // 能力:根据功能开关动态拼接
  const capabilities: string[] = ['回答前端技术问题']
  if (config.features.codeReview) capabilities.push('审查代码质量')
  if (config.features.docSearch) capabilities.push('基于文档检索回答问题')
  if (config.features.debugging) capabilities.push('协助排查 Bug')

  sections.push(`
<capabilities>
${capabilities.map(c => `- ${c}`).join('\n')}
</capabilities>`)

  // 权限:根据角色动态拼接
  if (config.role === 'admin') {
    sections.push(`
<permissions>
- 可以查看系统配置信息
- 可以查看所有用户的使用记录
</permissions>`)
  }

  if (config.role === 'developer') {
    sections.push(`
<permissions>
- 可以执行代码片段
- 可以访问 API 文档
- 可以查看错误日志
</permissions>`)
  }

  sections.push(`
<rules>
- 使用${config.language}回答
- 不确定的信息标注"不确定"
</rules>`)

  return sections.join('\n')
}

这样做的好处:

  • 一份模板覆盖所有场景,改规则只改一处
  • 按需加载,普通用户不会看到 admin 相关的能力描述,避免浪费 Token
  • 类型安全,TypeScript 会检查 config 的合法性

Dynamic Few-shot(动态少样本提示)

课 1 讲了 Few-shot 的写法,但示例是写死在 Prompt 里的。实际产品中,示例应该根据用户的问题动态选取

typescript
// 示例库:按领域分类
const exampleLibrary = {
  react: [
    {
      input: 'useState 的初始值可以是函数吗?',
      output: '可以。传入函数时,React 只在首次渲染时调用它(惰性初始化)……',
    },
    {
      input: 'useEffect 的清理函数什么时候执行?',
      output: '在组件卸载时,以及下一次 effect 执行前……',
    },
  ],
  typescript: [
    {
      input: 'interface 和 type 有什么区别?',
      output: '主要区别:interface 可以声明合并,type 支持联合类型……',
    },
  ],
  nodejs: [
    {
      input: 'require 和 import 的区别?',
      output: 'require 是 CommonJS,同步加载;import 是 ESM,静态分析……',
    },
  ],
}

// 根据用户问题选取相关示例
function selectExamples(
  question: string,
  maxExamples: number = 2
): string {
  // 简单关键词匹配(生产环境可以用 embedding 做语义匹配)
  const keywords: Record<string, string[]> = {
    react: ['react', 'hook', 'useState', 'useEffect', 'component', '组件'],
    typescript: ['typescript', 'ts', 'type', 'interface', '类型'],
    nodejs: ['node', 'require', 'import', 'express', '服务端'],
  }

  const lowerQ = question.toLowerCase()
  const matchedDomain = Object.entries(keywords).find(([, words]) =>
    words.some(w => lowerQ.includes(w))
  )?.[0]

  if (!matchedDomain || !exampleLibrary[matchedDomain as keyof typeof exampleLibrary]) {
    return '' // 没有匹配到领域,不插入示例
  }

  const examples = exampleLibrary[matchedDomain as keyof typeof exampleLibrary]
    .slice(0, maxExamples)

  return `
<examples>
${examples.map(e => `输入:${e.input}\n输出:${e.output}`).join('\n\n')}
</examples>`
}

整合到完整的 Prompt 构建中:

typescript
function buildFullPrompt(
  config: PromptConfig,
  vars: PromptVariables,
  userQuestion: string
): string {
  const base = buildSystemPrompt(config)
  const examples = selectExamples(userQuestion)

  return `${base}
${examples}`
}

模板维护的原则

  1. 变量命名明确{userName} 好过 {name},读代码的人一眼知道填什么
  2. 必填和选填分开 — 用 TypeScript 的类型标记哪些变量是可选的
  3. 段落顺序固定 — role → capabilities → rules → output_format → examples → security,保持一致性
  4. 版本控制 — Prompt 模板跟代码一样放在 Git 里,每次修改有记录

并入主线项目

本模块能力会在基础项目 basic/project/ 中收束,当前课内 Demo 见下方运行方式。

课 1 教了怎么写好一段 System Prompt,这一课在此基础上增加了两个维度:

  1. Meta Prompt — 用 AI 帮你写 Prompt,然后用失败 case 迭代改进。它更适合作为开发工作流能力,用来持续打磨最终 Agentic RAG 的各类 Prompt。
  2. 模板化 — 把 System Prompt 做成参数化模板,一份代码覆盖多种场景。当前 basic/project 先落地单一结构化 Prompt,后续主线会把这套模板化能力扩展到 planner、retriever、answerer、reviewer 等不同角色。

下一课我们会把视角拉高一层——看看一个 AI 应用的 Prompt System 全貌,System Prompt 只是其中的一环。

本课产物

  • ✅ Meta Prompt / System Prompt / User Prompt 的分层定位
  • ✅ Meta Prompt 生成 System Prompt 的方法
  • ✅ Meta Prompt 迭代改进的流程
  • ✅ Prompt 模板:变量插值、条件段落、Dynamic Few-shot
  • ✅ 模板维护的工程化原则

完整代码在 basic/examples/02-prompt/02-prompt-layers/index.ts

试试看

bash
cd daqi-ai-agent
pnpm exec tsx basic/examples/02-prompt/02-prompt-layers/index.ts
  1. meta 命令输入一个业务场景描述,观察 AI 生成的 System Prompt 结构
  2. template 命令切换不同的角色和功能开关,对比生成的 Prompt 差异
  3. chat 命令验证生成的 Prompt 实际效果

面试追问

Q:Meta Prompt 和 System Prompt 有什么区别?

Meta Prompt 是"写 Prompt 的 Prompt",作用于开发阶段,输出是一段 System Prompt 文本;System Prompt 作用于运行时,输出是对用户问题的回答。两者工作在不同阶段——Meta Prompt 属于开发工具,System Prompt 属于产品运行时配置。

Q:Prompt 模板用字符串拼接可以吗?需不需要用模板引擎?

字符串拼接完全可以,TypeScript 的模板字符串加上条件函数就够用。引入模板引擎(如 Handlebars、Mustache)会增加依赖但不会带来本质提升,因为 Prompt 模板的复杂度远低于 HTML 模板。关键是要有类型约束——用 TypeScript 的 interface 定义变量结构,确保不会遗漏必填字段。

Q:Dynamic Few-shot 怎么选示例?

简单场景用关键词匹配,从预定义的示例库中按领域选取。进阶做法是把示例做成 Embedding 存向量数据库,用户问题到来时做语义检索,选最相关的 2-3 个示例插入 Prompt。这和 RAG 的检索逻辑一样——本质上少样本提示示例也是一种"知识",可以用检索增强的方式管理。

面向前端工程师和独立开发者的 AI 应用工程课程