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工程化落地

相关文章

相关推荐
Black蜡笔小新1 小时前
企业AI算力工作站/AI大模型训练工作站DLTM企业私有化AI视觉中台建设方案设计
人工智能
墨虹雨茶1 小时前
Loop Engineering 正在重新定义"程序员"这个职业
人工智能
夕小瑶1 小时前
字节把 PS 做进了生图模型里,实测 Seedream 5.0 Pro 指哪改哪
人工智能
折哥的程序人生 · 物流技术专研1 小时前
Java 23 种设计模式:从踩坑到精通 | 番外:策略 vs 模板方法 —— 组合与继承的终极对决
java·策略模式·java面试·comparator·模版方法模式·java设计模式·从踩坑到精通
量化吞吐机1 小时前
2026年AI量化工具评估,先分开学习开发和验证
人工智能·python
IvorySQL1 小时前
PG 日报|新增 VFD 缓存监控视图,精细化数据库调优
大数据·数据库·人工智能·缓存·postgresql·区块链
小白狮ww1 小时前
告别「机械念经」:TADA-1b 让模型「说人话」
人工智能·语音识别·语音生成·ai声音
咏方舟【长江支流】1 小时前
【开源】跨语言·跨平台·跨数据库(3) —— 为用宝框架增加ORM,让AI执行
数据库·人工智能·开源·ai编程·orm·咏方舟-长江支流·用宝框架
AliCloudROS1 小时前
一次真实录屏:我只说每月别超过 200 块,小程序后端就搭好了
运维·人工智能·云计算