AI Agent 的灵魂对话:Prompt Engineering 系统提示词设计的艺术与工程

系列文章

本篇是 AI Agent 深度解析系列的第 9 篇。以下是系列完整目录,建议收藏作为学习索引。

🏗️ 基础理论篇

  1. 从零理解 RAG:检索增强生成完整指南
  2. Embedding 向量化的魔法:从文本到向量的数学之旅与 Java 实战
  3. 理解 AI Agent 的大脑:ReAct 模式从入门到实战
  4. Spring AI 核心架构全解析:从 ChatModel 到 Advisor Chain 的设计哲学

🧩 核心组件篇

  1. AI Agent 的记忆系统:从 ChatMemory 到持久化记忆的 Java 实战
  2. AI Agent 的记忆力是怎么实现的——LangChain4j Memory 机制深度解析
  3. AI Agent 的工具箱:深入理解 Tool Use 与 Spring AI Function Calling 实战
  4. 让 AI 学会”说人话”——Spring AI 结构化输出实战
  5. AI Agent 的灵魂对话:Prompt Engineering 系统提示词设计的艺术与工程

🏛️ 架构设计篇

  1. AI Agent 的规划大脑:从任务分解到自适应执行策略
  2. AI Agent 的工作流编排:从顺序链到自适应 DAG 的 Java 实战
  3. AI Agent 团队协作:多 Agent 系统架构设计与 Java 实战
  4. Agent 间如何对话:A2A 协议深度解析与 Java 实战

🔍 知识检索篇

  1. AI Agent 的知识检索引擎:从向量搜索到智能检索策略的 Java 实战
  2. 当 RAG 遇上知识图谱:GraphRAG 原理与 Java 实战
  3. 当 RAG 遇到 Agent:Agentic RAG 的架构设计与 Java 实战
  4. MCP 模型上下文协议:AI 的万能接口与 MCP Server 实战

🚀 进阶能力篇

  1. AI Agent 的推理引擎:从 Chain-of-Thought 到推理模型的深度解析与 Java 实战
  2. AI Agent 的多模态感知:从图片理解到语音交互的 Java 实战
  3. AI Agent 的自我反思与经验学习:从错误中进化的 Java 实战
  4. AI Agent 的上下文工程与 Token 预算管理:从窗口压缩到成本优化的 Java 实战
  5. AI Agent 的人机协作:从 Human-in-the-Loop 到渐进式自治的 Java 实战

🛡️ 生产保障篇

  1. AI Agent 的安全防线:Prompt 注入防御与生产级安全防护实战
  2. AI Agent 的可观测性:从链路追踪到成本监控的 Java 实战
  3. AI Agent 的流式响应与实时交互:从 SSE 到 WebSocket 的 Java 实战
  4. AI Agent 的容错与韧性:从错误处理到生产级可靠性保障的 Java 实战
  5. AI Agent 评估与优化:从基准测试到生产环境的质量守护实战
  6. AI Agent 的成本优化:从模型路由到缓存策略的 Java 实战

🧭 全景总结

  1. 从理论到生产:AI Agent 全景知识图谱与 Java 开发者成长路线

写在前面

如果你读过本系列之前的 ReAct 模式、Tool Use、Memory 记忆系统、RAG 检索增强 等文章,你会发现一个共同点——所有这些能力的”开关”,都藏在 Prompt 里。

ReAct 的思考链靠 Prompt 驱动,Tool Use 的工具描述靠 Prompt 传递,Memory 的上下文靠 Prompt 拼接,RAG 的检索结果靠 Prompt 注入。Prompt 就像 Agent 的灵魂,写得好,Agent 聪明得像人;写得差,Agent 蠢得像块石头。

今天这篇,我们聊聊 Prompt Engineering——不是那种”请你帮我写个 Prompt”的入门科普,而是从工程视角出发:System Prompt 的底层原理是什么?为什么有些 Prompt 写法效果好?在 Java 生态中,Spring AI 和 LangChain4j 分别怎么管理 Prompt?企业级场景下怎么组织多层提示词架构?

一、Prompt 的本质:你和模型之间的”合约”

