04-Prompt工程最佳实践

2 阅读25分钟

5分钟快速入门,30分钟深度讲解,学会和LLM"说人话"

时间:30分钟 | 难度:⭐⭐⭐


官方Example信息

  • GitHub链接PromptExample.java
  • 文件名:PromptExample.java
  • 所在路径:src/main/java/dev/langchain4j/examples/
  • 代码行数:约60行
  • 难度:中级 ⭐⭐⭐

学习目标

  • 理解什么是好的Prompt ✅ 2026-03-07
  • 掌握Prompt编写的5个核心原则 ✅ 2026-03-07
  • 学会Few-shot Learning技巧 ✅ 2026-03-07
  • 学会Chain-of-Thought推理 ✅ 2026-03-07
  • 能在实际项目中写出高效的Prompt ✅ 2026-03-07
  • 掌握LangChain4J中的Message类型
  • 理解SystemMessage、UserMessage、AiMessage的用途
  • 学会管理多轮对话的消息历史
  • 能优化Message对Token成本的影响

快速速查:Prompt的5个黄金法则

法则解释例子
1️⃣ 明确性告诉LLM具体要做什么❌ "帮我分析代码" → ✅ "找出这段代码的性能瓶颈"
2️⃣ 上下文提供足够的背景信息❌ "翻译" → ✅ "把以下Java代码注释翻译成英文"
3️⃣ 约束条件限制输出的格式和长度✅ "用100字以内概括" "用JSON格式"
4️⃣ 示例通过例子说明你要什么✅ Few-shot Learning
5️⃣ 结构化把问题分解成小步骤✅ Chain-of-Thought推理

深度讲解

原理:为什么Prompt很重要

LLM本质上是概率模型,它会基于你的输入(Prompt)预测最可能的下一个词。

Prompt质量差:
输入: "代码"
     ↓
[低信号→低质量输出]
     ↓
输出: "代码是什么?" (泛泛而谈)

Prompt质量好:
输入: "找出这段Java代码的N+1查询问题,并给出具体的行号"
     ↓
[高信号→高质量输出]
     ↓
输出: "第12行的循环中有N+1查询:for (Order o : orders) { db.query(...) }" (精准击中)

Prompt越清楚,LLM的"预测方向"就越准确。


法则1️⃣:明确性 - 告诉LLM具体要做什么

❌ 差的Prompt(模糊)

String poorPrompt = "帮我做代码审查";

LLM不知道:

  • 代码在哪里?
  • 关注什么方面?(性能?安全?可读性?)
  • 输出什么格式?

✅ 好的Prompt(明确)

String goodPrompt = """
    请进行Java代码审查,关注以下方面:
    1. 性能瓶颈(特别是数据库查询)
    2. 内存泄漏风险
    3. 线程安全问题

    代码:
    [代码]

    输出格式:
    - 问题描述
    - 严重程度(高/中/低)
    - 修复建议
    """;

LLM知道:

  • ✅ 关注具体的方面
  • ✅ 知道输出格式
  • ✅ 能提供有针对性的建议

法则2️⃣:上下文 - 提供背景信息

❌ 缺少上下文

String prompt = "这个SQL查询对吗?\nSELECT * FROM orders";

LLM回答很泛泛:

  • 语法对,但不知道表结构
  • 可能不适合某个特定场景

✅ 充分的上下文

String prompt = """
    我有一个电商系统,表结构如下:
    - orders表:id, user_id, total_amount, created_at, status
    - order_items表:id, order_id, product_id, quantity, price

    业务需求:查询2024年的所有订单,包括订单总金额和商品详情

    这个SQL查询对吗?
    SELECT o.*, oi.* FROM orders o
    JOIN order_items oi ON o.id = oi.order_id
    WHERE YEAR(o.created_at) = 2024
    """;

LLM能回答:

  • ✅ 语法是否正确
  • ✅ 是否满足业务需求
  • ✅ 是否有性能问题
  • ✅ 建议改进方案

法则3️⃣:约束条件 - 限制输出

问题:输出不可控

ChatModel model = OpenAiChatModel.builder().apiKey(...).build();
String prompt = "怎样学习Java?";
String response = model.chat(prompt);
// 输出可能是:
// - 500字的长文章
// - 或者10个简短要点
// - 或者一个故事
// → 不可控!

解决方案1:限制长度

String prompt = """
    用100字以内的中文回答:怎样学习Java?
    """;
// 输出会是简洁的100字以内

解决方案2:指定格式

String prompt = """
    用以下JSON格式回答"怎样学习Java":
    {
      "steps": ["第一步", "第二步", ...],
      "time_required": "3个月",
      "resources": ["书籍", "网课", ...]
    }
    """;
// 输出会是有效的JSON

解决方案3:限制风格

String prompt = """
    用简洁、技术性的语言回答(避免冗长的介绍):
    怎样学习Java?
    """;

法则4️⃣:示例 - Few-Shot Learning

❌ 无示例(Zero-Shot)

String prompt = "给这段代码起个变量名:\nint x = 10;";
// LLM可能回答:
// "变量x代表某个数值"
// → 太泛泛

✅ 有示例(Few-Shot)

String prompt = """
    根据上下文,为这些代码变量起个有意义的名字。

    示例1:
    输入:int x = 30;
    上下文:这是员工的年龄
    输出:int employeeAge = 30;

    示例2:
    输入:double y = 9.99;
    上下文:这是商品的价格
    输出:double productPrice = 9.99;

    现在轮到你:
    输入:int n = 100;
    上下文:这是查询结果的最大数量
    输出:
    """;
// LLM会按照示例的模式回答:
// 输出:int maxQueryResults = 100;

Few-Shot为什么有效?

LLM通过示例理解你的期望

  • 示例1、2展示了"命名规则"
  • 示例展示了"输入→输出"的模式
  • LLM会按这个模式处理新问题

法则5️⃣:结构化推理 - Chain-of-Thought

❌ 直接要求答案

String prompt = "这段代码有什么问题?\n[大段代码]";
// LLM可能只说表面问题,忽视深层风险

✅ 要求逐步推理

String prompt = """
    分析这段Java代码,按以下步骤回答:

    第1步:识别代码的目的
    第2步:检查逻辑是否正确
    第3步:检查是否有性能问题
    第4步:检查是否有安全风险
    第5步:给出改进建议

    代码:
    [大段代码]
    """;
// LLM会逐步思考,输出更全面的分析

Chain-of-Thought的威力

  • 强制LLM思考每个步骤
  • 避免跳过重要分析
  • 输出更有逻辑、更深入

官方Example代码分析

基础Prompt示例

public class PromptExample {
    public static void main(String[] args) {
        ChatModel model = OpenAiChatModel.builder()
            .apiKey(System.getenv("OPENAI_API_KEY"))
            .modelName("gpt-4o-mini")
            .build();

        // 示例1:基础问答
        System.out.println("=== 示例1:基础问答 ===");
        String basicPrompt = "Java中什么是多态?";
        String basicResponse = model.chat(basicPrompt);
        System.out.println(basicResponse);

        // 示例2:明确约束条件
        System.out.println("\n=== 示例2:明确约束条件 ===");
        String constrainedPrompt = "用50字以内用中文解释Java多态";
        String constrainedResponse = model.chat(constrainedPrompt);
        System.out.println(constrainedResponse);

        // 示例3:Few-Shot Learning
        System.out.println("\n=== 示例3:Few-Shot Learning ===");
        String fewShotPrompt = """
            根据以下示例分类,判断下面的代码片段属于哪种设计模式:

            示例1:
            代码:class Config { private static Config instance;
                  public static Config getInstance() { ... } }
            分类:单例模式

            示例2:
            代码:new ConcreteProductA(); new ConcreteProductB();
            分类:工厂模式

            现在判断这段代码:
            interface Shape { void draw(); }
            class Circle implements Shape { ... }
            class Square implements Shape { ... }
            分类:
            """;
        String fewShotResponse = model.chat(fewShotPrompt);
        System.out.println(fewShotResponse);

        // 示例4:Chain-of-Thought
        System.out.println("\n=== 示例4:Chain-of-Thought ===");
        String cotPrompt = """
            分析代码的时间复杂度,按步骤回答:

            第1步:识别循环结构
            第2步:计算每层循环的迭代次数
            第3步:累乘得到总复杂度

            代码:
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    System.out.println(i * j);
                }
            }
            """;
        String cotResponse = model.chat(cotPrompt);
        System.out.println(cotResponse);
    }
}

