Prompt工程在代码中的实现

目标读者:已经学习了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,面临以下挑战:

  1. 可读性差:长Prompt拼接字符串,代码很难读

    java 复制代码
    // ❌ 糟糕的写法
    String prompt = "你是一位" + role + ",请用" + style + "的语气回答。问题是:" + question;
  2. 难以维护:Prompt需要频繁迭代优化,硬编码在代码中很难修改

  3. 无法复用:相似的Prompt逻辑,每次都要重新写

  4. 版本管理困难:无法追踪Prompt的变更历史

  5. 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的指令,用于:

  1. 设定AI的角色:如"你是一位Java架构师"
  2. 定义行为边界:如"只回答技术问题,拒绝闲聊"
  3. 指定输出格式:如"用JSON格式返回"
  4. 设置语气风格:如"用简洁、专业的语言回答"

在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或注解,仍然面临以下挑战:

  1. Prompt太长:一个复杂的System Prompt可能超过500字,写在代码中很难读
  2. 难以维护:Prompt需要频繁迭代优化,每次都要重新编译代码
  3. 无法复用:多个AI Service可能需要相同的Prompt片段
  4. 版本管理困难:无法追踪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:

  1. 类路径(Classpath) :fromResource = "prompts/system/java-expert.txt"
  2. 文件系统:需要自定义加载逻辑

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工程在代码中的实现》中,我们学习了:

  1. 什么是System Prompt?

    • System Prompt的定义和作用
    • 优秀的System Prompt设计原则
    • System Prompt vs User Prompt
  2. 如何在Java代码中动态构建Prompt?

    • 方法1:字符串拼接(不推荐)
    • 方法2:String.format()(改进)
    • 方法3:LangChain4j PromptTemplate(推荐)
    • 方法4:注解方式(强烈推荐)
  3. 模板引擎在Prompt管理中的应用

    • 为什么需要模板引擎?
    • LangChain4j内置的文件加载功能
    • 自定义文件加载器
    • 使用Thymeleaf管理Prompt(用户要求)
    • 使用FreeMarker管理Prompt(推荐)
  4. 高级Prompt技术

    • Few-shot Learning
    • Chain of Thought
    • Self-Consistency
  5. Prompt版本控制与A/B测试

    • Prompt版本控制
    • A/B测试框架
  6. 最佳实践与常见陷阱

8.2 下一步学习路线

下一步应该学什么?

  1. 学习RAG(检索增强生成)

    • 让AI能够访问你的私有文档
    • 技术栈:LangChain4j + 向量数据库
  2. 学习AI Agent开发

    • 让AI自主规划和执行任务
    • 技术栈:LangChain4j Agent + Tools
  3. 学习生产级Prompt工程

    • Prompt自动优化
    • Prompt反馈循环
    • Prompt效果评估

8.3 系列文章关系

文章 类型 内容 适合读者
《Java程序员的AI突围之路》 深度研究 "为什么"和"是什么" 想了解AI应用开发全貌的读者
《Hello World------15分钟跑通第一个AI应用》 实践教程 "怎么做"(基础) 想动手实践、快速上手的读者
《深入LangChain4j------Java程序员的AI开发利器》 深入教程 "怎么做"(进阶) 想深入学习LangChain4j的读者
《Prompt工程在代码中的实现》 实践教程 Prompt工程 想学习Prompt工程的读者

九、参考资料

  1. LangChain4j官方文档 - Prompt Template
  2. LangChain4j官方文档 - AI Services
  3. OpenAI Prompt Engineering Guide
  4. Anthropic Prompt Engineering Guide
  5. Thymeleaf官方文档
  6. FreeMarker官方文档

十、结语

恭喜你!读完这篇文章,你已经深入掌握了Prompt工程在Java代码中的实现。

回顾一下你今天学到的技能:

  • ✅ 什么是System Prompt,如何设计优秀的System Prompt
  • ✅ 如何在Java代码中动态构建Prompt
  • ✅ 如何使用LangChain4j的PromptTemplate和注解
  • ✅ 如何使用模板引擎(Thymeleaf、FreeMarker)管理Prompt
  • ✅ 高级Prompt技术(Few-shot、CoT等)
  • ✅ Prompt版本控制与A/B测试

但这只是开始。

Prompt工程是一个需要持续学习和实践的技术。每天都有新的技巧和方法被发明出来。

记住 :最好的学习方式是动手实践。试着为你自己的项目设计一套Prompt管理体系。

下一篇预告:《结构化输出------让AI说"人话"也说"代码话"》


如果这篇文章对你有帮助,欢迎点赞、收藏、转发!

有任何问题,欢迎在评论区留言讨论! 💬

相关推荐
外收内放1 分钟前
06 | 优化篇① 游戏改名《鳞光纪》了:这次翻新,把“脸“全换了
python·游戏·pygame
AIGC小尼2 分钟前
8G 显存零基础本地部署 AI 漫剧全流程|ComfyUI+Wan2.2+FFmpeg 离线成片完整方案(含代码 / 指令 / 排坑)
人工智能·ffmpeg·php·comfyui·ai漫剧
悟天特斯3 分钟前
AI驱动的楼宇节能:从“经验省电“到“算法能效“的进化之路
人工智能·物联网
破无差4 分钟前
人工智能训练师(三级)理论知识复习题-KimiK3的参考答案
人工智能
计算机毕业编程指导师5 分钟前
【大数据毕设选题】基于Spark的电信网络诈骗话术语义特征挖掘分析系统源码 毕业设计 选题推荐 数据分析 机器学习 深度学习
大数据·hadoop·python·spark·毕业设计·课程设计·网络诈骗
网络毒刘7 分钟前
AtomGit Actions + AI 评审草稿:PR 描述自动生成与安全敏感词门禁示例
人工智能·安全
ai_xiaogui7 分钟前
PanelAI 1.1.1重磅更新:秒级安装脚本优化 + 无公网IP算力节点组网,私有化AI管理平台全面升级
人工智能·网络协议·tcp/ip·api聚合管理·开发者ai一键部署·ai底层架构解析·ai应用快速变现
for_ever_love__9 分钟前
电商订单数据清洗实战——Pandas 处理缺失值、重复单与异常金额
python·数据分析·pandas·数据清洗
计算机毕业编程指导师10 分钟前
计算机大数据毕设怎么选?基于Spark的个体肥胖健康风险评估的数据分析与可视化系统带你通关 源码 毕业设计 选题推荐 毕设选题 数据分析 机器学习
大数据·hadoop·python·计算机·spark·毕业设计·肥胖风险
坤坤子吖10 分钟前
Python基础语法学习:函数
开发语言·笔记·python·学习