1.1 先搞清楚一个认知误区

很多人以为 Prompt 就是”给模型写的一段话”。这个理解没错,但太浅了。

Prompt 本质上是你和模型之间的一份合约。你在这份合约里规定了模型的角色、行为边界、输出格式、思考方式。模型拿到这份合约后,会在它的概率空间里找到一个最符合合约描述的”人格切片”。

这就解释了为什么”你是一个有 10 年经验的 Java 架构师”和”回答以下问题”会让同一个模型给出完全不同质量的答案——不是模型变聪明了,而是你激活了不同的参数子空间。

1.2 System Prompt vs User Prompt:两种不同的”信号”

在 Chat API 中,消息分为三种角色:

1
2
3
4
5
┌─────────────────────────────────────────────────────┐
│ System Prompt → "你是谁,怎么做事"(底层指令) │
│ User Prompt → "用户想问什么"(具体任务) │
│ Assistant Prompt → "模型之前说了什么"(对话历史) │
└─────────────────────────────────────────────────────┘

System Prompt 之所以特殊,是因为它在 Attention 计算中通常被赋予更高的权重。虽然 OpenAI 等厂商没有公开具体的权重机制,但从实验结果来看,System Prompt 的指令对模型行为的约束力远强于同等长度的 User Prompt。

用一个类比:User Prompt 像是”今天我想吃什么”,System Prompt 像是”我是一个素食主义者”。前者改变一次决策,后者改变所有决策。

1.3 Token 经济学:为什么 Prompt 长度很重要

每个 Prompt 都要消耗 Token,而 Token 意味着钱和延迟。理解 Token 经济学是工程化 Prompt 设计的基础:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌───────────────────────────────────────────────────────┐
│ Prompt 长度的三个代价 │
│ │
│ 💰 成本:每千 Token 都要花钱 │
│ - GPT-4o System Prompt 2000 Token ≈ ¥0.04/次 │
│ - 100 个并发用户 × 10 次/天 = ¥40/天 │
│ │
│ ⏱️ 延迟:Prompt 趱长,首 Token 延迟越高 │
│ - 1000 Token → ~200ms │
│ - 4000 Token → ~500ms │
│ - 16000 Token → ~1500ms │
│ │
│ 🧠 注意力稀释:信息越多,模型越容易"走神" │
│ - Lost in the Middle 现象:中间信息容易被忽略 │
│ - 关键指令应放在开头或结尾 │
└───────────────────────────────────────────────────────┘

“Lost in the Middle” 是 2023 年斯坦福的一项研究发现:当上下文很长时,模型对开头和结尾的信息注意力最强,对中间的信息注意力最弱。这意味着——

你的 System Prompt 如果很长,关键指令一定要放在最前面或最后面。

二、System Prompt 设计的五层架构

一个好的 System Prompt 不是随手写的几句话,而是有结构的。我把它总结为五层架构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
┌─────────────────────────────────────────────────────────┐
│ 第 1 层:角色定义 │
│ "你是一个企业级 Java 架构师助手" │
├─────────────────────────────────────────────────────────┤
│ 第 2 层:能力边界 │
│ "你擅长 Spring Boot、微服务、数据库设计" │
│ "你不会提供投资建议或医疗诊断" │
├─────────────────────────────────────────────────────────┤
│ 第 3 行为规范 │
│ "回答先给结论,再展开解释" │
│ "代码示例用 Java 21,Spring Boot 3.x" │
│ "不确定的事情明确说不确定" │
├─────────────────────────────────────────────────────────┤
│ 第 4 层:输出格式 │
│ "用 Markdown 格式输出" │
│ "代码块标注语言" │
│ "长回答分小节" │
├─────────────────────────────────────────────────────────┤
│ 第 5 层:动态注入 │
│ "当前用户角色:{role}" │
│ "检索到的相关文档:{context}" │
│ "可用工具列表:{tools}" │
└─────────────────────────────────────────────────────────┘

为什么要分层? 因为每一层的更新频率不同:

