【 AI | 提示词工程与多轮对话记忆 】
前言:本文围绕 AI 应用开发,先讲 Prompt 工程,含其概念、分类及优化技巧;再介绍 SpringAI 核心特性,如多轮对话实现、ChatClient 及 Advisors 机制,重点讲 AroundAdvisor;最后给出多轮对话 AI 应用开发步骤与实例。


思维导图:

1. Prompt 工程
基本概念:Prompt 工程又叫提示词工程,简单来说,就是输入给 AI 的指令。
比如下面这段内容,就是提示词:
请问如何学算法?
1.1 Prompt分类
在 AI 对话中,基于角色的分类是最常见的,通常存在 3 种主要类型的 Prompt:
1)用户 Prompt :这是用户向 AI 提供的实际问题、指令或信息,传达了用户的直接需求。
用户:帮我写一篇算法文章
2)系统 Prompt :这是设置 AI 模型行为规则和角色定位的隐藏指令,用户通常不能直接看到。
系统:你是一位经验丰富的软件工程师,擅长分析代码问题并提供建设性建议。
回答时保持专业性,但避免使用过于学术的术语,确保新手能够理解你的建议。
3)助手 Prompt :这是 AI 模型的响应内容。在多轮对话中,之前的助手回复也会成为当前上下文的一部分,影响后续对话的理解和生成。
系统:你是编程导航的专业编程导师,擅长引导初学者入门编程并制定学习路径。
使用友好鼓励的语气,解释复杂概念时要通俗易懂,适当使用比喻让新手理解,避免过于晦涩的技术术语。
用户:我完全没有编程基础,想学习编程开发,但不知道从何开始,能给我一些建议吗?
助手:欢迎加入编程的世界!作为编程小白,建议你可以按照以下步骤开始学习之旅...
【多轮对话继续】
1.2 Prompt优化
高质量的 Prompt 可以显著提升 AI 输出的质量,因此掌握 Prompt 优化技巧非常重要。
1.2.1 基础提示技巧
1. 明确指定任务和角色
为 AI 提供清晰的任务描述和角色定位,帮助模型理解背景和期望。
系统:你是一位经验丰富的Python教师,擅长向初学者解释编程概念。
用户:请解释 Python 中的列表推导式,包括基本语法和 2-3 个实用示例。
2. 提供详细说明和具体示例
提供足够的上下文信息和期望的输出格式示例,减少模型的不确定性。
请提供一个社交媒体营销计划,针对一款新上市的智能手表。计划应包含:
1. 目标受众描述
2. 三个内容主题
示例格式:
目标受众: [描述]
内容主题: [主题1], [主题2], [主题3]
3. 明确输出格式要求
指定输出的格式、长度、风格等要求,获得更符合预期的结果。
撰写一篇关于气候变化的科普文章,要求:
- 使用通俗易懂的语言,适合高中生阅读
- 包含5个小标题,每个标题下2-3段文字
- 总字数控制在800字左右
- 结尾提供3个可行的个人行动建议
1.2.2 进阶提示技巧
1. 分步骤指导(Step-by-Step)
将复杂任务分解为可管理的步骤,确保模型完成每个关键环节。
请帮我创建一个简单的网站落地页设计方案,按照以下步骤:
步骤1: 分析目标受众(考虑年龄、职业、需求等因素)
步骤2: 确定页面核心信息(主标题、副标题、价值主张)
步骤3: 设计页面结构(至少包含哪些区块)
步骤4: 制定视觉引导策略(颜色、图像建议)
步骤5: 设计行动召唤(CTA)按钮和文案
2. 少样本学习(Few-Shot Learning)
通过提供几个输入-输出对的示例,帮助模型理解任务模式和期望输出。
我将给你一些情感分析的例子,然后请你按照同样的方式分析新句子的情感倾向。
输入: "这家餐厅的服务太差了,等了一个小时才上菜"
输出: 负面,因为描述了长时间等待和差评服务
输入: "新买的手机屏幕清晰,电池也很耐用"
输出: 正面,因为赞扬了产品的多个方面
现在分析这个句子:
"这本书内容还行,但是价格有点贵"
核心:任务越复杂,就越要给 Prompt 补充更多细节
2. SpringAI 核心特性
2.1 多轮对话实现
要实现具有 “记忆力” 的 AI 应用,让 AI 能够记住用户之前的对话内容并保持上下文连贯性,我们可以使用Spring AI 框架的 对话记忆能力。
2.1.1 ChatClient
之前我们是直接使用 Spring Boot 注入的 ChatModel 来调用大模型完成对话,而通过我们自己构造的 ChatClient,可实现功能更丰富、更灵活的 AI 对话客户端,也更推荐通过这种方式调用 AI。
ChatModel 和 ChatClient 的主要区别:ChatClient 支持更复杂灵活的链式调用
// 基础用法(ChatModel)
ChatResponse response = chatModel.call(new Prompt("你好"));
// 高级用法(ChatClient)
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultSystem("你是恋爱顾问")
.build();
String response = chatClient.prompt().user("你好").call().content();
2.1.2 构建方式
方式 1:构造器注入(Spring 项目专用)
服务类代码
@Service
public class CodeService { // 类名贴合编程场景
private final ChatClient chatClient; // 配置好的AI编程工具
// Spring自动传入建造者,只需配置编程助手身份
public CodeService(ChatClient.Builder builder) {
this.chatClient = builder
.defaultSystem("你是编程助手,擅长Java、Python代码解答,会解释原理") // 设定AI身份
.build(); // 生成工具
}
// 对外提供编程问题查询方法
public String askCodeQuestion(String question) {
return chatClient.send(question); // 调用工具发送问题
}
}
控制器使用示例
@RestController
public class CodeController {
@Autowired // Spring自动注入CodeService
private CodeService codeService;
@GetMapping("/ask-code")
public String askCode(String question) {
// 调用服务获取AI的编程解答
return codeService.askCodeQuestion(question);
}
}
方式 2:建造者模式(非 Spring 项目用)
public class CodeDemo {
public static void main(String[] args) {
// 1. 准备基础AI模型(工具内核)
ChatModel chatModel = new ChatModel("你的APIKey");
// 2. 用建造者模式组装工具(设定编程助手身份)
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultSystem("你是编程助手,会写代码、查bug")
.build();
// 3. 调用工具提问
String answer = chatClient.send("Python怎么读取Excel文件?");
System.out.println(answer); // 输出AI的解答
}
}
2.1.3 响应格式
// ChatClient支持多种响应格式
// 1. 返回 ChatResponse 对象(包含元数据如 token 使用量)
ChatResponse chatResponse = chatClient.prompt()
.user("Tell me a joke")
.call()
.chatResponse();
// 2. 返回实体对象(自动将 AI 输出映射为 Java 对象)
// 2.1 返回单个实体
record ActorFilms(String actor, List<String> movies) {}
ActorFilms actorFilms = chatClient.prompt()
.user("Generate the filmography for a random actor.")
.call()
.entity(ActorFilms.class);
// 2.2 返回泛型集合
List<ActorFilms> multipleActors = chatClient.prompt()
.user("Generate filmography for Tom Hanks and Bill Murray.")
.call()
.entity(new ParameterizedTypeReference<List<ActorFilms>>() {});
// 3. 流式返回(适用于打字机效果)
Flux<String> streamResponse = chatClient.prompt()
.user("Tell me a story")
.stream()
.content();
// 也可以流式返回ChatResponse
Flux<ChatResponse> streamWithMetadata = chatClient.prompt()
.user("Tell me a story")
.stream()
.chatResponse();
2.1.4 Advisors
Spring AI 使用 Advisors(顾问)机制来增强 AI 的能力,可以理解为一系列可插拔的拦截器,在调用 AI 前和调用 AI 后可以执行一些额外的操作,比如:
- 前置增强:调用 AI 前改写一下 Prompt 提示词、检查一下提示词是否安全
- 后置增强:调用 AI 后记录一下日志、处理一下返回的结果

