一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

Java实现Prompt工程的技巧——从模板到工程化:告别字符串拼接

时间:2026-07-15 19:24:50 编辑:袖梨 来源:一聚教程网

Java实现Prompt工程的技巧——从模板到工程化,告别字符串拼接


写在前面

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

Java实现Prompt工程的技巧——从模板到工程化,告别字符串拼接

 复制代码//  新手写法:Prompt全靠字符串拼接
String prompt = "你是" + role + "专家,请帮我" + task + 
                ",要求:" + requirements + 
                ",输出格式:" + format + 
                "。背景信息:" + context;
String result = chatModel.chat(prompt);

这种写法有3个致命问题:

  1. 可读性差:Prompt逻辑和业务逻辑混在一起
  2. 难维护:改一个字都要重新发版
  3. 易出错:转义符、引号、换行搞死人

Prompt工程的核心不是"写一句好Prompt",而是"建立一套Prompt管理体系"。这篇文章就是教你如何用Java思维做Prompt工程。


一、Prompt工程的3个层次

1.1 层次1:基础调用(新手级)

 复制代码// 直接传字符串,无模板
String answer = chatModel.chat("解释什么是RAG");

问题:无法复用,无法动态传参。

1.2 层次2:模板化(进阶级)

 复制代码// 用模板引擎渲染
String prompt = promptTemplate.render(Map.of(
    "role", "Java专家",
    "task", "解释RAG"
));
String answer = chatModel.chat(prompt);

进步:Prompt和代码分离,可复用。但仍缺统一管理。

1.3 层次3:工程化(生产级)

 复制代码┌─────────────────────────────────────┐
│        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让多行字符串写起来很舒服。

 复制代码@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没有语义

2.2 方式2:LangChain4j @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逻辑(条件分支、循环)需要回到代码层

2.3 方式3:Spring AI 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容易冲突

2.4 方式4:外部模板文件(生产级首选)

把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
));

优点

  • Prompt和代码完全解耦
  • 改Prompt不用重新编译
  • 方便A/B测试(多个模板文件)
  • 易于国际化(rag-system-zh.st / rag-system-en.st

缺点:需要额外加载逻辑


三、5种常用Prompt模式(Java实现)

3.1 模式1:角色扮演(Role-Playing)

给AI明确角色,约束输出风格。

 复制代码@AiService
public interface JavaExpertService {
    
    @SystemMessage("""
        你是一位资深Java架构师,特点:
        - 15年Java开发经验
        - 专注高并发、高可用系统设计
        - 回答风格:专业、简洁、有代码示例
        - 不说废话,直奔主题
        """)
    String answer(@UserMessage String question);
}

关键:角色描述要具体,"你是Java专家"不如"你是15年经验的Java架构师"。

3.2 模式2:少样本学习(Few-Shot)

给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
) {}

3.3 模式3:思维链(Chain-of-Thought)

让AI"先思考,再回答",提升复杂问题准确率。

 复制代码@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对象。

 复制代码// 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
    }
}

3.5 模式5:RAG增强(检索增强生成)

这是企业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工程体系

4.1 Prompt版本管理

像管理代码一样管理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);
    }
}

4.2 Prompt监控与调优

 复制代码@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应用最大的安全风险。

 复制代码@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封装

 复制代码@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 通用对话模板

 复制代码你是一位专业、友好的AI助手。行为准则:
- 回答准确、简洁
- 不确定时明确说明
- 使用中文回答
- 必要时给出代码示例用户问题:
{question}

5.2 代码审查模板

 复制代码你是资深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 邮件撰写模板

 复制代码
你是商务邮件撰写专家。请根据以下信息撰写邮件:要求:- 语气:{tone}(正式/友好/严肃)
- 长度:{length}(简短/适中/详细)
- 目的:{purpose}背景信息:
{context}收件人:{recipient}
发件人:{sender}请直接输出邮件正文,不需要主题行。

5.4 文档总结模板

 复制代码
你是文档总结专家。请总结以下文档的核心内容。要求:1. 提取3-5个关键要点
2. 每个要点一句话概括
3. 保留重要数据和结论
4. 用中文输出文档内容:
{content}输出格式:## 核心要点1. ...
2. ...
3. ...## 关键数据- ...## 结论...

5.5 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种单例实现,不知选哪个

调优后

 复制代码
你是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

 复制代码String prompt = "总结这段代码:" + code;

问题

  1. 没有角色设定
  2. 没有输出格式要求
  3. 没有约束长度
  4. 没有约束语言

优化后

 复制代码@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 PromptTemplateSpring生态项目⭐⭐⭐⭐
外部模板文件大型项目、生产级⭐⭐⭐⭐⭐

8.2 5种Prompt模式速查

模式用途关键技巧
角色扮演约束AI风格角色描述要具体
Few-Shot稳定输出格式给2-3个示例
思维链复杂推理要求"先思考再回答"
结构化输出返回Java对象规定JSON格式
RAG增强知识库问答注入检索上下文

8.3 核心原则

  1. Prompt是工程,不是聊天——要管理、要测试、要监控
  2. 模板和代码分离——改Prompt不用发版
  3. 类型安全——用{{varName}}而非%s
  4. 必有示例——Few-shot让输出稳定
  5. 必做防护——输入sanitize + 输出audit
  6. 必加监控——成功率、延迟、Token

8.4 行动建议

  • 今天:把现有项目里的字符串拼接Prompt,重构成@SystemMessage写法
  • 本周:建立Prompt模板文件目录,把核心Prompt抽出来
  • 下周:接入Prompt监控,看Grafana看板上的成功率曲线

参考资料

  • LangChain4j Prompt工程文档
  • Spring AI PromptTemplate文档
  • OpenAI Prompt工程指南
  • 我的开源项目:java-llm-production-ready

作者简介:花生智源,Java工程师转型AI应用开发,专注Java+AI工程化落地
相关文章

  • Java工程师为什么要学AI?(市场趋势分析)
  • 30分钟给Spring Boot加上AI能力
  • LangChain4j vs Spring AI,Java团队怎么选
  • Java调用通义千问API完整教程
  • Java工程师的AI学习路线图

热门栏目