层级 更新频率 典型场景
角色定义 几乎不变 产品定型后就固定
能力边界 偶尔调整 新增能力或合规要求变化
行为规范 随迭代优化 发现模型回复质量问题时调整
输出格式 按场景切换 给用户的 vs 给系统的格式不同
动态注入 每次请求 用户信息、检索结果、工具列表

分层设计让你不需要每次都重写整个 Prompt,只需要替换变化的部分。

下图展示了 System Prompt 的五层架构——从固定的角色定义到每次请求都变化的动态注入:

System Prompt五层架构:角色定义→能力边界→行为规范→输出格式→动态注入,更新频率递增

如图所示,五层架构按更新频率从低到高排列:角色定义(蓝色)几乎不变,产品定型后就固定;能力边界(紫色)偶尔调整,比如新增能力或合规要求变化;行为规范(橙色)随迭代优化,发现模型回复质量问题时调整;输出格式(绿色)按场景切换,给用户的和给系统的格式不同;动态注入(红色)每次请求都变化,包括用户信息、检索结果和工具列表。分层设计让你不需要每次都重写整个 Prompt,只需要替换变化的部分。

三、核心技巧:从原理到实践

3.1 Chain-of-Thought(CoT):让模型”想出来”

CoT 是最经典的 Prompt 技巧。核心思想很简单:让模型先想再说。

原理:Transformer 的每一层都是对上一层表示的”精炼”。当你要求模型”一步步思考”时,你在给模型更多的计算步骤来处理问题——相当于从 12 层 Transformer 的思考,变成了”12 层 × N 步”的思考。

1
2
3
4
5
6
7
8
9
10
11
12
❌ 普通 Prompt:
Q: 一个商店有 23 个苹果,卖了 7 个,又进了 12 个,还剩多少?
A: 28

✅ CoT Prompt:
Q: 一个商店有 23 个苹果,卖了 7 个,又进了 12 个,还剩多少?
请一步步思考。
A:
- 初始:23 个
- 卖了 7 个:23 - 7 = 16 个
- 又进了 12 个:16 + 12 = 28 个
- 答案:28 个

对于 Agent 场景,CoT 的价值更大。还记得 ReAct 模式 中的 Thought-Action-Observation 循环吗?ReAct 本质上就是 CoT 的工程化实现——模型在执行工具调用前,先用自然语言描述自己的推理过程。

3.2 Few-shot:用例子教模型”格式”

Few-shot 比”请用以下格式输出”有效 10 倍。原因很直观:模型通过示例学格式,比通过描述学格式快得多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
System Prompt:
你是一个代码审查助手。请按以下示例的格式输出审查结果。

示例输入:
public void process(String name) {
String sql = "SELECT * FROM users WHERE name = '" + name + "'";
db.execute(sql);
}

示例输出:
🔴 严重:SQL 注入漏洞
- 位置:process() 方法第 2 行
- 问题:直接拼接用户输入构造 SQL
- 修复:使用 PreparedStatement 参数化查询
- 影响:攻击者可执行任意 SQL

请审查以下代码:
{code}

关键要点:

  • 示例要有代表性,覆盖你期望的输出风格
  • 示例要有多样性,包括简单和复杂的 case
  • 示例数量通常 2-3 个就够,太多会浪费 Token

3.3 自我一致性(Self-Consistency):多数投票

这是 2022 年 Google 提出的技术,核心思想是:同一个问题问模型多次,取多数票。

1
2
3
4
5
6
7
8
9
温度 = 0.7 时:

第 1 次回答:答案是 A
第 2 次回答:答案是 A
第 3 次回答:答案是 B
第 4 次回答:答案是 A
第 5 次回答:答案是 A

多数票 → A(4/5)

在 Agent 场景中,这可以用于关键决策节点——比如让 Agent 对”是否需要调用工具”做多次判断,减少误触发。

3.4 结构化 Prompt:用 XML/JSON 约束输出

还记得 Spring AI 结构化输出 那篇文章吗?Prompt 层面的结构化约束是另一种思路——直接在 Prompt 中告诉模型”你只能输出以下格式”:

1
2
3
4
5
6
7
8
9
10
11
12
13
System Prompt:
分析用户的问题,输出以下 JSON 格式:

