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说"人话"也说"代码话"》


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

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

相关推荐
2501_901006471 小时前
MySQL主从复制过程中怎么增加从库_利用mysqldump快速扩容从库
jvm·数据库·python
曲幽1 小时前
让FastAPI Agent真正记住你:聊聊会话记忆与持久化存储的落地实践
redis·python·postgresql·fastapi·web·chat·async·session·ai agent
2301_769340671 小时前
Navicat导出CSV文件数据为空如何解决_过滤条件与权限排查
jvm·数据库·python
kexnjdcncnxjs1 小时前
bootstrap如何设置响应式导航栏的切换宽度
jvm·数据库·python
数智工坊1 小时前
【深度学习RL】DQN:深度强化学习的里程碑——让AI从像素中学会玩Atari游戏
论文阅读·人工智能·深度学习·游戏·transformer
2301_815901971 小时前
如何测试FSFO观察者进程的自动切换_模拟主库断网与Observer心跳超时
jvm·数据库·python
源码之家1 小时前
计算机毕业设计:Python基于知识图谱与深度学习的医疗智能问答系统 Django框架 Bert模型 深度学习 知识图谱 大模型(建议收藏)✅
python·深度学习·机器学习·数据分析·flask·知识图谱·课程设计
Xpower 171 小时前
从PHM到AI Agent-如何用OpenClaw构建设备健康诊断智能体
网络·人工智能·学习·算法
yzx9910131 小时前
软件脚本定制开发:从需求到交付的技术实战指南
大数据·人工智能·数据挖掘