关键组件解析
| 组件 | 作用 | 开发场景 |
|---|---|---|
| Prompt | 原始用户输入 | 开发者直接处理用户输入 |
| AdvisedRequest | 增强的请求对象 | 添加上下文/角色设定(如defaultSystem("恋爱顾问")) |
| AroundAdvisor | 拦截器(AOP 思想) | 核心扩展点: • 日志记录 • 请求改写 • 权限校验 • 敏感词过滤 |
| Chat Model | AI 模型接口 | 对接 OpenAI/Anthropic 等大模型 |
| ChatResponse | 原始 AI 响应 | 包含元数据(token 用量等) |
| AdvisedResponse | 增强的响应对象 | 结果格式化/业务逻辑封装 |
开发重点:AroundAdvisor(拦截器)
这是最强大的扩展点,可自定义处理逻辑:
1.动态角色设定
// 在 Before 拦截器中
if(request.getUser().isPremium()) {
request.setSystemRole("高级情感顾问");
} else {
request.setSystemRole("基础情感助手");
}
2.敏感词过滤
// 在 After 拦截器中
if(response.contains("暴力内容")) {
return response.filterSensitiveWords();
}
3.业务逻辑集成
// 调用前注入业务数据
request.addContext("用户订单历史: " + orderService.getHistory());
综上所述,AroundAdvisor(拦截器)作为核心扩展点,具备强大的自定义处理能力,能够灵活支撑多样化的业务需求:它可通过 Before 拦截器实现动态角色设定,依据用户属性(如是否为高级用户)分配对应的系统角色;借助 After 拦截器进行敏感词过滤,确保响应内容合规;还能在调用前注入业务数据(如用户订单历史),实现与业务逻辑的深度集成。这一扩展点为系统功能的个性化与智能化提供了关键支撑。
2.1.5 Chat Memory Advisor
前面我们提到了,想要实现对话记忆功能,可以使用 Spring AI 的 ChatMemoryAdvisor,它主要有几种内置的实现方式:
- MessageChatMemoryAdvisor:从记忆中检索历史对话,并将其作为消息集合添加到提示词中
- PromptChatMemoryAdvisor:从记忆中检索历史对话,并将其添加到提示词的系统文本中
- VectorStoreChatMemoryAdvisor:可以用向量数据库来存储检索历史对话