{
"intent": "分类意图",
"confidence": 0.0-1.0,
"entities": [
{"type": "实体类型", "value": "实体值"}
],
"response_strategy": "direct|search|clarify"
}

只输出 JSON,不要输出其他内容。

为什么结构化输出在 Prompt 层面很重要?

因为 Agent 的下游逻辑需要解析模型输出。如果模型返回自然语言,你需要再写一层解析逻辑;如果模型直接返回 JSON,你可以直接 JSON.parse(),省掉一层 NLU。

结构化输出 = Prompt 层面的 Schema 约束 + 生成层面的 Grammar 约束。两者配合才能实现可靠的结构化输出。

四、Prompt 与 Agent 组件的交互

Prompt 不是孤立存在的。在 Agent 架构中,Prompt 是连接各个组件的”总线”。

4.1 Prompt + Memory

当你为 Agent 添加 Memory 记忆系统 时,记忆是以 Prompt 的形式注入的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
System Prompt:
你是一个个人助手。

[长期记忆]
- 用户是 Java 开发者,偏好 Spring Boot
- 用户在北京,时区 UTC+8
- 用户不喜欢冗长的回答

[短期记忆 - 最近对话]
用户:帮我写个排序算法
助手:好的,这是冒泡排序...
用户:有没有更快的?
助手:快速排序更适合...

[当前问题]
用户:{new_message}

这里有个关键的工程问题:记忆注入的位置和格式。

研究表明,记忆放在 System Prompt 的中间偏后位置效果最好——既不会覆盖角色定义(开头),也不会被当前问题挤掉注意力(末尾)。

4.2 Prompt + RAG

在 RAG 场景 中,检索到的文档需要注入 Prompt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
System Prompt:
基于以下参考资料回答用户问题。
如果参考资料不足以回答,请明确说明。
不要编造参考资料中没有的信息。

---

参考资料:
[1] 文档标题:Spring Boot 自动配置原理
内容:Spring Boot 的自动配置基于 @EnableAutoConfiguration...

[2] 文档标题:Spring Boot Starter 机制
内容:Starter 是一组依赖的集合...

---

用户问题:{question}

RAG Prompt 的三个陷阱:

  1. 信息过载:塞太多检索结果,反而让模型”迷路”。通常 Top 3-5 条结果足够。
  2. 来源冲突:不同文档说法矛盾时,Prompt 要给模型”裁判规则”(如优先信源)。
  3. 幻觉转移:模型可能忽略参考资料,直接用自身知识回答。需要强约束:”只基于参考资料回答”。

4.3 Prompt + Tool Use

在 Tool Use 场景 中,工具的描述本身就是 Prompt 的一部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
System Prompt:
你可以使用以下工具:

## search_knowledge_base
描述:搜索企业知识库
参数:
- query (string, 必填): 搜索关键词
- top_k (int, 可选): 返回结果数量,默认 5
使用场景:当用户询问公司内部知识时使用

## send_email
描述:发送邮件
参数:
- to (string, 必填): 收件人邮箱
- subject (string, 必填): 邮件主题
- body (string, 必填): 邮件正文
使用场景:当用户要求发送邮件时使用

---
请根据用户的问题,判断是否需要调用工具。
如果需要,输出工具调用的 JSON 格式。
如果不需要,直接回答用户问题。

工具描述的质量直接影响调用准确率。 一个常见的坑是工具描述太简略——搜索知识库 这五个字的信息量远不如上面那段带参数说明和使用场景的描述。

五、Java 生态实战:Spring AI 与 LangChain4j

5.1 Spring AI 的 Prompt 模板引擎

Spring AI 提供了 PromptTemplate 类,基于 ST4(StringTemplate 4)模板引擎:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.stereotype.Service;

