目标读者:已经学习了LangChain4j基础知识,想深入学习如何在Java代码中有效管理Prompt的开发者。
学习目标:掌握System Prompt的设计原则,学会在Java代码中动态构建Prompt,以及使用模板引擎管理Prompt。
技术选型:JDK 17 + Spring Boot 3 + LangChain4j 1.0+ + Thymeleaf (可选)
一、前言:为什么Prompt工程很重要?
1.1 一个真实的例子
Prompt 1(糟糕):
翻译这段话
Prompt 2(优秀):
你是一位专业的翻译官,精通中文和英文。
请将以下英文翻译成中文,要求:
1. 准确传达原文意思
2. 符合中文表达习惯
3. 保留专业术语(如有)
英文文本:
{{english_text}}
请直接输出翻译结果,不要添加任何解释。
同样的AI模型,不同的Prompt,输出质量天差地别!
1.2 Prompt工程的核心挑战
在代码中管理Prompt,面临以下挑战:
-
可读性差:长Prompt拼接字符串,代码很难读
java// ❌ 糟糕的写法 String prompt = "你是一位" + role + ",请用" + style + "的语气回答。问题是:" + question; -
难以维护:Prompt需要频繁迭代优化,硬编码在代码中很难修改
-
无法复用:相似的Prompt逻辑,每次都要重新写
-
版本管理困难:无法追踪Prompt的变更历史
-
A/B测试复杂:难以对比不同Prompt的效果
1.3 本文的学习路径
┌────────────────────────────────────┐
│ 第2章:Prompt工程在代码中的实现 │
└────────────────┬───────────────────┘
│
▼
┌────────────────────────────────────┐
│ 2.1 什么是System Prompt? │
│ - 定义和作用 │
│ - 如何设计优秀的System Prompt │
└────────────────┬───────────────────┘
│
▼
┌────────────────────────────────────┐
│ 2.2 动态构建Prompt │
│ - 字符串拼接(不推荐) │
│ - String.format() │
│ - LangChain4j PromptTemplate │
│ - 注解方式(@SystemMessage) │
└────────────────┬───────────────────┘
│
▼
┌────────────────────────────────────┐
│ 2.3 模板引擎在Prompt管理中的应用 │
│ - 为什么需要模板引擎? │
│ - 使用外部文件管理Prompt │
│ - Thymeleaf集成(详细) │
│ - FreeMarker集成(推荐) │
└────────────────┬───────────────────┘
│
▼
┌────────────────────────────────────┐
│ 2.4 高级Prompt技术 │
│ - Few-shot Learning │
│ - Chain of Thought │
│ - 结构化输出 │
└────────────────┬───────────────────┘
│
▼
┌────────────────────────────────────┐
│ 2.5 Prompt版本控制与A/B测试 │
└────────────────────────────────────┘
二、什么是System Prompt?
2.1 System Prompt的定义
System Prompt(系统提示词) 是在对话开始时发送给AI的指令,用于:
- 设定AI的角色:如"你是一位Java架构师"
- 定义行为边界:如"只回答技术问题,拒绝闲聊"
- 指定输出格式:如"用JSON格式返回"
- 设置语气风格:如"用简洁、专业的语言回答"
在LangChain4j中,System Prompt对应SystemMessage:
java
SystemMessage systemMessage = SystemMessage.from(
"你是一位经验丰富的Java架构师,擅长性能优化和高并发设计。"
);
2.2 System Prompt vs User Prompt
| 类型 | 发送时机 | 作用 | 是否可动态变化 |
|---|---|---|---|
| System Prompt | 对话开始时(第一条消息) | 设定AI的角色和行为规则 | 通常固定 |
| User Prompt | 每次用户提问时 | 提出具体问题 | 每次都变化 |
示例对话:
【System Prompt】(只有第一条)
你是一位Java架构师,擅长性能优化。
【User Prompt 1】
请解释什么是JVM内存模型?
【AI回复 1】
JVM内存模型分为...
【User Prompt 2】
如何优化GC性能?
【AI回复 2】
优化GC性能可以从以下几个方面入手...
关键点:System Prompt只需要发送一次,AI会"记住"它。
2.3 优秀的System Prompt设计原则
原则1:明确角色和专业领域
❌ 糟糕的System Prompt:
你是一个AI助手。
✅ 优秀的System Prompt:
你是一位拥有10年经验的Java架构师,擅长:
- JVM内存模型和垃圾回收机制
- 高并发系统设计
- 分布式架构
- 性能调优
你的回答应该:
- 准确、专业
- 包含代码示例(如适用)
- 指出潜在的风险和注意事项
原则2:定义输出格式
✅ 优秀的System Prompt(包含输出格式):
你是一位Java代码审查助手。
对于每个代码片段,请按以下格式输出:
【问题分析】
简要描述代码的问题
【风险等级】
高/中/低
【优化建议】
1. ...
2. ...
【优化后的代码】
(代码块)
【解释】
解释为什么这样优化
原则3:设置行为边界
✅ 优秀的System Prompt(包含行为边界):
你是一位法律顾问,专门回答中国法律问题。
行为规则:
- 只回答法律问题,其他问题回复"抱歉,我只回答法律问题"
- 如果问题超出你的知识范围,回复"抱歉,这个问题超出了我的能力范围"
- 在回答末尾添加免责声明:"本回答仅供参考,不构成法律建议"
原则4:使用分隔符避免混淆
✅ 优秀的System Prompt(使用分隔符):
你是一位翻译助手。
用户会提供需要翻译的文本,文本会用###包裹:
###
(这里是用户要翻译的文本)
###
请将文本翻译成英文,只输出翻译结果,不要添加任何解释。
原则5:提供示例(Few-shot Learning)
✅ 优秀的System Prompt(包含示例):
你是一位情感分析助手。请将用户输入分类为:积极、消极、中性。
示例1:
输入:今天天气真好!
输出:积极
示例2:
输入:这个产品质量太差了!
输出:消极
示例3:
输入:会议将在下午三点开始。
输出:中性
现在请分析:
输入:{{user_input}}
输出:
2.4 System Prompt的最佳长度
问题:System Prompt应该写多长?
答案 :越长越好(在保证质量的前提下)。
| System Prompt长度 | 效果 | 说明 |
|---|---|---|
| 少于50字 | ❌ 效果差 | AI行为不可控 |
| 50-200字 | ⚠️ 一般 | 适合简单任务 |
| 200-500字 | ✅ 推荐 | 适合大多数场景 |
| 500-2000字 | ✅ 优秀 | 适合复杂任务 |
| 超过2000字 | ⚠️ 可能浪费Token | 需要权衡 |
关键点:System Prompt会占用每次请求的Token,但不影响对话历史。
三、如何在Java代码中动态构建Prompt?
3.1 方法1:字符串拼接(❌ 不推荐)
java
// ❌ 糟糕的写法
public String buildPrompt(String role, String style, String question) {
String prompt = "你是一位" + role + ",";
prompt += "请用" + style + "的语气回答。";
prompt += "问题是:" + question;
return prompt;
}
问题:
- 可读性差
- 容易出错(漏掉空格、换行等)
- 难以维护
3.2 方法2:使用String.format()
java
// ✅ 改进的写法
public String buildPrompt(String role, String style, String question) {
String template = """
你是一位%s,
请用%s的语气回答。
问题是:%s
""";
return String.format(template, role, style, question);
}
优点:
- 使用Text Block(JDK 17+),可读性好
- 占位符清晰
缺点:
- 需要按顺序传入参数(容易传错)
- 不支持命名参数
3.3 方法3:使用LangChain4j的PromptTemplate(✅ 推荐)
LangChain4j提供了PromptTemplate类,支持命名参数。
基础用法
java
import dev.langchain4j.model.input.PromptTemplate;
import java.util.Map;
public class PromptExample {
public static void main(String[] args) {
// 1. 定义模板(使用 {{变量名}} 作为占位符)
PromptTemplate template = PromptTemplate.from("""
你是一位{{role}},
请用{{style}}的语气回答。
问题是:{{question}}
""");
// 2. 填充变量
Map<String, Object> variables = Map.of(
"role", "Java架构师",
"style", "专业、简洁",
"question", "什么是JVM内存模型?"
);
// 3. 生成最终Prompt
Prompt prompt = template.apply(variables);
// 4. 获取Prompt文本
String promptText = prompt.text();
System.out.println(promptText);
}
}
输出:
你是一位Java架构师,
请用专业、简洁的语气回答。
问题是:什么是JVM内存模型?
简化用法(单个变量)
java
// 如果只有一个变量,可以使用 {{it}}
PromptTemplate template = PromptTemplate.from("将以下文本翻译成英文:{{it}}");
// 直接传入字符串
Prompt prompt = template.apply("你好,世界");
3.4 方法4:使用注解方式(✅✅ 强烈推荐)
LangChain4j提供了注解方式,让Prompt管理更加优雅。
@SystemMessage注解
java
import dev.langchain4j.service.AiService;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
@AiService
public interface JavaAssistant {
// 静态System Prompt(无变量)
@SystemMessage("""
你是一位Java架构师,
擅长性能优化和高并发设计。
请用专业、简洁的语言回答问题。
""")
String chat(@UserMessage String question);
// 动态System Prompt(带变量)
@SystemMessage("你是一位{{role}},请用{{style}}的语气回答。")
String chatWithRole(
@V("role") String role,
@V("style") String style,
@UserMessage String question
);
}
使用:
java
@SpringBootTest
class PromptTest {
@Autowired
private JavaAssistant assistant;
@Test
void testStaticSystemMessage() {
String response = assistant.chat("什么是JVM?");
System.out.println(response);
}
@Test
void testDynamicSystemMessage() {
String response = assistant.chatWithRole(
"Java架构师",
"专业、简洁",
"如何优化GC性能?"
);
System.out.println(response);
}
}
@UserMessage注解
java
@AiService
public interface Translator {
// 动态User Message
@SystemMessage("你是一位专业的翻译官。")
@UserMessage("将以下文本翻译成{{target_language}}:\n\n{{text}}")
String translate(
@V("text") String text,
@V("target_language") String targetLanguage
);
}
使用:
java
@Autowired
private Translator translator;
String result = translator.translate(
"Hello, World!",
"中文"
);
// 输出:你好,世界!
@StructuredPrompt注解(复杂Prompt)
对于复杂的Prompt,可以使用类来组织结构。
java
import dev.langchain4j.service.StructuredPrompt;
// 定义结构化Prompt
@Data
@AllArgsConstructor
@StructuredPrompt("""
你是一位{{role}}。
任务:{{task}}
要求:
{{#each requirements}}
- {{it}}
{{/each}}
输出格式:{{output_format}}
""")
class CodeReviewPrompt {
private String role;
private String task;
private List<String> requirements;
private String outputFormat;
}
@AiService
public interface CodeReviewer {
@UserMessage("{{it}}")
String review(CodeReviewPrompt prompt);
}
使用:
java
@Autowired
private CodeReviewer codeReviewer;
CodeReviewPrompt prompt = new CodeReviewPrompt(
"Java代码审查助手",
"审查以下代码",
List.of(
"指出潜在的bug",
"提出性能优化建议",
"检查代码规范"
),
"JSON格式,包含:issues、suggestions、optimized_code"
);
String result = codeReviewer.review(prompt);
四、模板引擎在Prompt管理中的应用
4.1 为什么需要模板引擎?
问题 :即使使用了PromptTemplate或注解,仍然面临以下挑战:
- Prompt太长:一个复杂的System Prompt可能超过500字,写在代码中很难读
- 难以维护:Prompt需要频繁迭代优化,每次都要重新编译代码
- 无法复用:多个AI Service可能需要相同的Prompt片段
- 版本管理困难:无法追踪Prompt的变更历史
解决方案 :使用模板引擎,将Prompt外部化到文件中!
┌────────────────────────────────────┐
│ 使用前(硬编码在代码中) │
├────────────────────────────────────┤
│ @SystemMessage("你是一位...") │
│ String chat(@UserMessage...) │
└────────────────────────────────────┘
│
▼
┌────────────────────────────────────┐
│ 使用后(外部化到文件) │
├────────────────────────────────────┤
│ @SystemMessage(fromFile = │
│ "prompts/system/java-expert.txt") │
│ String chat(@UserMessage...) │
└────────────────────────────────────┘
优势:
| 维度 | 硬编码 | 外部化到文件 |
|---|---|---|
| 可读性 | ❌ 差 | ✅ 好(独立的文本文件) |
| 可维护性 | ❌ 需要重新编译 | ✅ 直接修改文件 |
| 版本管理 | ⚠️ 可以但混乱 | ✅ Git追踪方便 |
| 复用性 | ❌ 困难 | ✅ 多个Service可引用同一文件 |
| A/B测试 | ❌ 需要改代码 | ✅ 只需替换文件 |
4.2 LangChain4j内置的文件加载功能
好消息:LangChain4j已经内置了从文件加载Prompt的功能!
使用fromResource()加载类路径下的文件
java
@AiService
public interface JavaAssistant {
// 从类路径加载System Prompt
@SystemMessage(fromResource = "prompts/system/java-expert.txt")
String chat(@UserMessage String question);
}
文件位置 :src/main/resources/prompts/system/java-expert.txt
文件内容 (java-expert.txt):
你是一位拥有10年经验的Java架构师,擅长:
- JVM内存模型和垃圾回收机制
- 高并发系统设计
- 分布式架构
- 性能调优
你的回答应该:
- 准确、专业
- 包含代码示例(如适用)
- 指出潜在的风险和注意事项
请用专业、简洁的中文回答问题。
支持的文件格式
LangChain4j支持从以下位置加载Prompt:
- 类路径(Classpath) :
fromResource = "prompts/system/java-expert.txt" - 文件系统:需要自定义加载逻辑
4.3 自定义文件加载器
如果需要从文件系统、数据库或远程API加载Prompt,可以自定义加载器。
从文件系统加载Prompt
java
@Component
public class FilePromptLoader {
private final String promptBasePath;
public FilePromptLoader(@Value("${prompt.base-path}") String promptBasePath) {
this.promptBasePath = promptBasePath;
}
/**
* 从文件系统加载Prompt
* @param fileName 文件名(如 "java-expert.txt")
* @return Prompt文本
*/
public String loadPrompt(String fileName) {
Path filePath = Paths.get(promptBasePath, fileName);
try {
return Files.readString(filePath, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new RuntimeException("加载Prompt文件失败:" + filePath, e);
}
}
/**
* 从文件系统加载Prompt,并替换变量
* @param fileName 文件名
* @param variables 变量Map
* @return 填充后的Prompt文本
*/
public String loadAndFillPrompt(String fileName, Map<String, Object> variables) {
String template = loadPrompt(fileName);
PromptTemplate promptTemplate = PromptTemplate.from(template);
Prompt prompt = promptTemplate.apply(variables);
return prompt.text();
}
}
配置 (application.yml):
yaml
prompt:
base-path: /opt/app/prompts # 文件系统路径
使用:
java
@SpringBootTest
class FilePromptTest {
@Autowired
private FilePromptLoader promptLoader;
@Test
void testLoadPromptFromFile() {
// 加载Prompt
String promptText = promptLoader.loadPrompt("java-expert.txt");
System.out.println(promptText);
}
@Test
void testLoadAndFillPrompt() {
// 加载并填充变量
Map<String, Object> variables = Map.of(
"role", "Java架构师",
"style", "专业、简洁"
);
String promptText = promptLoader.loadAndFillPrompt("dynamic-prompt.txt", variables);
System.out.println(promptText);
}
}
4.4 使用Thymeleaf管理Prompt(✅ 用户要求)
Thymeleaf 是一个Java模板引擎,主要用于HTML渲染。但也可以用于渲染文本模板(包括Prompt)。
添加依赖
xml
<!-- Thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
配置Thymeleaf
java
@Configuration
public class ThymeleafConfig {
@Bean
public SpringResourceTemplateResolver promptTemplateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setPrefix("classpath:/prompts/"); // Prompt文件位置
resolver.setSuffix(".txt"); // 文件扩展名
resolver.setTemplateMode(TemplateMode.TEXT); // 文本模式(不是HTML)
resolver.setCharacterEncoding("UTF-8");
return resolver;
}
@Bean
public SpringTemplateEngine promptTemplateEngine() {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.addTemplateResolver(promptTemplateResolver());
return engine;
}
}
创建Thymeleaf模板
文件位置 :src/main/resources/prompts/java-expert.txt
文件内容 (java-expert.txt):
你是一位[[${role}]],
任务:[[${task}]]
要求:
[# th:each="req : ${requirements}"]
- [[${req}]]
[/]
输出格式:[[${output_format}]]
请用[[${language}]]回答问题。
使用Thymeleaf渲染Prompt
java
@Service
@RequiredArgsConstructor
public class PromptService {
private final SpringTemplateEngine promptTemplateEngine;
/**
* 使用Thymeleaf渲染Prompt
* @param templateName 模板名称(不含扩展名)
* @param variables 变量Map
* @return 渲染后的Prompt文本
*/
public String renderPrompt(String templateName, Map<String, Object> variables) {
// 创建Context
Context context = new Context();
context.setVariables(variables);
// 渲染模板
return promptTemplateEngine.process(templateName, context);
}
}
使用:
java
@SpringBootTest
class ThymeleafPromptTest {
@Autowired
private PromptService promptService;
@Test
void testRenderPrompt() {
Map<String, Object> variables = Map.of(
"role", "Java架构师",
"task", "审查以下代码",
"requirements", List.of(
"指出潜在的bug",
"提出性能优化建议",
"检查代码规范"
),
"output_format", "JSON格式",
"language", "中文"
);
String promptText = promptService.renderPrompt("java-expert", variables);
System.out.println(promptText);
}
}
Thymeleaf语法官网
| 语法 | 说明 | 示例 |
|---|---|---|
[[${variable}]] |
变量替换 | 你是一位[[${role}]] |
[# th:if="${condition}"] |
条件判断 | [# th:if="${show_details}"]详情...[/] |
[# th:each="item : ${list}"] |
循环 | [# th:each="req : ${requirements}"]- [[${req}]][/] |
4.5 使用FreeMarker管理Prompt(✅ 推荐)
FreeMarker 是一个更轻量级的模板引擎,适合渲染文本模板(包括Prompt)。
添加依赖
xml
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.32</version>
</dependency>
配置FreeMarker
java
@Configuration
public class FreeMarkerConfig {
@Bean
public Configuration freeMarkerConfiguration() {
Configuration cfg = new Configuration(Configuration.VERSION_2_3_32);
// 设置模板加载路径
try {
cfg.setDirectoryForTemplateLoading(new File("src/main/resources/prompts"));
} catch (IOException e) {
throw new RuntimeException("Failed to configure FreeMarker", e);
}
cfg.setDefaultEncoding("UTF-8");
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
return cfg;
}
}
创建FreeMarker模板
文件位置 :src/main/resources/prompts/java-expert.ftl
文件内容 (java-expert.ftl):
你是一位${role},
任务:${task}
要求:
<#list requirements as req>
- ${req}
</#list>
输出格式:${output_format}
请用${language}回答问题。
使用FreeMarker渲染Prompt
java
@Service
@RequiredArgsConstructor
public class FreeMarkerPromptService {
private final Configuration freeMarkerConfiguration;
/**
* 使用FreeMarker渲染Prompt
*/
public String renderPrompt(String templateName, Map<String, Object> variables) {
try {
Template template = freeMarkerConfiguration.getTemplate(templateName);
StringWriter writer = new StringWriter();
template.process(variables, writer);
return writer.toString();
} catch (Exception e) {
throw new RuntimeException("渲染Prompt失败", e);
}
}
}
4.6 Thymeleaf vs FreeMarker:如何选择?
| 维度 | Thymeleaf | FreeMarker |
|---|---|---|
| 学习曲线 | 中等(HTML背景易上手) | 低(语法简单) |
| 适合场景 | Web页面渲染 + Prompt管理 | 纯文本模板(推荐用于Prompt) |
| 语法 | [[${var}]] |
${var} |
| 性能 | 中等 | 高 |
| 推荐度(Prompt管理) | ⚠️ 一般 | ✅✅ 强烈推荐 |
我的建议:
- 如果项目已经使用了Thymeleaf(用于Web页面),可以继续用它管理Prompt
- 如果是新项目,推荐用FreeMarker(更轻量、性能更好)
五、高级Prompt技术
5.1 Few-shot Learning(少样本学习)
原理:在Prompt中提供几个示例,引导AI按照示例的格式输出。
在LangChain4j中实现Few-shot
java
@AiService
public interface SentimentAnalyzer {
@SystemMessage("你是情感分析助手,将文本分类为:积极、消极、中性")
@UserMessage({
"示例1:",
"输入:今天天气真好!",
"输出:积极",
"",
"示例2:",
"输入:这个产品质量太差了",
"输出:消极",
"",
"示例3:",
"输入:会议将在下午三点开始",
"输出:中性",
"",
"现在请分析:",
"输入:{{text}}",
"输出:"
})
String analyze(@V("text") String text);
}
使用:
java
@Autowired
private SentimentAnalyzer sentimentAnalyzer;
String result = sentimentAnalyzer.analyze("我对这次旅行非常满意!");
// 输出:积极
5.2 Chain of Thought(思维链)
原理:让AI在输出最终答案之前,先展示思考过程。
在LangChain4j中实现CoT
java
@AiService
public interface MathProblemSolver {
@SystemMessage("你是数学老师,擅长解题。请一步一步思考,并给出最终答案。")
@UserMessage({
"问题:{{problem}}",
"",
"请按以下格式回答:",
"步骤1:[思考过程]",
"步骤2:[思考过程]",
"...",
"最终答案:[答案]"
})
String solve(@V("problem") String problem);
}
使用:
java
@Autowired
private MathProblemSolver solver;
String result = solver.solve("一个球的半径是3cm,求它的体积。(π取3.14)");
输出:
步骤1:题目要求球的体积,已知半径r=3cm,π=3.14
步骤2:回忆球的体积公式 V = (4/3)πr³
步骤3:代入数值计算 V = (4/3) × 3.14 × 3³ = (4/3) × 3.14 × 27 = 113.04
最终答案:113.04 cm³
5.3 Self-Consistency(自我一致性)
原理:让AI生成多个答案,然后选择最一致的那个。
实现方式:调用多次,然后投票选择。
java
@Service
@RequiredArgsConstructor
public class SelfConsistencyService {
private final ChatModel chatModel;
/**
* 自我一致性:生成多个答案,选择最一致的
* @param prompt Prompt
* @param numSamples 生成答案的数量
* @return 最一致的答案
*/
public String selfConsistency(String prompt, int numSamples) {
List<String> answers = new ArrayList<>();
// 生成多个答案
for (int i = 0; i < numSamples; i++) {
String answer = chatModel.chat(UserMessage.from(prompt)).aiMessage().text();
answers.add(answer);
}
// 简单投票:选择出现次数最多的答案
return answers.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
.orElse(answers.get(0));
}
}
六、Prompt版本控制与A/B测试
6.1 Prompt版本控制
问题:Prompt需要频繁迭代优化,如何管理不同版本?
解决方案:使用Git管理Prompt文件!
目录结构
src/main/resources/prompts/
├── system/
│ ├── java-expert.v1.txt
│ ├── java-expert.v2.txt
│ └── java-expert.v3.txt
├── user/
│ ├── code-review.v1.txt
│ └── code-review.v2.txt
└── templates/
├── sentiment-analysis.ftl
└── translation.ftl
在代码中指定版本
java
@AiService
public interface JavaAssistant {
// 使用v2版本
@SystemMessage(fromResource = "prompts/system/java-expert.v2.txt")
String chat(@UserMessage String question);
}
使用配置文件管理版本
配置文件 (application.yml):
yaml
prompt:
version: v2
代码中使用配置:
java
@AiService
public interface JavaAssistant {
@SystemMessage(fromResource = "prompts/system/java-expert.${prompt.version}.txt")
String chat(@UserMessage String question);
}
6.2 Prompt A/B测试
问题:如何对比不同Prompt的效果?
解决方案:实现A/B测试框架。
简单A/B测试实现
java
@Service
@Slf4j
@RequiredArgsConstructor
public class PromptABTestService {
private final ChatModel chatModel;
/**
* A/B测试两个Prompt
* @param promptA Prompt A
* @param promptB Prompt B
* @param testCases 测试用例
* @return A/B测试结果
*/
public ABTestResult runABTest(String promptA, String promptB, List<String> testCases) {
List<String> resultsA = new ArrayList<>();
List<String> resultsB = new ArrayList<>();
for (String testCase : testCases) {
// 测试Prompt A
String resultA = chatModel.chat(
UserMessage.from(promptA.replace("{{input}}", testCase))
).aiMessage().text();
resultsA.add(resultA);
// 测试Prompt B
String resultB = chatModel.chat(
UserMessage.from(promptB.replace("{{input}}", testCase))
).aiMessage().text();
resultsB.add(resultB);
}
// 输出结果
log.info("===== Prompt A 结果 =====");
resultsA.forEach(result -> log.info(result));
log.info("===== Prompt B 结果 =====");
resultsB.forEach(result -> log.info(result));
return new ABTestResult(resultsA, resultsB);
}
/**
* A/B测试结果
*/
@Data
@AllArgsConstructor
public static class ABTestResult {
private List<String> resultsA;
private List<String> resultsB;
}
}
使用:
java
@SpringBootTest
class ABTestTest {
@Autowired
private PromptABTestService abTestService;
@Test
void testABTest() {
String promptA = "翻译以下文本成中文:{{input}}";
String promptB = "你是一位翻译官,请将以下文本翻译成地道的中文:{{input}}";
List<String> testCases = List.of(
"Hello, World!",
"How are you?",
"Thank you very much."
);
abTestService.runABTest(promptA, promptB, testCases);
}
}
七、最佳实践与常见陷阱
7.1 最佳实践
实践1:将Prompt外部化到文件
❌ 糟糕:
@SystemMessage("你是一位Java架构师...")
String chat(@UserMessage String question);
✅ 优秀:
@SystemMessage(fromResource = "prompts/system/java-expert.v2.txt")
String chat(@UserMessage String question);
实践2:使用版本控制
✅ 优秀:
prompts/system/java-expert.v1.txt
prompts/system/java-expert.v2.txt
实践3:为不同的任务创建不同的Prompt
✅ 优秀:
prompts/system/
├── code-reviewer.txt
├── translator.txt
├── sentiment-analyzer.txt
└── math-teacher.txt
实践4:在Prompt中使用分隔符
✅ 优秀:
用户会提供需要翻译的文本,文本会用###包裹:
###
{{text}}
###
请将文本翻译成英文。
实践5:提供示例(Few-shot)
✅ 优秀:
示例1:
输入:今天天气真好!
输出:积极
示例2:
输入:这个产品质量太差了
输出:消极
现在请分析:
输入:{{text}}
输出:
7.2 常见陷阱
陷阱1:Prompt过长,浪费Token
❌ 糟糕:
(5000字的System Prompt,大部分是废话)
✅ 优秀:
(500字的System Prompt,简洁、精准)
陷阱2:变量名不匹配
❌ 糟糕:
PromptTemplate: "你是一位{{role}}"
变量Map: Map.of("user_role", "Java架构师") // 变量名不匹配!
✅ 优秀:
PromptTemplate: "你是一位{{role}}"
变量Map: Map.of("role", "Java架构师")
陷阱3:忘记处理特殊字符
❌ 糟糕:
用户输入包含 {{}} ,会导致模板解析错误
✅ 优秀:
使用分隔符隔离用户输入:
###
{{user_input}}
###
陷阱4:没有版本控制
❌ 糟糕:
每次修改Prompt都直接覆盖,无法回溯
✅ 优秀:
使用Git管理Prompt文件,每次修改都提交
八、总结与下一步
8.1 本章总结
在这篇《Prompt工程在代码中的实现》中,我们学习了:
-
什么是System Prompt?
- System Prompt的定义和作用
- 优秀的System Prompt设计原则
- System Prompt vs User Prompt
-
如何在Java代码中动态构建Prompt?
- 方法1:字符串拼接(不推荐)
- 方法2:String.format()(改进)
- 方法3:LangChain4j PromptTemplate(推荐)
- 方法4:注解方式(强烈推荐)
-
模板引擎在Prompt管理中的应用
- 为什么需要模板引擎?
- LangChain4j内置的文件加载功能
- 自定义文件加载器
- 使用Thymeleaf管理Prompt(用户要求)
- 使用FreeMarker管理Prompt(推荐)
-
高级Prompt技术
- Few-shot Learning
- Chain of Thought
- Self-Consistency
-
Prompt版本控制与A/B测试
- Prompt版本控制
- A/B测试框架
-
最佳实践与常见陷阱
8.2 下一步学习路线
下一步应该学什么?
-
学习RAG(检索增强生成)
- 让AI能够访问你的私有文档
- 技术栈:LangChain4j + 向量数据库
-
学习AI Agent开发
- 让AI自主规划和执行任务
- 技术栈:LangChain4j Agent + Tools
-
学习生产级Prompt工程
- Prompt自动优化
- Prompt反馈循环
- Prompt效果评估
8.3 系列文章关系
| 文章 | 类型 | 内容 | 适合读者 |
|---|---|---|---|
| 《Java程序员的AI突围之路》 | 深度研究 | "为什么"和"是什么" | 想了解AI应用开发全貌的读者 |
| 《Hello World------15分钟跑通第一个AI应用》 | 实践教程 | "怎么做"(基础) | 想动手实践、快速上手的读者 |
| 《深入LangChain4j------Java程序员的AI开发利器》 | 深入教程 | "怎么做"(进阶) | 想深入学习LangChain4j的读者 |
| 《Prompt工程在代码中的实现》 | 实践教程 | Prompt工程 | 想学习Prompt工程的读者 |
九、参考资料
- LangChain4j官方文档 - Prompt Template
- LangChain4j官方文档 - AI Services
- OpenAI Prompt Engineering Guide
- Anthropic Prompt Engineering Guide
- Thymeleaf官方文档
- FreeMarker官方文档
十、结语
恭喜你!读完这篇文章,你已经深入掌握了Prompt工程在Java代码中的实现。
回顾一下你今天学到的技能:
- ✅ 什么是System Prompt,如何设计优秀的System Prompt
- ✅ 如何在Java代码中动态构建Prompt
- ✅ 如何使用LangChain4j的PromptTemplate和注解
- ✅ 如何使用模板引擎(Thymeleaf、FreeMarker)管理Prompt
- ✅ 高级Prompt技术(Few-shot、CoT等)
- ✅ Prompt版本控制与A/B测试
但这只是开始。
Prompt工程是一个需要持续学习和实践的技术。每天都有新的技巧和方法被发明出来。
记住 :最好的学习方式是动手实践。试着为你自己的项目设计一套Prompt管理体系。
下一篇预告:《结构化输出------让AI说"人话"也说"代码话"》
如果这篇文章对你有帮助,欢迎点赞、收藏、转发!
有任何问题,欢迎在评论区留言讨论! 💬