Java实现Prompt工程的技巧------从模板到工程化,告别字符串拼接
Prompt不是"和AI聊天",而是"给AI写需求文档"。本文从Java工程师视角,教你把Prompt从字符串拼接升级为可维护、可测试、可复用的工程化体系。
写在前面
很多Java工程师调通第一个AI接口后,都会遇到同一个问题:
java
// ❌ 新手写法:Prompt全靠字符串拼接
String prompt = "你是" + role + "专家,请帮我" + task +
",要求:" + requirements +
",输出格式:" + format +
"。背景信息:" + context;
String result = chatModel.chat(prompt);
这种写法有3个致命问题:
- 可读性差:Prompt逻辑和业务逻辑混在一起
- 难维护:改一个字都要重新发版
- 易出错:转义符、引号、换行搞死人
Prompt工程的核心不是"写一句好Prompt",而是"建立一套Prompt管理体系"。这篇文章就是教你如何用Java思维做Prompt工程。
一、Prompt工程的3个层次
1.1 层次1:基础调用(新手级)
java
// 直接传字符串,无模板
String answer = chatModel.chat("解释什么是RAG");
问题:无法复用,无法动态传参。
1.2 层次2:模板化(进阶级)
java
// 用模板引擎渲染
String prompt = promptTemplate.render(Map.of(
"role", "Java专家",
"task", "解释RAG"
));
String answer = chatModel.chat(prompt);
进步:Prompt和代码分离,可复用。但仍缺统一管理。
1.3 层次3:工程化(生产级)
css
┌─────────────────────────────────────┐
│ Prompt工程化体系 │
├─────────────────────────────────────┤
│ │
│ 1. 模板管理(统一存储、版本控制) │
│ 2. 变量注入(类型安全、自动校验) │
│ 3. Prompt组装(System+User+Context)│
│ 4. 输出解析(结构化返回Java对象) │
│ 5. A/B测试(多版本Prompt对比) │
│ 6. 效果监控(成功率、Token消耗) │
│ 7. 安全防护(防注入、内容审核) │
│ │
└─────────────────────────────────────┘
这才是企业级Java AI应用需要的Prompt工程。
二、Java Prompt工程的4种实现方式
2.1 方式1:Java Text Block + String.format(最简单)
适合小型项目,JDK 17+的Text Block让多行字符串写起来很舒服。
java
@Component
public class PromptTemplate {
private static final String RAG_SYSTEM_PROMPT = """
你是一个智能知识库助手,请根据以下参考资料回答用户问题。
规则:
1. 仅基于参考资料回答,不要编造信息
2. 如果参考资料不足以回答问题,请明确说明
3. 回答要简洁、准确、有条理
4. 使用中文回答
参考资料:
%s
""";
public String build(String question, String context) {
return String.format(RAG_SYSTEM_PROMPT, context) + "\n\n用户问题:" + question;
}
}
优点 :零依赖,简单直接
缺点 :变量多了容易混乱,%s没有语义
2.2 方式2:LangChain4j @SystemMessage + @UserMessage(推荐)
声明式写法,Prompt直接写在注解里,类型安全。
java
@AiService
public interface CodeReviewService {
@SystemMessage("你是资深Java代码审查专家,只关注代码质量,不修改功能")
@UserMessage("请审查以下代码,找出潜在问题:\n\n{{code}}")
String reviewCode(@V("code") String code);
}
变量占位符用 {{varName}} ,比%s清晰得多。
多变量示例:
java
@AiService
public interface TranslationService {
@SystemMessage("你是专业翻译,精通{{from}}和{{to}}两种语言")
@UserMessage("请将以下{{from}}文本翻译为{{to}}:\n\n{{text}}")
String translate(
@V("from") String fromLanguage,
@V("to") String toLanguage,
@V("text") String text
);
}
// 调用
String result = translationService.translate("中文", "英文", "你好世界");
优点 :声明式、类型安全、变量名即语义
缺点:复杂Prompt逻辑(条件分支、循环)需要回到代码层
2.3 方式3:Spring AI PromptTemplate(Spring生态首选)
Spring AI提供了专门的PromptTemplate类,语法类似Thymeleaf。
java
@Service
public class PromptTemplateService {
private final ChatClient chatClient;
public PromptTemplateService(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
public String generateCode(String language, String functionality) {
String template = """
请用{language}编写一个实现{functionality}的完整代码示例。
要求:
1. 代码要有注释
2. 包含异常处理
3. 使用现代最佳实践
""";
PromptTemplate promptTemplate = new PromptTemplate(template);
String rendered = promptTemplate.render(
Map.of("language", language, "functionality", functionality)
);
return chatClient.prompt()
.user(rendered)
.call()
.content();
}
// 内联模板(更简洁)
public String analyzeLog(String logContent) {
return chatClient.prompt()
.user(u -> u.text("""
分析以下日志,找出可能的错误原因:
{logContent}
请以JSON格式返回分析结果。
""")
.param("logContent", logContent))
.call()
.content();
}
}
优点 :Spring风格,与ChatClient无缝集成
缺点 :变量用{var},和JSON容易冲突
2.4 方式4:外部模板文件(生产级首选)
把Prompt从代码中完全抽离,放到文件或数据库。
bash
resources/
├── prompts/
│ ├── rag-system.st # RAG系统提示词
│ ├── code-review.st # 代码审查
│ ├── translation.st # 翻译
│ └── email-compose.st # 邮件撰写
markdown
你是一个智能知识库助手,请根据以下参考资料回答用户问题。
规则:
1. 仅基于参考资料回答,不要编造信息
2. 如果参考资料不足以回答问题,请明确说明
3. 回答要简洁、准确、有条理
参考资料:
{context}
用户问题:
{question}
Java加载器:
java
@Component
public class PromptFileManager {
private final Map<String, String> promptCache = new ConcurrentHashMap<>();
public String render(String promptName, Map<String, Object> variables) {
String template = promptCache.computeIfAbsent(promptName, this::loadTemplate);
// 用Spring AI的PromptTemplate渲染
PromptTemplate pt = new PromptTemplate(template);
return pt.render(variables);
}
private String loadTemplate(String name) {
try {
Path path = Path.of("src/main/resources/prompts/" + name + ".st");
return Files.readString(path);
} catch (IOException e) {
throw new RuntimeException("加载Prompt模板失败: " + name, e);
}
}
}
// 使用
String prompt = promptFileManager.render("rag-system", Map.of(
"context", retrievedContext,
"question", userQuestion
));
优点:
- Prompt和代码完全解耦
- 改Prompt不用重新编译
- 方便A/B测试(多个模板文件)
- 易于国际化(
rag-system-zh.st/rag-system-en.st)
缺点:需要额外加载逻辑
三、5种常用Prompt模式(Java实现)
3.1 模式1:角色扮演(Role-Playing)
给AI明确角色,约束输出风格。
java
@AiService
public interface JavaExpertService {
@SystemMessage("""
你是一位资深Java架构师,特点:
- 15年Java开发经验
- 专注高并发、高可用系统设计
- 回答风格:专业、简洁、有代码示例
- 不说废话,直奔主题
""")
String answer(@UserMessage String question);
}
关键:角色描述要具体,"你是Java专家"不如"你是15年经验的Java架构师"。
3.2 模式2:少样本学习(Few-Shot)
给AI几个示例,让它学会输出格式。
java
@AiService
public interface SentimentAnalysisService {
@SystemMessage("""
你是情感分析专家,分析文本的情感倾向。
只返回JSON格式,不要其他内容。
示例:
输入:这个产品太棒了,用起来很流畅
输出:{"sentiment":"positive","score":0.9}
输入:质量很差,不推荐购买
输出:{"sentiment":"negative","score":0.8}
输入:一般般,没有特别的感觉
输出:{"sentiment":"neutral","score":0.5}
""")
SentimentResult analyze(@UserMessage String text);
}
注意:用LangChain4j时,可以直接返回Java对象(record),框架自动解析JSON。
java
public record SentimentResult(
String sentiment, // positive / negative / neutral
double score // 0.0 - 1.0
) {}
3.3 模式3:思维链(Chain-of-Thought)
让AI"先思考,再回答",提升复杂问题准确率。
java
@AiService
public interface ProblemSolvingService {
@SystemMessage("""
你是问题解决专家。解决复杂问题时,请按以下步骤:
1. 理解问题:复述问题,明确目标
2. 分析约束:列出已知条件和限制
3. 思考方案:列出2-3种可能的方案
4. 评估方案:对比优缺点
5. 选择最优:给出最终方案和理由
请用以下格式输出:
## 问题理解
...
## 约束分析
...
## 方案思考
...
## 方案评估
...
## 最终方案
...
""")
String solve(@UserMessage String problem);
}
效果:复杂推理任务准确率提升30-50%。
3.4 模式4:结构化输出(Structured Output)
让AI返回结构化数据,直接映射为Java对象。
java
// LangChain4j方式
@AiService
public interface CodeReviewService {
@SystemMessage("""
你是代码审查专家。审查代码后,返回JSON格式:
{
"issues": [
{
"type": "bug/performance/style/security",
"severity": "high/medium/low",
"line": 行号,
"description": "问题描述",
"suggestion": "修改建议"
}
],
"score": 代码质量评分(0-100),
"summary": "总体评价"
}
""")
CodeReviewResult review(@UserMessage String code);
}
// 返回对象
public record CodeReviewResult(
List<Issue> issues,
int score,
String summary
) {
public record Issue(String type, String severity, int line,
String description, String suggestion) {}
}
java
// Spring AI方式
@Service
public class CodeReviewService {
public CodeReviewResult review(String code) {
return chatClient.prompt()
.system("你是代码审查专家...")
.user("审查代码:\n" + code)
.call()
.entity(CodeReviewResult.class); // 自动解析JSON
}
}
3.5 模式5:RAG增强(检索增强生成)
这是企业AI应用最核心的模式。把检索到的上下文注入Prompt。
java
@Component
public class RagPromptBuilder {
private static final String RAG_TEMPLATE = """
你是一个智能知识库助手,请根据以下参考资料回答用户问题。
规则:
1. 仅基于参考资料回答,不要编造信息
2. 如果参考资料不足以回答问题,请明确说"根据现有资料无法回答"
3. 回答要简洁、准确、有条理
4. 引用资料时标注来源编号 [1] [2]
参考资料:
%s
用户问题:
%s
""";
public String build(String question, List<DocumentMatch> matches) {
// 拼接检索结果作为上下文
StringBuilder context = new StringBuilder();
for (int i = 0; i < matches.size(); i++) {
DocumentMatch match = matches.get(i);
context.append(String.format("[%d] (来源: %s, 相似度: %.2f)\n%s\n\n",
i + 1,
match.getSource(),
match.getScore(),
match.getContent()
));
}
return String.format(RAG_TEMPLATE, context, question);
}
}
实际项目中的RAG Prompt示例 (来自 java-llm-production-ready 项目):
markdown
你是一个智能知识库助手,请根据以下参考资料回答用户问题。
规则:
1. 仅基于参考资料回答,不要编造信息
2. 如果参考资料不足以回答问题,请明确说明
3. 回答要简洁、准确、有条理
4. 使用中文回答
参考资料:
[1] (来源: employee-handbook.pdf, 相似度: 0.89)
员工年假政策:入职满1年享有5天年假,满3年享有10天年假...
[2] (来源: hr-faq.md, 相似度: 0.85)
年假申请流程:在OA系统提交申请,直属领导审批...
用户问题:
我刚入职半年,能请年假吗?
四、生产级Prompt工程体系
4.1 Prompt版本管理
像管理代码一样管理Prompt版本。
java
@Component
public class VersionedPromptManager {
// 每个Prompt有版本号,支持灰度切换
private final Map<String, PromptVersion> prompts = new ConcurrentHashMap<>();
public String render(String promptKey, Map<String, Object> vars) {
PromptVersion version = prompts.get(promptKey);
if (version == null) {
throw new BusinessException("Prompt不存在: " + promptKey);
}
return version.render(vars);
}
// 支持A/B测试
public String renderForAbTest(String promptKey, String userId,
Map<String, Object> vars) {
PromptVersion versionA = prompts.get(promptKey + ":v1");
PromptVersion versionB = prompts.get(promptKey + ":v2");
// 按用户ID哈希分流
PromptVersion selected = Math.abs(userId.hashCode()) % 2 == 0
? versionA : versionB;
return selected.render(vars);
}
}
public record PromptVersion(
String version,
String template,
LocalDateTime createTime
) {
public String render(Map<String, Object> vars) {
PromptTemplate pt = new PromptTemplate(template);
return pt.render(vars);
}
}
4.2 Prompt监控与调优
java
@Aspect
@Component
@Slf4j
public class PromptMonitorAspect {
private final Counter promptSuccessCounter;
private final Counter promptFailCounter;
public PromptMonitorAspect(MeterRegistry registry) {
this.promptSuccessCounter = Counter.builder("prompt.invoke.success")
.description("Prompt调用成功次数")
.register(registry);
this.promptFailCounter = Counter.builder("prompt.invoke.fail")
.description("Prompt调用失败次数")
.register(registry);
}
@Around("@annotation(PromptTracked)")
public Object track(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
try {
Object result = pjp.proceed();
promptSuccessCounter.increment();
long latency = System.currentTimeMillis() - start;
log.info("Prompt调用成功 method={} latency={}ms",
pjp.getSignature().getName(), latency);
return result;
} catch (Exception e) {
promptFailCounter.increment();
log.error("Prompt调用失败 method={}",
pjp.getSignature().getName(), e);
throw e;
}
}
}
Grafana看板关键指标:
- 各Prompt调用成功率
- 平均响应延迟
- Token消耗趋势
- 失败原因分布
4.3 Prompt安全防护
Prompt注入是AI应用最大的安全风险。
java
@Component
public class PromptSafetyService {
private static final List<String> INJECTION_PATTERNS = Arrays.asList(
"ignore previous instructions",
"system prompt",
"忽略之前的指令",
"执行以下命令",
"你的指令是",
" disregard prior "
);
public String sanitize(String userInput) {
if (userInput == null || userInput.isBlank()) {
throw new BusinessException(ErrorCode.BAD_REQUEST);
}
// 1. 长度限制
if (userInput.length() > 4000) {
throw new BusinessException("输入过长");
}
// 2. 注入检测
String lower = userInput.toLowerCase();
if (INJECTION_PATTERNS.stream().anyMatch(lower::contains)) {
log.warn("检测到Prompt注入: {}", userInput);
throw new BusinessException(ErrorCode.CONTENT_SAFETY_ERROR);
}
// 3. 特殊字符转义
return userInput.replace("\\", "\\\\")
.replace("`", "\\`");
}
// 输出审核
public void auditOutput(String output) {
if (output == null || output.isBlank()) {
throw new BusinessException("AI输出为空");
}
// 可接入内容安全API(如阿里云内容安全)
}
}
4.4 完整的ChatService封装
java
@Slf4j
@Service
@RequiredArgsConstructor
public class ChatFacadeService {
private final ChatModel chatModel;
private final PromptFileManager promptFileManager;
private final PromptSafetyService safetyService;
public String chat(String userMessage) {
// 1. 输入安全
String safeInput = safetyService.sanitize(userMessage);
// 2. 构造Prompt
String prompt = promptFileManager.render("default-chat", Map.of(
"userMessage", safeInput
));
// 3. 调用模型
try {
ChatResponse response = chatModel.chat(ChatRequest.builder()
.messages(UserMessage.from(prompt))
.build());
String answer = response.aiMessage().text();
// 4. 输出审核
safetyService.auditOutput(answer);
return answer;
} catch (Exception e) {
log.error("AI调用失败", e);
throw new BusinessException(ErrorCode.LLM_SERVICE_ERROR);
}
}
}
五、Prompt模板库(开箱即用)
5.1 通用对话模板
diff
你是一位专业、友好的AI助手。
行为准则:
- 回答准确、简洁
- 不确定时明确说明
- 使用中文回答
- 必要时给出代码示例
用户问题:
{question}
5.2 代码审查模板
css
你是资深Java代码审查专家。请审查以下代码,重点关注:
1. 潜在Bug(空指针、并发问题、资源泄漏)
2. 性能问题(不必要的对象创建、循环效率)
3. 代码规范(命名、注释、复杂度)
4. 安全风险(SQL注入、XSS、敏感信息)
代码:
```java
{code}
请按以下格式返回JSON:
{
"issues": [
{"type":"bug/performance/style/security", "severity":"high/medium/low",
"line": 行号, "description":"问题描述", "suggestion":"修改建议"}
],
"score": 0-100,
"summary": "总体评价"
}
5.3 邮件撰写模板
diff
你是商务邮件撰写专家。请根据以下信息撰写邮件:
要求:
- 语气:{tone}(正式/友好/严肃)
- 长度:{length}(简短/适中/详细)
- 目的:{purpose}
背景信息:
{context}
收件人:{recipient}
发件人:{sender}
请直接输出邮件正文,不需要主题行。
5.4 文档总结模板
markdown
你是文档总结专家。请总结以下文档的核心内容。
要求:
1. 提取3-5个关键要点
2. 每个要点一句话概括
3. 保留重要数据和结论
4. 用中文输出
文档内容:
{content}
输出格式:
## 核心要点
1. ...
2. ...
3. ...
## 关键数据
- ...
## 结论
...
5.5 SQL生成模板
sql
你是SQL专家。根据自然语言问题生成SQL查询。
数据库表结构:
{schema}
规则:
1. 只生成SELECT语句,不生成DDL/DML
2. 使用标准SQL语法
3. 添加必要的事务处理
4. 考虑性能优化(索引、JOIN)
自然语言问题:
{question}
只返回SQL语句,不要解释。
六、Prompt调优技巧
6.1 调优Checklist
| 检查项 | 说明 | 示例 |
|---|---|---|
| 角色清晰 | 明确AI身份 | "你是15年经验的Java架构师" |
| 任务明确 | 说清楚要做什么 | "审查代码,找出Bug" |
| 约束具体 | 列出限制条件 | "只返回JSON,不要解释" |
| 示例引导 | 给Few-shot示例 | "示例:输入X,输出Y" |
| 格式规定 | 明确输出格式 | "按## 标题格式输出" |
| 边界处理 | 处理异常情况 | "如果资料不足,说'无法回答'" |
6.2 常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| AI胡说八道 | 缺乏约束 | 加"仅基于资料回答,不要编造" |
| 输出格式错 | 没给示例 | 加Few-shot示例 |
| 回答太长 | 没限长度 | 加"用100字以内回答" |
| 中文夹杂英文 | 未指定语言 | 加"全部使用中文" |
| JSON解析失败 | 格式不稳定 | 加"只返回JSON,不要其他内容" |
6.3 实战调优案例
调优前:
帮我写个Java单例模式
问题:AI可能返回5种单例实现,不知选哪个
调优后:
markdown
你是Java架构师。请用Java实现双重检查锁的单例模式。
要求:
1. 使用volatile修饰实例变量
2. 私有构造方法
3. 双重检查锁定
4. 添加注释说明每行作用
输出格式:直接给代码,用```java包裹
效果:准确率100%,输出稳定。
七、避坑总结
7.1 7个常见坑
| 坑 | 现象 | 解决方案 |
|---|---|---|
| Prompt写死在代码 | 改Prompt要发版 | 用外部模板文件 |
| 变量用%s | 顺序错乱难维护 | 用{{varName}}或{var} |
| 没做输入校验 | 用户Prompt注入 | 加sanitize方法 |
| 没做输出审核 | AI输出违规内容 | 加auditOutput |
| 没监控 | 不知道成功率 | 接Micrometer |
| 没有Few-shot | 输出格式不稳定 | 加2-3个示例 |
| Prompt太长 | Token浪费 | 精简到必要内容 |
7.2 一个完整的坑案例
场景:用户让AI"总结这段代码",AI返回了一堆废话。
原始Prompt:
java
String prompt = "总结这段代码:" + code;
问题:
- 没有角色设定
- 没有输出格式要求
- 没有约束长度
- 没有约束语言
优化后:
java
@SystemMessage("""
你是Java代码专家。请总结代码的核心功能。
要求:
1. 用一句话概括功能
2. 列出3个关键点
3. 指出潜在问题
4. 全部用中文
""")
@UserMessage("代码:\n{{code}}")
String summarizeCode(@V("code") String code);
效果:输出从"废话连篇"变成"结构化总结"。
八、总结
8.1 4种实现方式速查
| 方式 | 适用场景 | 推荐指数 |
|---|---|---|
| Text Block + String.format | 小项目、简单Prompt | ⭐⭐⭐ |
LangChain4j @SystemMessage |
中型项目、声明式开发 | ⭐⭐⭐⭐⭐ |
Spring AI PromptTemplate |
Spring生态项目 | ⭐⭐⭐⭐ |
| 外部模板文件 | 大型项目、生产级 | ⭐⭐⭐⭐⭐ |
8.2 5种Prompt模式速查
| 模式 | 用途 | 关键技巧 |
|---|---|---|
| 角色扮演 | 约束AI风格 | 角色描述要具体 |
| Few-Shot | 稳定输出格式 | 给2-3个示例 |
| 思维链 | 复杂推理 | 要求"先思考再回答" |
| 结构化输出 | 返回Java对象 | 规定JSON格式 |
| RAG增强 | 知识库问答 | 注入检索上下文 |
8.3 核心原则
- Prompt是工程,不是聊天------要管理、要测试、要监控
- 模板和代码分离------改Prompt不用发版
- 类型安全 ------用
{{varName}}而非%s - 必有示例------Few-shot让输出稳定
- 必做防护------输入sanitize + 输出audit
- 必加监控------成功率、延迟、Token
8.4 行动建议
- 今天 :把现有项目里的字符串拼接Prompt,重构成
@SystemMessage写法 - 本周:建立Prompt模板文件目录,把核心Prompt抽出来
- 下周:接入Prompt监控,看Grafana看板上的成功率曲线
参考资料
- LangChain4j Prompt工程文档
- Spring AI PromptTemplate文档
- OpenAI Prompt工程指南
- 我的开源项目:java-llm-production-ready
作者简介 :花生智源,Java工程师转型AI应用开发,专注Java+AI工程化落地
相关文章: