乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • Spring AI 学习路径
  • 第1章 Spring AI 概述与核心理念

    • 章节导读
    • Spring AI 概述与可移植 API
    • 核心模块与依赖关系
  • 第2章 快速入门与第一个AI应用

    • 章节导读
    • 环境准备与配置
    • 第一个 AI 对话应用
  • 第3章 聊天模型与ChatClient

    • 章节导读
    • ChatClient 详解
    • ChatModel 底层抽象
    • 多轮对话与 ChatMemory
  • 第4章 提示词管理与模板

    • 章节导读
    • Prompt 与 Message 体系
    • 提示词模板与动态构建
  • 第5章 输出解析与结构化响应

    • 章节导读
    • 输出解析器与 BeanOutputConverter
  • 第6章 嵌入模型与向量存储

    • 章节导读
    • EmbeddingModel 与文本向量化
    • ETL 数据注入流水线
    • 向量存储抽象与配置
  • 第7章 检索增强生成(RAG)

    • 章节导读
    • RAG 核心机制与流程
    • QuestionAnswerAdvisor 详解
    • RAG 实战案例
  • 第8章 函数调用(Function Calling)

    • 章节导读
    • Tool 注解与函数声明
    • 函数调用实战
  • 第9章 多模态

    • 章节导读
    • 图像输入与视觉模型
    • 图像生成
  • 第10章 Advisor拦截器链

    • 章节导读
    • Advisor 链与内置拦截器
  • 第11章 MCP 协议与跨语言工具集成

    • 章节导读
    • MCP 协议深入
  • 第12章 可观测性测试与最佳实践

    • 章节导读
    • 可观测性与指标监控
    • 测试策略与 Mock 实践
    • 最佳实践与面试考点汇总

孔蓝拿着原型说:"不同专业的课程推荐要不一样!计算机系和人文系看到的推荐肯定不同啊。"白歌说:"用 PromptTemplate,占位符 {major} 动态注入,一次编写模板,适配所有专业。"

提示词模板与动态构建

定义与作用

PromptTemplate 是 Spring AI 提供的 占位符模板引擎,支持 {variable} 语法,运行时用实际值替换。核心方法:

PromptTemplate template = new PromptTemplate("""
    推荐适合 {major} 专业大{year}学生的 {count} 门选修课
    """);
template.add("major", "计算机科学");
template.add("year", "二");
template.add("count", "3");
Prompt prompt = template.create();

核心原理:从模板到 Prompt 的转换流程

图释:PromptTemplate 内部使用 StringTemplate 做占位符替换,生成最终的 Message 并包装为 Prompt。

与 RAG 配合:动态注入检索上下文

// RAG 场景的标准模板
PromptTemplate template = new PromptTemplate("""
    基于以下文档内容回答用户问题。
    如果文档中没有相关信息,请明确说明"未找到相关信息"。
    
    相关文档片段:
    {context}
    
    用户问题:{question}
    
    请用中文回答,引用时注明来源页码:
    """);
template.add("context", vectorSearchResult);   // 向量检索结果
template.add("question", userQuestion);
Prompt prompt = template.create();

完整示例一:根据学生专业动态生成课程推荐

场景说明

孔蓝需要学生管理系统根据登录学生的专业,动态生成个性化课程推荐。

关键代码

@RestController
public class CourseRecommendController {

    private final ChatClient chatClient;

    public CourseRecommendController(ChatClient.Builder builder) {
        this.chatClient = builder
                .defaultSystem("你是飞翔科技大学的课程推荐专家。")
                .build();
    }

    @GetMapping("/course-recommend")
    public String recommend(@RequestParam String major,
                            @RequestParam(defaultValue = "二") String year) {
        // 步骤1:准备模板
        PromptTemplate template = new PromptTemplate("""
            请为{major}专业大{year}学生推荐3门选修课。
            
            要求:
            1. 每门课程说明适合什么方向的学生
            2. 列出课程的先修条件
            3. 给出课程编号(格式:DEPT-编号)
            
            请用结构化格式输出。
            """);

        // 步骤2:动态填充参数
        template.add("major", major);
        template.add("year", year);

        // 步骤3:使用模板
        return chatClient.prompt()
                .user(template.create().getInstructions().get(0).getContent())
                .call()
                .content();
    }
}

运行结果

GET /course-recommend?major=计算机科学&year=二

回复:
1. CS310 人工智能导论
   适合方向:对机器学习、自然语言处理感兴趣的学生
   先修条件:CS201 离散数学、CS202 数据结构
   
2. CS320 软件工程
   适合方向:想进入企业级开发的工程型学生
   先修条件:CS101 程序设计基础
   
3. MATH305 概率论与数理统计
   适合方向:数据科学、AI 方向必备
   先修条件:MATH101 高等数学A
GET /course-recommend?major=汉语言文学&year=二

回复:
1. CHN210 中国现代文学
   适合方向:对现当代文学流派感兴趣的学生
   先修条件:CHN101 文学概论
   
