第4天 - Prompt 工程与模板引擎

系列: Spring AI Alibaba 技术博客系列
日期: 2026-04-28
难度: ⭐⭐⭐
前置知识: ChatModel 基础使用、AI Model 体系


目录

  1. 什么是 Prompt 工程?
  2. Spring AI Alibaba 中的 Prompt 模型
  3. PromptTemplate 模板引擎详解
  4. 高级模板功能
  5. Structured Prompt 结构化提示词
  6. Prompt 工程最佳实践
  7. 实战案例:多场景 Prompt 管理
  8. 常见问题与调试技巧
  9. 总结

1. 什么是 Prompt 工程?

Prompt 工程(Prompt Engineering)是一门研究如何设计和优化输入提示词(Prompt),以引导大语言模型生成高质量、符合预期输出的技术与艺术。

在 Spring AI Alibaba 应用中,Prompt 工程绝非”随便写几句话让 AI 回答”那么简单。一个精心设计的 Prompt 可以:

  • 显著提升输出质量:同样的模型,好的 Prompt 和差的 Prompt 产出差距可能天壤之别
  • 减少幻觉(Hallucination):通过约束和结构化指令,降低模型编造信息的概率
  • 控制输出格式:确保输出符合下游处理所需的格式(JSON、XML、Markdown 等)
  • 提高可维护性:将 Prompt 从代码中解耦,使其可配置、可测试、可版本管理

为什么需要模板引擎?

在实际项目中,我们经常遇到以下痛点:

❌ 硬编码在代码中:难以修改,不利于协作
❌ 拼接字符串:容易出错,缺乏类型安全
❌ 重复内容多:相似的 Prompt 无法复用
❌ 无法动态化:参数无法灵活替换

Spring AI Alibaba 提供了完善的 PromptTemplate 模板引擎,优雅地解决了上述问题。


2. Spring AI Alibaba 中的 Prompt 模型

在深入模板引擎之前,我们先理清 Spring AI Alibaba 中 Prompt 相关的核心类及其关系:

┌─────────────────────────────────────────────────┐
│                  Prompt                           │
│  ┌─────────────────┐  ┌───────────────────────┐  │
│  │   SystemMessage  │  │    UserMessage         │  │
│  │  (系统指令)       │  │  (用户输入 + 变量)      │  │
│  └─────────────────┘  └───────────────────────┘  │
│                       ┌───────────────────────┐  │
│                       │   AssistantMessage     │  │
│                       │  (对话历史/模型回复)    │  │
│                       └───────────────────────┘  │
└─────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────┐
│              PromptTemplate                       │
│  模板文件(.txt/.yaml) + 变量占位符 → Prompt       │
└─────────────────────────────────────────────────┘

核心类说明

类名作用说明
Message消息接口所有消息类型的父接口
SystemMessage系统消息设定模型的行为和角色
UserMessage用户消息用户的输入内容
AssistantMessage助手消息模型的回复或对话历史
Prompt提示词封装包含一组 Message,发送给模型
PromptTemplate模板引擎将模板+变量生成 Prompt
ChatPromptTemplate对话模板支持多轮对话的模板

3. PromptTemplate 模板引擎详解

3.1 基础使用

Maven 依赖

确保你的项目已引入 Spring AI Alibaba 依赖:

<dependency>
    <groupId>com.alibaba.cloud.ai</groupId>
    <artifactId>spring-ai-alibaba-starter</artifactId>
    <version>1.0.0.2</version>
</dependency>

最简单的模板

步骤一:创建模板文件

src/main/resources/prompts/ 目录下创建 greeting.txt

你是一个友好的助手。请用 {language} 语言,用 {tone} 的语气,
向 {name} 打招呼。请确保回答不超过 50 个字。

模板中使用 {variableName} 语法定义变量占位符。

步骤二:在 Java 代码中使用

package com.example.demo.controller;

import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Value;

import java.util.Map;

@RestController
@RequestMapping("/api/prompt")
public class PromptTemplateController {

    @Autowired
    private ChatModel chatModel;

    @Value("classpath:/prompts/greeting.txt")
    private Resource greetingTemplate;

