System Prompt vs User Prompt:一个管「你是谁」,一个管「你要什么」
🦞 一只用 AI Agent 搭副业产线的程序员
上篇我们说了 Prompt 要当代码写。但代码需要架构。
这篇文章给你 Prompt 架构的第一层:System Prompt 和 User Prompt 的分工。很多人混在一起用,结果就是 Prompt 一长就失控。
别混。这两个角色天然不同。
先搞清楚:AI 收到的不是一段话,是两个人
每次调用 API,你传的 Messages 数组里有一个 role 字段。可以填三种:
| Role | 谁说的 | 干什么的 |
|---|---|---|
system |
你(开发者) | 定义 AI 的行为规则、角色、边界 |
user |
用户 | 具体的任务、问题、输入 |
assistant |
AI | 历史回复(多轮对话用) |
这三者的关系像什么?
System Prompt = 员工手册(入职第一天发的,管所有行为)
User Prompt = 任务单(每天变化的,管这一个任务)
Assistant = 工作日志(之前干了啥,管上下文连续性)
一个反例:混在一起会怎样
很多人这样写 Prompt:
你是一个优秀的 Go 后端开发,有 10 年经验,擅长写高性能代码。
你的回答要简洁、准确、代码完整可运行。不要写废话。
现在帮我写一个 LRU 缓存。
哦对了,用泛型。要有测试。还有……
这有什么问题?角色定义和具体任务搅在一起。 每次新任务,你都要把角色定义再说一遍。想复用角色设定?复制粘贴。想换一个角色?全删了重写。
更糟的是——如果你在一个循环 Agent 里用这种 Prompt,每一轮都要重复发送角色描述。10 轮对话,角色重复了 10 次,全是钱的流失。
正确的分层
// System Prompt —— 一次定义,永久复用
systemPrompt := `你是 Go 后端开发专家。
行为规则:
1. 代码完整可编译,不省略 import 和 package 声明
2. 所有公开函数包含 godoc 注释
3. 不确定的 API 明确说"不确定",不编造
4. 不要解释代码为什么这样写,除非被问到
输出约束:
- 代码块使用 ```go 标记
- 错误处理不要用 panic,用 fmt.Errorf 包装
- 时间相关的逻辑使用 time.Time,不要用字符串`
// User Prompt —— 每次不同,只管任务
userPrompt := `用 Go 泛型实现一个线程安全的 LRU 缓存。
要求:
- 支持 Get、Put、Len 方法
- 容量满了淘汰最久未使用的
- 并发安全`
这样分开后,System Prompt 你可以复用 100 次、1000 次。User Prompt 只管这一次的任务。
用 JSON 配置驱动 Prompt 管理
更进一步——把 System Prompt 从代码里抽出来,用配置文件管理:
{
"roles": {
"go-backend-dev": {
"name": "Go 后端开发专家",
"system": "你是 Go 后端开发专家。代码完整可编译,不确定的 API 明确说不知道。输出简洁精准。",
"temperature": 0.1,
"max_tokens": 500
},
"code-reviewer": {
"name": "代码审查员",
"system": "你是代码审查员。只找问题,不表扬。按严重程度排序:高危 > 中危 > 低危。每个问题标注行号。",
"temperature": 0.0,
"max_tokens": 800
},
"doc-writer": {
"name": "技术文档写手",
"system": "你是技术文档写手。写中文 godoc 风格注释。准确、简洁、不说废话。",
"temperature": 0.3,
"max_tokens": 1000
},
"rubber-duck": {
"name": "小黄鸭调试助手",
"system": "你是小黄鸭调试助手。用户会向你解释代码逻辑。你的任务是通过提问帮他发现思维盲区。不要直接给答案。用苏格拉底式提问。",
"temperature": 0.7,
"max_tokens": 300
}
}
}
然后在 Go 代码里加载:
package main
import (
"encoding/json"
"fmt"
"os"
)
type RoleConfig struct {
Name string `json:"name"`
System string `json:"system"`
Temperature float64 `json:"temperature"`
MaxTokens int `json:"max_tokens"`
}
type Config struct {
Roles map[string]RoleConfig `json:"roles"`
}
type PromptManager struct {
config *Config
}
func NewPromptManager(configPath string) (*PromptManager, error) {
data, err := os.ReadFile(configPath)
if err != nil {
return nil, err
}
var cfg Config
json.Unmarshal(data, &cfg)
return &PromptManager{config: &cfg}, nil
}
func (pm *PromptManager) BuildMessages(roleKey string, userPrompt string) ([]Message, float64, int, error) {
role, ok := pm.config.Roles[roleKey]
if !ok {
return nil, 0, 0, fmt.Errorf("未知角色: %s", roleKey)
}
messages := []Message{
{Role: "system", Content: role.System},
{Role: "user", Content: userPrompt},
}
return messages, role.Temperature, role.MaxTokens, nil
}
func main() {
pm, _ := NewPromptManager("roles.json")
// 换角色只需要换一个 key
messages, temp, maxTok, _ := pm.BuildMessages("go-backend-dev",
"写一个 WebSocket 心跳检测函数")
result := callLLM(messages, temp, maxTok)
fmt.Println(result)
// 同一个代码库,不同的角色
messages2, temp2, maxTok2, _ := pm.BuildMessages("code-reviewer",
result) // 把刚生成的代码丢给审查员
review := callLLM(messages2, temp2, maxTok2)
fmt.Println(review)
}
切换角色现在是一行代码的事。团队里不同的人可以复用同一个角色库。
System Prompt 写什么的 4 层模型
| 层 | 内容 | 示例 |
|---|---|---|
| 第 1 层:角色定义 | AI 是什么身份 | 「你是 Go 后端开发专家」 |
| 第 2 层:行为规则 | 怎么做事 | 「代码完整可编译,不确定的说不知道」 |
| 第 3 层:输出约束 | 输出什么格式 | 「用 ```go 代码块,不输出解释」 |
| 第 4 层:禁止事项 | 不要做什么 | 「不要编造 API,不要用 panic」 |
第 1 层是必须的,第 2-4 层根据任务复杂度选配。简单任务可能只需要第 1 层 + 第 3 层。
一个真实的坑:System Prompt 太长
我一开始给 Agent 写的 System Prompt 有 3000 字,规定了各种场景下的行为。结果呢?
AI 经常「忘记」后面的规则。因为 System Prompt 太长,离得远的规则权重降低了。
后来我优化成 200 字以内,只保留最重要的 3-4 条规则。准确率反而提高了。
System Prompt 不是越长越好。是越精越好。
User Prompt 怎么写:三段式模板
[任务] 用 Go 实现一个带过期时间的本地缓存。
[输入] 无
[输出] 完整可编译的 Go 代码,包含:
- Set(key, value, ttl) 方法
- Get(key) (value, bool) 方法
- 过期自动清理机制
三段式:任务 + 输入 + 输出。简洁明确,不给 AI 发挥空间。
总结:一张表分清 System 和 User
| System Prompt | User Prompt | |
|---|---|---|
| 谁写 | 你(开发者) | 你(或最终用户) |
| 频率 | 写一次,复用多次 | 每次任务不同 |
| 内容 | 角色、规则、约束 | 具体任务、输入数据 |
| 维护方式 | JSON 配置文件 | 代码里拼接或模板 |
| 长度 | 尽量短,200 字内 | 根据任务复杂度 |
| 类比 | 员工手册 | 任务单 |
下一篇我们搞一个实验——同一个任务,用 Few-shot(给示例)和 Zero-shot(不给示例)各跑 50 次,统计准确率差异。告诉你什么时候该给示例,什么时候给了反而坏事。
关注我,别错过。
🦞 一只用 AI Agent 搭副业产线的程序员
全平台同名:虾哥不加班
需要定制 AI 工具?来聊聊 → lob_ai
更多推荐




所有评论(0)