LangChain4J Message 类型深度讲解

核心概念:Message 的本质

在LangChain4J中,对话不是简单的字符串交互,而是通过**消息对象(Message)**来结构化管理的。这样做的好处是:

字符串方式(不推荐):
request = "你是Java专家" + "\n" + "请审查这段代码" + "\n" + code
→ 容易出错,难以维护

Message方式(推荐):
messages = [
  SystemMessage("你是Java专家"),
  UserMessage("请审查这段代码:" + code)
]
→ 结构清晰,易于扩展

Message 的层级结构

┌─────────────────────────────────────┐
│          ChatMessage (接口)          │
└────────────────┬────────────────────┘
                 │
        ┌────────┼────────┐
        │        │        │
┌───────▼────┐  ┌─▼──────────┐  ┌────────▼──────┐
│UserMessage │  │ SystemMsg  │  │   AiMessage    │
└────────────┘  └────────────┘  └────────────────┘
        │             │                 │
    用户说话      系统指令          AI回复

1️⃣ UserMessage - 用户消息

定义:用户发送给LLM的消息

本质:代表用户的输入,LLM基于此生成回复

// 最简单的形式
UserMessage msg = UserMessage.from("请帮我写一个Java排序算法");

// 等价写法
UserMessage msg = new UserMessage("请帮我写一个Java排序算法");

// 在对话中的使用
List<ChatMessage> messages = new ArrayList<>();
messages.add(UserMessage.from("你好"));
messages.add(UserMessage.from("今天天气怎样?"));

String response = model.chat(messages);

UserMessage 的特点

  • ✅ 代表用户的真实意图
  • ✅ LLM会优先理解这个消息
  • ✅ 可以在对话历史中任何位置
  • ❌ 不应该包含系统指令(那是 SystemMessage 的工作)

2️⃣ SystemMessage - 系统消息

定义:给LLM的系统级指令,定义其角色和行为

本质:约束和指导LLM的生成方向

// 基础用法
SystemMessage roleMsg = SystemMessage.from("你是一位资深的Java工程师");

// 完整的系统提示词
String systemPrompt = """
    你是一位20年经验的Java架构师。

    你的特点:
    1. 代码审查时关注性能和安全
    2. 解释复杂概念时用通俗语言
    3. 提建议时说明理由
    4. 代码示例用最新的Java特性
    """;

SystemMessage systemMsg = SystemMessage.from(systemPrompt);

// 在对话中使用
List<ChatMessage> messages = new ArrayList<>();
messages.add(systemMsg);                          // 第一条总是SystemMessage
messages.add(UserMessage.from("请审查这段代码"));
messages.add(UserMessage.from("为什么要这样改?"));

String response = model.chat(messages);

SystemMessage 的作用

  • ✅ 定义LLM的角色和身份
  • ✅ 设置输出风格和格式
  • ✅ 约束LLM的行为范围
  • ✅ 优先级最高,影响整个对话

SystemMessage 应该在对话中的位置

// ✅ 正确:SystemMessage 在最前面
List<ChatMessage> messages = new ArrayList<>();
messages.add(SystemMessage.from("你是..."));     // 第1条
messages.add(UserMessage.from("..."));            // 第2条
messages.add(UserMessage.from("..."));            // 第3条

// ❌ 错误:SystemMessage 不在最前面
List<ChatMessage> messages = new ArrayList<>();
messages.add(UserMessage.from("..."));            // 第1条
messages.add(SystemMessage.from("你是..."));     // ❌ 位置不对

3️⃣ AiMessage - AI消息(完整讲解)

定义:LLM生成的回复消息

本质:LLM输出,也是下一轮对话的参考,包含多个重要信息

AiMessage 的结构

// AiMessage 包含的信息
AiMessage {
    String text;              // AI的文本回复
    List<ToolCall> toolCalls; // 工具调用(Agent场景)
    Map metadata;             // 元数据
}

// 获取方式
Response<AiMessage> response = model.generate(messages);
AiMessage aiMsg = response.content();

// 详细信息
String textContent = aiMsg.text();                    // AI回复文本
List<ToolCall> calls = aiMsg.toolCalls();            // 工具调用列表
TokenUsage usage = response.tokenUsage();             // Token使用统计

int inputTokens = usage.inputTokens();               // 输入Token数
int outputTokens = usage.outputTokens();             // 输出Token数
int totalTokens = inputTokens + outputTokens;        // 总Token数

AiMessage 的常见用法

用法1:简单问答(不保存消息)

// 一次性调用,不需要保存 AiMessage
UserMessage question = UserMessage.from("2+2等于多少?");
AiMessage answer = model.generate(List.of(question)).content();
System.out.println(answer.text());  // 输出:4

// ❌ 不保存 AiMessage,因为不需要多轮对话

用法2:多轮对话(必须保存)

List<ChatMessage> history = new ArrayList<>();
history.add(SystemMessage.from("你是数学家"));

// 第1轮
history.add(UserMessage.from("什么是质数?"));
Response<AiMessage> resp1 = model.generate(history);
AiMessage aiMsg1 = resp1.content();
history.add(aiMsg1);  // ✅ 保存到历史

System.out.println("AI回复:" + aiMsg1.text());

// 第2轮:可以继续对话
history.add(UserMessage.from("100以内有多少个质数?"));
Response<AiMessage> resp2 = model.generate(history);
AiMessage aiMsg2 = resp2.content();
history.add(aiMsg2);  // ✅ 保存到历史

System.out.println("AI回复:" + aiMsg2.text());
// → resp2 能看到第1轮的对话,理解上下文

用法3:访问 Token 使用信息

// 获取 Token 使用统计
Response<AiMessage> response = model.generate(messages);
AiMessage aiMsg = response.content();
TokenUsage usage = response.tokenUsage();

// 成本计算
double costPerInputToken = 0.15 / 1000000;    // OpenAI GPT-4o-mini 的输入价格
double costPerOutputToken = 0.60 / 1000000;   // OpenAI GPT-4o-mini 的输出价格

double inputCost = usage.inputTokens() * costPerInputToken;
double outputCost = usage.outputTokens() * costPerOutputToken;
double totalCost = inputCost + outputCost;

System.out.printf("输入Token: %d, 成本: $%.6f%n", usage.inputTokens(), inputCost);
System.out.printf("输出Token: %d, 成本: $%.6f%n", usage.outputTokens(), outputCost);
System.out.printf("总成本: $%.6f%n", totalCost);

用法4:Agent 中处理工具调用(完整实现)

这是最复杂的场景,需要理解 API 的实际工具调用格式。

4.1 工具定义(发送给 API)

// 定义可用的工具
List<Tool> tools = Arrays.asList(
    Tool.function(
        "add",
        "计算两个数的加法",
        Map.of(
            "arg0", Map.of("type", "integer"),
            "arg1", Map.of("type", "integer")
        ),
        Arrays.asList("arg0", "arg1")
    ),
    Tool.function(
        "multiply",
        "计算两个数的乘法",
        Map.of(
            "arg0", Map.of("type", "integer"),
            "arg1", Map.of("type", "integer")
        ),
        Arrays.asList("arg0", "arg1")
    )
);

// 准备对话
List<ChatMessage> messages = new ArrayList<>();
messages.add(SystemMessage.from("你可以使用数学计算工具"));
messages.add(UserMessage.from("2加3等于多少?"));

// 调用模型(带工具)
Response<AiMessage> response = model.generateWithTools(messages, tools);

4.2 API 的实际响应格式

