Skip to content

Prompt 工程的工程化最佳实践

"Prompt 是新的代码。但很多团队还在像写 commit message 一样写 Prompt。"

如果你的产品里有任何 LLM 调用,Prompt 就是一种会影响生产的产物,必须当代码对待。这一篇不讲"怎么写好 Prompt"——网上太多了,而是讲怎么把 Prompt 工程化地管起来

工程化 Prompt 的五件事

  1. 把 Prompt 从代码里抽出来,模板化;
  2. 输入做变量替换、约束输出格式;
  3. 版本管理 + diff 评审;
  4. 灰度 / A/B / Fallback;
  5. 监控 + 回放。

一、把 Prompt 从代码里抽出来

最常见的反模式:

ts
// ❌ 反模式
const res = await llm.chat([
  { role: 'system', content: '你是客服助手,请友好地回答用户...' },
  { role: 'user', content: question },
]);

Prompt 散在业务代码里,改一个字就要改代码、过 review、走发布。建议改成:

src/
  prompts/
    customer-service/
      v1.md           # Prompt 内容
      v1.meta.yaml    # 模型、温度、版本、负责人
      v1.eval.ts      # 这个版本的回归测试样本

v1.md

md
---
input:
  - question: string
  - history: ChatMessage[]
output:
  format: markdown
  max_tokens: 500
---

你是 Aaron 商城的客服助手。回答规则:

1. 一律使用中文,礼貌但不啰嗦。
2. 不知道的事情说"我去帮您确认",不要编造。
3. 引用商品时用 [商品名](url) 的格式。

历史对话:
{{#each history}}
- {{role}}: {{content}}
{{/each}}

用户问题:{{question}}

加载时只是 loadPrompt('customer-service@v1')。改 Prompt = 改这个文件。

二、约束输出格式

让 LLM 返回结构化数据时,永远 用三层保险:

1. Prompt 里明确写 schema    →  软约束
2. 用 JSON Schema / Zod 验证  →  硬约束
3. 失败重试一次(让模型看错误信息再来)  →  容错
ts
import { z } from 'zod';

const Article = z.object({
  title: z.string().max(60),
  tags: z.array(z.string()).max(5),
  summary: z.string().max(200),
});

async function extractArticle(html: string) {
  const prompt = `
请从下面 HTML 中提取文章信息,严格按以下 JSON 输出,不要任何额外解释:
${JSON.stringify(zodToJsonSchema(Article))}

HTML:
${html}
`;

  for (let i = 0; i < 2; i++) {
    const raw = await llm.chat(prompt, { responseFormat: 'json' });
    try {
      return Article.parse(JSON.parse(raw));
    } catch (e) {
      if (i === 1) throw e;
      // 给模型一次纠正机会
      prompt += `\n\n上一轮你的输出无法解析:${e.message},请严格按 schema 重新输出。`;
    }
  }
}

很多时候 responseFormat: 'json' + Schema 校验比反复调 Prompt 可靠得多。

三、版本管理:把 Prompt 当数据库迁移

每次 Prompt 改动新建版本,不要在原版本上覆盖。原因:

  • 上线后发现新版回归,可以一键回滚;
  • A/B 时两个版本要同时存在;
  • 监控数据按版本归类。

我建议的目录约定:

prompts/
  customer-service/
    v1.md       # 2026-01-10  初版
    v2.md       # 2026-02-03  加入退货流程
    v3.md       # 2026-03-15  改用 chain-of-thought
    current.md  # symlink → v3.md

current.md 是默认版本,环境变量或灰度规则可以覆盖到具体版本。

四、灰度与 A/B

至少给每个 Prompt 调用配两个旋钮:

ts
async function chat(question: string, userId: string) {
  const variant = pickVariant(userId, {
    'customer-service@v3': 0.9,
    'customer-service@v4-experimental': 0.1,  // 10% 流量
  });

  const result = await llm.chat(loadPrompt(variant), { question });

  // 上报指标
  reportMetric({
    promptVersion: variant,
    userId,
    latencyMs: result.latency,
    tokenCount: result.usage.total_tokens,
    rating: null,         // 等用户反馈再补
  });

  return result;
}

pickVariant 用 userId 哈希保证同一用户始终落到同一变体——避免用户来回看到不同风格。

五、Fallback

LLM 比传统服务更容易出问题:超时、限流、模型暂时变蠢、output 解析失败。一定要有降级链:

ts
const providers = [
  { name: 'claude-sonnet', fn: claudeSonnet },
  { name: 'gpt-4o', fn: gpt4o },
  { name: 'deepseek-v3', fn: deepseek },
];

for (const p of providers) {
  try {
    return await withTimeout(p.fn(prompt), 8_000);
  } catch (err) {
    log.warn(`${p.name} failed: ${err.message}`);
  }
}
throw new Error('all providers failed');

六、监控指标

每次 LLM 调用至少记录:

字段用途
prompt_version按版本聚合质量
provider / model比较各 provider 表现
latency_ms性能监控
tokens_in / tokens_out成本监控
parse_ok输出格式是否能解析
user_rating用户反馈(thumbs up/down)
tags业务维度,比如 query 类型

接到任意时序数据库(ClickHouse / Datadog / Grafana),就有了 Prompt 维度的 dashboard。

七、回放(Eval)

Prompt 改动前后必须跑回归测试。最简单的版本:

ts
// prompts/customer-service/v3.eval.ts
export const cases = [
  {
    name: '咨询退货',
    input: { question: '我买的鞋子尺码不对,怎么退?', history: [] },
    assert: (output: string) => {
      expect(output).toContain('退货');
      expect(output.length).toBeLessThan(500);
      expect(output).not.toContain('我不知道');
    },
  },
  // ... 通常 20-50 条覆盖核心场景
];

跑一次 = 把所有 case 喂给当前版本,记录通过率。新版本必须通过率不低于旧版本 才能 promote。

更高级的玩法是用 LLM-as-Judge——再调一次模型让它打分,但这就是另一篇了。

八、组织上的小建议

Prompt 的修改 PR 必须有人 review。 它不是 README,它能改产品行为。建议规范:

  • PR title 用 prompt(customer-service): bump v3 to v4 开头;
  • diff 要展示前后对比 + 至少一条 eval 失败截图;
  • merge 后默认走 1% 灰度,观察 24h 没问题再 promote 100%。

九、不要做的事

  • ❌ 把 API key 写进 Prompt(被泄漏的概率比代码大很多);
  • ❌ Prompt 里嵌业务密文 / PII(日志会泄漏);
  • ❌ 让用户输入直接拼到 system prompt(Prompt Injection 是真问题);
  • ❌ "线上有问题先临时改 Prompt"——必须走版本流程,否则三个月后没人知道为什么是这样。

本站总访问量 --heart 本站访客数 -- 人次