@Service
public class AgentPromptService {

// 系统提示词模板
private static final String SYSTEM_PROMPT = """
你是一个 {role}。

## 能力
{capabilities}

## 行为规范
- 回答先给结论,再展开解释
- 代码示例使用 {language} {version}
- 不确定的事情明确说"我不确定"

## 输出格式
{output_format}
""";

// RAG 上下文注入模板
private static final String RAG_PROMPT = """
基于以下参考资料回答用户问题。
如果参考资料不足,说明信息不完整。

---
参考资料:
{context}
---

用户问题:{question}
""";

/**
* 构建完整的 System Prompt
*/
public Prompt buildSystemPrompt(String role, String capabilities,
String language, String version,
String outputFormat) {
PromptTemplate template = new PromptTemplate(SYSTEM_PROMPT);

return new Prompt(
List.of(template.createMessage(
Map.of(
"role", role,
"capabilities", capabilities,
"language", language,
"version", version,
"output_format", outputFormat
),
org.springframework.ai.chat.model.ChatModelMessage.SYSTEM
))
);
}

/**
* 构建带 RAG 上下文的 Prompt
*/
public Prompt buildRagPrompt(String question, List<String> references) {
// 将检索结果格式化为带编号的引用
String formattedContext = IntStream.range(0, references.size())
.mapToObj(i -> "[%d] %s".formatted(i + 1, references.get(i)))
.collect(Collectors.joining("\n\n"));

PromptTemplate template = new PromptTemplate(RAG_PROMPT);
return new Prompt(template.create(
Map.of("context", formattedContext, "question", question)
));
}
}

**Spring AI Prompt 的核心设计思路是”模板化 + 结构化”**。PromptTemplate 把 Prompt 当作带变量的模板,运行时填充变量。这比字符串拼接优雅得多,也更安全(避免注入攻击)。

5.2 LangChain4j 的 SystemMessage 构建

LangChain4j 的 API 风格更函数式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
import dev.langchain4j.service.TokenStream;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.ToolExecutionResultMessage;
import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.Result;

import java.util.List;
import java.util.Map;

// 1. 定义助手接口(声明式 System Prompt)
public interface CodeReviewAssistant {

@SystemMessage("""
你是一个高级 Java 代码审查专家。

## 审查维度
1. 安全性:SQL 注入、XSS、权限绕过
2. 性能:N+1 查询、内存泄漏、锁竞争
3. 可维护性:命名、抽象、单一职责
4. 健壮性:异常处理、空指针、边界条件

## 输出格式
对每个问题输出:
- 严重等级:🔴 严重 / 🟡 警告 / 🔵 建议
- 问题描述
- 修复建议(附代码)

## 约束
- 只关注 Java/Spring Boot 代码
- 忽略代码风格问题(交给 Checkstyle)
""")
String review(@UserMessage String code);
}

// 2. 使用 AiServices 构建助手
ChatLanguageModel model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o")
.temperature(0.3) // 代码审查用低温度,减少随机性
.build();

CodeReviewAssistant assistant = AiServices.create(
CodeReviewAssistant.class, model
);

// 3. 调用
String review = assistant.review("""
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
String sql = "SELECT * FROM users WHERE id = " + id;
return jdbcTemplate.queryForObject(sql, User.class);
}
""");

System.out.println(review);

LangChain4j 的 @SystemMessage 注解方式有一个巨大优势:System Prompt 和业务代码紧耦合,重构时不会遗漏。Spring AI 的模板方式则更灵活,适合 Prompt 需要动态组合的场景。

5.3 两种框架的 Prompt 管理对比

维度 Spring AI LangChain4j
Prompt 定义 PromptTemplate + 字符串模板 @SystemMessage 注解
变量替换 Map<String, Object> 填充 @V 注解 + 参数绑定
多轮对话 手动管理 List<Message> ChatMemory 自动管理
工具描述 @Tool 注解 + FunctionCallback @Tool 注解 + ToolSpecification
灵活度 高(模板可外部化到文件/数据库) 中(注解编译期确定)
类型安全 低(Map 传参) 高(接口强类型)
适用场景 Prompt 频繁变化、需要 A/B 测试 Prompt 稳定、追求代码可维护性

我的建议:两者可以混用。System Prompt 用 Spring AI 的模板引擎管理(方便热更新),业务接口用 LangChain4j 的注解方式(类型安全)。

六、企业级多层 Prompt 架构