2. CHN215 古代汉语
   适合方向:古典文献研究方向
   先修条件:无
   
3. CHN220 创意写作
   适合方向:文学创作方向
   先修条件:CHN105 基础写作

完整示例二:RAG 场景的动态 Prompt 构建

场景说明

飞翔科技的智能客服需要先检索产品手册,再基于检索结果回答问题。

关键代码

@Service
public class ProductQAService {

    private final ChatClient chatClient;
    private final VectorStore vectorStore;

    public ProductQAService(ChatClient.Builder builder,
                            VectorStore vectorStore) {
        this.chatClient = builder
                .defaultSystem("你是飞翔科技产品顾问。")
                .build();
        this.vectorStore = vectorStore;
    }

    public String ask(String question) {
        // 1. 从向量库检索相关文档
        List<Document> docs = vectorStore.similaritySearch(
            SearchRequest.builder()
                .query(question)
                .topK(3)
                .build()
        );

        // 2. 拼接检索结果
        String context = docs.stream()
                .map(doc -> doc.getContent() + "(来源:" 
                        + doc.getMetadata().get("source") + ")")
                .collect(Collectors.joining("\n---\n"));

        // 3. 用模板构造 Prompt
        PromptTemplate template = new PromptTemplate("""
            你是飞翔科技的产品顾问。基于以下产品手册内容回答用户问题。
            
            手册相关内容:
            {context}
            
            用户问题:{question}
            
            回答要求:
            - 如果手册中有明确信息,直接引用并注明来源
            - 如果手册中信息不完整,说明哪些是确定的,哪些需要进一步确认
            - 绝对不要编造手册中没有的信息
            """);
        template.add("context", context);
        template.add("question", question);

        // 4. 发送给 LLM
        return chatClient.prompt()
                .user(template.create().getInstructions().get(0).getContent())
                .call()
                .content();
    }
}

System Prompt 最佳实践

在飞翔科技的项目实践中,白歌总结了 System Prompt 的设计准则:

DO(推荐做法):

1. 角色 + 边界:"你是飞翔科技大学的课程顾问,只回答课程相关问题"
2. 格式约束:"回答用 Markdown 表格,字段为:课程名、编号、学分、时间"
3. 行为规范:"如果不确定,明确说'我不确定',不要编造"
4. 语气设定:"使用正式礼貌的学术语气"

DON'T(避免做法):

1. 不要写过长:"你是...(500字角色描述)"——超过 200 字 AI 容易忽略细节
2. 不要自相矛盾:"回答要详细...但每句不超过 10 字"
3. 不要覆盖模型安全机制——尝试让 AI 绕过安全限制会被拒绝

易错场景与面试考点

易错场景一:占位符未填充导致原始模板发给 AI

// ❌ 错误:忘记填充 {major}
PromptTemplate template = new PromptTemplate("推荐{major}专业的课程");
// 忘记调用 template.add("major", "计算机科学")
Prompt prompt = template.create();
// AI 收到:"推荐{major}专业的课程" → 回复"请问你指的是什么专业?"

问题分析

PromptTemplate.create() 不会对未填充的占位符报错或跳过——它会将原始 {variable} 文本原封不动发送给 AI,造成奇怪的对话体验。

// ✅ 正确:确保所有占位符已填充
PromptTemplate template = new PromptTemplate("推荐{major}专业的课程");
template.add("major", major);    // 从方法参数获取
Prompt prompt = template.create();

防御性编程建议:在模板创建前检查所有必填参数。

public Prompt createPrompt(String templateStr, Map<String, Object> params) {
    PromptTemplate template = new PromptTemplate(templateStr);
    params.forEach((k, v) -> {
        if (v == null || v.toString().isBlank()) {
            throw new IllegalArgumentException("参数 " + k + " 不能为空");
        }
        template.add(k, v);
    });
    return template.create();
}

面试高频题

Q1:PromptTemplate 和 String.format 有什么区别?

PromptTemplate 基于 Antlr 的 StringTemplate 引擎,支持更丰富的占位符语法和集合迭代,且与 Spring AI 的消息体系集成。String.format 只能做简单的 %s 替换,不适用于复杂的 RAG 场景。

Q2:RAG 场景中如何组织 PromptTemplate 以减少幻觉?

关键三点:①明确约束"只基于提供的上下文回答";②要求引用来源("注明来源页码");③明确兜底策略("如果文档中没有相关信息,请说'未找到'")。

本章小结

  • PromptTemplate 使用 {variable} 占位符语法,template.add(key, value) 填充
  • 与 RAG 配合时,将向量检索结果拼接到 {context} 占位符中
  • 未填充的占位符不会报错,会把原始 {xxx} 发给 AI,需要防御性编码
  • System Prompt 遵循"角色 + 边界 + 格式 + 兜底"四要素
  • 模板让一次编写、多场景复用成为可能
上一页
Prompt 与 Message 体系