5分钟快速入门,30分钟深度讲解,学会和LLM"说人话"
时间:30分钟 | 难度:⭐⭐⭐
官方Example信息
- GitHub链接:PromptExample.java
- 文件名:PromptExample.java
- 所在路径:src/main/java/dev/langchain4j/examples/
- 代码行数:约60行
- 难度:中级 ⭐⭐⭐
学习目标
- 理解什么是好的Prompt ✅ 2026-03-07
- 掌握Prompt编写的5个核心原则 ✅ 2026-03-07
- 学会Few-shot Learning技巧 ✅ 2026-03-07
- 学会Chain-of-Thought推理 ✅ 2026-03-07
- 能在实际项目中写出高效的Prompt ✅ 2026-03-07
- 掌握LangChain4J中的Message类型
- 理解SystemMessage、UserMessage、AiMessage的用途
- 学会管理多轮对话的消息历史
- 能优化Message对Token成本的影响
快速速查:Prompt的5个黄金法则
| 法则 | 解释 | 例子 |
|---|---|---|
| 1️⃣ 明确性 | 告诉LLM具体要做什么 | ❌ "帮我分析代码" → ✅ "找出这段代码的性能瓶颈" |
| 2️⃣ 上下文 | 提供足够的背景信息 | ❌ "翻译" → ✅ "把以下Java代码注释翻译成英文" |
| 3️⃣ 约束条件 | 限制输出的格式和长度 | ✅ "用100字以内概括" "用JSON格式" |
| 4️⃣ 示例 | 通过例子说明你要什么 | ✅ Few-shot Learning |
| 5️⃣ 结构化 | 把问题分解成小步骤 | ✅ Chain-of-Thought推理 |
深度讲解
原理:为什么Prompt很重要
LLM本质上是概率模型,它会基于你的输入(Prompt)预测最可能的下一个词。
Prompt质量差:
输入: "代码"
↓
[低信号→低质量输出]
↓
输出: "代码是什么?" (泛泛而谈)
Prompt质量好:
输入: "找出这段Java代码的N+1查询问题,并给出具体的行号"
↓
[高信号→高质量输出]
↓
输出: "第12行的循环中有N+1查询:for (Order o : orders) { db.query(...) }" (精准击中)
Prompt越清楚,LLM的"预测方向"就越准确。
法则1️⃣:明确性 - 告诉LLM具体要做什么
❌ 差的Prompt(模糊)
String poorPrompt = "帮我做代码审查";
LLM不知道:
- 代码在哪里?
- 关注什么方面?(性能?安全?可读性?)
- 输出什么格式?
✅ 好的Prompt(明确)
String goodPrompt = """
请进行Java代码审查,关注以下方面:
1. 性能瓶颈(特别是数据库查询)
2. 内存泄漏风险
3. 线程安全问题
代码:
[代码]
输出格式:
- 问题描述
- 严重程度(高/中/低)
- 修复建议
""";
LLM知道:
- ✅ 关注具体的方面
- ✅ 知道输出格式
- ✅ 能提供有针对性的建议
法则2️⃣:上下文 - 提供背景信息
❌ 缺少上下文
String prompt = "这个SQL查询对吗?\nSELECT * FROM orders";
LLM回答很泛泛:
- 语法对,但不知道表结构
- 可能不适合某个特定场景
✅ 充分的上下文
String prompt = """
我有一个电商系统,表结构如下:
- orders表:id, user_id, total_amount, created_at, status
- order_items表:id, order_id, product_id, quantity, price
业务需求:查询2024年的所有订单,包括订单总金额和商品详情
这个SQL查询对吗?
SELECT o.*, oi.* FROM orders o
JOIN order_items oi ON o.id = oi.order_id
WHERE YEAR(o.created_at) = 2024
""";
LLM能回答:
- ✅ 语法是否正确
- ✅ 是否满足业务需求
- ✅ 是否有性能问题
- ✅ 建议改进方案
法则3️⃣:约束条件 - 限制输出
问题:输出不可控
ChatModel model = OpenAiChatModel.builder().apiKey(...).build();
String prompt = "怎样学习Java?";
String response = model.chat(prompt);
// 输出可能是:
// - 500字的长文章
// - 或者10个简短要点
// - 或者一个故事
// → 不可控!
解决方案1:限制长度
String prompt = """
用100字以内的中文回答:怎样学习Java?
""";
// 输出会是简洁的100字以内
解决方案2:指定格式
String prompt = """
用以下JSON格式回答"怎样学习Java":
{
"steps": ["第一步", "第二步", ...],
"time_required": "3个月",
"resources": ["书籍", "网课", ...]
}
""";
// 输出会是有效的JSON
解决方案3:限制风格
String prompt = """
用简洁、技术性的语言回答(避免冗长的介绍):
怎样学习Java?
""";
法则4️⃣:示例 - Few-Shot Learning
❌ 无示例(Zero-Shot)
String prompt = "给这段代码起个变量名:\nint x = 10;";
// LLM可能回答:
// "变量x代表某个数值"
// → 太泛泛
✅ 有示例(Few-Shot)
String prompt = """
根据上下文,为这些代码变量起个有意义的名字。
示例1:
输入:int x = 30;
上下文:这是员工的年龄
输出:int employeeAge = 30;
示例2:
输入:double y = 9.99;
上下文:这是商品的价格
输出:double productPrice = 9.99;
现在轮到你:
输入:int n = 100;
上下文:这是查询结果的最大数量
输出:
""";
// LLM会按照示例的模式回答:
// 输出:int maxQueryResults = 100;
Few-Shot为什么有效?
LLM通过示例理解你的期望:
- 示例1、2展示了"命名规则"
- 示例展示了"输入→输出"的模式
- LLM会按这个模式处理新问题
法则5️⃣:结构化推理 - Chain-of-Thought
❌ 直接要求答案
String prompt = "这段代码有什么问题?\n[大段代码]";
// LLM可能只说表面问题,忽视深层风险
✅ 要求逐步推理
String prompt = """
分析这段Java代码,按以下步骤回答:
第1步:识别代码的目的
第2步:检查逻辑是否正确
第3步:检查是否有性能问题
第4步:检查是否有安全风险
第5步:给出改进建议
代码:
[大段代码]
""";
// LLM会逐步思考,输出更全面的分析
Chain-of-Thought的威力:
- 强制LLM思考每个步骤
- 避免跳过重要分析
- 输出更有逻辑、更深入
官方Example代码分析
基础Prompt示例
public class PromptExample {
public static void main(String[] args) {
ChatModel model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.build();
// 示例1:基础问答
System.out.println("=== 示例1:基础问答 ===");
String basicPrompt = "Java中什么是多态?";
String basicResponse = model.chat(basicPrompt);
System.out.println(basicResponse);
// 示例2:明确约束条件
System.out.println("\n=== 示例2:明确约束条件 ===");
String constrainedPrompt = "用50字以内用中文解释Java多态";
String constrainedResponse = model.chat(constrainedPrompt);
System.out.println(constrainedResponse);
// 示例3:Few-Shot Learning
System.out.println("\n=== 示例3:Few-Shot Learning ===");
String fewShotPrompt = """
根据以下示例分类,判断下面的代码片段属于哪种设计模式:
示例1:
代码:class Config { private static Config instance;
public static Config getInstance() { ... } }
分类:单例模式
示例2:
代码:new ConcreteProductA(); new ConcreteProductB();
分类:工厂模式
现在判断这段代码:
interface Shape { void draw(); }
class Circle implements Shape { ... }
class Square implements Shape { ... }
分类:
""";
String fewShotResponse = model.chat(fewShotPrompt);
System.out.println(fewShotResponse);
// 示例4:Chain-of-Thought
System.out.println("\n=== 示例4:Chain-of-Thought ===");
String cotPrompt = """
分析代码的时间复杂度,按步骤回答:
第1步:识别循环结构
第2步:计算每层循环的迭代次数
第3步:累乘得到总复杂度
代码:
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
System.out.println(i * j);
}
}
""";
String cotResponse = model.chat(cotPrompt);
System.out.println(cotResponse);
}
}
LangChain4J Message 类型深度讲解
核心概念:Message 的本质
在LangChain4J中,对话不是简单的字符串交互,而是通过**消息对象(Message)**来结构化管理的。这样做的好处是:
字符串方式(不推荐):
request = "你是Java专家" + "\n" + "请审查这段代码" + "\n" + code
→ 容易出错,难以维护
Message方式(推荐):
messages = [
SystemMessage("你是Java专家"),
UserMessage("请审查这段代码:" + code)
]
→ 结构清晰,易于扩展
Message 的层级结构
┌─────────────────────────────────────┐
│ ChatMessage (接口) │
└────────────────┬────────────────────┘
│
┌────────┼────────┐
│ │ │
┌───────▼────┐ ┌─▼──────────┐ ┌────────▼──────┐
│UserMessage │ │ SystemMsg │ │ AiMessage │
└────────────┘ └────────────┘ └────────────────┘
│ │ │
用户说话 系统指令 AI回复
1️⃣ UserMessage - 用户消息
定义:用户发送给LLM的消息
本质:代表用户的输入,LLM基于此生成回复
// 最简单的形式
UserMessage msg = UserMessage.from("请帮我写一个Java排序算法");
// 等价写法
UserMessage msg = new UserMessage("请帮我写一个Java排序算法");
// 在对话中的使用
List<ChatMessage> messages = new ArrayList<>();
messages.add(UserMessage.from("你好"));
messages.add(UserMessage.from("今天天气怎样?"));
String response = model.chat(messages);
UserMessage 的特点:
- ✅ 代表用户的真实意图
- ✅ LLM会优先理解这个消息
- ✅ 可以在对话历史中任何位置
- ❌ 不应该包含系统指令(那是 SystemMessage 的工作)
2️⃣ SystemMessage - 系统消息
定义:给LLM的系统级指令,定义其角色和行为
本质:约束和指导LLM的生成方向
// 基础用法
SystemMessage roleMsg = SystemMessage.from("你是一位资深的Java工程师");
// 完整的系统提示词
String systemPrompt = """
你是一位20年经验的Java架构师。
你的特点:
1. 代码审查时关注性能和安全
2. 解释复杂概念时用通俗语言
3. 提建议时说明理由
4. 代码示例用最新的Java特性
""";
SystemMessage systemMsg = SystemMessage.from(systemPrompt);
// 在对话中使用
List<ChatMessage> messages = new ArrayList<>();
messages.add(systemMsg); // 第一条总是SystemMessage
messages.add(UserMessage.from("请审查这段代码"));
messages.add(UserMessage.from("为什么要这样改?"));
String response = model.chat(messages);
SystemMessage 的作用:
- ✅ 定义LLM的角色和身份
- ✅ 设置输出风格和格式
- ✅ 约束LLM的行为范围
- ✅ 优先级最高,影响整个对话
SystemMessage 应该在对话中的位置:
// ✅ 正确:SystemMessage 在最前面
List<ChatMessage> messages = new ArrayList<>();
messages.add(SystemMessage.from("你是...")); // 第1条
messages.add(UserMessage.from("...")); // 第2条
messages.add(UserMessage.from("...")); // 第3条
// ❌ 错误:SystemMessage 不在最前面
List<ChatMessage> messages = new ArrayList<>();
messages.add(UserMessage.from("...")); // 第1条
messages.add(SystemMessage.from("你是...")); // ❌ 位置不对
3️⃣ AiMessage - AI消息(完整讲解)
定义:LLM生成的回复消息
本质:LLM输出,也是下一轮对话的参考,包含多个重要信息
AiMessage 的结构
// AiMessage 包含的信息
AiMessage {
String text; // AI的文本回复
List<ToolCall> toolCalls; // 工具调用(Agent场景)
Map metadata; // 元数据
}
// 获取方式
Response<AiMessage> response = model.generate(messages);
AiMessage aiMsg = response.content();
// 详细信息
String textContent = aiMsg.text(); // AI回复文本
List<ToolCall> calls = aiMsg.toolCalls(); // 工具调用列表
TokenUsage usage = response.tokenUsage(); // Token使用统计
int inputTokens = usage.inputTokens(); // 输入Token数
int outputTokens = usage.outputTokens(); // 输出Token数
int totalTokens = inputTokens + outputTokens; // 总Token数
AiMessage 的常见用法
用法1:简单问答(不保存消息)
// 一次性调用,不需要保存 AiMessage
UserMessage question = UserMessage.from("2+2等于多少?");
AiMessage answer = model.generate(List.of(question)).content();
System.out.println(answer.text()); // 输出:4
// ❌ 不保存 AiMessage,因为不需要多轮对话
用法2:多轮对话(必须保存)
List<ChatMessage> history = new ArrayList<>();
history.add(SystemMessage.from("你是数学家"));
// 第1轮
history.add(UserMessage.from("什么是质数?"));
Response<AiMessage> resp1 = model.generate(history);
AiMessage aiMsg1 = resp1.content();
history.add(aiMsg1); // ✅ 保存到历史
System.out.println("AI回复:" + aiMsg1.text());
// 第2轮:可以继续对话
history.add(UserMessage.from("100以内有多少个质数?"));
Response<AiMessage> resp2 = model.generate(history);
AiMessage aiMsg2 = resp2.content();
history.add(aiMsg2); // ✅ 保存到历史
System.out.println("AI回复:" + aiMsg2.text());
// → resp2 能看到第1轮的对话,理解上下文
用法3:访问 Token 使用信息
// 获取 Token 使用统计
Response<AiMessage> response = model.generate(messages);
AiMessage aiMsg = response.content();
TokenUsage usage = response.tokenUsage();
// 成本计算
double costPerInputToken = 0.15 / 1000000; // OpenAI GPT-4o-mini 的输入价格
double costPerOutputToken = 0.60 / 1000000; // OpenAI GPT-4o-mini 的输出价格
double inputCost = usage.inputTokens() * costPerInputToken;
double outputCost = usage.outputTokens() * costPerOutputToken;
double totalCost = inputCost + outputCost;
System.out.printf("输入Token: %d, 成本: $%.6f%n", usage.inputTokens(), inputCost);
System.out.printf("输出Token: %d, 成本: $%.6f%n", usage.outputTokens(), outputCost);
System.out.printf("总成本: $%.6f%n", totalCost);
用法4:Agent 中处理工具调用(完整实现)
这是最复杂的场景,需要理解 API 的实际工具调用格式。
4.1 工具定义(发送给 API)
// 定义可用的工具
List<Tool> tools = Arrays.asList(
Tool.function(
"add",
"计算两个数的加法",
Map.of(
"arg0", Map.of("type", "integer"),
"arg1", Map.of("type", "integer")
),
Arrays.asList("arg0", "arg1")
),
Tool.function(
"multiply",
"计算两个数的乘法",
Map.of(
"arg0", Map.of("type", "integer"),
"arg1", Map.of("type", "integer")
),
Arrays.asList("arg0", "arg1")
)
);
// 准备对话
List<ChatMessage> messages = new ArrayList<>();
messages.add(SystemMessage.from("你可以使用数学计算工具"));
messages.add(UserMessage.from("2加3等于多少?"));
// 调用模型(带工具)
Response<AiMessage> response = model.generateWithTools(messages, tools);
4.2 API 的实际响应格式
{
"choices": [{
"finish_reason": "tool_calls",
"message": {
"role": "assistant",
"content": "我来帮你计算 2 加 3:",
"tool_calls": [
{
"id": "toolu_01XaorEWqfHsU31b9ikpYGc9",
"type": "function",
"function": {
"name": "add",
"arguments": "{\"arg0\":2,\"arg1\":3}"
}
}
]
}
}],
"usage": {
"prompt_tokens": 968,
"completion_tokens": 86,
"total_tokens": 1054
}
}
关键点:
finish_reason: "tool_calls"→ LLM 想调用工具tool_calls[].function.name→ 工具名称("add")tool_calls[].function.arguments→ 工具参数(JSON字符串)
4.3 在 LangChain4J 中处理工具调用
// API 返回的 AiMessage
AiMessage aiMsg = response.content();
// 检查是否有工具调用
List<ToolCall> toolCalls = aiMsg.toolCalls();
if (toolCalls != null && !toolCalls.isEmpty()) {
// 有工具调用
for (ToolCall toolCall : toolCalls) {
String toolName = toolCall.id(); // "add"
String argumentsJson = toolCall.arguments(); // "{\"arg0\":2,\"arg1\":3}"
// 解析参数
Map<String, Object> args = parseJsonArguments(argumentsJson);
// args = {"arg0": 2, "arg1": 3}
// 执行工具
String result = executeToolByName(toolName, args); // "5"
// 创建工具结果消息
ToolExecutionResultMessage toolResult =
ToolExecutionResultMessage.from(toolName, result);
// 保存消息序列
messages.add(aiMsg); // ✅ 保存原始 AiMessage
messages.add(toolResult); // ✅ 添加工具结果
}
} else {
// 没有工具调用,AI直接生成了回复
messages.add(aiMsg); // ✅ 保存 AiMessage
}
// 继续调用模型
Response<AiMessage> nextResponse = model.generate(messages);
AiMessage finalAnswer = nextResponse.content();
4.4 生产级别的工具调用处理
@Service
public class ToolCallHandlingService {
private final ChatModel model;
private static final Logger logger = LoggerFactory.getLogger(ToolCallHandlingService.class);
/**
* 处理 LLM 的工具调用请求(可能多轮)
*/
public String handleToolCalls(List<ChatMessage> messages, List<Tool> tools) {
int maxIterations = 10; // 防止无限循环
int iteration = 0;
while (iteration < maxIterations) {
iteration++;
// Step 1: 调用模型
Response<AiMessage> response = model.generateWithTools(messages, tools);
AiMessage aiMsg = response.content();
messages.add(aiMsg); // 保存 LLM 的响应
// Step 2: 检查 finish_reason
String finishReason = getFinishReason(aiMsg);
logger.info("迭代 {}: finish_reason = {}", iteration, finishReason);
if ("stop".equals(finishReason)) {
// LLM 完成了,直接返回答案
return aiMsg.text();
}
if (!"tool_calls".equals(finishReason)) {
// 其他原因(length 等),返回当前内容
logger.warn("非预期的 finish_reason: {}", finishReason);
return aiMsg.text();
}
// Step 3: 处理工具调用
List<ToolCall> toolCalls = aiMsg.toolCalls();
if (toolCalls == null || toolCalls.isEmpty()) {
logger.warn("finish_reason 是 tool_calls 但没有 tool_calls");
return aiMsg.text();
}
boolean hasError = false;
for (ToolCall toolCall : toolCalls) {
try {
String toolName = toolCall.id();
String argumentsJson = toolCall.arguments();
// 执行工具
String toolResult = executeToolSafely(toolName, argumentsJson);
logger.info("工具 {} 执行结果: {}", toolName, toolResult);
// 添加工具结果
ToolExecutionResultMessage toolResultMsg =
ToolExecutionResultMessage.from(toolName, toolResult);
messages.add(toolResultMsg);
} catch (Exception e) {
logger.error("工具执行失败", e);
hasError = true;
// 添加错误结果
ToolExecutionResultMessage errorMsg =
ToolExecutionResultMessage.from(
toolCall.id(),
"错误:" + e.getMessage()
);
messages.add(errorMsg);
}
}
// Step 4: 继续循环,让 LLM 看到工具结果并继续
}
throw new RuntimeException("达到最大迭代次数 " + maxIterations);
}
private String executeToolSafely(String toolName, String argumentsJson) {
try {
Map<String, Object> args = parseArguments(argumentsJson);
return executeToolByName(toolName, args);
} catch (Exception e) {
throw new RuntimeException("执行工具 " + toolName + " 失败:" + e.getMessage(), e);
}
}
private String executeToolByName(String toolName, Map<String, Object> args) {
return switch (toolName) {
case "add" -> {
int a = ((Number) args.get("arg0")).intValue();
int b = ((Number) args.get("arg1")).intValue();
yield String.valueOf(a + b);
}
case "multiply" -> {
int a = ((Number) args.get("arg0")).intValue();
int b = ((Number) args.get("arg1")).intValue();
yield String.valueOf(a * b);
}
default -> throw new IllegalArgumentException("未知工具:" + toolName);
};
}
private Map<String, Object> parseArguments(String json) {
try {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(json, new TypeReference<Map<String, Object>>() {});
} catch (JsonProcessingException e) {
throw new RuntimeException("解析工具参数失败:" + json, e);
}
}
private String getFinishReason(AiMessage aiMsg) {
Map<String, Object> metadata = aiMsg.metadata();
if (metadata != null) {
Object reason = metadata.get("finish_reason");
if (reason != null) {
return reason.toString();
}
}
return "unknown";
}
}
4.5 多轮工具调用的完整示例
用户问题:
"2加3的结果乘以4等于多少?"
工作流程:
┌─────────────────────────┐
│ 轮次1:LLM 看到问题 │
└──────────────┬──────────┘
│
↓ 决定调用 add(2,3)
┌──────────────┴──────────┐
│ finish_reason=tool_calls│
│ tool_call: add(2,3) │
└──────────────┬──────────┘
│
↓ Agent 执行 add(2,3)→5
┌──────────────┴──────────┐
│ 轮次2:LLM 看到结果 5 │
└──────────────┬──────────┘
│
↓ 决定调用 multiply(5,4)
┌──────────────┴──────────────┐
│ finish_reason=tool_calls │
│ tool_call: multiply(5,4) │
└──────────────┬──────────────┘
│
↓ Agent 执行 multiply(5,4)→20
┌──────────────┴──────────┐
│ 轮次3:LLM 看到结果 20 │
└──────────────┬──────────┘
│
↓ 生成最终答案
┌──────────────┴──────────────────┐
│ finish_reason=stop │
│ message: "结果是20" │
└─────────────────────────────────┘
用法5:处理 AiMessage 的元数据
// AiMessage 可能包含额外的元数据
Map<String, Object> metadata = aiMsg.metadata();
if (metadata != null) {
// 访问模型返回的额外信息
Object finishReason = metadata.get("finish_reason"); // "stop", "tool_calls", "length"
Object model = metadata.get("model"); // 实际使用的模型
Object created = metadata.get("created"); // 时间戳
System.out.println("完成原因: " + finishReason);
System.out.println("模型: " + model);
// finish_reason 的含义:
// - "stop" → LLM 正常完成
// - "tool_calls" → LLM 想调用工具
// - "length" → 达到最大 token 限制
// - "content_filter" → 内容被过滤
}
#### AiMessage 提取文本的三种方式
```java
Response<AiMessage> response = model.generate(messages);
AiMessage aiMsg = response.content();
// 方式1:直接调用 text()
String text1 = aiMsg.text();
// 方式2:通过 Response 获取
String text2 = response.content().text();
// 方式3:获取后赋值给新的 Message
messages.add(aiMsg); // 保存到历史
UserMessage nextMsg = UserMessage.from(aiMsg.text() + " 继续...");
AiMessage 的常见陷阱和优化
陷阱1:忘记保存 AiMessage
// ❌ 错误:不保存 AiMessage
messages.add(UserMessage.from("问题1"));
AiMessage response1 = model.generate(messages).content();
System.out.println(response1.text());
// 没有 messages.add(response1)!
// 下一轮对话 response1 看不到第1轮的对话
messages.add(UserMessage.from("问题2"));
AiMessage response2 = model.generate(messages).content();
// → 上下文丢失,对话不连贯
// ✅ 正确做法
messages.add(UserMessage.from("问题1"));
AiMessage response1 = model.generate(messages).content();
messages.add(response1); // 保存
messages.add(UserMessage.from("问题2"));
AiMessage response2 = model.generate(messages).content();
messages.add(response2); // 保存
陷阱2:直接修改 AiMessage
// ❌ 错误:AiMessage 通常是不可变的
AiMessage aiMsg = model.generate(messages).content();
aiMsg.text = "修改后的文本"; // ❌ 可能会失败
// ✅ 正确做法:创建新的 Message
AiMessage original = model.generate(messages).content();
String modified = original.text().replaceAll("旧", "新");
AiMessage newMsg = AiMessage.from(modified);
messages.add(newMsg);
陷阱3:重复获取 TokenUsage
// ❌ 效率低:每次都调用
Response<AiMessage> response = model.generate(messages);
TokenUsage usage1 = response.tokenUsage();
TokenUsage usage2 = response.tokenUsage(); // 重复调用
TokenUsage usage3 = response.tokenUsage(); // 重复调用
// ✅ 正确做法:保存一次
Response<AiMessage> response = model.generate(messages);
TokenUsage usage = response.tokenUsage(); // 只调用一次
int inputTokens = usage.inputTokens();
int outputTokens = usage.outputTokens();
// 多次使用 inputTokens 和 outputTokens
AiMessage 的最佳实践
@Service
public class BestPracticeAiMessageService {
private final ChatModel model;
private static final Logger logger = LoggerFactory.getLogger(BestPracticeAiMessageService.class);
/**
* ✅ 生产级别的 AiMessage 处理
*/
public String chat(List<ChatMessage> history, String userInput) {
// Step 1: 添加用户消息
UserMessage userMsg = UserMessage.from(userInput);
history.add(userMsg);
// Step 2: 调用模型并获取响应
Response<AiMessage> response = model.generate(history);
AiMessage aiMsg = response.content();
TokenUsage usage = response.tokenUsage(); // ✅ 一次性获取
// Step 3: 验证 AiMessage
if (aiMsg == null || aiMsg.text() == null) {
throw new RuntimeException("AI响应为空");
}
// Step 4: 记录 Token 使用(用于成本监控)
logTokenUsage(usage);
// Step 5: 检查是否有工具调用(Agent场景)
if (hasToolCalls(aiMsg)) {
return handleToolCalls(history, aiMsg);
}
// Step 6: 保存 AiMessage 到历史
history.add(aiMsg);
// Step 7: 管理历史大小(防止无限增长)
trimHistoryIfNeeded(history);
// Step 8: 返回文本
return aiMsg.text();
}
private void logTokenUsage(TokenUsage usage) {
logger.info("Token使用 - 输入: {}, 输出: {}, 总计: {}",
usage.inputTokens(),
usage.outputTokens(),
usage.inputTokens() + usage.outputTokens());
}
private boolean hasToolCalls(AiMessage aiMsg) {
List<ToolCall> calls = aiMsg.toolCalls();
return calls != null && !calls.isEmpty();
}
private String handleToolCalls(List<ChatMessage> history, AiMessage aiMsg) {
history.add(aiMsg); // ✅ 先保存原始 AiMessage
for (ToolCall toolCall : aiMsg.toolCalls()) {
String result = executeTool(toolCall);
ToolExecutionResultMessage toolResult =
ToolExecutionResultMessage.from(toolCall.id(), result);
history.add(toolResult);
}
// 继续调用模型
Response<AiMessage> nextResponse = model.generate(history);
AiMessage nextMsg = nextResponse.content();
history.add(nextMsg);
return nextMsg.text();
}
private void trimHistoryIfNeeded(List<ChatMessage> history) {
int MAX_HISTORY = 20;
if (history.size() > MAX_HISTORY) {
SystemMessage sys = (SystemMessage) history.get(0);
history.subList(1, history.size() - MAX_HISTORY).clear();
}
}
private String executeTool(ToolCall toolCall) {
// 实现工具执行逻辑
return "工具执行结果";
}
}
AiMessage 完整对比总结
| 方面 | 说明 |
|---|---|
| 来源 | 模型生成,不是用户输入 |
| 内容 | 包含文本、工具调用、元数据 |
| 保存 | 多轮对话必须保存到历史 |
| 修改 | 通常不可修改(不可变对象) |
| 使用频率 | 多轮对话中频繁使用 |
| Token影响 | 输出Token计入总成本 |
| 关键方法 | text()、toolCalls()、metadata() |
| 获取方式 | Response response = model.generate(messages) |
4️⃣ ToolExecutionResultMessage - 工具执行结果
定义:工具执行的结果消息(在Agent中使用)
使用场景:Agent 调用工具后,将结果作为消息返回给LLM
// Agent调用工具的流程:
// 1. LLM 决定调用工具 → 返回包含 tool_call 的消息
// 2. Agent 执行工具 → 得到执行结果
// 3. 创建 ToolExecutionResultMessage → 返回给LLM
// 4. LLM 基于结果继续生成
ToolExecutionResultMessage toolResult = ToolExecutionResultMessage.from(
"calculator", // 工具名称
"2 + 2 = 4" // 执行结果
);
List<ChatMessage> messages = new ArrayList<>();
messages.add(SystemMessage.from("你可以使用calculator工具"));
messages.add(UserMessage.from("2加2等于多少?"));
messages.add(toolResult); // 工具结果
String response = model.chat(messages);
5️⃣ 完整的多轮对话示例
@Service
public class MultiTurnConversationService {
private final ChatModel model;
private final List<ChatMessage> conversationHistory = new ArrayList<>();
public MultiTurnConversationService(@Value("${openai.api-key}") String apiKey) {
this.model = OpenAiChatModel.builder()
.apiKey(apiKey)
.modelName("gpt-4o-mini")
.build();
// 初始化系统消息
conversationHistory.add(SystemMessage.from("""
你是一位Python专家。
特点:
1. 代码示例用Python 3.11+
2. 解释时用数据科学的背景
3. 优先推荐使用pandas和numpy
"""));
}
/**
* 进行一轮对话
*/
public String chat(String userInput) {
// Step 1: 添加用户消息
conversationHistory.add(UserMessage.from(userInput));
// Step 2: 调用LLM
Response<AiMessage> response = model.generate(conversationHistory);
AiMessage aiMessage = response.content();
// Step 3: 保存AI回复到历史
conversationHistory.add(aiMessage);
// Step 4: 返回回复
return aiMessage.text();
}
/**
* 获取完整的对话历史
*/
public List<ChatMessage> getHistory() {
return new ArrayList<>(conversationHistory);
}
/**
* 清空对话历史
*/
public void clearHistory() {
conversationHistory.clear();
conversationHistory.add(SystemMessage.from("""
你是一位Python专家。
特点:
1. 代码示例用Python 3.11+
2. 解释时用数据科学的背景
3. 优先推荐使用pandas和numpy
"""));
}
}
// 使用示例
MultiTurnConversationService service = new MultiTurnConversationService(apiKey);
// 第1轮
String response1 = service.chat("什么是列表推导式?");
System.out.println("AI: " + response1);
// 第2轮
String response2 = service.chat("能给出一个实际的数据处理例子吗?");
System.out.println("AI: " + response2);
// 第3轮
String response3 = service.chat("与传统循环相比,性能有多大提升?");
System.out.println("AI: " + response3);
6️⃣ Message 优化最佳实践
问题 1:SystemMessage 的设计
❌ 坏的 SystemMessage:
SystemMessage.from("你是一个助手。请帮我。谢谢。");
// → 太模糊,没有清晰的指导
✅ 好的 SystemMessage:
SystemMessage.from("""
你是一位资深的代码审查专家,具有以下特点:
【角色】:
- 20年Java开发经验
- 熟悉微服务架构
- 关注代码性能和安全
【审查重点】:
1. 性能瓶颈(N+1查询、内存泄漏等)
2. 安全漏洞(SQL注入、XSS等)
3. 代码质量(可读性、可维护性)
【输出格式】:
- 问题编号 | 严重程度 | 问题描述 | 修复建议
【语气】:
- 专业但友好
- 给出具体改进方案
- 解释为什么要这样改
""");
问题 2:多个 SystemMessage
❌ 错误做法:一个对话中有多个 SystemMessage
List<ChatMessage> messages = new ArrayList<>();
messages.add(SystemMessage.from("你是Java专家")); // ❌ 第1个
messages.add(UserMessage.from("审查代码"));
messages.add(SystemMessage.from("要关注性能")); // ❌ 第2个,会覆盖第1个
✅ 正确做法:合并到一个 SystemMessage
List<ChatMessage> messages = new ArrayList<>();
messages.add(SystemMessage.from("""
你是Java专家。
要关注性能问题。
""")); // ✅ 只有一个
messages.add(UserMessage.from("审查代码"));
问题 3:SystemMessage 的粒度
❌ 粒度过大:一个SystemMessage包含所有规则(难以管理)
SystemMessage.from("做这个 做那个 还有这个 还有那个...");
// 信息太多,LLM 容易遗忘某些规则
✅ 粒度适中:清晰的分类和分层
SystemMessage.from("""
你是一位代码审查专家。
【必须检查的问题】:
1. 性能问题
2. 安全漏洞
3. 代码规范
【优先级】:
安全 > 性能 > 可读性
【输出要求】:
- 找出3-5个主要问题
- 每个问题给出修复建议
- 用Markdown格式输出
""");
7️⃣ Message 与 Token 成本的关系
/**
* 理解 Message 对 Token 消耗的影响
*/
// 同一个用户问题,不同的 Message 结构,Token 消耗不同
// 方式1:简单的 SystemMessage
List<ChatMessage> messages1 = new ArrayList<>();
messages1.add(SystemMessage.from("你是助手"));
messages1.add(UserMessage.from("2+2等于多少?"));
// 预计 Token: ~30个
// 方式2:详细的 SystemMessage
List<ChatMessage> messages2 = new ArrayList<>();
messages2.add(SystemMessage.from("""
你是一位数学教师,有20年教学经验。
你的特点是...(1000字详细描述)
"""));
messages2.add(UserMessage.from("2+2等于多少?"));
// 预计 Token: ~1030个(增加了1000个!)
// 优化建议:
// ✅ 重复的对话中,SystemMessage 被重复发送,浪费 Token
// ✅ 考虑在应用启动时加载 SystemMessage,而不是每次都重新创建
8️⃣ Message 在不同场景中的应用
场景1:简单问答(一次性)
List<ChatMessage> messages = new ArrayList<>();
messages.add(SystemMessage.from("你是一个数学家"));
messages.add(UserMessage.from("微积分是什么?"));
String response = model.chat(messages);
场景2:多轮对话(有历史)
List<ChatMessage> history = new ArrayList<>();
history.add(SystemMessage.from("你是一个编程导师"));
// 第1轮
history.add(UserMessage.from("什么是设计模式?"));
history.add(model.generate(history).content());
// 第2轮:历史保留了 SystemMessage 和第1轮的对话
history.add(UserMessage.from("能举个例子吗?"));
history.add(model.generate(history).content());
// 第3轮:继续用完整历史
history.add(UserMessage.from("为什么这样设计?"));
String response = model.chat(history);
场景3:并行对话(多个独立会话)
// 会话1:代码审查
List<ChatMessage> session1 = new ArrayList<>();
session1.add(SystemMessage.from("你是代码审查专家"));
// ...
// 会话2:文档生成
List<ChatMessage> session2 = new ArrayList<>();
session2.add(SystemMessage.from("你是技术文档专家"));
// ...
// 两个会话独立进行,互不影响
9️⃣ Message 类型对比总结
| Message类型 | 用途 | 位置 | 数量 | 影响范围 |
|---|---|---|---|---|
| SystemMessage | 定义LLM角色和行为 | 最开始 | 1个 | 整个对话 |
| UserMessage | 用户的输入和问题 | 任何地方 | 多个 | 当前和后续 |
| AiMessage | LLM的回复 | UserMessage后 | 多个 | 后续对话的Context |
| ToolExecResultMsg | 工具执行结果 | 工具调用后 | 多个 | Agent决策 |
🔟 Message 实战:构建聊天机器人
@Service
public class ChatBotService {
private final ChatModel model;
private final int MAX_HISTORY_SIZE = 10; // 保留最近10条消息
public ChatBotService(@Value("${openai.api-key}") String apiKey) {
this.model = OpenAiChatModel.builder()
.apiKey(apiKey)
.modelName("gpt-4o-mini")
.build();
}
/**
* 创建初始系统消息
*/
private SystemMessage createSystemMessage(String role) {
return SystemMessage.from(switch(role) {
case "teacher" -> """
你是一位耐心的编程教师。
- 用通俗易懂的语言解释复杂概念
- 经常举实例
- 鼓励学生提问
- 代码示例要清晰注释
""";
case "expert" -> """
你是一位资深的技术专家。
- 深度分析技术问题
- 提供最佳实践建议
- 权衡trade-off
- 引用相关论文或资源
""";
case "assistant" -> """
你是一位乐于助人的助手。
- 快速回答问题
- 提供多个解决方案
- 如果不确定,说明不清楚
- 友好和专业的语气
""";
default -> "你是一个有帮助的助手。";
});
}
/**
* 管理对话历史(定长,避免无限增长)
*/
private List<ChatMessage> manageHistory(
List<ChatMessage> history,
UserMessage newMessage,
AiMessage response) {
// 保留SystemMessage
SystemMessage systemMsg = (SystemMessage) history.get(0);
List<ChatMessage> recentHistory = new ArrayList<>();
recentHistory.add(systemMsg);
// 添加新的对话
recentHistory.add(newMessage);
recentHistory.add(response);
// 添加历史对话,最多保留MAX_HISTORY_SIZE条
for (int i = 1; i < history.size() && recentHistory.size() < MAX_HISTORY_SIZE + 1; i++) {
recentHistory.add(history.get(i));
}
return recentHistory;
}
/**
* 进行一次对话
*/
public String chat(String role, List<ChatMessage> history, String userInput) {
// Step 1: 初始化历史(如果为空)
if (history.isEmpty()) {
history.add(createSystemMessage(role));
}
// Step 2: 添加用户消息
UserMessage userMsg = UserMessage.from(userInput);
history.add(userMsg);
// Step 3: 获取AI回复
Response<AiMessage> response = model.generate(history);
AiMessage aiMsg = response.content();
// Step 4: 保存到历史
history.add(aiMsg);
// Step 5: 管理历史大小
List<ChatMessage> managedHistory = manageHistory(history, userMsg, aiMsg);
history.clear();
history.addAll(managedHistory);
return aiMsg.text();
}
}
Message 最佳实践检查清单
- 是否为每个对话会话都创建了 SystemMessage?
- SystemMessage 是否清晰定义了LLM的角色和行为?
- 是否正确地保存了 AiMessage 到对话历史中?
- 是否有机制管理对话历史的大小(避免无限增长)?
- 是否考虑了 Message 对 Token 成本的影响?
- 并行对话的会话是否完全独立?
- 是否正确处理了多轮对话中的 Context 维持?
- 是否有错误处理(如模型异常时的 Message 管理)?
扩展实现:Prompt模板系统
@Service
public class PromptTemplateService {
private final ChatModel model;
public PromptTemplateService(@Value("${openai.api-key}") String apiKey) {
this.model = OpenAiChatModel.builder()
.apiKey(apiKey)
.modelName("gpt-4o-mini")
.build();
}
/**
* 代码审查模板
*/
public String codeReview(String code, String focusAreas) {
String prompt = String.format("""
请进行Java代码审查,重点关注以下方面:
%s
代码:
```java
%s
```
输出格式:
1. 问题描述
2. 严重程度(高/中/低)
3. 具体行号
4. 修复建议
""", focusAreas, code);
return model.chat(prompt);
}
/**
* Few-Shot学习模板
*/
public String fewShotLearn(String category, List<String> examples, String input) {
StringBuilder prompt = new StringBuilder();
prompt.append("根据以下示例,完成分类任务:\n\n");
for (int i = 0; i < examples.size(); i++) {
prompt.append(String.format("示例%d:\n%s\n\n", i + 1, examples.get(i)));
}
prompt.append(String.format("现在判断:\n%s\n\n分类结果:", input));
return model.chat(prompt.toString());
}
/**
* Chain-of-Thought模板
*/
public String chainOfThought(String problem, List<String> steps) {
StringBuilder prompt = new StringBuilder();
prompt.append("请按以下步骤解决问题:\n\n");
for (int i = 0; i < steps.size(); i++) {
prompt.append(String.format("第%d步:%s\n", i + 1, steps.get(i)));
}
prompt.append(String.format("\n问题:%s", problem));
return model.chat(prompt.toString());
}
/**
* 结构化输出模板
*/
public String structuredOutput(String prompt, String format) {
String fullPrompt = String.format("""
%s
请用以下格式回答:
%s
""", prompt, format);
return model.chat(fullPrompt);
}
/**
* 温度参数+Prompt共同优化
*/
public String optimizedResponse(
String prompt,
PromptConfig config) {
ChatModel optimizedModel = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.temperature(config.getTemperature())
.maxTokens(config.getMaxTokens())
.build();
return optimizedModel.chat(buildEnhancedPrompt(prompt, config));
}
/**
* 增强Prompt质量的辅助方法
*/
private String buildEnhancedPrompt(String originalPrompt, PromptConfig config) {
return String.format("""
%s
回答时请注意:
- 准确性:%s
- 风格:%s
- 长度限制:%s
""",
originalPrompt,
config.getAccuracy(),
config.getStyle(),
config.getLengthLimit());
}
/**
* 配置类
*/
public static class PromptConfig {
private double temperature; // 0.0-1.0,决定创意度
private int maxTokens; // 输出长度
private String accuracy; // "准确" or "创意"
private String style; // "技术性" or "易读"
private String lengthLimit; // "100字" or "详细"
// Getters and Setters
public double getTemperature() { return temperature; }
public void setTemperature(double temperature) { this.temperature = temperature; }
public int getMaxTokens() { return maxTokens; }
public void setMaxTokens(int maxTokens) { this.maxTokens = maxTokens; }
public String getAccuracy() { return accuracy; }
public void setAccuracy(String accuracy) { this.accuracy = accuracy; }
public String getStyle() { return style; }
public void setStyle(String style) { this.style = style; }
public String getLengthLimit() { return lengthLimit; }
public void setLengthLimit(String lengthLimit) { this.lengthLimit = lengthLimit; }
}
}
实战项目集成
Spring Boot Controller使用Prompt
@RestController
@RequestMapping("/api/code-analysis")
public class CodeAnalysisController {
@Autowired
private PromptTemplateService promptService;
@PostMapping("/review")
public String reviewCode(@RequestBody CodeReviewRequest request) {
return promptService.codeReview(request.getCode(), request.getFocusAreas());
}
@PostMapping("/classify")
public String classify(@RequestBody ClassificationRequest request) {
return promptService.fewShotLearn(
request.getCategory(),
request.getExamples(),
request.getInput()
);
}
@PostMapping("/analyze")
public String analyze(@RequestBody AnalysisRequest request) {
return promptService.chainOfThought(
request.getProblem(),
request.getSteps()
);
}
}
单元测试
@SpringBootTest
public class PromptTemplateTest {
@Autowired
private PromptTemplateService promptService;
@Test
public void testCodeReviewPrompt() {
String code = "for (int i = 0; i < list.size(); i++) { " +
"list.get(i); }";
String focusAreas = "性能瓶颈和最佳实践";
String result = promptService.codeReview(code, focusAreas);
assertNotNull(result);
assertTrue(result.length() > 0);
// 验证包含关键词
assertTrue(result.contains("O(n)") || result.contains("性能"));
}
@Test
public void testFewShotPrompt() {
List<String> examples = Arrays.asList(
"输入:int x = 5; 上下文:年龄\n输出:int age = 5;",
"输入:double y = 3.14; 上下文:π值\n输出:double pi = 3.14;"
);
String input = "int z = 100; 上下文:最大值";
String result = promptService.fewShotLearn("命名", examples, input);
assertNotNull(result);
assertTrue(result.contains("maxValue") || result.contains("max"));
}
@Test
public void testChainOfThoughtPrompt() {
String problem = "计算时间复杂度:for(int i=0; i<n; i++) { for(int j=0; j<n; j++) { } }";
List<String> steps = Arrays.asList(
"识别循环层数",
"计算每层迭代次数",
"累乘得到总复杂度"
);
String result = promptService.chainOfThought(problem, steps);
assertNotNull(result);
assertTrue(result.contains("O(n") || result.contains("复杂度"));
}
@Test
public void testTemperatureEffect() {
// 低temperature:保守答案
PromptTemplateService.PromptConfig conservativeConfig =
new PromptTemplateService.PromptConfig();
conservativeConfig.setTemperature(0.0);
conservativeConfig.setMaxTokens(100);
conservativeConfig.setStyle("技术性");
// 高temperature:创意答案
PromptTemplateService.PromptConfig creativeConfig =
new PromptTemplateService.PromptConfig();
creativeConfig.setTemperature(1.0);
creativeConfig.setMaxTokens(200);
creativeConfig.setStyle("易读");
String prompt = "解释什么是设计模式";
String conservative = promptService.optimizedResponse(prompt, conservativeConfig);
String creative = promptService.optimizedResponse(prompt, creativeConfig);
assertNotNull(conservative);
assertNotNull(creative);
}
}
常见问题解答
Q1:如何让LLM生成JSON格式的输出?
A: 在Prompt中明确说明格式:
String prompt = """
请分析这段代码,用JSON格式回答:
{
"issues": [...],
"severity": "high/medium/low",
"suggestions": [...]
}
代码:[代码]
""";
Q2:Few-Shot中应该给多少个示例?
A: 通常2-3个就够了:
- 1个:太少,LLM可能不理解
- 2-3个:最优,LLM能理解模式
- 5个+:太多,浪费token,成本增加
Q3:什么时候用Chain-of-Thought?
A: 在需要深度分析的场景:
- ✅ 代码审查、架构设计、问题诊断
- ❌ 简单问答("这是什么?")
- ❌ 事实性查询("2+2等于几?")
Q4:Prompt太长会影响质量吗?
A: 不会降低质量,但会:
- 增加成本(token更多)
- 增加延迟(处理时间更长)
建议:
- 必要信息必写
- 冗余信息删除
- 用代码格式化清晰地展示信息
Q5:如何测试Prompt的质量?
A: 用A/B测试:
// 同一个问题,用两个不同的Prompt
String prompt_v1 = "分析性能";
String prompt_v2 = "按以下步骤分析性能:...";
String result_v1 = model.chat(prompt_v1);
String result_v2 = model.chat(prompt_v2);
// 比较结果的质量(准确性、深度、有用性)
Q6:SystemMessage 和 UserMessage 有什么区别?
A: 根本区别在于角色和权重:
| 方面 | SystemMessage | UserMessage |
|---|---|---|
| 角色 | 系统指令,定义行为 | 用户需求 |
| 权重 | 优先级最高,影响全局 | 影响当前回复 |
| 内容 | 约束、规则、身份 | 问题、任务、请求 |
| 数量 | 只有1个 | 多个 |
| 位置 | 必须在最前面 | 可在任意位置 |
| 频率 | 整个会话只设置一次 | 每一轮对话都有 |
例子:
// SystemMessage:定义 LLM 的身份和风格
SystemMessage.from("""
你是一位Python专家,代码示例优先使用:
- 现代Python特性(3.10+)
- Type hints
- Dataclass而不是dict
""");
// UserMessage:用户的具体需求
UserMessage.from("怎样用Python实现一个简单的缓存?");
// → LLM 会用"Python专家"的身份来回答这个问题
Q7:对话历史太长会怎样?
A: 两个问题:
- Token成本增加:历史越长,发送的Token越多,成本越高
- LLM容易遗忘:历史太长时,LLM可能遗忘早期的信息
解决方案:
private static final int MAX_HISTORY = 10; // 只保留最近10条消息
public void trimHistory(List<ChatMessage> history) {
if (history.size() > MAX_HISTORY + 1) { // +1 是 SystemMessage
// 保留 SystemMessage 和最近的消息
SystemMessage system = (SystemMessage) history.get(0);
List<ChatMessage> recent = history.subList(
history.size() - MAX_HISTORY,
history.size()
);
history.clear();
history.add(system);
history.addAll(recent);
}
}
Q8:如何在多个并行对话中使用 Message?
A: 每个对话会话独立维护自己的Message列表:
// 错误:共享历史
List<ChatMessage> sharedHistory = new ArrayList<>();
// 会话1添加信息
sharedHistory.add(SystemMessage.from("你是编程导师"));
sharedHistory.add(UserMessage.from("什么是OOP?"));
// 会话2也用这个历史,会被污染!
sharedHistory.add(UserMessage.from("怎样学Python?"));
// ❌ 会话2会看到会话1的对话,混乱!
// 正确:每个会话独立
Map<String, List<ChatMessage>> sessionHistories = new HashMap<>();
// 会话1
String session1Id = UUID.randomUUID().toString();
List<ChatMessage> session1History = new ArrayList<>();
session1History.add(SystemMessage.from("你是编程导师"));
session1History.add(UserMessage.from("什么是OOP?"));
sessionHistories.put(session1Id, session1History);
// 会话2
String session2Id = UUID.randomUUID().toString();
List<ChatMessage> session2History = new ArrayList<>();
session2History.add(SystemMessage.from("你是Python教师"));
session2History.add(UserMessage.from("怎样学Python?"));
sessionHistories.put(session2Id, session2History);
// ✅ 两个会话完全独立
Q9:Message 中的内容可以包含代码吗?
A: 完全可以,而且推荐这样做:
// ✅ 推荐:用代码块标记代码
UserMessage.from("""
请审查以下Java代码:
```java
for (int i = 0; i < list.size(); i++) {
Object obj = list.get(i);
System.out.println(obj);
}
```
重点检查性能问题。
""");
// ❌ 不推荐:纯文本混合
UserMessage.from("请审查这个代码:for (int i = 0; i < list.size(); i++) {...}");
Q10:AiMessage 一定要保存到历史吗?
A: 取决于是否需要多轮对话:
// 场景1:一次性问答,不需要保存
String response = model.chat(UserMessage.from("今天天气怎样?"));
// → 不保存 AiMessage
// 场景2:多轮对话,必须保存
List<ChatMessage> history = new ArrayList<>();
history.add(SystemMessage.from("你是编程专家"));
// 第1轮
history.add(UserMessage.from("什么是设计模式?"));
AiMessage response1 = model.generate(history).content();
history.add(response1); // ✅ 必须保存
// 第2轮
history.add(UserMessage.from("能举个例子吗?"));
AiMessage response2 = model.generate(history).content();
history.add(response2); // ✅ 必须保存
// 第3轮继续需要之前的历史
history.add(UserMessage.from("为什么这样做?"));
// history 包含了所有之前的对话,LLM 能理解上下文
最佳实践速查表
✅ 做这些
| 实践 | 例子 |
|---|---|
| 明确的指令 | "找出N+1查询问题" 而不是 "分析代码" |
| 提供上下文 | "电商系统中,用户的订单查询" |
| 限制输出 | "100字以内" "JSON格式" |
| 给出示例 | Few-Shot Learning |
| 分步骤 | Chain-of-Thought |
| 清晰的结构 | 用序号、代码块、表格 |
❌ 不要做这些
| 反面 | 为什么 |
|---|---|
| 模糊指令 | "帮我分析" → LLM不知道分析什么 |
| 没有示例 | LLM只能猜测你的期望 |
| 长篇大论 | 信息混乱,LLM分不清主次 |
| 混合多个问题 | "代码审查 + 性能优化 + ..." |
| 没有约束 | 输出可能很冗长或格式乱 |
PromptTemplate 工程实践
什么是 PromptTemplate?
在生产环境中,我们经常需要重复使用相同结构的 Prompt,只是替换其中的变量部分。PromptTemplate 就是为了解决这个问题——它提供了一个参数化的 Prompt 模板。
使用场景:
代码审查 → 每次审查的代码不同,但 Prompt 结构相同
问答系统 → 每个问题不同,但系统指令相同
翻译系统 → 每段文本不同,但翻译规则相同
PromptTemplate 的优势
| 优势 | 说明 |
|---|---|
| 代码复用 | 同一个模板可以复用,避免重复编写 |
| 易于管理 | 修改 Prompt 只需改模板,不影响代码 |
| 参数灵活 | 动态替换变量,生成不同的 Prompt |
| 易于测试 | Prompt 和业务逻辑分离,便于单元测试 |
| 性能优化 | 预定义模板,减少动态拼接的开销 |
PromptTemplate 基础用法
1️⃣ 简单模板(单个变量)
@Service
public class SimplePromptTemplateService {
private final ChatModel model;
public SimplePromptTemplateService(@Value("${openai.base-url}") String baseUrl,
@Value("${openai.api-key}") String apiKey) {
this.model = OpenAiChatModel.builder()
.baseUrl(baseUrl)
.apiKey(apiKey)
.modelName("gpt-4o-mini")
.build();
}
/**
* 代码审查模板
* 模板变量:{code}, {focusArea}
*/
public String codeReview(String code, String focusArea) {
String promptTemplate = """
请进行Java代码审查。
关注方面:{focusArea}
代码:
{code}
输出格式:
1. 问题描述
2. 严重程度(高/中/低)
3. 修复建议
""";
// 替换模板变量
String prompt = promptTemplate
.replace("{code}", code)
.replace("{focusArea}", focusArea);
return model.chat(prompt);
}
}
2️⃣ 复杂模板(多个变量和结构)
@Service
public class AdvancedPromptTemplateService {
private final ChatModel model;
public AdvancedPromptTemplateService(@Value("${openai.base-url}") String baseUrl,
@Value("${openai.api-key}") String apiKey) {
this.model = OpenAiChatModel.builder()
.baseUrl(baseUrl)
.apiKey(apiKey)
.modelName("gpt-4o-mini")
.build();
}
/**
* Few-Shot Learning 模板
*/
public String fewShotTemplate(String taskDescription,
List<String> examples,
String input) {
StringBuilder promptBuilder = new StringBuilder();
promptBuilder.append("任务:").append(taskDescription).append("\n\n");
promptBuilder.append("示例:\n");
for (int i = 0; i < examples.size(); i++) {
promptBuilder.append(String.format("示例%d:\n%s\n\n", i + 1, examples.get(i)));
}
promptBuilder.append("现在请处理:\n").append(input);
return model.chat(promptBuilder.toString());
}
/**
* Chain-of-Thought 模板
*/
public String chainOfThoughtTemplate(String problem,
List<String> steps) {
StringBuilder promptBuilder = new StringBuilder();
promptBuilder.append("请按以下步骤解决问题:\n\n");
for (int i = 0; i < steps.size(); i++) {
promptBuilder.append(String.format("第%d步:%s\n", i + 1, steps.get(i)));
}
promptBuilder.append("\n问题:").append(problem);
promptBuilder.append("\n\n请逐步回答:");
return model.chat(promptBuilder.toString());
}
}
3️⃣ 使用 String.format() 的优雅方式
public String formatPrompt(String template, Map<String, String> variables) {
String result = template;
for (Map.Entry<String, String> entry : variables.entrySet()) {
result = result.replace("{" + entry.getKey() + "}", entry.getValue());
}
return result;
}
// 使用示例
Map<String, String> vars = new HashMap<>();
vars.put("code", codeSnippet);
vars.put("language", "Java");
vars.put("focusArea", "性能优化");
String prompt = formatPrompt("""
请进行{language}代码审查。
关注:{focusArea}
代码:
{code}
""", vars);
String result = model.chat(prompt);
生产级 PromptTemplate 实现
@Service
public class ProductionPromptTemplateService {
private final ChatModel model;
private final Map<String, String> templates = new ConcurrentHashMap<>();
@PostConstruct
public void initTemplates() {
// 代码审查模板
templates.put("codeReview", """
你是一位资深的{language}工程师。
请审查以下代码,关注:
{focusAreas}
代码:
```{language}
{code}
```
输出格式:
1. 问题列表(严重程度标记)
2. 改进建议
3. 最佳实践提示
""");
// 文档生成模板
templates.put("generateDocs", """
根据以下{language}代码,生成清晰的API文档:
代码:
```{language}
{code}
```
文档格式:
- 方法功能描述
- 参数说明
- 返回值说明
- 使用示例
""");
// 翻译模板
templates.put("translate", """
将以下内容从{sourceLanguage}翻译到{targetLanguage}。
保持原有的技术术语准确性。
原文:
{text}
""");
}
/**
* 执行模板,替换所有变量
*/
public String executeTemplate(String templateName, Map<String, String> variables) {
String template = templates.get(templateName);
if (template == null) {
throw new IllegalArgumentException("模板不存在:" + templateName);
}
String result = template;
for (Map.Entry<String, String> entry : variables.entrySet()) {
result = result.replace("{" + entry.getKey() + "}", entry.getValue());
}
return model.chat(result);
}
/**
* 代码审查的便利方法
*/
public String reviewCode(String code, String language, String focusAreas) {
Map<String, String> vars = new HashMap<>();
vars.put("code", code);
vars.put("language", language);
vars.put("focusAreas", focusAreas);
return executeTemplate("codeReview", vars);
}
/**
* 生成文档的便利方法
*/
public String generateDocumentation(String code, String language) {
Map<String, String> vars = new HashMap<>();
vars.put("code", code);
vars.put("language", language);
return executeTemplate("generateDocs", vars);
}
}
PromptTemplate 的最佳实践
| 实践 | 为什么 |
|---|---|
| 模板中使用清晰的占位符 | {variable} 易于识别和替换 |
| 模板集中管理 | 配置文件或数据库,便于更新 |
| 版本控制模板 | 保留历史版本,便于回滚 |
| 验证变量 | 替换前检查必需的变量 |
| 注释和文档 | 说明模板的用途和变量含义 |
| 避免过度参数化 | 参数太多会降低可读性 |
PromptTemplate 和 Few-Shot 的结合
/**
* 结合 PromptTemplate 和 Few-Shot Learning
*/
public String fewShotWithTemplate(List<Example> examples, String input) {
StringBuilder prompt = new StringBuilder();
prompt.append("根据以下示例进行分类:\n\n");
for (int i = 0; i < examples.size(); i++) {
Example example = examples.get(i);
prompt.append(String.format("""
示例%d:
输入:%s
输出:%s
""", i + 1, example.getInput(), example.getOutput()));
}
prompt.append("现在请处理:\n");
prompt.append("输入:").append(input).append("\n");
prompt.append("输出:");
return model.chat(prompt.toString());
}
// 定义 Example 类
@Data
class Example {
private String input;
private String output;
public Example(String input, String output) {
this.input = input;
this.output = output;
}
}
-
为什么Few-Shot Learning比Zero-Shot更有效?
- 思考LLM的学习方式
- 示例如何减少歧义
-
Chain-of-Thought为什么能提升质量?
- 追踪推理过程
- 对比直接答案vs分步骤
-
如何找到Prompt的最优平衡点?
- 详细程度 vs token成本
- 灵活性 vs 可预测性
-
不同类型的任务,Prompt策略应该怎样调整?
- 创意任务 vs 精确任务
- 简单问答 vs 复杂分析
实战练习
任务1:写出更好的Code Review Prompt
改进这个Prompt:
String poorPrompt = "审查这段代码";
要求:
- 指定关注的方面(至少3个) ✅ 2026-03-07
- 限制输出格式(JSON或Markdown) ✅ 2026-03-07
- 限制输出长度 ✅ 2026-03-07
- 给出代码上下文 ✅ 2026-03-07
任务2:设计Few-Shot Learning的示例
为"识别数据库连接池问题"设计Few-Shot Prompt:
- 创建3个示例(问题+分类) ✅ 2026-03-07
- 保证示例的多样性 ✅ 2026-03-07
- 验证新问题的分类是否准确 ✅ 2026-03-07
任务3:使用Chain-of-Thought优化某个分析任务
选择一个复杂的分析任务,用Chain-of-Thought改进:
- 分解成5-7个步骤 ✅ 2026-03-07
- 每个步骤明确说明 ✅ 2026-03-07
- 验证输出是否更深入 ✅ 2026-03-07
相关Examples推荐
下一步学习这些来深化理解:
-
[[2003-Message类型快速参考.md|Message 类型快速参考]] ⭐ 新增
- 5分钟速记 Message 的用法
- 常见错误和修正
- 成本对比分析
-
[[2005-Token成本控制与优化]] (TokenCountingExample)
- 长Prompt会增加成本,学会优化
-
[[013-Memory会话管理深度解析]] (MemoryExample)
- 多轮对话中的Prompt管理
-
[[030-Agent设计原理和基础实战]] (AgentExample)
- Agent中的Prompt工程
补充资源
官方文档
在线工具
- OpenAI Playground - 快速测试Prompt
- Anthropic Claude对比 - 对比不同模型
总结
🎯 这一篇你学到了
✨ 技术知识:
- Prompt工程的5大法则
- Few-Shot Learning的力量
- Chain-of-Thought推理的价值
- LangChain4J中的Message类型和用途
- 多轮对话中的消息管理
✨ 实战技能:
- 如何写出清晰的Prompt
- 如何用示例指导LLM
- 如何通过步骤化提升质量
- 如何正确使用SystemMessage、UserMessage、AiMessage
- 如何管理和优化对话历史
✨ 下一步方向:
- 学Token计数,优化成本
- 学Memory管理,处理对话
- 学Agent设计,实现复杂流程
💡 核心认知
好的Prompt = 明确 + 示例 + 约束 + 结构
正确的Message管理 = SystemMessage定义 + UserMessage输入 + AiMessage保存 + 历史优化
记住这些要素,你就能写出高质量的Prompt并正确管理对话。
写作完成 ✅
- 整理到项目README
字数统计:约4500字 | 阅读时间:40分钟
下一步:→ [[2005-Token成本控制与优化|004-Token成本控制与优化]]