在真实的企业场景中,Prompt 不是一段文字,而是一个分层的系统。

6.1 三层 Prompt 架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌───────────────────────────────────────────────────────────┐
│ Layer 1:基础层(Base Prompt) │
│ ├── 角色定义、合规约束、输出格式 │
│ ├── 存储位置:代码库 / 配置中心 │
│ └── 更新频率:月级 │
├───────────────────────────────────────────────────────────┤
│ Layer 2:场景层(Scenario Prompt) │
│ ├── 客服场景 / 数据分析场景 / 代码审查场景 │
│ ├── 存储位置:Prompt 管理平台 │
│ └── 更新频率:周级 │
├───────────────────────────────────────────────────────────┤
│ Layer 3:动态层(Dynamic Context) │
│ ├── RAG 检索结果、用户画像、对话历史 │
│ ├── 存储位置:内存(运行时拼接) │
│ └── 更新频率:每次请求 │
└───────────────────────────────────────────────────────────┘

6.2 代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;

@Service
public class PromptOrchestrator {

private final PromptRepository promptRepository;
private final UserProfileService userProfileService;
private final RagService ragService;

/**
* 组装完整的 Prompt
*/
public Prompt assemble(String userId, String scenario,
String userMessage) {
List<ChatMessage> messages = new ArrayList<>();

// Layer 1: 基础层 — 从配置加载
String basePrompt = loadBasePrompt();
messages.add(new SystemMessage(basePrompt));

// Layer 2: 场景层 — 从 Prompt 管理平台加载
String scenarioPrompt = promptRepository.findByScenario(scenario);
messages.add(new SystemMessage(scenarioPrompt));

// Layer 3: 动态层 — 运行时拼接
UserProfile profile = userProfileService.getProfile(userId);
List<Document> docs = ragService.retrieve(userMessage, 5);

String dynamicContext = buildDynamicContext(profile, docs);
messages.add(new SystemMessage(dynamicContext));

// 用户消息
messages.add(new UserMessage(userMessage));

return new Prompt(messages);
}

/**
* 构建动态上下文
*/
private String buildDynamicContext(UserProfile profile,
List<Document> docs) {
StringBuilder sb = new StringBuilder();
sb.append("## 用户信息\n");
sb.append("- 角色:").append(profile.getRole()).append("\n");
sb.append("- 偏好语言:").append(profile.getPreferredLang()).append("\n");
sb.append("- 历史交互次数:").append(profile.getInteractionCount()).append("\n\n");

if (!docs.isEmpty()) {
sb.append("## 参考资料\n");
for (int i = 0; i < docs.size(); i++) {
sb.append("[").append(i + 1).append("] ");
sb.append(docs.get(i).getTitle()).append("\n");
sb.append(docs.get(i).getContent()).append("\n\n");
}
}

return sb.toString();
}
}

6.3 Prompt 版本管理

在生产环境中,Prompt 需要像代码一样做版本管理:

1
2
3
4
5
6
7
8
9
10
CREATE TABLE prompt_templates (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
scenario VARCHAR(50) NOT NULL, -- 场景标识
version INT NOT NULL, -- 版本号
content TEXT NOT NULL, -- Prompt 内容
is_active BOOLEAN DEFAULT FALSE, -- 是否激活
metrics JSON, -- A/B 测试指标
created_at TIMESTAMP DEFAULT NOW(),
UNIQUE KEY uk_scenario_version (scenario, version)
);

这样做有三个好处:

  1. A/B 测试:同一场景部署两个版本,对比效果
  2. 快速回滚:新 Prompt 效果差,一键切回上一版
  3. 效果追踪:记录每个版本的满意度、准确率等指标

七、常见陷阱与最佳实践

7.1 五个致命陷阱

陷阱 1:指令矛盾

1
2
3
❌ 你是一个友好的助手。回答要简洁。
...
对每个问题提供详细、全面的解释,不要遗漏任何细节。

模型收到矛盾指令后,行为会变得不可预测——有时简洁,有时冗长,完全看运气。

陷阱 2:否定指令的滥用

1
2
3
❌ 不要输出代码
❌ 不要提到 Java
❌ 不要太长