{
  "choices": [{
    "finish_reason": "tool_calls",
    "message": {
      "role": "assistant",
      "content": "我来帮你计算 2 加 3:",
      "tool_calls": [
        {
          "id": "toolu_01XaorEWqfHsU31b9ikpYGc9",
          "type": "function",
          "function": {
            "name": "add",
            "arguments": "{\"arg0\":2,\"arg1\":3}"
          }
        }
      ]
    }
  }],
  "usage": {
    "prompt_tokens": 968,
    "completion_tokens": 86,
    "total_tokens": 1054
  }
}

关键点

  • finish_reason: "tool_calls" → LLM 想调用工具
  • tool_calls[].function.name → 工具名称("add")
  • tool_calls[].function.arguments → 工具参数(JSON字符串)

4.3 在 LangChain4J 中处理工具调用

// API 返回的 AiMessage
AiMessage aiMsg = response.content();

// 检查是否有工具调用
List<ToolCall> toolCalls = aiMsg.toolCalls();

if (toolCalls != null && !toolCalls.isEmpty()) {
    // 有工具调用
    for (ToolCall toolCall : toolCalls) {
        String toolName = toolCall.id();                    // "add"
        String argumentsJson = toolCall.arguments();        // "{\"arg0\":2,\"arg1\":3}"

        // 解析参数
        Map<String, Object> args = parseJsonArguments(argumentsJson);
        // args = {"arg0": 2, "arg1": 3}

        // 执行工具
        String result = executeToolByName(toolName, args);  // "5"

        // 创建工具结果消息
        ToolExecutionResultMessage toolResult =
            ToolExecutionResultMessage.from(toolName, result);

        // 保存消息序列
        messages.add(aiMsg);           // ✅ 保存原始 AiMessage
        messages.add(toolResult);      // ✅ 添加工具结果
    }
} else {
    // 没有工具调用,AI直接生成了回复
    messages.add(aiMsg);  // ✅ 保存 AiMessage
}

// 继续调用模型
Response<AiMessage> nextResponse = model.generate(messages);
AiMessage finalAnswer = nextResponse.content();

4.4 生产级别的工具调用处理

@Service
public class ToolCallHandlingService {
    private final ChatModel model;
    private static final Logger logger = LoggerFactory.getLogger(ToolCallHandlingService.class);

    /**
     * 处理 LLM 的工具调用请求(可能多轮)
     */
    public String handleToolCalls(List<ChatMessage> messages, List<Tool> tools) {
        int maxIterations = 10;  // 防止无限循环
        int iteration = 0;

        while (iteration < maxIterations) {
            iteration++;

            // Step 1: 调用模型
            Response<AiMessage> response = model.generateWithTools(messages, tools);
            AiMessage aiMsg = response.content();
            messages.add(aiMsg);  // 保存 LLM 的响应

            // Step 2: 检查 finish_reason
            String finishReason = getFinishReason(aiMsg);
            logger.info("迭代 {}: finish_reason = {}", iteration, finishReason);

            if ("stop".equals(finishReason)) {
                // LLM 完成了,直接返回答案
                return aiMsg.text();
            }

            if (!"tool_calls".equals(finishReason)) {
                // 其他原因(length 等),返回当前内容
                logger.warn("非预期的 finish_reason: {}", finishReason);
                return aiMsg.text();
            }

            // Step 3: 处理工具调用
            List<ToolCall> toolCalls = aiMsg.toolCalls();
            if (toolCalls == null || toolCalls.isEmpty()) {
                logger.warn("finish_reason 是 tool_calls 但没有 tool_calls");
                return aiMsg.text();
            }

            boolean hasError = false;
            for (ToolCall toolCall : toolCalls) {
                try {
                    String toolName = toolCall.id();
                    String argumentsJson = toolCall.arguments();

                    // 执行工具
                    String toolResult = executeToolSafely(toolName, argumentsJson);
                    logger.info("工具 {} 执行结果: {}", toolName, toolResult);

                    // 添加工具结果
                    ToolExecutionResultMessage toolResultMsg =
                        ToolExecutionResultMessage.from(toolName, toolResult);
                    messages.add(toolResultMsg);

                } catch (Exception e) {
                    logger.error("工具执行失败", e);
                    hasError = true;

                    // 添加错误结果
                    ToolExecutionResultMessage errorMsg =
                        ToolExecutionResultMessage.from(
                            toolCall.id(),
                            "错误:" + e.getMessage()
                        );
                    messages.add(errorMsg);
                }
            }

            // Step 4: 继续循环,让 LLM 看到工具结果并继续
        }

        throw new RuntimeException("达到最大迭代次数 " + maxIterations);
    }

    private String executeToolSafely(String toolName, String argumentsJson) {
        try {
            Map<String, Object> args = parseArguments(argumentsJson);
            return executeToolByName(toolName, args);
        } catch (Exception e) {
            throw new RuntimeException("执行工具 " + toolName + " 失败:" + e.getMessage(), e);
        }
    }

    private String executeToolByName(String toolName, Map<String, Object> args) {
        return switch (toolName) {
            case "add" -> {
                int a = ((Number) args.get("arg0")).intValue();
                int b = ((Number) args.get("arg1")).intValue();
                yield String.valueOf(a + b);
            }
            case "multiply" -> {
                int a = ((Number) args.get("arg0")).intValue();
                int b = ((Number) args.get("arg1")).intValue();
                yield String.valueOf(a * b);
            }
            default -> throw new IllegalArgumentException("未知工具:" + toolName);
        };
    }

    private Map<String, Object> parseArguments(String json) {
        try {
            ObjectMapper mapper = new ObjectMapper();
            return mapper.readValue(json, new TypeReference<Map<String, Object>>() {});
        } catch (JsonProcessingException e) {
            throw new RuntimeException("解析工具参数失败:" + json, e);
        }
    }

    private String getFinishReason(AiMessage aiMsg) {
        Map<String, Object> metadata = aiMsg.metadata();
        if (metadata != null) {
            Object reason = metadata.get("finish_reason");
            if (reason != null) {
                return reason.toString();
            }
        }
        return "unknown";
    }
}

4.5 多轮工具调用的完整示例

用户问题:
"23的结果乘以4等于多少?"

工作流程:
┌─────────────────────────┐
│ 轮次1:LLM 看到问题      │
└──────────────┬──────────┘
               │
        ↓ 决定调用 add(2,3)
┌──────────────┴──────────┐
│ finish_reason=tool_calls│
│ tool_call: add(2,3)     │
└──────────────┬──────────┘
               │
        ↓ Agent 执行 add(2,3)→5
┌──────────────┴──────────┐
│ 轮次2:LLM 看到结果 5   │
└──────────────┬──────────┘
               │
        ↓ 决定调用 multiply(5,4)
┌──────────────┴──────────────┐
│ finish_reason=tool_calls    │
│ tool_call: multiply(5,4)    │
└──────────────┬──────────────┘
               │
        ↓ Agent 执行 multiply(5,4)→20
┌──────────────┴──────────┐
│ 轮次3:LLM 看到结果 20  │
└──────────────┬──────────┘
               │
        ↓ 生成最终答案
┌──────────────┴──────────────────┐
│ finish_reason=stop              │
│ message: "结果是20"             │
└─────────────────────────────────┘

用法5:处理 AiMessage 的元数据

// AiMessage 可能包含额外的元数据
Map<String, Object> metadata = aiMsg.metadata();

if (metadata != null) {
    // 访问模型返回的额外信息
    Object finishReason = metadata.get("finish_reason");  // "stop", "tool_calls", "length"
    Object model = metadata.get("model");                  // 实际使用的模型
    Object created = metadata.get("created");              // 时间戳

    System.out.println("完成原因: " + finishReason);
    System.out.println("模型: " + model);

    // finish_reason 的含义:
    // - "stop"       → LLM 正常完成
    // - "tool_calls" → LLM 想调用工具
    // - "length"     → 达到最大 token 限制
    // - "content_filter" → 内容被过滤
}

#### AiMessage 提取文本的三种方式

