Skip to content

Prompt 工程入门:从咒语到工程

本文是 AI 应用系统学习系列的 L1 入门篇。前置:31. LLM 核心概念:Token、上下文与能力边界。 学完可以配合面试题食用:03-prompt-engineering-cot-fewshot-react

为什么 Prompt 不是"随便写句人话就行"

大多数人第一次用 ChatGPT 时,输入"帮我写个 xx",结果要么啰嗦要么跑偏。这不是模型傻,而是你的 prompt 只给了任务,没给约束。

LLM 的工作方式是 next-token prediction:它输出的是"概率上最合理的下一个词",而非"执行你内心想法的精准指令"。你要的是精确定位,它给的是概率分布中心。Prompt 的本质是把概率分布往你希望的方向推——越精确的约束,越高的命中率。

一个反例:说"翻译这段文字",模型可能给出意译、逐字译、带注释的译,取决于它训练数据里哪种"翻译"最常见。说"翻译成中文,逐句对应,不增不减",结果就稳定。

四要素模板:角色、任务、约束、示例

把 Prompt 看作一个模板,每次填充四个区域:

要素作用反面例子
角色限定输出风格和知识范围不加角色,模型按通用模式输出
任务明确要做什么,而非"想要什么效果""帮我写个好文案"(模糊)
约束禁止什么、必须遵守什么格式不加约束,输出长度/格式不可控
示例给出期望的输入输出对不给示例,模型按统计规律猜

下面是一个中文抽取任务的具体模板:

你是一个信息抽取助手。你的任务是从用户输入的文本中提取"人物"、"组织"、"地点"三类实体。
输出格式:每行一个实体,格式为"实体名|类型|原文位置(起始字符索引)"。
如果某类实体不出现,不输出该类。
不允许添加原文中没有的信息。

文本:{用户输入}

看,角色、任务、输出格式、空白位置、负面约束,全在一段里说清楚。这就是"工程化"的起点——不是写一句人话,而是写一个参数化的模板。

Few-shot:什么时候涨点,什么时候白给

Few-shot 是在 prompt 里塞几个输入输出示例。它的效果受两个因素制约:

有效的情况:示例覆盖了任务中边界情况。比如命名实体抽取,如果示例里同时包含"单字人名"和"多字组织名",模型就能学会区分。如果两个示例全是"张三|人名",那模型看到"阿里巴巴"也会猜成"人名"。

无效的情况

  • 任务本身简单(分类俩标签),zero-shot 已经 95%+,加示例反而可能引入噪声
  • 示例格式不一致:一个输出 JSON 一个输出 CSV,模型会困惑
  • 示例数量超过 5-6 个后,边际收益断崖下降(受限于上下文窗口利用率)

一个实用原则:先跑 zero-shot 看基线,如果准确率低于 80%,再加 few-shot。示例要挑"最难"的那几个,而不是"最典型"的那几个。

python
# few-shot vs zero-shot 效果对比脚本
import openai

client = openai.OpenAI(api_key="sk-xxx", base_url="...")

texts = [
    "昨天张伟在杭州阿里巴巴总部开会,讨论了和腾讯的合作。",
    "李娜在巴黎机场接受了采访。",
    "北京海淀区中关村大街有一家刚开的咖啡店。"
]

# zero-shot prompt
zero_prompt = "从文本中提取人名、地名、组织名。每行一个:类型:实体名"

# few-shot prompt
few_prompt = """从文本中提取人名、地名、组织名。每行一个:类型:实体名

示例:
文本:马化腾在深圳腾讯大厦召开了董事会。
人名:马化腾
组织名:腾讯
地名:深圳

文本:{text}"""

for t in texts:
    zr = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": zero_prompt.format(text=t)}]
    )
    fr = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": few_prompt.format(text=t)}]
    )
    print(f"Text: {t}")
    print(f"  Zero-shot: {zr.choices[0].message.content}")
    print(f"  Few-shot:  {fr.choices[0].message.content}")
    print("---")

CoT 思维链:为什么"step by step"有效

Chain-of-Thought 的核心是把推理过程外显化。当模型在多个步骤之间传递中间结果时,每一步的注意力范围只关心当前步,而不是一次性从问题跳到答案。

