跳转至

02. 提示词工程与 Java 模板

🎓 本节目标:从“随便聊聊”到“精准控制”

你是否发现,AI 有时候很聪明,有时候又答非所问? 问题的关键不在于 AI 笨,而在于你的 Prompt (提示词) 写得不够好

1
2
3
4
作为后端开发,我们不能像普通用户那样每次手敲问题。我们需要:
1.  **结构化**:区分“人设”和“指令”。
2.  **模板化**:用 Java 代码动态组装 Prompt。
3.  **示例化**:教会 AI 举一反三 (Few-Shot)。

🎭 第一部分:Prompt 的三明治结构

在 OpenAI/ModelScope 的协议中,一条完整的 Prompt 并不是一个简单的字符串,而是由不同角色的消息组成的列表。

最经典的是 System (系统/人设) 和 User (用户/指令) 的配合。

角色 (Role) 作用 比喻
System 设定 AI 的行为模式、语气、身份。 导演:“你现在是一个资深的 Java 面试官,语气要严厉。”
User 用户具体的提问或指令。 演员:“请问 HashMap 的底层原理是什么?”
Assistant AI 的回复历史 (用于多轮对话)。 搭档:“(AI 之前的回答...)”

❌ 错误示范 (只有 User)

User: "把这句话翻译成英文:你好"

✅ 正确示范 (System + User)

System: "你是一个精通中英互译的翻译官,只能输出翻译结果,不要多说话。" User: "你好"


🧱 第二部分:Java 新特性——文本块 (Text Blocks)

在 JDK 13 以前,要在 Java 里拼接一个多行的 Prompt 或者 JSON,简直是噩梦:

1
2
3
4
5
// ❌ JDK 8 的写法:丑陋、易错、难以阅读
String json = "{\n" +
              "  \"role\": \"system\",\n" +
              "  \"content\": \"你是个助手\"\n" +
              "}";

JDK 15 开始,Java 正式引入了 Text Blocks (文本块),使用 """ 包裹。这简直是为 AI 开发量身定做的功能!

1
2
3
4
5
6
7
// ✅ JDK 15+ 的写法:清爽、所见即所得
String json = """
    {
        "role": "system",
        "content": "你是个助手"
    }
    """;

配合 .formatted() 方法(相当于 String.format 的简化版),我们可以轻松创建 Prompt 模板


🧠 第三部分:技巧——Few-Shot (少样本提示)

这是 Prompt Engineering 中性价比最高的技巧。 不要只告诉 AI “做什么”,而是给它看几个“成功的例子”

场景:情感分析

我们希望 AI 判断用户评论是“正面”还是“负面”,并只返回 POSITIVENEGATIVE

Zero-Shot (零样本 - 直接问)

User: "这家店很难吃。是正面还是负面?" AI: "这句话表达了对食物的不满,所以是负面评价。" (废话太多,不好解析)

Few-Shot (少样本 - 给例子)

User: 这是一个情感分析任务。 例子 1: 输入:"味道不错" -> 输出:POSITIVE 例子 2: 输入:"服务员态度很差" -> 输出:NEGATIVE 请分析以下输入:"这家店很难吃" -> 输出:

AI: "NEGATIVE" (非常听话)


💻 第四部分:代码实战

我们将结合 System 人设文本块Few-Shot,写一个“智能情感分析器”。

src/test/java 下新建 PromptTest.java

PromptTest.java
package com.example.demo;

import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.web.client.RestClient;

@SpringBootTest
public class PromptTest {

    private static final String API_KEY = "sk-你的Token";
    private static final String API_URL = "https://api-inference.modelscope.cn/v1/chat/completions";

    @Test
    void testSentimentAnalysis() {
        // ==========================================
        // 1. 定义 Prompt 模板 (Java 文本块)
        // ==========================================

        // System Prompt: 确立人设
        String systemPrompt = "你是一个情感分析专家。请根据用户的输入,只返回 'POSITIVE' 或 'NEGATIVE',不要包含任何其他文字。";

        // User Prompt: 包含 Few-Shot 示例 + 动态变量 (%s)
        String userPromptTemplate = """
            这里有一些示例:
            输入:"快递很快,包装完好" -> 输出:POSITIVE
            输入:"虽然便宜,但是质量太差了" -> 输出:NEGATIVE
            输入:"老板人很好,下次还来" -> 输出:POSITIVE

            现在请分析这个输入:
            输入:"%s" -> 输出:
            """;

        // 模拟用户输入
        String userInput = "衣服很漂亮,但是物流太慢了,等了一周!";

        // 组装最终的 User Prompt
        String finalUserPrompt = userPromptTemplate.formatted(userInput);

        // ==========================================
        // 2. 构建 JSON 请求体
        // ==========================================
        // 注意:这里为了演示方便直接拼 JSON。
        // 真实项目中,如果 input 包含换行或引号,直接拼会报错。下一节我们会用 JSON 库来解决这个问题。
        String jsonBody = """
            {
                "model": "deepseek-ai/DeepSeek-R1-0528",
                "messages": [
                    {
                        "role": "system", 
                        "content": "%s"
                    },
                    {
                        "role": "user", 
                        "content": "%s"
                    }
                ]
            }
            """.formatted(systemPrompt, finalUserPrompt.replace("\n", "\\n").replace("\"", "\\\"")); 
            // 👆 简单的转义处理,防止 JSON 格式破损

        // ==========================================
        // 3. 发送请求
        // ==========================================
        RestClient client = RestClient.builder()
                .baseUrl(API_URL)
                .defaultHeader("Authorization", "Bearer " + API_KEY)
                .build();

        System.out.println("🤖 正在分析情感...");
        String response = client.post()
                .contentType(MediaType.APPLICATION_JSON)
                .body(jsonBody)
                .retrieve()
                .body(String.class);

        System.out.println("✅ AI 原始回复: " + response);
    }
}

运行结果预期

由于我们使用了 System 限定Few-Shot 引导,AI 应该会非常克制,只输出我们想要的结果(可能 DeepSeek-R1 会先输出 <think>,但最终结论会很标准)。

1
2
3
4
{
  ...
  "content": "<think>...</think>NEGATIVE"
}

⚠️ 痛点预告:JSON 拼接的风险

仔细看上面的代码,为了把 prompt 塞进 JSON,我不得不写了这样一行代码:

finalUserPrompt.replace("\n", "\\n").replace("\"", "\\\"")

这是非常危险且不优雅的!

  • 如果用户输入里有复杂的字符(比如 Emoji、Tab),手写替换很容易漏掉,导致 JSON 格式错误(400 Bad Request)。
  • 代码里充斥着大量的转义符,难以维护。

如何优雅地把 Java 对象转换成 JSON,并让 AI 稳定地返回 JSON 格式的数据? 下一节,我们将引入 JacksonJSON Schema,让程序交互变得真正“专业化”。


📝 总结

  1. 结构化:Prompt 不是一句话,而是 System (人设) + User (指令) 的组合。
  2. 工具:使用 Java 15+ 的 Text Blocks (""").formatted(),让 Prompt 代码清晰可读。
  3. 心法Few-Shot (少样本) 是提高 AI 准确率的神器,给它几个例子,它就能懂你的规矩。

下一节:让 AI 说“机器话”:JSON Schema 与解析