```java
Response<AiMessage> response = model.generate(messages);
AiMessage aiMsg = response.content();

// 方式1:直接调用 text()
String text1 = aiMsg.text();

// 方式2:通过 Response 获取
String text2 = response.content().text();

// 方式3:获取后赋值给新的 Message
messages.add(aiMsg);  // 保存到历史
UserMessage nextMsg = UserMessage.from(aiMsg.text() + " 继续...");

AiMessage 的常见陷阱和优化

陷阱1:忘记保存 AiMessage

// ❌ 错误:不保存 AiMessage
messages.add(UserMessage.from("问题1"));
AiMessage response1 = model.generate(messages).content();
System.out.println(response1.text());
// 没有 messages.add(response1)!

// 下一轮对话 response1 看不到第1轮的对话
messages.add(UserMessage.from("问题2"));
AiMessage response2 = model.generate(messages).content();
// → 上下文丢失,对话不连贯

// ✅ 正确做法
messages.add(UserMessage.from("问题1"));
AiMessage response1 = model.generate(messages).content();
messages.add(response1);  // 保存

messages.add(UserMessage.from("问题2"));
AiMessage response2 = model.generate(messages).content();
messages.add(response2);  // 保存

陷阱2:直接修改 AiMessage

// ❌ 错误:AiMessage 通常是不可变的
AiMessage aiMsg = model.generate(messages).content();
aiMsg.text = "修改后的文本";  // ❌ 可能会失败

// ✅ 正确做法:创建新的 Message
AiMessage original = model.generate(messages).content();
String modified = original.text().replaceAll("旧", "新");
AiMessage newMsg = AiMessage.from(modified);
messages.add(newMsg);

陷阱3:重复获取 TokenUsage

// ❌ 效率低:每次都调用
Response<AiMessage> response = model.generate(messages);
TokenUsage usage1 = response.tokenUsage();
TokenUsage usage2 = response.tokenUsage();  // 重复调用
TokenUsage usage3 = response.tokenUsage();  // 重复调用

// ✅ 正确做法:保存一次
Response<AiMessage> response = model.generate(messages);
TokenUsage usage = response.tokenUsage();   // 只调用一次

int inputTokens = usage.inputTokens();
int outputTokens = usage.outputTokens();
// 多次使用 inputTokens 和 outputTokens

AiMessage 的最佳实践

@Service
public class BestPracticeAiMessageService {
    private final ChatModel model;
    private static final Logger logger = LoggerFactory.getLogger(BestPracticeAiMessageService.class);

    /**
     * ✅ 生产级别的 AiMessage 处理
     */
    public String chat(List<ChatMessage> history, String userInput) {
        // Step 1: 添加用户消息
        UserMessage userMsg = UserMessage.from(userInput);
        history.add(userMsg);

        // Step 2: 调用模型并获取响应
        Response<AiMessage> response = model.generate(history);
        AiMessage aiMsg = response.content();
        TokenUsage usage = response.tokenUsage();  // ✅ 一次性获取

        // Step 3: 验证 AiMessage
        if (aiMsg == null || aiMsg.text() == null) {
            throw new RuntimeException("AI响应为空");
        }

        // Step 4: 记录 Token 使用(用于成本监控)
        logTokenUsage(usage);

        // Step 5: 检查是否有工具调用(Agent场景)
        if (hasToolCalls(aiMsg)) {
            return handleToolCalls(history, aiMsg);
        }

        // Step 6: 保存 AiMessage 到历史
        history.add(aiMsg);

        // Step 7: 管理历史大小(防止无限增长)
        trimHistoryIfNeeded(history);

        // Step 8: 返回文本
        return aiMsg.text();
    }

    private void logTokenUsage(TokenUsage usage) {
        logger.info("Token使用 - 输入: {}, 输出: {}, 总计: {}",
            usage.inputTokens(),
            usage.outputTokens(),
            usage.inputTokens() + usage.outputTokens());
    }

    private boolean hasToolCalls(AiMessage aiMsg) {
        List<ToolCall> calls = aiMsg.toolCalls();
        return calls != null && !calls.isEmpty();
    }

    private String handleToolCalls(List<ChatMessage> history, AiMessage aiMsg) {
        history.add(aiMsg);  // ✅ 先保存原始 AiMessage

        for (ToolCall toolCall : aiMsg.toolCalls()) {
            String result = executeTool(toolCall);
            ToolExecutionResultMessage toolResult =
                ToolExecutionResultMessage.from(toolCall.id(), result);
            history.add(toolResult);
        }

        // 继续调用模型
        Response<AiMessage> nextResponse = model.generate(history);
        AiMessage nextMsg = nextResponse.content();
        history.add(nextMsg);

        return nextMsg.text();
    }

    private void trimHistoryIfNeeded(List<ChatMessage> history) {
        int MAX_HISTORY = 20;
        if (history.size() > MAX_HISTORY) {
            SystemMessage sys = (SystemMessage) history.get(0);
            history.subList(1, history.size() - MAX_HISTORY).clear();
        }
    }

    private String executeTool(ToolCall toolCall) {
        // 实现工具执行逻辑
        return "工具执行结果";
    }
}

AiMessage 完整对比总结

方面说明
来源模型生成,不是用户输入
内容包含文本、工具调用、元数据
保存多轮对话必须保存到历史
修改通常不可修改(不可变对象)
使用频率多轮对话中频繁使用
Token影响输出Token计入总成本
关键方法text()、toolCalls()、metadata()
获取方式Response response = model.generate(messages)

4️⃣ ToolExecutionResultMessage - 工具执行结果

定义:工具执行的结果消息(在Agent中使用)

使用场景:Agent 调用工具后,将结果作为消息返回给LLM

// Agent调用工具的流程:
// 1. LLM 决定调用工具 → 返回包含 tool_call 的消息
// 2. Agent 执行工具 → 得到执行结果
// 3. 创建 ToolExecutionResultMessage → 返回给LLM
// 4. LLM 基于结果继续生成

ToolExecutionResultMessage toolResult = ToolExecutionResultMessage.from(
    "calculator",                              // 工具名称
    "2 + 2 = 4"                               // 执行结果
);

List<ChatMessage> messages = new ArrayList<>();
messages.add(SystemMessage.from("你可以使用calculator工具"));
messages.add(UserMessage.from("2加2等于多少?"));
messages.add(toolResult);  // 工具结果

String response = model.chat(messages);

5️⃣ 完整的多轮对话示例

@Service
public class MultiTurnConversationService {
    private final ChatModel model;
    private final List<ChatMessage> conversationHistory = new ArrayList<>();

    public MultiTurnConversationService(@Value("${openai.api-key}") String apiKey) {
        this.model = OpenAiChatModel.builder()
            .apiKey(apiKey)
            .modelName("gpt-4o-mini")
            .build();

        // 初始化系统消息
        conversationHistory.add(SystemMessage.from("""
            你是一位Python专家。
            特点:
            1. 代码示例用Python 3.11+
            2. 解释时用数据科学的背景
            3. 优先推荐使用pandas和numpy
            """));
    }

    /**
     * 进行一轮对话
     */
    public String chat(String userInput) {
        // Step 1: 添加用户消息
        conversationHistory.add(UserMessage.from(userInput));

        // Step 2: 调用LLM
        Response<AiMessage> response = model.generate(conversationHistory);
        AiMessage aiMessage = response.content();

        // Step 3: 保存AI回复到历史
        conversationHistory.add(aiMessage);

        // Step 4: 返回回复
        return aiMessage.text();
    }

    /**
     * 获取完整的对话历史
     */
    public List<ChatMessage> getHistory() {
        return new ArrayList<>(conversationHistory);
    }

    /**
     * 清空对话历史
     */
    public void clearHistory() {
        conversationHistory.clear();
        conversationHistory.add(SystemMessage.from("""
            你是一位Python专家。
            特点:
            1. 代码示例用Python 3.11+
            2. 解释时用数据科学的背景
            3. 优先推荐使用pandas和numpy
            """));
    }
}

// 使用示例
MultiTurnConversationService service = new MultiTurnConversationService(apiKey);

// 第1轮
String response1 = service.chat("什么是列表推导式?");
System.out.println("AI: " + response1);

// 第2轮
String response2 = service.chat("能给出一个实际的数据处理例子吗?");
System.out.println("AI: " + response2);

// 第3轮
String response3 = service.chat("与传统循环相比,性能有多大提升?");
System.out.println("AI: " + response3);

6️⃣ Message 优化最佳实践

问题 1:SystemMessage 的设计

坏的 SystemMessage

SystemMessage.from("你是一个助手。请帮我。谢谢。");
// → 太模糊,没有清晰的指导

好的 SystemMessage

SystemMessage.from("""
    你是一位资深的代码审查专家,具有以下特点:

    【角色】:
    - 20年Java开发经验
    - 熟悉微服务架构
    - 关注代码性能和安全

    【审查重点】:
    1. 性能瓶颈(N+1查询、内存泄漏等)
    2. 安全漏洞(SQL注入、XSS等)
    3. 代码质量(可读性、可维护性)

    【输出格式】:
    - 问题编号 | 严重程度 | 问题描述 | 修复建议

    【语气】:
    - 专业但友好
    - 给出具体改进方案
    - 解释为什么要这样改
    """);

问题 2:多个 SystemMessage

错误做法:一个对话中有多个 SystemMessage

List<ChatMessage> messages = new ArrayList<>();
messages.add(SystemMessage.from("你是Java专家"));      // ❌ 第1个
messages.add(UserMessage.from("审查代码"));
messages.add(SystemMessage.from("要关注性能"));        // ❌ 第2个,会覆盖第1个

正确做法:合并到一个 SystemMessage

List<ChatMessage> messages = new ArrayList<>();
messages.add(SystemMessage.from("""
    你是Java专家。
    要关注性能问题。
    """));  // ✅ 只有一个
messages.add(UserMessage.from("审查代码"));

问题 3:SystemMessage 的粒度

粒度过大:一个SystemMessage包含所有规则(难以管理)

SystemMessage.from("做这个 做那个 还有这个 还有那个...");
// 信息太多,LLM 容易遗忘某些规则

粒度适中:清晰的分类和分层

SystemMessage.from("""
    你是一位代码审查专家。

    【必须检查的问题】:
    1. 性能问题
    2. 安全漏洞
    3. 代码规范

    【优先级】:
    安全 > 性能 > 可读性

    【输出要求】:
    - 找出3-5个主要问题
    - 每个问题给出修复建议
    - 用Markdown格式输出
    """);

7️⃣ Message 与 Token 成本的关系

/**
 * 理解 Message 对 Token 消耗的影响
 */

// 同一个用户问题,不同的 Message 结构,Token 消耗不同

// 方式1:简单的 SystemMessage
List<ChatMessage> messages1 = new ArrayList<>();
messages1.add(SystemMessage.from("你是助手"));
messages1.add(UserMessage.from("2+2等于多少?"));
// 预计 Token: ~30个

// 方式2:详细的 SystemMessage
List<ChatMessage> messages2 = new ArrayList<>();
messages2.add(SystemMessage.from("""
    你是一位数学教师,有20年教学经验。
    你的特点是...(1000字详细描述)
    """));
messages2.add(UserMessage.from("2+2等于多少?"));
// 预计 Token: ~1030个(增加了1000个!)

// 优化建议:
// ✅ 重复的对话中,SystemMessage 被重复发送,浪费 Token
// ✅ 考虑在应用启动时加载 SystemMessage,而不是每次都重新创建

8️⃣ Message 在不同场景中的应用

场景1:简单问答(一次性)

List<ChatMessage> messages = new ArrayList<>();
messages.add(SystemMessage.from("你是一个数学家"));
messages.add(UserMessage.from("微积分是什么?"));

String response = model.chat(messages);

场景2:多轮对话(有历史)

List<ChatMessage> history = new ArrayList<>();
history.add(SystemMessage.from("你是一个编程导师"));

// 第1轮
history.add(UserMessage.from("什么是设计模式?"));
history.add(model.generate(history).content());

// 第2轮:历史保留了 SystemMessage 和第1轮的对话
history.add(UserMessage.from("能举个例子吗?"));
history.add(model.generate(history).content());

// 第3轮:继续用完整历史
history.add(UserMessage.from("为什么这样设计?"));
String response = model.chat(history);

场景3:并行对话(多个独立会话)

// 会话1:代码审查
List<ChatMessage> session1 = new ArrayList<>();
session1.add(SystemMessage.from("你是代码审查专家"));
// ...

// 会话2:文档生成
List<ChatMessage> session2 = new ArrayList<>();
session2.add(SystemMessage.from("你是技术文档专家"));
// ...

// 两个会话独立进行,互不影响

9️⃣ Message 类型对比总结

Message类型用途位置数量影响范围
SystemMessage定义LLM角色和行为最开始1个整个对话
UserMessage用户的输入和问题任何地方多个当前和后续
AiMessageLLM的回复UserMessage后多个后续对话的Context
ToolExecResultMsg工具执行结果工具调用后多个Agent决策

🔟 Message 实战:构建聊天机器人

@Service
public class ChatBotService {
    private final ChatModel model;
    private final int MAX_HISTORY_SIZE = 10;  // 保留最近10条消息

    public ChatBotService(@Value("${openai.api-key}") String apiKey) {
        this.model = OpenAiChatModel.builder()
            .apiKey(apiKey)
            .modelName("gpt-4o-mini")
            .build();
    }

    /**
     * 创建初始系统消息
     */
    private SystemMessage createSystemMessage(String role) {
        return SystemMessage.from(switch(role) {
            case "teacher" -> """
                你是一位耐心的编程教师。
                - 用通俗易懂的语言解释复杂概念
                - 经常举实例
                - 鼓励学生提问
                - 代码示例要清晰注释
                """;

            case "expert" -> """
                你是一位资深的技术专家。
                - 深度分析技术问题
                - 提供最佳实践建议
                - 权衡trade-off
                - 引用相关论文或资源
                """;

            case "assistant" -> """
                你是一位乐于助人的助手。
                - 快速回答问题
                - 提供多个解决方案
                - 如果不确定,说明不清楚
                - 友好和专业的语气
                """;

            default -> "你是一个有帮助的助手。";
        });
    }

    /**
     * 管理对话历史(定长,避免无限增长)
     */
    private List<ChatMessage> manageHistory(
            List<ChatMessage> history,
            UserMessage newMessage,
            AiMessage response) {

        // 保留SystemMessage
        SystemMessage systemMsg = (SystemMessage) history.get(0);
        List<ChatMessage> recentHistory = new ArrayList<>();
        recentHistory.add(systemMsg);

        // 添加新的对话
        recentHistory.add(newMessage);
        recentHistory.add(response);

        // 添加历史对话,最多保留MAX_HISTORY_SIZE条
        for (int i = 1; i < history.size() && recentHistory.size() < MAX_HISTORY_SIZE + 1; i++) {
            recentHistory.add(history.get(i));
        }

        return recentHistory;
    }

    /**
     * 进行一次对话
     */
    public String chat(String role, List<ChatMessage> history, String userInput) {
        // Step 1: 初始化历史(如果为空)
        if (history.isEmpty()) {
            history.add(createSystemMessage(role));
        }

        // Step 2: 添加用户消息
        UserMessage userMsg = UserMessage.from(userInput);
        history.add(userMsg);

        // Step 3: 获取AI回复
        Response<AiMessage> response = model.generate(history);
        AiMessage aiMsg = response.content();

        // Step 4: 保存到历史
        history.add(aiMsg);

        // Step 5: 管理历史大小
        List<ChatMessage> managedHistory = manageHistory(history, userMsg, aiMsg);
        history.clear();
        history.addAll(managedHistory);

        return aiMsg.text();
    }
}

Message 最佳实践检查清单

  • 是否为每个对话会话都创建了 SystemMessage?
  • SystemMessage 是否清晰定义了LLM的角色和行为?
  • 是否正确地保存了 AiMessage 到对话历史中?
  • 是否有机制管理对话历史的大小(避免无限增长)?
  • 是否考虑了 Message 对 Token 成本的影响?
  • 并行对话的会话是否完全独立?
  • 是否正确处理了多轮对话中的 Context 维持?
  • 是否有错误处理(如模型异常时的 Message 管理)?

扩展实现:Prompt模板系统

@Service
public class PromptTemplateService {
    private final ChatModel model;

    public PromptTemplateService(@Value("${openai.api-key}") String apiKey) {
        this.model = OpenAiChatModel.builder()
            .apiKey(apiKey)
            .modelName("gpt-4o-mini")
            .build();
    }

    /**
     * 代码审查模板
     */
    public String codeReview(String code, String focusAreas) {
        String prompt = String.format("""
            请进行Java代码审查,重点关注以下方面:
            %s

            代码:
            ```java
            %s
            ```

            输出格式:
            1. 问题描述
            2. 严重程度(高/中/低)
            3. 具体行号
            4. 修复建议
            """, focusAreas, code);

        return model.chat(prompt);
    }

    /**
     * Few-Shot学习模板
     */
    public String fewShotLearn(String category, List<String> examples, String input) {
        StringBuilder prompt = new StringBuilder();
        prompt.append("根据以下示例,完成分类任务:\n\n");

        for (int i = 0; i < examples.size(); i++) {
            prompt.append(String.format("示例%d:\n%s\n\n", i + 1, examples.get(i)));
        }

        prompt.append(String.format("现在判断:\n%s\n\n分类结果:", input));

        return model.chat(prompt.toString());
    }

    /**
     * Chain-of-Thought模板
     */
    public String chainOfThought(String problem, List<String> steps) {
        StringBuilder prompt = new StringBuilder();
        prompt.append("请按以下步骤解决问题:\n\n");

        for (int i = 0; i < steps.size(); i++) {
            prompt.append(String.format("第%d步:%s\n", i + 1, steps.get(i)));
        }

        prompt.append(String.format("\n问题:%s", problem));

        return model.chat(prompt.toString());
    }

    /**
     * 结构化输出模板
     */
    public String structuredOutput(String prompt, String format) {
        String fullPrompt = String.format("""
            %s

            请用以下格式回答:
            %s
            """, prompt, format);

        return model.chat(fullPrompt);
    }

    /**
     * 温度参数+Prompt共同优化
     */
    public String optimizedResponse(
            String prompt,
            PromptConfig config) {

        ChatModel optimizedModel = OpenAiChatModel.builder()
            .apiKey(System.getenv("OPENAI_API_KEY"))
            .modelName("gpt-4o-mini")
            .temperature(config.getTemperature())
            .maxTokens(config.getMaxTokens())
            .build();

        return optimizedModel.chat(buildEnhancedPrompt(prompt, config));
    }

    /**
     * 增强Prompt质量的辅助方法
     */
    private String buildEnhancedPrompt(String originalPrompt, PromptConfig config) {
        return String.format("""
            %s

            回答时请注意:
            - 准确性:%s
            - 风格:%s
            - 长度限制:%s
            """,
            originalPrompt,
            config.getAccuracy(),
            config.getStyle(),
            config.getLengthLimit());
    }

    /**
     * 配置类
     */
    public static class PromptConfig {
        private double temperature;      // 0.0-1.0,决定创意度
        private int maxTokens;           // 输出长度
        private String accuracy;         // "准确" or "创意"
        private String style;            // "技术性" or "易读"
        private String lengthLimit;      // "100字" or "详细"

        // Getters and Setters
        public double getTemperature() { return temperature; }
        public void setTemperature(double temperature) { this.temperature = temperature; }

        public int getMaxTokens() { return maxTokens; }
        public void setMaxTokens(int maxTokens) { this.maxTokens = maxTokens; }

        public String getAccuracy() { return accuracy; }
        public void setAccuracy(String accuracy) { this.accuracy = accuracy; }

        public String getStyle() { return style; }
        public void setStyle(String style) { this.style = style; }

        public String getLengthLimit() { return lengthLimit; }
        public void setLengthLimit(String lengthLimit) { this.lengthLimit = lengthLimit; }
    }
}

实战项目集成

Spring Boot Controller使用Prompt

@RestController
@RequestMapping("/api/code-analysis")
public class CodeAnalysisController {
    @Autowired
    private PromptTemplateService promptService;

    @PostMapping("/review")
    public String reviewCode(@RequestBody CodeReviewRequest request) {
        return promptService.codeReview(request.getCode(), request.getFocusAreas());
    }

    @PostMapping("/classify")
    public String classify(@RequestBody ClassificationRequest request) {
        return promptService.fewShotLearn(
            request.getCategory(),
            request.getExamples(),
            request.getInput()
        );
    }

    @PostMapping("/analyze")
    public String analyze(@RequestBody AnalysisRequest request) {
        return promptService.chainOfThought(
            request.getProblem(),
            request.getSteps()
        );
    }
}

单元测试

@SpringBootTest
public class PromptTemplateTest {
    @Autowired
    private PromptTemplateService promptService;

    @Test
    public void testCodeReviewPrompt() {
        String code = "for (int i = 0; i < list.size(); i++) { " +
                      "list.get(i); }";
        String focusAreas = "性能瓶颈和最佳实践";

        String result = promptService.codeReview(code, focusAreas);

        assertNotNull(result);
        assertTrue(result.length() > 0);
        // 验证包含关键词
        assertTrue(result.contains("O(n)") || result.contains("性能"));
    }

    @Test
    public void testFewShotPrompt() {
        List<String> examples = Arrays.asList(
            "输入:int x = 5; 上下文:年龄\n输出:int age = 5;",
            "输入:double y = 3.14; 上下文:π值\n输出:double pi = 3.14;"
        );
        String input = "int z = 100; 上下文:最大值";

        String result = promptService.fewShotLearn("命名", examples, input);

        assertNotNull(result);
        assertTrue(result.contains("maxValue") || result.contains("max"));
    }

    @Test
    public void testChainOfThoughtPrompt() {
        String problem = "计算时间复杂度:for(int i=0; i<n; i++) { for(int j=0; j<n; j++) { } }";
        List<String> steps = Arrays.asList(
            "识别循环层数",
            "计算每层迭代次数",
            "累乘得到总复杂度"
        );

        String result = promptService.chainOfThought(problem, steps);

        assertNotNull(result);
        assertTrue(result.contains("O(n") || result.contains("复杂度"));
    }

    @Test
    public void testTemperatureEffect() {
        // 低temperature:保守答案
        PromptTemplateService.PromptConfig conservativeConfig =
            new PromptTemplateService.PromptConfig();
        conservativeConfig.setTemperature(0.0);
        conservativeConfig.setMaxTokens(100);
        conservativeConfig.setStyle("技术性");

        // 高temperature:创意答案
        PromptTemplateService.PromptConfig creativeConfig =
            new PromptTemplateService.PromptConfig();
        creativeConfig.setTemperature(1.0);
        creativeConfig.setMaxTokens(200);
        creativeConfig.setStyle("易读");

        String prompt = "解释什么是设计模式";

        String conservative = promptService.optimizedResponse(prompt, conservativeConfig);
        String creative = promptService.optimizedResponse(prompt, creativeConfig);

        assertNotNull(conservative);
        assertNotNull(creative);
    }
}

常见问题解答

Q1:如何让LLM生成JSON格式的输出?

A: 在Prompt中明确说明格式:

String prompt = """
    请分析这段代码,用JSON格式回答:
    {
      "issues": [...],
      "severity": "high/medium/low",
      "suggestions": [...]
    }

    代码:[代码]
    """;

Q2:Few-Shot中应该给多少个示例?

A: 通常2-3个就够了:

  • 1个:太少,LLM可能不理解
  • 2-3个:最优,LLM能理解模式
  • 5个+:太多,浪费token,成本增加

Q3:什么时候用Chain-of-Thought?

A: 在需要深度分析的场景:

  • ✅ 代码审查、架构设计、问题诊断
  • ❌ 简单问答("这是什么?")
  • ❌ 事实性查询("2+2等于几?")

Q4:Prompt太长会影响质量吗?

A: 不会降低质量,但会:

  • 增加成本(token更多)
  • 增加延迟(处理时间更长)

建议

  • 必要信息必写
  • 冗余信息删除
  • 用代码格式化清晰地展示信息

Q5:如何测试Prompt的质量?

A: 用A/B测试:

// 同一个问题,用两个不同的Prompt
String prompt_v1 = "分析性能";
String prompt_v2 = "按以下步骤分析性能:...";

String result_v1 = model.chat(prompt_v1);
String result_v2 = model.chat(prompt_v2);

// 比较结果的质量(准确性、深度、有用性)

Q6:SystemMessage 和 UserMessage 有什么区别?

A: 根本区别在于角色和权重:

方面SystemMessageUserMessage
角色系统指令,定义行为用户需求
权重优先级最高,影响全局影响当前回复
内容约束、规则、身份问题、任务、请求
数量只有1个多个
位置必须在最前面可在任意位置
频率整个会话只设置一次每一轮对话都有

例子

// SystemMessage:定义 LLM 的身份和风格
SystemMessage.from("""
    你是一位Python专家,代码示例优先使用:
    - 现代Python特性(3.10+)
    - Type hints
    - Dataclass而不是dict
    """);

// UserMessage:用户的具体需求
UserMessage.from("怎样用Python实现一个简单的缓存?");
// → LLM 会用"Python专家"的身份来回答这个问题

Q7:对话历史太长会怎样?

A: 两个问题:

  1. Token成本增加:历史越长,发送的Token越多,成本越高
  2. LLM容易遗忘:历史太长时,LLM可能遗忘早期的信息

解决方案

private static final int MAX_HISTORY = 10;  // 只保留最近10条消息

public void trimHistory(List<ChatMessage> history) {
    if (history.size() > MAX_HISTORY + 1) {  // +1 是 SystemMessage
        // 保留 SystemMessage 和最近的消息
        SystemMessage system = (SystemMessage) history.get(0);
        List<ChatMessage> recent = history.subList(
            history.size() - MAX_HISTORY,
            history.size()
        );

        history.clear();
        history.add(system);
        history.addAll(recent);
    }
}

Q8:如何在多个并行对话中使用 Message?

A: 每个对话会话独立维护自己的Message列表:

// 错误:共享历史
List<ChatMessage> sharedHistory = new ArrayList<>();

// 会话1添加信息
sharedHistory.add(SystemMessage.from("你是编程导师"));
sharedHistory.add(UserMessage.from("什么是OOP?"));

// 会话2也用这个历史,会被污染!
sharedHistory.add(UserMessage.from("怎样学Python?"));

// ❌ 会话2会看到会话1的对话,混乱!


// 正确:每个会话独立
Map<String, List<ChatMessage>> sessionHistories = new HashMap<>();

// 会话1
String session1Id = UUID.randomUUID().toString();
List<ChatMessage> session1History = new ArrayList<>();
session1History.add(SystemMessage.from("你是编程导师"));
session1History.add(UserMessage.from("什么是OOP?"));
sessionHistories.put(session1Id, session1History);

// 会话2
String session2Id = UUID.randomUUID().toString();
List<ChatMessage> session2History = new ArrayList<>();
session2History.add(SystemMessage.from("你是Python教师"));
session2History.add(UserMessage.from("怎样学Python?"));
sessionHistories.put(session2Id, session2History);

// ✅ 两个会话完全独立

Q9:Message 中的内容可以包含代码吗?

A: 完全可以,而且推荐这样做:

// ✅ 推荐:用代码块标记代码
UserMessage.from("""
    请审查以下Java代码:

    ```java
    for (int i = 0; i < list.size(); i++) {
        Object obj = list.get(i);
        System.out.println(obj);
    }
    ```

    重点检查性能问题。
    """);

// ❌ 不推荐:纯文本混合
UserMessage.from("请审查这个代码:for (int i = 0; i < list.size(); i++) {...}");

Q10:AiMessage 一定要保存到历史吗?

A: 取决于是否需要多轮对话:

// 场景1:一次性问答,不需要保存
String response = model.chat(UserMessage.from("今天天气怎样?"));
// → 不保存 AiMessage

// 场景2:多轮对话,必须保存
List<ChatMessage> history = new ArrayList<>();
history.add(SystemMessage.from("你是编程专家"));

// 第1轮
history.add(UserMessage.from("什么是设计模式?"));
AiMessage response1 = model.generate(history).content();
history.add(response1);  // ✅ 必须保存

// 第2轮
history.add(UserMessage.from("能举个例子吗?"));
AiMessage response2 = model.generate(history).content();
history.add(response2);  // ✅ 必须保存

// 第3轮继续需要之前的历史
history.add(UserMessage.from("为什么这样做?"));
// history 包含了所有之前的对话,LLM 能理解上下文

最佳实践速查表

✅ 做这些

实践例子
明确的指令"找出N+1查询问题" 而不是 "分析代码"
提供上下文"电商系统中,用户的订单查询"
限制输出"100字以内" "JSON格式"
给出示例Few-Shot Learning
分步骤Chain-of-Thought
清晰的结构用序号、代码块、表格

❌ 不要做这些

反面为什么
模糊指令"帮我分析" → LLM不知道分析什么
没有示例LLM只能猜测你的期望
长篇大论信息混乱,LLM分不清主次
混合多个问题"代码审查 + 性能优化 + ..."
没有约束输出可能很冗长或格式乱

PromptTemplate 工程实践

什么是 PromptTemplate?

在生产环境中,我们经常需要重复使用相同结构的 Prompt,只是替换其中的变量部分。PromptTemplate 就是为了解决这个问题——它提供了一个参数化的 Prompt 模板。

使用场景:
代码审查 → 每次审查的代码不同,但 Prompt 结构相同
问答系统 → 每个问题不同,但系统指令相同
翻译系统 → 每段文本不同,但翻译规则相同

PromptTemplate 的优势

优势说明
代码复用同一个模板可以复用,避免重复编写
易于管理修改 Prompt 只需改模板,不影响代码
参数灵活动态替换变量,生成不同的 Prompt
易于测试Prompt 和业务逻辑分离,便于单元测试
性能优化预定义模板,减少动态拼接的开销

PromptTemplate 基础用法

1️⃣ 简单模板(单个变量)

@Service
public class SimplePromptTemplateService {
    private final ChatModel model;

    public SimplePromptTemplateService(@Value("${openai.base-url}") String baseUrl,
                                     @Value("${openai.api-key}") String apiKey) {
        this.model = OpenAiChatModel.builder()
            .baseUrl(baseUrl)
            .apiKey(apiKey)
            .modelName("gpt-4o-mini")
            .build();
    }

    /**
     * 代码审查模板
     * 模板变量:{code}, {focusArea}
     */
    public String codeReview(String code, String focusArea) {
        String promptTemplate = """
            请进行Java代码审查。

            关注方面:{focusArea}

            代码:
            {code}

            输出格式:
            1. 问题描述
            2. 严重程度(高/中/低)
            3. 修复建议
            """;

        // 替换模板变量
        String prompt = promptTemplate
            .replace("{code}", code)
            .replace("{focusArea}", focusArea);

        return model.chat(prompt);
    }
}

2️⃣ 复杂模板(多个变量和结构)

@Service
public class AdvancedPromptTemplateService {
    private final ChatModel model;

    public AdvancedPromptTemplateService(@Value("${openai.base-url}") String baseUrl,
                                        @Value("${openai.api-key}") String apiKey) {
        this.model = OpenAiChatModel.builder()
            .baseUrl(baseUrl)
            .apiKey(apiKey)
            .modelName("gpt-4o-mini")
            .build();
    }

    /**
     * Few-Shot Learning 模板
     */
    public String fewShotTemplate(String taskDescription,
                                   List<String> examples,
                                   String input) {
        StringBuilder promptBuilder = new StringBuilder();
        promptBuilder.append("任务:").append(taskDescription).append("\n\n");

        promptBuilder.append("示例:\n");
        for (int i = 0; i < examples.size(); i++) {
            promptBuilder.append(String.format("示例%d:\n%s\n\n", i + 1, examples.get(i)));
        }

        promptBuilder.append("现在请处理:\n").append(input);

        return model.chat(promptBuilder.toString());
    }

    /**
     * Chain-of-Thought 模板
     */
    public String chainOfThoughtTemplate(String problem,
                                         List<String> steps) {
        StringBuilder promptBuilder = new StringBuilder();
        promptBuilder.append("请按以下步骤解决问题:\n\n");

        for (int i = 0; i < steps.size(); i++) {
            promptBuilder.append(String.format("第%d步:%s\n", i + 1, steps.get(i)));
        }

        promptBuilder.append("\n问题:").append(problem);
        promptBuilder.append("\n\n请逐步回答:");

        return model.chat(promptBuilder.toString());
    }
}

3️⃣ 使用 String.format() 的优雅方式

public String formatPrompt(String template, Map<String, String> variables) {
    String result = template;
    for (Map.Entry<String, String> entry : variables.entrySet()) {
        result = result.replace("{" + entry.getKey() + "}", entry.getValue());
    }
    return result;
}

// 使用示例
Map<String, String> vars = new HashMap<>();
vars.put("code", codeSnippet);
vars.put("language", "Java");
vars.put("focusArea", "性能优化");

String prompt = formatPrompt("""
    请进行{language}代码审查。

    关注:{focusArea}

    代码:
    {code}
    """, vars);

String result = model.chat(prompt);

生产级 PromptTemplate 实现

@Service
public class ProductionPromptTemplateService {
    private final ChatModel model;
    private final Map<String, String> templates = new ConcurrentHashMap<>();

    @PostConstruct
    public void initTemplates() {
        // 代码审查模板
        templates.put("codeReview", """
            你是一位资深的{language}工程师。

            请审查以下代码,关注:
            {focusAreas}

            代码:
            ```{language}
            {code}
            ```

            输出格式:
            1. 问题列表(严重程度标记)
            2. 改进建议
            3. 最佳实践提示
            """);

        // 文档生成模板
        templates.put("generateDocs", """
            根据以下{language}代码,生成清晰的API文档:

            代码:
            ```{language}
            {code}
            ```

            文档格式:
            - 方法功能描述
            - 参数说明
            - 返回值说明
            - 使用示例
            """);

        // 翻译模板
        templates.put("translate", """
            将以下内容从{sourceLanguage}翻译到{targetLanguage}。
            保持原有的技术术语准确性。

            原文:
            {text}
            """);
    }

    /**
     * 执行模板,替换所有变量
     */
    public String executeTemplate(String templateName, Map<String, String> variables) {
        String template = templates.get(templateName);
        if (template == null) {
            throw new IllegalArgumentException("模板不存在:" + templateName);
        }

        String result = template;
        for (Map.Entry<String, String> entry : variables.entrySet()) {
            result = result.replace("{" + entry.getKey() + "}", entry.getValue());
        }

        return model.chat(result);
    }

    /**
     * 代码审查的便利方法
     */
    public String reviewCode(String code, String language, String focusAreas) {
        Map<String, String> vars = new HashMap<>();
        vars.put("code", code);
        vars.put("language", language);
        vars.put("focusAreas", focusAreas);
        return executeTemplate("codeReview", vars);
    }

    /**
     * 生成文档的便利方法
     */
    public String generateDocumentation(String code, String language) {
        Map<String, String> vars = new HashMap<>();
        vars.put("code", code);
        vars.put("language", language);
        return executeTemplate("generateDocs", vars);
    }
}

PromptTemplate 的最佳实践

实践为什么
模板中使用清晰的占位符{variable} 易于识别和替换
模板集中管理配置文件或数据库,便于更新
版本控制模板保留历史版本,便于回滚
验证变量替换前检查必需的变量
注释和文档说明模板的用途和变量含义
避免过度参数化参数太多会降低可读性

PromptTemplate 和 Few-Shot 的结合

/**
 * 结合 PromptTemplate 和 Few-Shot Learning
 */
public String fewShotWithTemplate(List<Example> examples, String input) {
    StringBuilder prompt = new StringBuilder();

    prompt.append("根据以下示例进行分类:\n\n");

    for (int i = 0; i < examples.size(); i++) {
        Example example = examples.get(i);
        prompt.append(String.format("""
            示例%d:
            输入:%s
            输出:%s

            """, i + 1, example.getInput(), example.getOutput()));
    }

    prompt.append("现在请处理:\n");
    prompt.append("输入:").append(input).append("\n");
    prompt.append("输出:");

    return model.chat(prompt.toString());
}

// 定义 Example 类
@Data
class Example {
    private String input;
    private String output;

    public Example(String input, String output) {
        this.input = input;
        this.output = output;
    }
}

  1. 为什么Few-Shot Learning比Zero-Shot更有效?

    • 思考LLM的学习方式
    • 示例如何减少歧义
  2. Chain-of-Thought为什么能提升质量?

    • 追踪推理过程
    • 对比直接答案vs分步骤
  3. 如何找到Prompt的最优平衡点?

    • 详细程度 vs token成本
    • 灵活性 vs 可预测性
  4. 不同类型的任务,Prompt策略应该怎样调整?

    • 创意任务 vs 精确任务
    • 简单问答 vs 复杂分析

实战练习

任务1:写出更好的Code Review Prompt

改进这个Prompt:

String poorPrompt = "审查这段代码";

要求:

  • 指定关注的方面(至少3个) ✅ 2026-03-07
  • 限制输出格式(JSON或Markdown) ✅ 2026-03-07
  • 限制输出长度 ✅ 2026-03-07
  • 给出代码上下文 ✅ 2026-03-07

任务2:设计Few-Shot Learning的示例

为"识别数据库连接池问题"设计Few-Shot Prompt:

  • 创建3个示例(问题+分类) ✅ 2026-03-07
  • 保证示例的多样性 ✅ 2026-03-07
  • 验证新问题的分类是否准确 ✅ 2026-03-07

任务3:使用Chain-of-Thought优化某个分析任务

选择一个复杂的分析任务,用Chain-of-Thought改进:

  • 分解成5-7个步骤 ✅ 2026-03-07
  • 每个步骤明确说明 ✅ 2026-03-07
  • 验证输出是否更深入 ✅ 2026-03-07

相关Examples推荐

下一步学习这些来深化理解:

  • [[2003-Message类型快速参考.md|Message 类型快速参考]] ⭐ 新增

    • 5分钟速记 Message 的用法
    • 常见错误和修正
    • 成本对比分析
  • [[2005-Token成本控制与优化]] (TokenCountingExample)

    • 长Prompt会增加成本,学会优化
  • [[013-Memory会话管理深度解析]] (MemoryExample)

    • 多轮对话中的Prompt管理
  • [[030-Agent设计原理和基础实战]] (AgentExample)

    • Agent中的Prompt工程

补充资源

官方文档

在线工具


总结

🎯 这一篇你学到了

技术知识

  • Prompt工程的5大法则
  • Few-Shot Learning的力量
  • Chain-of-Thought推理的价值
  • LangChain4J中的Message类型和用途
  • 多轮对话中的消息管理

实战技能

  • 如何写出清晰的Prompt
  • 如何用示例指导LLM
  • 如何通过步骤化提升质量
  • 如何正确使用SystemMessage、UserMessage、AiMessage
  • 如何管理和优化对话历史

下一步方向

  • 学Token计数,优化成本
  • 学Memory管理,处理对话
  • 学Agent设计,实现复杂流程

💡 核心认知

好的Prompt = 明确 + 示例 + 约束 + 结构

正确的Message管理 = SystemMessage定义 + UserMessage输入 + AiMessage保存 + 历史优化

记住这些要素,你就能写出高质量的Prompt并正确管理对话。


写作完成

  • 发布到Obsidian
  • 整理到项目README

字数统计:约4500字 | 阅读时间:40分钟

下一步:→ [[05-Token成本控制与优化]]