mermaid
graph LR
    A[问题] --> B[中间推理1]
    B --> C[中间推理2]
    C --> D[最终答案]
    style A fill:#e1f5fe
    style D fill:#c8e6c9

对于数学题、逻辑推理、多步决策,CoT 能显著提升准确率。但对于简单分类任务,CoT 反而有害——模型多绕一圈,多一个犯错的机会。

python
# 简单分类任务:CoT 反而有害
prompt_plain = """判断以下文本的情感倾向,只输出"正面"或"负面":
文本:{text}
情感:"""

prompt_cot = """判断以下文本的情感倾向,先思考再输出。
文本:{text}
思考:"""

# 对"今天天气不错"这种简单文本,plain 直接出"正面"
# CoT 版本可能输出"思考:这句话表达了正面情绪…情感:正面"
# 多了"思考:"前缀,后面解析还要额外处理

实用建议:只在需要多步推理的任务上用 CoT。简单抽取、分类、格式化输出,用直接指令。

结构化输出:从 JSON 到正则兜底

LLM 输出是自然语言,但 API 消费方要的是结构化数据。三层防线:

第一层:模型侧约束
  - JSON mode(response_format={"type":"json_object"})
  - 函数调用(tools 声明,由模型生成结构化参数)

第二层:解析层校验
  - JSON.parse 套 try-catch
  - 字段类型/必填/枚举值校验

第三层:兜底
  - 正则模式提取("从输出里找 JSON 块")
  - 失败重试(最多 3 次,每次打印错误供模型修正)
  - 降级:返回默认值,打日志报警
python
import json, re

def safe_parse(response: str, retries=3):
    for _ in range(retries):
        # 第一层:直接解析
        try:
            return json.loads(response)
        except json.JSONDecodeError:
            pass
        # 第二层:正则提取
        match = re.search(r'```json\n(.*?)\n```', response, re.DOTALL)
        if match:
            try:
                return json.loads(match.group(1))
            except json.JSONDecodeError:
                pass
        # 第三层:重试(让模型修正)
        response = retry_call(response)  # 把解析错误回传给模型
    return {"error": "parse_failed", "raw": response}

Prompt 的工程化管理

把 prompt 当代码管,不是当文案写。

  • 版本化:每个 prompt 版本存文件(prompt_v1.md, prompt_v2.md),关联 git commit
  • 评测集回归:维护 20-50 条评测用例,改 prompt 后全量跑一遍,看准确率变化
  • 线上 A-B:新 prompt 先切 5% 流量,不降级才全量推
/prompts/
  extract-entities/
    v1.md          # 初始版本
    v2.md          # 加 few-shot 示例
    v3.md          # 改输出格式为 JSON
    test-cases.yaml # 评测用例
    eval-results/   # 每次评测结果

改 prompt = 改代码。改 prompt 前写清楚变更原因,改后跑回归,上线前做 diff。跟改业务代码一样严肃。

常见误区与小结

  • 误区 1:示例越多越好。超过 5-6 个示例效果不涨,反而浪费 token。精挑 3 个最有代表性的就够了。
  • 误区 2:CoT 万能。简单任务用 CoT 画蛇添足,控制好什么场景用。
  • 误区 3:输出格式只靠"说清楚"。必须加 JSON mode / 函数调用 + 解析层校验,两层才稳。
  • 误区 4:prompt 是一次性产品。prompt 需要持续迭代,跟代码一样要有版本号。
  • 误区 5:角色设定是玄学。角色设定是给模型一个"概率先验",不是魔法。说"你是专家"不如说"你是一个有 10 年经验的 xx 工程师"。

小结:Prompt 工程的核心是"把模糊需求变成精确约束",四要素模板定基础,few-shot 和 CoT 提上限,结构化输出兜底,工程化管理保可持续。下一篇进入 33. LLM API 开发详解:流式、函数调用与结构化输出,把这里的 prompt 知识落地到 API 调用实战。

参考

参考:OpenAI Prompt Engineering Guide (https://platform.openai.com/docs/guides/prompt-engineering) — 官方文档,四要素/CoT/评测的基础来源 参考:Anthropic Prompt Engineering Guide (https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering) — 同领域不同视角,XML 标签等技巧

手撕 → 框架 → 生产化,一步步把 AI Agent 工程化搞透。
粤ICP备2026104257号-1