模型对否定指令的遵循率远低于肯定指令。原因是模型的训练数据中,”不要做 X”的样本远少于”做 Y”的样本。

更好的写法:

1
2
3
✅ 用纯文字描述方案,不包含代码块
✅ 使用 Python 作为示例语言
✅ 控制在 200 字以内

陷阱 3:角色定义过于宽泛

1
❌ 你是一个全栈开发专家

“全栈”太宽泛,模型无法聚焦。改为:

1
2
3
✅ 你是一个专注于 Spring Boot 后端 + Vue 3 前端的全栈开发者
擅长领域:REST API 设计、数据库优化、前端状态管理
技术栈:Java 21, Spring Boot 3.x, Vue 3, TypeScript, PostgreSQL

陷阱 4:没有给”退路”

1
❌ 回答用户的所有问题

模型遇到超出能力范围的问题会编造答案。加一句”退路”指令:

1
2
✅ 如果你不确定答案,明确说"这个问题我不确定,建议查阅官方文档"
如果问题超出你的知识范围,建议用户咨询相关专家

陷阱 5:Prompt 过长导致性能下降

Prompt 超过 4000 Token 后,模型的指令遵循率开始下降。如果你的 Prompt 太长,考虑:

  • 精简冗余描述
  • 将不常用指令移到动态层
  • 使用 Prompt 压缩技术(如摘要替代原文)

7.2 最佳实践清单

1
2
3
4
5
6
7
8
9
10
✅ 指令用肯定句,不用否定句
✅ 关键指令放在开头或结尾(利用注意力分布)
✅ 用分隔符区分指令和内容(如 --- 或 XML 标签)
✅ 提供 2-3 个 Few-shot 示例
✅ 指定输出格式(Markdown / JSON / XML)
✅ 给模型"退路"(不确定时怎么说)
✅ 控制 System Prompt 在 1000-3000 Token 以内
✅ 做 A/B 测试,用数据驱动 Prompt 优化
✅ 版本管理 Prompt,支持快速回滚
✅ 用低温度(0.1-0.3)处理确定性任务,高温度(0.7-1.0)处理创意任务

八、扩展思考:Prompt Engineering 的边界在哪?

8.1 Prompt 不是万能的

再好的 Prompt 也解决不了这些问题:

  • 模型本身的能力天花板:GPT-3.5 不可能通过 Prompt 变成 GPT-4
  • 需要精确计算的场景:数学计算、日期推算,模型容易出错
  • 实时信息:模型不知道今天的天气和股价
  • 长文本的一致性:超过模型上下文窗口的内容,Prompt 无法弥补

这些问题的解决方案不是更好的 Prompt,而是更好的架构——用工具调用解决计算问题,用 RAG 解决知识问题,用 Memory 解决上下文问题。

8.2 未来的方向

  1. Prompt 自动生成:让 AI 帮你写 Prompt(DSPy 框架已经在做这件事)
  2. Prompt 优化器:自动 A/B 测试,用遗传算法搜索最优 Prompt
  3. 多模态 Prompt:图文混合的 Prompt 设计(图片 + 文字指令)
  4. Prompt-as-Code:Prompt 纳入 CI/CD 流水线,自动化测试和部署

总结

Prompt Engineering 不是”玄学”,而是一门工程学科。它的核心原则可以用三句话概括:

  1. 清晰胜过聪明:模型不需要你的文学才华,它需要你的精确指令
  2. 结构胜过长度:一个 500 Token 的结构化 Prompt,效果远好于 3000 Token 的散文
  3. 迭代胜过完美:没有一稿就完美的 Prompt,持续测试和优化才是正道

回到我们 AI Agent 系列的脉络:ReAct 给了 Agent 思考能力,Tool Use 给了 Agent 动手能力,Memory 给了 Agent 记忆能力,RAG 给了 Agent 知识检索能力,Planning 给了 Agent 规划能力,结构化输出 让 Agent 能”说人话”。

而 Prompt Engineering,是让这些能力协调运转的粘合剂。

好的 Prompt 不是让模型更聪明,而是让模型的聪明用对地方。