目录
- [Prompt Engineering](#Prompt Engineering)
-
- [为什么需要 Prompt Engineering?](#为什么需要 Prompt Engineering?)
- [System Prompt ------ 如何建立一个"不会乱跑"的 AI 角色](#System Prompt —— 如何建立一个“不会乱跑”的 AI 角色)
-
- [一个真实的 AI 应用通常有两类指令](#一个真实的 AI 应用通常有两类指令)
- [System Prompt 是什么?](#System Prompt 是什么?)
- 为什么要分开?
- 一个常见错误
- [System Prompt 最重要的几个组成部分](#System Prompt 最重要的几个组成部分)
- "边界"为什么特别重要?
- [一个工程化的 System Prompt](#一个工程化的 System Prompt)
- [User Prompt 就简单很多](#User Prompt 就简单很多)
- 一个非常重要的设计原则
- [Few-shot Prompt ------ 为什么"给例子"有时比"讲规则"更有效?](#Few-shot Prompt —— 为什么“给例子”有时比“讲规则”更有效?)
-
- [什么是 Few-shot?](#什么是 Few-shot?)
- 为什么例子这么有用?
- Few-shot最重要的能力:示范
- [Few-shot 不等于训练模型](#Few-shot 不等于训练模型)
- [Zero-shot vs Few-shot](#Zero-shot vs Few-shot)
- Few-shot最适合什么任务?
- 例子质量比数量更重要
- 一个非常实用的技巧:覆盖边界情况
- [Structured Output ------ 让 LLM 输出程序可以直接使用的数据](#Structured Output —— 让 LLM 输出程序可以直接使用的数据)
-
- 先看最原始的问题
- [于是我们告诉模型输出 JSON](#于是我们告诉模型输出 JSON)
- 但是,"请输出JSON"仍然不够可靠
- [这就是 Schema](#这就是 Schema)
- 三个层次
- [为什么 Structured Output 对 AI 应用非常重要?](#为什么 Structured Output 对 AI 应用非常重要?)
- LLM负责什么?Python负责什么?
- [第一次 API 调用](#第一次 API 调用)
-
- [API 调用本质](#API 调用本质)
- 一个请求通常包含什么?
- [为什么 messages 是数组?](#为什么 messages 是数组?)
之前我们一直在学:
LLM内部是怎么工作的?
接下来开始:
作为开发者,我怎么控制LLM?
Prompt Engineering
先建立一个非常重要的认知:
Prompt不是:
"跟AI聊天时随便输入的一句话。"
对于开发者来说:
Prompt 是给模型的任务规范。
可以把它理解成:
Prompt
≈
给LLM的一份运行时指令
一个最简单的例子
Prompt:
总结这篇文章。
模型当然可能完成。
但问题是:
你没有告诉模型:
总结多少?
从什么角度?
给谁看?
什么格式?
有哪些限制?
更好的 Prompt
例如:
你是一名技术文档分析助手。
任务: 总结下面的技术文章。
要求:
- 提取3个核心观点
- 每个观点不超过50字
- 提取5个关键词
- 使用JSON格式输出
文章: {{article}}
这里已经出现了几个 Prompt Engineering 核心组成部分:
Role
Task
Context
Constraints
Output Format
可以先记成:
角色
任务
上下文
约束
输出格式
为什么需要 Prompt Engineering?
因为:
LLM本质上是:
根据上下文
预测下一个Token
所以你给它的上下文越清晰:
模型越容易进入你希望的生成空间。
一个非常有意思的对比
Prompt A:
帮我分析这个用户。
Prompt B:
你是一名用户研究分析助手。
任务: 根据用户行为数据判断用户的主要兴趣。
要求:
- 给出3个最可能的兴趣方向
- 每个方向给出证据
- 不允许凭空假设不存在的数据
- 最终使用JSON输出
用户数据: {{data}}
两个 Prompt:
模型还是同一个模型。
但:
任务定义不同
上下文不同
约束不同
输出要求不同
最后结果可能差很多。
System Prompt ------ 如何建立一个"不会乱跑"的 AI 角色
一个真实的 AI 应用通常有两类指令
假设做:
个人知识库 AI 助手。
用户可能输入:
帮我整理一下这段Transformer笔记。
这是:
用户任务。
但是我们的系统还希望模型始终遵守:
你是AI学习助手。
只能根据提供的信息回答。
不能编造知识。
输出必须符合指定JSON结构。
这些是:
系统规则。
所以可以拆成:
System Prompt
User Prompt
System Prompt 是什么?
可以先简单理解为:
定义模型在这个应用中"应该是谁、应该怎么工作、应该遵守什么规则"的高层指令。
例如:
System:
你是一名AI学习知识助手。
你的职责:
- 整理用户学习记录
- 提取知识主题
- 生成标签
- 总结学习内容
规则:
- 只根据提供的信息回答
- 不得编造事实
- 输出必须符合JSON格式
- 不要输出JSON之外的内容
用户再输入:
User:
我今天学习了Self-Attention......
模型应该按照 System 中的规则处理这个任务。
为什么要分开?
想象 Python 项目。
你以前可能会写:
service层
↓
业务规则
然后:
↓
用户输入
System Prompt 和 User Prompt 其实也有类似思想。
System
↓
定义系统行为
User↓
提供具体任务
比如:
System:
你是一名知识分类助手。
只能选择指定分类。
必须返回JSON。
User:把这段Transformer笔记分类。
这样:
规则和数据分离。
这对后面的应用开发非常重要。
一个常见错误
很多初学者会把所有东西塞进一个 Prompt:
你是一个AI助手,
只能输出JSON,
不能编造,
只能选择这些分类,
现在帮我分析下面的数据,
数据是......
当然可以工作。
但项目变大以后会很难维护。
例如:
用户A:
帮我分类笔记
用户B:
帮我总结笔记
用户C:
帮我生成复习问题
如果所有规则都写在一起:
Prompt 会越来越长。
更合理:
System Prompt
↓
通用行为规则
User Prompt↓
具体任务
然后每个任务只改变 User 部分。
System Prompt 最重要的几个组成部分
你可以先记住:
身份
职责
规则
边界
输出要求
例如:
身份:
你是一名AI学习助手。
职责:
帮助用户整理学习知识。
规则:
不得编造。 只使用提供的信息。
边界:
不知道就明确说明不知道。
输出:
使用JSON格式。
"边界"为什么特别重要?
这是 AI 应用和普通聊天最大的区别之一。
例如你的知识助手:
用户问:
"Transformer是哪一年提出的?"
但系统当前没有提供相关资料。
如果没有边界:
模型可能直接凭自己的知识回答。
如果你的应用要求:
严格基于个人知识库回答。
那么 System Prompt 应该明确:
只能使用提供的知识库内容。
如果知识库中没有答案,
返回: "knowledge_not_found"
这就很重要。
因为:
一个可靠的 AI 应用,不只是告诉模型"做什么",还要告诉模型"什么时候不能做"。
一个工程化的 System Prompt
依旧个人知识库 AI 助手:
你是一名个人知识库 AI 助手。
职责:
- 整理用户学习记录
- 对知识进行分类
- 提取关键词
- 生成简短摘要
行为规则:
- 只能根据用户提供的知识内容进行判断。
- 不得编造用户没有提供的信息。
- 如果信息不足,明确说明信息不足。
- 不得修改用户原始知识的事实含义。
分类规则: category 必须从以下类别中选择:
- Python
- Machine Learning
- Deep Learning
- LLM
- Transformer
- RAG
- Agent
输出规则: 必须输出合法 JSON。 不得输出 JSON 之外的文字。
注意:
这里定义的是:
整个助手的长期行为。
User Prompt 就简单很多
例如:
请分析下面这条学习记录:
我今天学习了Transformer的Self-Attention,
理解了Attention可以让不同Token之间建立关系。
这样:
System
User
组合起来就成为一次完整请求。
一个非常重要的设计原则
以后写 AI 应用时:
不要把业务规则全部依赖 Prompt。
例如:
你要求:
category只能是:
Python
LLM
RAG
Agent
Prompt 可以告诉模型。
但 Python 程序也应该验证:
python
allowed_categories = {
"Python",
"LLM",
"RAG",
"Agent",
}
然后:
python
if result["category"] not in allowed_categories:
raise ValueError("Invalid category")
为什么?
因为:
LLM 是概率模型,不是传统确定性程序。
Prompt 是约束。
程序验证才是最后一道防线。
这会成为你以后开发生产级 AI 系统时非常重要的原则:
LLM负责智能
程序负责控制
Few-shot Prompt ------ 为什么"给例子"有时比"讲规则"更有效?
什么是 Few-shot?
先看最简单的情况。
我们不给例子:
请判断下面内容属于什么类别:
"我学习了Python中的装饰器。"
这是:
Zero-shot
也就是:
没有给模型具体示例,直接让它完成任务。
现在给模型两个例子:
输入:
我学习了Python列表推导式。
输出:
{ "category": "Python",
"tags": "列表推导式", "Python" }
输入:
我学习了Self-Attention如何建立Token之间的关系。
输出:
{ "category": "Transformer",
"tags": "Self-Attention","Token" }
然后:
输入:
我学习了RAG如何通过向量检索寻找相关知识。
让模型继续输出。
这就是:
Few-shot Prompt
为什么例子这么有用?
因为自然语言规则有时候比较抽象。
例如我们告诉模型:
category只能从以下类别选择:
Python
Transformer
RAG
Agent
模型知道规则。
但它不一定完全知道:
"什么样的内容应该归到 Transformer?"
你可以继续解释:
涉及Attention、Token、Transformer Block
→ Transformer
但规则会越来越长。
而给一个例子:
Self-Attention建立Token之间的关系
→ Transformer
模型就能直接看到:
输入模式 → 输出模式
Few-shot最重要的能力:示范
可以把它理解成:
规则:
"你应该这样做。"
例子:
"看,我就是这样做的。"
对于很多任务:
具体示范比抽象描述更容易让模型理解目标。
Few-shot 不等于训练模型
这是一个非常重要的区别。
假设:
System Prompt
Few-shot Examples
User Input
模型只是:
在当前上下文里参考这些例子。
它没有修改模型参数。
所以:
Training
=
修改模型参数
而:
Few-shot
=
把例子放进Context
这两个概念千万不能混淆。
Zero-shot vs Few-shot
假设任务:
把学习笔记分类。
Zero-shot
请将下面的知识分类到:
Python / Transformer / RAG / Agent
内容:
我学习了Self-Attention。
模型自己判断。
Few-shot
示例1:
输入:
我学习了Python字典。
输出:
{ "category": "Python" }
示例2:
输入:
我学习了Self-Attention。
输出:
{ "category": "Transformer" }
现在分类:
输入:
我学习了Transformer Encoder。
模型会更容易模仿:
{ "category": "Transformer" }
Few-shot最适合什么任务?
特别适合:
分类
正面 / 负面
Python / LLM / RAG
Bug / Feature
风格转换
例如:
输入:
很开心
输出:
积极情绪
固定结构输出
例如:
输入:
张三25岁北京人
输出:
{ "name": "张三", "age": 25, "city": "北京" }
例子质量比数量更重要
这是 Prompt Engineering 很重要的一点。
不是:
例子越多越好
而是:
例子要有代表性,并且质量稳定。
例如你给:
例子1:
Self-Attention → Transformer
例子2:
RAG → Transformer
那你其实在教模型一个错误规律。
模型可能学到:
RAG ≈ Transformer
所以:
Few-shot本身也可能把错误模式教给模型。
一个非常实用的技巧:覆盖边界情况
例如你的分类:
Python
Transformer
RAG
Agent
最简单的例子:
Python代码
→ Python
但是更有价值的例子可能是:
我用Python实现了一个RAG系统
这时候到底:
Python
还是
RAG?
就产生了边界。
如果你的业务规则规定:
按"主要学习主题"分类。
那么你最好给模型一个类似案例:
输入:
我使用Python实现了RAG检索。
输出:
{ "category": "RAG" }
这样模型才能学习:
当多个类别同时出现时,应该怎么决策。
这就比简单地给它:
Python → Python
RAG → RAG
有价值得多。
Structured Output ------ 让 LLM 输出程序可以直接使用的数据
先看最原始的问题
假设我们让 LLM 分析学习笔记:
我今天学习了Self-Attention,可以让不同Token建立关系。
如果只说:
请分析这条笔记。
模型可能输出:
这条笔记主要学习了Transformer中的Self-Attention机制。
它能够让不同Token之间建立联系。
相关标签包括Attention、Token、Transformer。
对于人:
✅ 很好。
对于 Python:
❌ 不方便。
因为程序需要知道:
category在哪里?
tags在哪里?
summary在哪里?
于是我们告诉模型输出 JSON
例如:
请使用JSON格式输出。
{
"category": "...",
"tags": \[\],
"summary": "..."
}
模型可能输出:
{
"category": "Transformer",
"tags": "Self-Attention", "Token",
"summary": "学习了Self-Attention建立Token关系。"
}
这样 Python 就可以:
python
import json
data = json.loads(response)
然后:
python
category = data["category"]
tags = data["tags"]
summary = data["summary"]
已经比自然语言好多了。
但是,"请输出JSON"仍然不够可靠
这是今天最重要的认识。
你不能认为:
Prompt:
请输出JSON
就等于:
一定得到合法JSON
模型还是可能产生:
好的,以下是分析结果:
{
"category": "Transformer",
...
}
或者:
```json
{
"category": "Transformer"
}
或者:
```json
{
"category": "Transformer",
"tags": "Self-Attention"
}
这里最后一个虽然看起来像 JSON:
"tags": "Self-Attention"
但它和我们的预期:
"tags": "Self-Attention"
数据类型已经不一样。
所以:
合法 JSON ≠ 符合你的业务结构。
这就是 Schema
Schema:
可以理解为:
规定数据应该长什么样。
例如我们的知识对象:
json
{
"category": "Transformer",
"tags": ["Self-Attention", "Token"],
"summary": "学习了Self-Attention建立Token之间的关系。"
}
我们定义:
category
必须是字符串
tags
必须是字符串数组
summary
必须是字符串
甚至进一步:
category 只能是:
Python
Machine
Learning
Deep Learning
LLM
Transformer
RAG
Agent
unknown
这样结构就非常明确了。
三个层次
现在把你前面学过的知识串起来:
Level 1:自然语言
请帮我分类这条知识。
约束非常弱。
Level 2:JSON Prompt
请输出JSON:
{
"category": "...",
"tags": \[\],
"summary": "..."
}
结构更明确。
Level 3:Structured Output / Schema
告诉 API:
category
→ string
→ enum
tags
→ arraystring
summary
→ string
这时候系统可以在接口层面帮助约束输出结构。
这比单纯在 Prompt 里说一句:
"请输出 JSON"
可靠得多。
为什么 Structured Output 对 AI 应用非常重要?
因为传统程序喜欢:
确定的数据结构
例如:
knowledge = {
"title": "...",
"content": "...",
"tags": \[\] }
Python知道:
tags
一定是list
但是 LLM:
本质是概率生成系统。
它可能输出:
tags
也可能:
tag
也可能:
keywords
也可能:
tags = "Self-Attention"
这就是:
LLM 和传统程序之间的接口问题。
Structured Output就是在解决这个问题。
LLM负责什么?Python负责什么?
这个问题一定要记住。
LLM擅长:
理解自然语言
分类
摘要
提取信息
推理
生成内容
Python擅长:
数据校验
数据库操作
文件读写
权限控制
流程控制
异常处理
业务规则
所以好的 AI 应用不是:
LLM负责一切
而是:
LLM负责智能部分
Python负责确定性部分
例如:
LLM:
判断这条笔记属于Transformer还是RAG
Python:
python
if category not in allowed_categories:
raise ValueError(...)
这就是我们前面反复强调的:
LLM负责智能,代码负责控制。
第一次 API 调用
API 调用本质
你以前调用:
python
json.load()
是 Python 调用本地库。
LLM API 则是:
Python
↓
HTTP Request
↓
远程模型服务
↓
HTTP Response
↓
JSON
也就是:
Python程序
|
| Request
|
v
LLM API
|
| Response
|
v
Python程序
一个请求通常包含什么?
概念上:
json
{
"model": "...",
"messages": [
{
"role": "system",
"content": "..."
},
{
"role": "user",
"content": "..."
}
]
}
这里你已经能看懂很多东西:
model
→ 使用哪个模型
system
→ System Prompt
user
→ 用户任务
为什么 messages 是数组?
因为一次对话不是只有一个字符串。
可以理解成:
System
↓
User
↓
Assistant
↓
User
↓
Assistant
这是一个消息序列。
以后 Agent 里的上下文管理,也会建立在类似的消息结构上。