    @GetMapping("/greet")
    public String greet(
            @RequestParam(defaultValue = "中文") String language,
            @RequestParam(defaultValue = "热情") String tone,
            @RequestParam(defaultValue = "老兰") String name) {

        // 1. 创建模板实例,传入 Resource
        PromptTemplate template = new PromptTemplate(greetingTemplate);

        // 2. 替换变量,生成 Prompt
        Prompt prompt = template.create(Map.of(
                "language", language,
                "tone", tone,
                "name", name
        ));

        // 3. 调用模型
        ChatResponse response = chatModel.call(prompt);

        return response.getResult().getOutput().getText();
    }
}

请求示例:

curl "http://localhost:8080/api/prompt/greet?language=中文&tone=幽默&name=老兰"

输出示例:

嘿,老兰!今天过得怎么样?我这心情好得就像中了彩票一样,有啥事儿尽管说,包在我身上!😄

3.2 系统消息 + 用户消息的组合模板

实际场景中,我们通常需要同时设置系统提示词和用户提示词。这时可以使用 ChatPromptTemplate

模板文件 src/main/resources/prompts/code-review.yaml

messages:
  - role: SYSTEM
    content: |
      你是一位资深的 Java 代码审查专家,拥有 15 年以上的开发经验。
      你的审查标准包括:
      1. 代码规范性(命名、格式、注释)
      2. 性能问题(循环、IO、内存)
      3. 安全隐患(SQL注入、XSS、敏感信息泄露)
      4. 架构设计(职责单一、依赖倒置)
      
      请用 Markdown 格式输出审查报告,包含以下章节:
      - 📋 总体评价
      - ✅ 优点
      - ❌ 问题(按严重程度排序)
      - 💡 改进建议
  - role: USER
    content: "请审查以下 Java 代码:\n{code}"

Java 代码:

package com.example.demo.service;

import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.ChatPromptTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Value;

import java.util.Map;

@Service
public class CodeReviewService {

    @Autowired
    private ChatModel chatModel;

    @Value("classpath:/prompts/code-review.yaml")
    private Resource codeReviewTemplate;

    public String reviewCode(String sourceCode) {
        // ChatPromptTemplate 会自动解析 YAML 中的多消息结构
        ChatPromptTemplate template = new ChatPromptTemplate(codeReviewTemplate);

        Prompt prompt = template.create(Map.of("code", sourceCode));

        ChatResponse response = chatModel.call(prompt);

        return response.getResult().getOutput().getText();
    }
}

3.3 消息参数(Message Parameters)

除了简单变量替换,Spring AI 还支持为模板中的消息设置额外参数(metadata):

import org.springframework.ai.chat.messages.MessageParameter;

// 为特定消息设置 temperature 等参数
ChatPromptTemplate template = new ChatPromptTemplate(
    """
    messages:
      - role: SYSTEM
        content: "你是一个专业的翻译助手。"
      - role: USER
        content: "请将以下内容翻译成 {targetLanguage}:{text}"
        parameters:
          temperature: 0.3
    """
);

// YAML 中也可以定义参数

4. 高级模板功能

4.1 条件渲染

Spring AI Alibaba 的模板引擎支持条件渲染,根据变量值决定是否渲染某段内容:

你是一个{role}助手。
{{#if includeExamples}}
以下是一些示例:
{{examples}}
{{/if}}
{{#unless includeExamples}}
请直接回答问题,不需要提供示例。
{{/unless}}

用户问题:{question}

代码使用:

PromptTemplate template = new PromptTemplate(templateResource);

// 不渲染示例部分
Prompt prompt1 = template.create(Map.of(
    "role", "翻译",
    "includeExamples", false,
    "question", "什么是 AI?"
));

// 渲染示例部分
Prompt prompt2 = template.create(Map.of(
    "role", "翻译",
    "includeExamples", true,
    "examples", "示例1:...\n示例2:...",
    "question", "什么是 AI?"
));

4.2 循环渲染

当需要渲染列表数据时,可以使用循环语法:

你是一个技术文档助手。请根据以下 API 列表生成文档:

{{#each apis}}
## {{name}}
- **路径:** {{path}}
- **方法:** {{method}}
- **描述:** {{description}}

{{/each}}

请确保文档格式统一,每个 API 都包含请求参数和响应示例。

代码使用:

record ApiDoc(String name, String path, String method, String description) {}

List<ApiDoc> apis = List.of(
    new ApiDoc("用户登录", "/api/auth/login", "POST", "用户认证登录"),
    new ApiDoc("获取用户信息", "/api/users/{id}", "GET", "获取指定用户的详细信息"),
    new ApiDoc("更新用户资料", "/api/users/{id}", "PUT", "更新用户的个人资料")
);

Prompt prompt = template.create(Map.of("apis", apis));

生成效果:

你是一个技术文档助手。请根据以下 API 列表生成文档:

## 用户登录
- **路径:** /api/auth/login
- **方法:** POST
- **描述:** 用户认证登录

## 获取用户信息
- **路径:** /api/users/{id}
- **方法:** GET
- **描述:** 获取指定用户的详细信息

## 更新用户资料
- **路径:** /api/users/{id}
- **方法:** PUT
- **描述:** 更新用户的个人资料

请确保文档格式统一,每个 API 都包含请求参数和响应示例。

4.3 多语言 Prompt 管理

对于国际化应用,可以在不同目录下管理不同语言的 Prompt:

src/main/resources/
└── prompts/
    ├── zh/
    │   ├── greeting.txt
    │   └── code-review.yaml
    ├── en/
    │   ├── greeting.txt
    │   └── code-review.yaml
    └── ja/
        ├── greeting.txt
        └── code-review.yaml
@Service
public class I18nPromptService {

    @Autowired
    private ApplicationContext applicationContext;

    public Prompt loadPrompt(String language, String templateName, Map<String, Object> variables) {
        // 根据语言加载对应模板
        String resourcePath = String.format("classpath:/prompts/%s/%s.txt", language, templateName);
        Resource templateResource = applicationContext.getResource(resourcePath);
        
        PromptTemplate template = new PromptTemplate(templateResource);
        return template.create(variables);
    }
}

5. Structured Prompt 结构化提示词

5.1 什么是 Structured Prompt?

Spring AI 2.0+ 引入了结构化 Prompt 的概念,允许使用更丰富的结构来组织提示词,包括多个消息、工具定义、输出格式约束等。

import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.messages.AssistantMessage;
import java.util.List;

// 构建包含对话历史的结构化 Prompt
Prompt prompt = new Prompt(List.of(
    new SystemMessage("""
        你是一位专业的 Java 技术面试官。
        请根据候选人的回答进行评估,并给出评分(1-10分)和评语。
        """),
    new UserMessage("请解释 Java 中的垃圾回收机制。"),
    new AssistantMessage("垃圾回收(GC)是 Java 虚拟机自动管理内存的机制..."),
    new UserMessage("那么 G1 收集器和 CMS 收集器有什么区别呢?")
));

ChatResponse response = chatModel.call(prompt);

5.2 输出格式约束

通过系统提示词强制模型输出特定格式:

@Service
public class JsonOutputService {

    @Autowired
    private ChatModel chatModel;

    public String extractEntities(String text) {
        String systemPrompt = """
            你是一个信息抽取专家。请从用户输入的文本中提取以下信息:
            - 人名(person)
            - 地点(location)
            - 时间(date)
            - 组织(organization)
            
            你必须严格按照以下 JSON Schema 格式输出,不要包含任何其他内容:
            {
              "entities": {
                "persons": ["字符串数组"],
                "locations": ["字符串数组"],
                "dates": ["字符串数组"],
                "organizations": ["字符串数组"]
              }
            }
            
            如果某类信息不存在,对应的数组为空数组 []。
            """;

        Prompt prompt = new Prompt(List.of(
            new SystemMessage(systemPrompt),
            new UserMessage("请分析:2026年4月28日,张三在北京参加了阿里巴巴组织的AI技术大会。")
        ));

        ChatResponse response = chatModel.call(prompt);
        return response.getResult().getOutput().getText();
    }
}

5.3 使用 Bean Output Converter(推荐方式)

Spring AI 提供了更优雅的方式将模型输出映射为 Java Bean:

package com.example.demo.dto;

// 定义输出 Bean
public class EntityExtractionResult {
    private List<String> persons;
    private List<String> locations;
    private List<String> dates;
    private List<String> organizations;
    
    // getters, setters, toString...
    public List<String> getPersons() { return persons; }
    public void setPersons(List<String> persons) { this.persons = persons; }
    public List<String> getLocations() { return locations; }
    public void setLocations(List<String> locations) { this.locations = locations; }
    public List<String> getDates() { return dates; }
    public void setDates(List<String> dates) { this.dates = dates; }
    public List<String> getOrganizations() { return organizations; }
    public void setOrganizations(List<String> organizations) { this.organizations = organizations; }
}
@Service
public class StructuredOutputService {

    @Autowired
    private ChatModel chatModel;

    public EntityExtractionResult extractEntities(String text) {
        // 使用 BeanOutputConverter 自动将 JSON 映射为 Java 对象
        BeanOutputConverter<EntityExtractionResult> converter = 
            new BeanOutputConverter<>(EntityExtractionResult.class);

        String formatInstructions = converter.getFormatInstructions();

        String systemPrompt = """
            你是一个信息抽取专家。请从用户输入的文本中提取人名、地点、时间和组织。
            
            %s
            
            如果某类信息不存在,对应字段为空数组。
            """.formatted(formatInstructions);

        Prompt prompt = new Prompt(List.of(
            new SystemMessage(systemPrompt),
            new UserMessage(text)
        ));

        ChatResponse response = chatModel.call(prompt);
        
        // 自动解析 JSON 并映射为 Java Bean
        return converter.convert(response.getResult().getOutput().getText());
    }
}

这是 Spring AI 推荐的 Structured Output 方式,类型安全且易于维护。


6. Prompt 工程最佳实践

6.1 角色设定(Role Prompting)

始终在系统消息中为模型设定明确的角色:

# ✅ 好的做法
messages:
  - role: SYSTEM
    content: |
      你是一位拥有 20 年经验的高级软件架构师,
      精通分布式系统、微服务架构和云原生技术。
      你擅长用简洁清晰的方式解释复杂的技术概念。
  - role: USER
    content: "{question}"

# ❌ 不好的做法
messages:
  - role: SYSTEM
    content: "你是一个助手。"  # 过于笼统,模型行为不可控

6.2 提供上下文和示例(Few-shot Prompting)

通过提供少量示例,可以显著提升模型输出的准确性和一致性:

你是一个 SQL 生成助手。请根据用户的自然语言描述生成对应的 SQL 查询。

表结构:
- users(id, name, email, department_id, created_at)
- departments(id, name, location)

示例:
用户输入:找出所有在北京部门的员工
SQL:SELECT u.* FROM users u JOIN departments d ON u.department_id = d.id WHERE d.location = '北京'

用户输入:统计每个部门的员工数量
SQL:SELECT d.name, COUNT(u.id) as count FROM departments d LEFT JOIN users u ON d.id = u.department_id GROUP BY d.id, d.name

用户输入:{naturalLanguage}
SQL:

6.3 分步思考(Chain of Thought)

对于复杂任务,引导模型分步思考:

请按以下步骤解决问题:

1. 首先,理解用户的需求,复述核心问题
2. 其次,分析问题涉及的技术要点
3. 然后,给出解决方案的详细步骤
4. 最后,总结关键要点和注意事项

用户问题:{question}

6.4 输出格式约束

明确指定输出格式,避免模型自由发挥:

请按以下格式回答:

## 概述
[2-3句话概括核心答案]

## 详细分析
[分点说明,每点不超过 100 字]

## 代码示例
[如有,提供可运行的代码]

## 注意事项
- [列出关键注意事项]

用户问题:{question}

6.5 温度参数调优

不同的任务需要不同的 temperature 设置:

场景Temperature说明
代码生成0.1-0.3需要确定性输出
翻译0.2-0.4需要准确一致
摘要0.3-0.5平衡准确性和创造性
创意写作0.7-0.9需要多样性和创意
头脑风暴0.8-1.0最大化创意输出

在 Spring AI 中设置 temperature:

Prompt prompt = new Prompt(List.of(
    new SystemMessage("你是一个创意写作助手。"),
    new UserMessage("请写一个关于 AI 的短故事。")
), ChatOptions.builder()
    .temperature(0.8)
    .maxTokens(500)
    .build());

6.6 Prompt 长度管理

  • 注意 Token 限制:不同模型有不同的上下文窗口,超出会被截断
  • 精简冗余信息:去掉不必要的描述,保留核心指令
  • 优先级排序:最重要的指令放在最前面(模型对开头和结尾的内容更敏感)

7. 实战案例:多场景 Prompt 管理

下面展示一个完整的实战案例:在 Spring Boot 项目中统一管理多个场景的 Prompt。

7.1 项目结构

src/main/
├── java/com/example/demo/
│   ├── config/
│   │   └── PromptConfig.java          # Prompt 配置
│   ├── service/
│   │   └── PromptService.java         # Prompt 服务
│   └── controller/
│       └── PromptController.java      # API 接口
└── resources/
    └── prompts/
        ├── system-rules.txt           # 全局系统规则
        ├── scenarios/
        │   ├── code-review.yaml       # 代码审查
        │   ├── sql-generator.txt      # SQL 生成
        │   ├── document-summarizer.txt # 文档摘要
        │   └── bug-analyzer.yaml      # Bug 分析
        └── examples/
            └── code-review-examples.txt  # Few-shot 示例

7.2 配置类

package com.example.demo.config;

import org.springframework.ai.chat.model.ChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ai.retry.RetryUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class PromptConfig {

    @Value("classpath:/prompts/system-rules.txt")
    private Resource systemRules;

    @Value("classpath:/prompts/scenarios/code-review.yaml")
    private Resource codeReviewPrompt;

    @Value("classpath:/prompts/scenarios/sql-generator.txt")
    private Resource sqlGeneratorPrompt;

    @Value("classpath:/prompts/scenarios/document-summarizer.txt")
    private Resource documentSummarizerPrompt;

    @Value("classpath:/prompts/scenarios/bug-analyzer.yaml")
    private Resource bugAnalyzerPrompt;

    /**
     * 将所有 Prompt 模板注册为 Bean,方便注入使用
     */
    @Bean
    public Map<String, Resource> promptTemplates() {
        Map<String, Resource> templates = new HashMap<>();
        templates.put("code-review", codeReviewPrompt);
        templates.put("sql-generator", sqlGeneratorPrompt);
        templates.put("document-summarizer", documentSummarizerPrompt);
        templates.put("bug-analyzer", bugAnalyzerPrompt);
        return templates;
    }
}

7.3 Prompt 服务

package com.example.demo.service;

import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.chat.prompt.ChatPromptTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class PromptService {

    private static final Logger log = LoggerFactory.getLogger(PromptService.class);

    @Autowired
    private ChatModel chatModel;

    @Autowired
    private Map<String, Resource> promptTemplates;

    // 模板缓存,避免重复加载
    private final Map<String, PromptTemplate> templateCache = new ConcurrentHashMap<>();

    /**
     * 执行 Prompt 模板调用
     * 
     * @param scenarioName 场景名称
     * @param variables    模板变量
     * @return AI 回复结果
     */
    public String execute(String scenarioName, Map<String, Object> variables) {
        Resource templateResource = promptTemplates.get(scenarioName);
        if (templateResource == null) {
            throw new IllegalArgumentException("未知的 Prompt 场景: " + scenarioName);
        }

        try {
            // 获取或创建模板实例
            PromptTemplate template = templateCache.computeIfAbsent(scenarioName, 
                key -> createTemplate(templateResource));

            // 创建 Prompt 并调用模型
            Prompt prompt = template.create(variables);
            
            log.info("执行 Prompt 场景: {}, 变量: {}", scenarioName, variables.keySet());
            
            ChatResponse response = chatModel.call(prompt);
            
            String result = response.getResult().getOutput().getText();
            log.info("Prompt 场景 {} 执行完成,结果长度: {}", scenarioName, result.length());
            
            return result;
        } catch (Exception e) {
            log.error("Prompt 场景 {} 执行失败", scenarioName, e);
            throw new RuntimeException("Prompt 执行失败: " + e.getMessage(), e);
        }
    }

    private PromptTemplate createTemplate(Resource resource) {
        String filename = resource.getFilename();
        if (filename != null && filename.endsWith(".yaml")) {
            return new ChatPromptTemplate(resource);
        }
        return new PromptTemplate(resource);
    }
}

7.4 Controller 层

package com.example.demo.controller;

import com.example.demo.service.PromptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

@RestController
@RequestMapping("/api/ai")
public class PromptController {

    @Autowired
    private PromptService promptService;

    @PostMapping("/code-review")
    public Map<String, String> codeReview(@RequestBody CodeReviewRequest request) {
        String result = promptService.execute("code-review", Map.of(
            "code", request.code(),
            "language", request.language() != null ? request.language() : "Java"
        ));
        return Map.of("result", result);
    }

    @PostMapping("/sql")
    public Map<String, String> generateSql(@RequestBody SqlRequest request) {
        String result = promptService.execute("sql-generator", Map.of(
            "description", request.description(),
            "schema", request.schema()
        ));
        return Map.of("sql", result);
    }

    @PostMapping("/summarize")
    public Map<String, String> summarize(@RequestBody SummarizeRequest request) {
        String result = promptService.execute("document-summarizer", Map.of(
            "content", request.content(),
            "maxLength", request.maxLength() != null ? request.maxLength() : "200"
        ));
        return Map.of("summary", result);
    }

    // Request 记录类
    public record CodeReviewRequest(String code, String language) {}
    public record SqlRequest(String description, String schema) {}
    public record SummarizeRequest(String content, String maxLength) {}
}

7.5 模板文件示例

prompts/scenarios/document-summarizer.txt

你是一位专业的文档摘要专家。请将以下文档内容进行摘要。

要求:
1. 保留文档的核心观点和关键信息
2. 摘要长度不超过 {maxLength} 字
3. 使用清晰的条理结构(分点列出)
4. 如果文档包含代码,请保留关键代码片段的说明
5. 不要添加原文中没有的信息

待摘要的文档:
---
{content}
---

8. 常见问题与调试技巧

8.1 Prompt 调试方法

@Service
public class PromptDebugger {

    @Autowired
    private ChatModel chatModel;

    /**
     * 打印完整的 Prompt 内容,方便调试
     */
    public void debugPrompt(String templatePath, Map<String, Object> variables) {
        Resource resource = new ClassPathResource(templatePath);
        PromptTemplate template = new PromptTemplate(resource);
        Prompt prompt = template.create(variables);

        // 打印渲染后的 Prompt
        System.out.println("=== 渲染后的 Prompt ===");
        prompt.getMessages().forEach(msg -> {
            System.out.println("[" + msg.getMessageType() + "]: " + msg.getText());
            System.out.println("---");
        });

        // 调用模型
        ChatResponse response = chatModel.call(prompt);
        System.out.println("=== AI 回复 ===");
        System.out.println(response.getResult().getOutput().getText());
    }
}

8.2 常见问题

问题原因解决方案
变量未被替换变量名不匹配检查模板中的 {name} 和代码中的 key 是否一致
输出格式不符合预期缺少格式约束在系统提示词中明确指定输出格式
模型回答太短Token 限制或 temperature 过低调整 maxTokens 和 temperature 参数
模型编造信息(幻觉)缺少约束或上下文不足添加 “如果不知道,请明确说明” 的指令
Prompt 过长导致截断超出模型上下文窗口精简 Prompt 或使用分块处理
YAML 模板解析失败缩进错误或格式不对检查 YAML 缩进(必须使用空格,不能用 Tab)

8.3 Prompt 版本管理建议

// 在文件名中加入版本号
// prompts/v1/code-review.yaml
// prompts/v2/code-review.yaml

@Service
public class VersionedPromptService {

    private final Map<String, Resource> promptVersions = Map.of(
        "code-review-v1", new ClassPathResource("/prompts/v1/code-review.yaml"),
        "code-review-v2", new ClassPathResource("/prompts/v2/code-review.yaml")
    );

    public String execute(String scenario, String version, Map<String, Object> variables) {
        String key = scenario + "-" + version;
        Resource resource = promptVersions.get(key);
        // ...
    }
}

9. 总结

本文深入介绍了 Spring AI Alibaba 中的 Prompt 工程与模板引擎,核心要点如下:

核心知识点回顾

  1. PromptTemplate 是最基础的模板引擎,支持简单的变量替换({variable} 语法)
  2. ChatPromptTemplate 支持 YAML 格式的多消息模板(System + User + Assistant)
  3. 条件渲染{{#if}})和循环渲染{{#each}})让模板更加灵活
  4. Structured Prompt 通过 BeanOutputConverter 实现类型安全的结构化输出
  5. Prompt 工程最佳实践:角色设定、Few-shot 示例、分步思考、格式约束、温度调优

Prompt 设计检查清单

在将 Prompt 投入生产环境之前,请确认:

  • 是否设定了明确的角色?
  • 是否提供了足够的上下文?
  • 是否有输出格式约束?
  • 是否考虑了边界情况?
  • 是否进行了充分的测试?
  • 温度等参数是否合理?
  • Prompt 长度是否在模型限制内?

下一篇预告

第5天我们将探讨 系统提示词与用户提示词的最佳实践,包括如何设计高效的全局系统规则、如何在不同场景间共享和复用系统提示词,以及系统提示词的安全注意事项。