MessageChatMemoryAdvisor 和 PromptChatMemoryAdvisor 用法类似,但是略有一些区别:
1)MessageChatMemoryAdvisor 将对话历史作为一系列独立的消息添加到提示中,保留原始对话的完整结构,包括每条消息的角色标识(用户、助手、系统)。
[
{"role": "user", "content": "你好"},
{"role": "assistant", "content": "你好!有什么我能帮助你的吗?"},
{"role": "user", "content": "讲个笑话"}
]
2)PromptChatMemoryAdvisor 将对话历史添加到提示词的系统文本部分,因此可能会失去原始的消息边界。
以下是之前的对话历史:
用户: 你好
助手: 你好!有什么我能帮助你的吗?
用户: 讲个笑话
现在请继续回答用户的问题。
一般情况下,更建议使用 MessageChatMemoryAdvisor。更符合大多数现代 LLM 的对话模型设计,能更好地保持上下文连贯性。
2.1.6 Chat Memory
上述 ChatMemoryAdvisor 都依赖 Chat Memory 进行构造,Chat Memory 负责历史对话的存储,定义了保存消息、查询消息、清空消息历史的方法。

Spring AI 内置了几种 Chat Memory,可以将对话保存到不同的数据源中,比如:
- InMemoryChatMemory:内存存储
- CassandraChatMemory:在 Cassandra 中带有过期时间的持久化存储
- Neo4jChatMemory:在 Neo4j 中没有过期时间限制的持久化存储
- JdbcChatMemory:在 JDBC 中没有过期时间限制的持久化存储
当然也可以通过实现 ChatMemory 接口自定义数据源的存储。
3.多轮对话 AI 应用开发
3.1 首先初始化 ChatClient 对象
// 聊天客户端
private final ChatClient chatClient;
// 系统提示
private static final String SYSTEM_PROMPT = "你是一个算法大神,帮我回答各种算法题目的java代码示例与讲解";
/**
* 构造函数,初始化基于内存的聊天客户端
* @param dashscopeChatModel
*/
public AiCodeApp(ChatModel dashscopeChatModel) {
//初始化基于内存的对话记忆
ChatMemory chatMemory = new InMemoryChatMemory();
chatClient = ChatClient.builder(dashscopeChatModel)
.defaultSystem(SYSTEM_PROMPT)
.defaultAdvisors(
new MessageChatMemoryAdvisor(chatMemory)
)
.build();
}
3.2 编写对话方法
/**
* 聊天方法
* @param message
* @param chatId
* @return
*/
public String doChat(String message, String chatId) {
ChatResponse chatResponse = chatClient.prompt()
.user(message)
// 设置对话记忆
.advisors(
advisorSpec ->
// 设置对话记忆的会话ID
advisorSpec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
// 设置对话记忆的检索数量
.param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))
.call()
.chatResponse();
// 返回结果
String content = chatResponse.getResult().getOutput().getText();
log.info("content:{}", content);
return content;
}
3.3 编写单元测试
@SpringBootTest
class AIcodeAppTest {
@Resource
private AiCodeApp aicodeApp;
@Test
void chat(){
String chat = aicodeApp.doChat("请帮我写出冒泡排序", String.valueOf(1));
String chat2 = aicodeApp.doChat("我的上一个问题是什么", String.valueOf(1));
System.out.println(chat);
System.out.println(chat2);
}
}
3.4 AI回答
✅ 冒泡排序简介:
冒泡排序是一种简单的排序算法。它重复地遍历要排序的列表,比较相邻的两个元素,如果顺序错误(如前一个比后一个大),就交换他们,直到没有需要交换的元素为止。
每一轮遍历会把最大的元素“冒泡”到末尾。
✅ Java代码实现:
public class BubbleSort { // 冒泡排序方法 public static void bubbleSort(int[] arr) { int n = arr.length; boolean swapped; // 外层循环控制轮数(共 n-1 轮) for (int i = 0; i < n - 1; i++) { swapped = false; // 优化:如果某一轮没有发生交换,说明已经有序 // 内层循环控制每轮比较的次数(每次减少一个元素) for (int j = 0; j < n - 1 - i; j++) { // 如果前一个元素比后一个大,则交换 if (arr[j] > arr[j + 1]) { // 交换 arr[j] 和 arr[j+1] int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; swapped = true; } } // 如果本轮没有发生交换,提前结束排序 if (!swapped) break; } } // 测试代码 public static void main(String[] args) { int[] nums = {64, 34, 25, 12, 22, 11, 90}; System.out.println("排序前:"); printArray(nums); bubbleSort(nums); System.out.println("排序后:"); printArray(nums); } // 打印数组的方法 private static void printArray(int[] arr) { for (int num : arr) { System.out.print(num + " "); } System.out.println(); } }
✅ 输出示例:
排序前: 64 34 25 12 22 11 90 排序后: 11 12 22 25 34 64 90
✅ 时间复杂度分析:
最坏情况(完全倒序): O(n²)
最好情况(已经有序): O(n)(通过
swapped优化可以实现)平均情况: O(n²)
✅ 空间复杂度:
O(1),是原地排序算法。
✅ 稳定性:
冒泡排序是稳定排序算法,即相等元素的相对顺序不会改变。
如果你还想看其他排序算法(如快速排序、归并排序、插入排序等)的 Java 实现,我也可以继续为你提供! 2025-08-02T09:48:22.174+08:00 INFO 31464 --- [ main] com.guochang.chanaiagent.app.AiCodeApp : content:你之前的提问是:
“请帮我写出冒泡排序”
我当时已经为你详细讲解并提供了 Java 实现的冒泡排序代码,包括算法原理、完整代码、运行结果、时间复杂度分析等内容。
如果你现在继续想学习其他算法题目或者排序、查找、动态规划、DFS/BFS、二分查找等内容,我非常乐意继续为你提供 Java 示例代码 + 详细讲解!
你可以这样开始你的新问题:
“请帮我写出快速排序的 Java 实现”
“如何用 Java 实现二分查找?”
“请讲讲动态规划,并给出一个经典例题”
“LeetCode 第 1 题两数之和的 Java 解法?”
随便你问,我随时在线为你解答 💪
是否现在开始?😊 当然可以!下面是一个使用 Java 编写的 冒泡排序(Bubble Sort) 算法的完整示例,并附有详细讲解。
✅ 冒泡排序简介:
冒泡排序是一种简单的排序算法。它重复地遍历要排序的列表,比较相邻的两个元素,如果顺序错误(如前一个比后一个大),就交换他们,直到没有需要交换的元素为止。
每一轮遍历会把最大的元素“冒泡”到末尾。
✅ Java代码实现:
public class BubbleSort { // 冒泡排序方法 public static void bubbleSort(int[] arr) { int n = arr.length; boolean swapped; // 外层循环控制轮数(共 n-1 轮) for (int i = 0; i < n - 1; i++) { swapped = false; // 优化:如果某一轮没有发生交换,说明已经有序 // 内层循环控制每轮比较的次数(每次减少一个元素) for (int j = 0; j < n - 1 - i; j++) { // 如果前一个元素比后一个大,则交换 if (arr[j] > arr[j + 1]) { // 交换 arr[j] 和 arr[j+1] int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; swapped = true; } } // 如果本轮没有发生交换,提前结束排序 if (!swapped) break; } } // 测试代码 public static void main(String[] args) { int[] nums = {64, 34, 25, 12, 22, 11, 90}; System.out.println("排序前:"); printArray(nums); bubbleSort(nums); System.out.println("排序后:"); printArray(nums); } // 打印数组的方法 private static void printArray(int[] arr) { for (int num : arr) { System.out.print(num + " "); } System.out.println(); } }
✅ 输出示例:
排序前: 64 34 25 12 22 11 90 排序后: 11 12 22 25 34 64 90
✅ 时间复杂度分析:
最坏情况(完全倒序): O(n²)
最好情况(已经有序): O(n)(通过
swapped优化可以实现)平均情况: O(n²)
✅ 空间复杂度:
O(1),是原地排序算法。
✅ 稳定性:
冒泡排序是稳定排序算法,即相等元素的相对顺序不会改变。
如果你还想看其他排序算法(如快速排序、归并排序、插入排序等)的 Java 实现,我也可以继续为你提供!
你之前的提问是:
“请帮我写出冒泡排序”
我当时已经为你详细讲解并提供了 Java 实现的冒泡排序代码,包括算法原理、完整代码、运行结果、时间复杂度分析等内容。
如果你现在继续想学习其他算法题目或者排序、查找、动态规划、DFS/BFS、二分查找等内容,我非常乐意继续为你提供 Java 示例代码 + 详细讲解!
你可以这样开始你的新问题:
“请帮我写出快速排序的 Java 实现”
“如何用 Java 实现二分查找?”
“请讲讲动态规划,并给出一个经典例题”
“LeetCode 第 1 题两数之和的 Java 解法?”
随便你问,我随时在线为你解答 💪
是否现在开始?😊
可见,AI仍然记得我上一个问题问的问题,实现了多轮对话记忆功能,大功告成!
更多推荐
所有评论(0)