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

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

Prompt不是"和AI聊天",而是"给AI写需求文档"。本文从Java工程师视角,教你把Prompt从字符串拼接升级为可维护、可测试、可复用的工程化体系。


写在前面

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

java 复制代码
// ❌ 新手写法: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:基础调用(新手级)

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      # 邮件撰写

rag-system.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;

问题

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

优化后

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 核心原则

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

8.4 行动建议

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

参考资料


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

相关文章

相关推荐
江畔柳前堤3 小时前
roLabelImg 详细安装教程
开发语言·人工智能·后端·云原生
阿里云大数据AI技术3 小时前
分链路差异化设计的DSP准实时数仓|钛动科技基于阿里云实时计算 Flink 版 + DLF Paimon + EMR Serverless StarRocks 的实践
人工智能·flink
陕西企来客3 小时前
2026年7月AI智能搜索曝光趋势研判
大数据·人工智能·机器学习·ai智能搜索曝光
白鸽(二般)4 小时前
MinIO Java Client API
java·开发语言
阿里云大数据AI技术4 小时前
从算力到智能体,面向 Agentic AI 的基础设施演进
人工智能·agent
hangyuekejiGEO5 小时前
GEO技术服务选型指南
大数据·人工智能·python
阿里云大数据AI技术5 小时前
EMR Serverless Spark AI Function 的双维降本实践
人工智能·sql·spark
软萌萌的15 小时前
Java Spring Boot 修改yml配置&加载顺序规则
java·spring boot·python
维基框架5 小时前
GitHub源码处理提速 一趟扫描反而更慢
人工智能·github
冬奇Lab5 小时前
代码库知识库系列(05):向量检索 vs 知识图谱——加了调用图并没有变更好
人工智能