很多Java工程师调通第一个AI接口后,都会遇到同一个问题:

复制代码// 新手写法:Prompt全靠字符串拼接
String prompt = "你是" + role + "专家,请帮我" + task +
",要求:" + requirements +
",输出格式:" + format +
"。背景信息:" + context;
String result = chatModel.chat(prompt);
这种写法有3个致命问题:
Prompt工程的核心不是"写一句好Prompt",而是"建立一套Prompt管理体系"。这篇文章就是教你如何用Java思维做Prompt工程。
复制代码// 直接传字符串,无模板
String answer = chatModel.chat("解释什么是RAG");
问题:无法复用,无法动态传参。
复制代码// 用模板引擎渲染
String prompt = promptTemplate.render(Map.of(
"role", "Java专家",
"task", "解释RAG"
));
String answer = chatModel.chat(prompt);
进步:Prompt和代码分离,可复用。但仍缺统一管理。
复制代码┌─────────────────────────────────────┐
│ Prompt工程化体系 │
├─────────────────────────────────────┤
│ │
│ 1. 模板管理(统一存储、版本控制) │
│ 2. 变量注入(类型安全、自动校验) │
│ 3. Prompt组装(System+User+Context)│
│ 4. 输出解析(结构化返回Java对象) │
│ 5. A/B测试(多版本Prompt对比) │
│ 6. 效果监控(成功率、Token消耗) │
│ 7. 安全防护(防注入、内容审核) │
│ │
└─────────────────────────────────────┘
这才是企业级Java AI应用需要的Prompt工程。
适合小型项目,JDK 17+的Text Block让多行字符串写起来很舒服。
复制代码@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) + "nn用户问题:" + question;
}
}
优点:零依赖,简单直接
缺点:变量多了容易混乱,%s没有语义
@SystemMessage + @UserMessage(推荐)声明式写法,Prompt直接写在注解里,类型安全。
复制代码@AiService
public interface CodeReviewService { @SystemMessage("你是资深Java代码审查专家,只关注代码质量,不修改功能")
@UserMessage("请审查以下代码,找出潜在问题:nn{{code}}")
String reviewCode(@V("code") String code);
}
变量占位符用 {{varName}},比%s清晰得多。
多变量示例:
复制代码@AiService
public interface TranslationService { @SystemMessage("你是专业翻译,精通{{from}}和{{to}}两种语言")
@UserMessage("请将以下{{from}}文本翻译为{{to}}:nn{{text}}")
String translate(
@V("from") String fromLanguage,
@V("to") String toLanguage,
@V("text") String text
);
}// 调用
String result = translationService.translate("中文", "英文", "你好世界");
优点:声明式、类型安全、变量名即语义
缺点:复杂Prompt逻辑(条件分支、循环)需要回到代码层
PromptTemplate(Spring生态首选)Spring AI提供了专门的PromptTemplate类,语法类似Thymeleaf。
复制代码@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容易冲突
把Prompt从代码中完全抽离,放到文件或数据库。
复制代码resources/
├── prompts/
│ ├── rag-system.st # RAG系统提示词
│ ├── code-review.st # 代码审查
│ ├── translation.st # 翻译
│ └── email-compose.st # 邮件撰写
rag-system.st:
复制代码你是一个智能知识库助手,请根据以下参考资料回答用户问题。规则:
1. 仅基于参考资料回答,不要编造信息
2. 如果参考资料不足以回答问题,请明确说明
3. 回答要简洁、准确、有条理参考资料:
{context}用户问题:
{question}
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
));
优点:
rag-system-zh.st / rag-system-en.st)缺点:需要额外加载逻辑
给AI明确角色,约束输出风格。
复制代码@AiService
public interface JavaExpertService {
@SystemMessage("""
你是一位资深Java架构师,特点:
- 15年Java开发经验
- 专注高并发、高可用系统设计
- 回答风格:专业、简洁、有代码示例
- 不说废话,直奔主题
""")
String answer(@UserMessage String question);
}
关键:角色描述要具体,"你是Java专家"不如"你是15年经验的Java架构师"。
给AI几个示例,让它学会输出格式。
复制代码@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。
复制代码public record SentimentResult(
String sentiment, // positive / negative / neutral
double score // 0.0 - 1.0
) {}
让AI"先思考,再回答",提升复杂问题准确率。
复制代码@AiService
public interface ProblemSolvingService {
@SystemMessage("""
你是问题解决专家。解决复杂问题时,请按以下步骤:
1. 理解问题:复述问题,明确目标
2. 分析约束:列出已知条件和限制
3. 思考方案:列出2-3种可能的方案
4. 评估方案:对比优缺点
5. 选择最优:给出最终方案和理由
请用以下格式输出:
## 问题理解
...
## 约束分析
...
## 方案思考
...
## 方案评估
...
## 最终方案
...
""")
String solve(@UserMessage String problem);
}
效果:复杂推理任务准确率提升30-50%。
让AI返回结构化数据,直接映射为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) {}
}
复制代码// Spring AI方式
@Service
public class CodeReviewService {
public CodeReviewResult review(String code) {
return chatClient.prompt()
.system("你是代码审查专家...")
.user("审查代码:n" + code)
.call()
.entity(CodeReviewResult.class); // 自动解析JSON
}
}
这是企业AI应用最核心的模式。把检索到的上下文注入Prompt。
复制代码@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%snn",
i + 1,
match.getSource(),
match.getScore(),
match.getContent()
));
}
return String.format(RAG_TEMPLATE, context, question);
}
}
实际项目中的RAG Prompt示例(来自 java-llm-production-ready 项目):
复制代码你是一个智能知识库助手,请根据以下参考资料回答用户问题。规则:
1. 仅基于参考资料回答,不要编造信息
2. 如果参考资料不足以回答问题,请明确说明
3. 回答要简洁、准确、有条理
4. 使用中文回答参考资料:
[1] (来源: employee-handbook.pdf, 相似度: 0.89)
员工年假政策:入职满1年享有5天年假,满3年享有10天年假...[2] (来源: hr-faq.md, 相似度: 0.85)
年假申请流程:在OA系统提交申请,直属领导审批...用户问题:
我刚入职半年,能请年假吗?
像管理代码一样管理Prompt版本。
复制代码@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);
}
}
复制代码@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注入是AI应用最大的安全风险。
复制代码@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(如阿里云内容安全)
}
}
复制代码@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);
}
}
}
复制代码你是一位专业、友好的AI助手。行为准则:
- 回答准确、简洁
- 不确定时明确说明
- 使用中文回答
- 必要时给出代码示例用户问题:
{question}
复制代码你是资深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": "总体评价"
}
复制代码
你是商务邮件撰写专家。请根据以下信息撰写邮件:要求:- 语气:{tone}(正式/友好/严肃)
- 长度:{length}(简短/适中/详细)
- 目的:{purpose}背景信息:
{context}收件人:{recipient}
发件人:{sender}请直接输出邮件正文,不需要主题行。
复制代码
你是文档总结专家。请总结以下文档的核心内容。要求:1. 提取3-5个关键要点
2. 每个要点一句话概括
3. 保留重要数据和结论
4. 用中文输出文档内容:
{content}输出格式:## 核心要点1. ...
2. ...
3. ...## 关键数据- ...## 结论...
复制代码
你是SQL专家。根据自然语言问题生成SQL查询。数据库表结构:
{schema}规则:1. 只生成SELECT语句,不生成DDL/DML
2. 使用标准SQL语法
3. 添加必要的事务处理
4. 考虑性能优化(索引、JOIN)自然语言问题:
{question}只返回SQL语句,不要解释。
| 检查项 | 说明 | 示例 |
|---|---|---|
| 角色清晰 | 明确AI身份 | "你是15年经验的Java架构师" |
| 任务明确 | 说清楚要做什么 | "审查代码,找出Bug" |
| 约束具体 | 列出限制条件 | "只返回JSON,不要解释" |
| 示例引导 | 给Few-shot示例 | "示例:输入X,输出Y" |
| 格式规定 | 明确输出格式 | "按## 标题格式输出" |
| 边界处理 | 处理异常情况 | "如果资料不足,说'无法回答'" |
| 问题 | 原因 | 解决方案 |
|---|---|---|
| AI胡说八道 | 缺乏约束 | 加"仅基于资料回答,不要编造" |
| 输出格式错 | 没给示例 | 加Few-shot示例 |
| 回答太长 | 没限长度 | 加"用100字以内回答" |
| 中文夹杂英文 | 未指定语言 | 加"全部使用中文" |
| JSON解析失败 | 格式不稳定 | 加"只返回JSON,不要其他内容" |
调优前:
复制代码
帮我写个Java单例模式
问题:AI可能返回5种单例实现,不知选哪个
调优后:
复制代码
你是Java架构师。请用Java实现双重检查锁的单例模式。要求:1. 使用volatile修饰实例变量
2. 私有构造方法
3. 双重检查锁定
4. 添加注释说明每行作用输出格式:直接给代码,用```java包裹
效果:准确率100%,输出稳定。
| 坑 | 现象 | 解决方案 |
|---|---|---|
| Prompt写死在代码 | 改Prompt要发版 | 用外部模板文件 |
| 变量用%s | 顺序错乱难维护 | 用{{varName}}或{var} |
| 没做输入校验 | 用户Prompt注入 | 加sanitize方法 |
| 没做输出审核 | AI输出违规内容 | 加auditOutput |
| 没监控 | 不知道成功率 | 接Micrometer |
| 没有Few-shot | 输出格式不稳定 | 加2-3个示例 |
| Prompt太长 | Token浪费 | 精简到必要内容 |
场景:用户让AI"总结这段代码",AI返回了一堆废话。
原始Prompt:
复制代码String prompt = "总结这段代码:" + code;
问题:
优化后:
复制代码@SystemMessage("""
你是Java代码专家。请总结代码的核心功能。
要求:
1. 用一句话概括功能
2. 列出3个关键点
3. 指出潜在问题
4. 全部用中文
""")
@UserMessage("代码:n{{code}}")
String summarizeCode(@V("code") String code);
效果:输出从"废话连篇"变成"结构化总结"。
| 方式 | 适用场景 | 推荐指数 |
|---|---|---|
| Text Block + String.format | 小项目、简单Prompt | ⭐⭐⭐ |
LangChain4j @SystemMessage | 中型项目、声明式开发 | ⭐⭐⭐⭐⭐ |
Spring AI PromptTemplate | Spring生态项目 | ⭐⭐⭐⭐ |
| 外部模板文件 | 大型项目、生产级 | ⭐⭐⭐⭐⭐ |
| 模式 | 用途 | 关键技巧 |
|---|---|---|
| 角色扮演 | 约束AI风格 | 角色描述要具体 |
| Few-Shot | 稳定输出格式 | 给2-3个示例 |
| 思维链 | 复杂推理 | 要求"先思考再回答" |
| 结构化输出 | 返回Java对象 | 规定JSON格式 |
| RAG增强 | 知识库问答 | 注入检索上下文 |
{{varName}}而非%s@SystemMessage写法作者简介:花生智源,Java工程师转型AI应用开发,专注Java+AI工程化落地
相关文章: