Spring AI:基于 “助手角色” 消息实现聊天记忆功能 && 提示词模板
Spring AI:基于“助手角色”消息实现聊天记录功能,已经提示词模板介绍
Spring AI 历史文章列表
Spring AI:对接DeepSeek实战
Spring AI:对接官方 DeepSeek-R1 模型 —— 实现推理效果
Spring AI:ChatClient实现对话效果
Spring AI:使用 Advisor 组件 - 打印请求大模型出入参日志
Spring AI:ChatMemory 实现聊天记忆功能
Spring AI:本地安装 Ollama 并运行 Qwen3 模型
Spring AI:提示词工程
Spring AI:提示词工程 - Prompt 角色分类(系统角色与用户角色)
在之前的文章中,我们提到了,“助手角色” 消息对于维持对话的连贯性至关重要,也就是聊天记忆功能。之前文章中,我们通过 Spring AI 内置的 ChatMemory 来实现的,那假设我们自己手动来实现,要怎么做呢?本文,我们就来讲一讲底层的实现逻辑。
定义存储聊天对话容器
现在,我们已经知道了,AI 大模型是没有记忆功能的! 要想后续的对话中,AI 大模型能够记得历史对话内容,就需要每次都将之前的 “聊天记录” 发送给它。
编辑 Controller 控制器,定义一个 Map 字典,键为 “对话的唯一标识”,用于归类不同的对话;值为 List 集合,存储不同角色的消息,如用户角色消息、助理角色消息等等。代码如下:
@RestController
@RequestMapping("/v1/ai")
public class DeepSeekR1ChatController {
// 省略...
// 存储聊天对话
private Map<String, List<Message>> chatMemoryStore = new ConcurrentHashMap<>();
// 省略...
}
存储不同角色 Message
@RestController
@RequestMapping("/v1/ai")
public class DeepSeekR1ChatController {
// 省略...
/**
* 普通对话
* @param message
* @return
*/
@GetMapping("/generate")
public String generate(@RequestParam(value = "message", defaultValue = "你是谁?") String message,
@RequestParam(value = "chatId") String chatId) {
// 根据 chatId 获取对话记录
List<Message> messages = chatMemoryStore.get(chatId);
// 若不存在,则初始化一份
if (CollectionUtils.isEmpty(messages)) {
messages = new ArrayList<>();
chatMemoryStore.put(chatId, messages);
}
// 添加 “用户角色消息” 到聊天记录中
messages.add(new UserMessage(message));
// 构建提示词
Prompt prompt = new Prompt(messages);
// 一次性返回结果
String responseText = chatModel.call(prompt).getResult().getOutput().getText();
// 添加 “助手角色消息” 到聊天记录中
messages.add(new AssistantMessage(responseText));
return responseText;
}
// 省略...
}
解释一下上述代码:
- 接口入参中,添加 chatId 字段,用于区分不同对话;
- 通过 chatId 从 chatMemoryStore 字典中,获取对应的聊天记录 List 集合;
- 判断记录 List 集合是否存在,若不存在,则进行初始化操作,并放到 chatMemoryStore 字典中;
- 接着,先新建一个 UserMessage 用户角色消息,将用户提问的内容,添加到 chatMemoryStore 字典中,存储聊天记录;
- 构建提示词,并调用 AI 大模型;
- 拿到响应结果后,再新建一个 AssistantMessage 助手角色消息,同样保存到 chatMemoryStore 字典中;
- 返回响应结果;
- 这样,每次的对话内容,都会被保存起来,每次调用 AI 大模型时,再统一发送给大模型,从而实现了聊天记忆功能;
重启后端项目,浏览器请求地址如下,第一次对话内容为 “让 AI 大模型扮演一个客服”,记得携带上对话的唯一标识,这里指定 chatId 为 1:
http://localhost:8080/v1/ai/generate?message=请你扮演一个智能客服&chatId=1
接着,进行第二次对话,问 AI 大模型 “你是谁” , 看看它是否记得之前的会话内容,如下:
http://localhost:8080/v1/ai/generate?message=你是谁&chatId=1
什么是提示词模板?
“AI 提示词模板” 是专门为指导人工智能(尤其是大语言模型)生成所需输出而设计的结构化提示框架。它本质上是一个 “配方” 或 “蓝图”,包含了固定的指令、格式要求、背景信息以及一些可替换的占位符(变量),如 {}。
你可以把它理解为 “给 AI 的填空题模板”,目的是让用户无需每次都从头编写复杂的提示词,只需填写关键信息,就能高效、稳定地获得符合预期的结果
提示词模板组成
提示词模板的组成,一般分为两个部分:
1、固定部分(核心指令与框架):
- 角色定义: 明确要求 AI 扮演什么角色(例如:“你是一位资深营养师”、“你是一位代码优化专家”)。
- 核心任务: 清晰说明需要 AI 做什么(例如:“撰写一篇关于…的文章”、“总结以下文本的核心观点”、“将以下代码从 Python 转换为 Java”)。
- 输出格式要求: 严格规定输出的结构和样式(例如:“使用 Markdown 格式”、“用表格呈现”、“分点列出”、“包含标题和摘要”)。
- 风格与语气: 指定所需的写作风格(例如:“专业严谨”、“通俗易懂”、“幽默风趣”、“正式商务口吻”)。
- 背景信息/上下文: 提供必要的背景知识或限制条件(例如:“目标受众是 10-15 岁的青少年”、“这是一份给公司高管的报告”、“请避免使用专业术语”)。
- 思考过程/步骤(可选): 引导 AI 按照特定逻辑或步骤思考(例如:“第一步,分析问题关键点;第二步,列举解决方案;第三步,评估优- 缺点;第四步,给出最终建议”)。
- 示例(Few-Shot Learning,可选): 提供几个输入-输出的例子,让 AI 更清楚地理解任务模式
2、占位符/变量(用户填充部分):
- 用特定的符号(如 {}, [], {{变量名}}, $变量名)标记出来。
- 代表每次使用时需要用户或系统动态填入的具体内容。
- 例如:[主题]、[目标语言]、[产品名称]、[客户姓名]、[具体数据] 等。
为什么需要提示词模板?
使用提示词模板的好处如下:
-
大幅提升效率: 避免重复编写复杂指令,尤其适合批量处理相似任务(如生成多个产品描述、分析多份报告)。
-
保证输出一致性与质量: 确保每次针对同类任务的提示都包含所有必要的关键指令和格式要求,减少输出结果的随机性,提升可靠性。
-
降低使用门槛: 让不擅长编写精细提示词的用户也能轻松利用 AI 的强大功能,只需填写关键信息。
-
便于优化和复用: 一个好的模板可以被保存、共享、并在后续不断迭代优化(调整固定指令部分),效果提升惠及所有使用该模板的人。
-
促进团队协作与标准化: 团队内部可以共享统一的模板,确保大家使用相同的标准和方式与 AI 交互,产出格式统一的内容。
-
解锁复杂任务: 精心设计的模板可以引导 AI 完成多步骤、需要特定逻辑推理或格式化的复杂任务。
Spring AI 定义提示词模板
接下来,我们准备在 Spring AI 中上手感受一下,如何定义一个提示词模板。先提个需求:假设我们想要基于 AI 大模型,开发一个 “智能代码生成器” 工具,用户只需简单的填写功能描述,以及生成的目标语言即可。
在 /controller 包下,创建一个 PromptTemplateController 控制器:
@RestController
@RequestMapping("/v7/ai")
public class PromptTemplateController {
@Resource
private DeepSeekChatModel chatModel;
/**
* 智能代码生成
* @param message
* @param lang
* @return
*/
@GetMapping(value = "/generateStream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<AIResponse> generateStream(@RequestParam(value = "message") String message,
@RequestParam(value = "lang") String lang) {
// 提示词模板
String template = """
你是一位资深 {lang} 开发工程师。请严格遵循以下要求编写代码:
1. 功能描述:{description}
2. 代码需包含详细注释
3. 使用业界最佳实践
""";
PromptTemplate promptTemplate = new PromptTemplate(template);
// 填充提示词占位符,转换为 Prompt 提示词对象
Prompt prompt = promptTemplate.create(Map.of("description", message, "lang", lang));
// 流式输出
return chatModel.stream(prompt)
.mapNotNull(chatResponse -> {
Generation generation = chatResponse.getResult();
String text = generation.getOutput().getText();
return AIResponse.builder().v(text).build();
});
}
}
解释一下上述代码:
- 定义一个 PromptTemplate 提示词模板类,提示词部分,这里只是简单写一下(可以优化的更细致),包含设置一个角色,以及相关要求。然后,通过占位符 {}, 将用户输入的动态内容,单独提取出来;
- 接口入参,支持接收两个字段,message 表示用户填写的 “功能描述”;lang 表示用户想要生成的 “目标语言”;
- 通过 promptTemplate.create() 方法,将用户输入的内容,填充到提示词模板中占位符中;
- 调用 AI 大模型,响应结果;
重启项目调用接口测试
从文件中读取提示词
在实际项目中,可能会定义非常多的提示词模板,如果都以 “硬编码” 的方式,直接写在代码中,非常不利于维护。为了解决这个问题,我们可以将提示词模板单独放到一个文件中。
接下来,准备上手重构一下上述代码。在 /resources 资源目录下,创建一个 /prompts 文件夹,用于统一放置提示词模板。并在里面新建一个 code-assistant.st 文件:
你是一位资深 {lang} 开发工程师。请严格遵循以下要求编写代码:
1. 功能描述:{description}
2. 代码需包含详细注释
3. 使用业界最佳实践
回到 PromptTemplateController 控制器中,重构一下代码,改为从 code-assistant.st 中读取提示词模板:
@RestController
@RequestMapping("/v7/ai")
public class PromptTemplateController {
@Resource
private DeepSeekChatModel chatModel;
@Value("classpath:/prompts/code-assistant.st")
private org.springframework.core.io.Resource templateResource;
/**
* 智能代码生成
* @param message
* @param lang
* @return
*/
@GetMapping(value = "/generateStream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<AIResponse> generateStream(@RequestParam(value = "message") String message,
@RequestParam(value = "lang") String lang) {
// 提示词模板
PromptTemplate promptTemplate = new PromptTemplate(templateResource);
// 省略...
}
}
重启项目调用接口测试
自定义占位符
默认情况下,Spring AI 中提示词模板中的占位符为 {} 花括号, 那么,如果想自定义占位符,要怎么做呢?比如想更改为 <> 尖括号。
编辑 PromptTemplateController 控制器,新增一个 /v7/ai/generateStream2 接口,代码如下:
@RestController
@RequestMapping("/v7/ai")
public class PromptTemplateController {
@Resource
private DeepSeekChatModel chatModel;
// 省略...
/**
* 智能代码生成 2
* @param message
* @param lang
* @return
*/
@GetMapping(value = "/generateStream2", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<AIResponse> generateStream2(@RequestParam(value = "message") String message,
@RequestParam(value = "lang") String lang) {
// 提示词模板
PromptTemplate promptTemplate = PromptTemplate.builder()
.renderer(StTemplateRenderer.builder().startDelimiterToken('<').endDelimiterToken('>').build()) // 自定义占位符
.template("""
你是一位资深 <lang> 开发工程师。请严格遵循以下要求编写代码:
1. 功能描述:<description>
2. 代码需包含详细注释
3. 使用业界最佳实践
""")
.build();
// 填充提示词占位符,转换为 Prompt 提示词对象
Prompt prompt = promptTemplate.create(Map.of("description", message, "lang", lang));
// 流式输出
return chatModel.stream(prompt)
.mapNotNull(chatResponse -> {
Generation generation = chatResponse.getResult();
String text = generation.getOutput().getText();
return AIResponse.builder().v(text).build();
});
}
}
上述代码中,核心在于 .renderer(StTemplateRenderer.builder().startDelimiterToken('<').endDelimiterToken('>').build()) 这行代码,通过 字符串模板渲染器 StTemplateRenderer,设置了模板变量的起始分隔符为 <,结束分隔符为 >,其他部分和之前差不多,除了提示词模板中的占位符,从花括号 {} 修改为了 <> 尖括号。
设置多角色
前面的代码中,我们的提示词模板,都是针对 “用户角色消息” 来设置的,那 “系统角色消息”,是否也能够使用提示词模板呢?答案是肯定的。
编辑 PromptTemplateController , 新增一个 /v7/ai/generateStream3 接口,代码如下:
@RestController
@RequestMapping("/v7/ai")
public class PromptTemplateController {
@Resource
private DeepSeekChatModel chatModel;
// 省略...
/**
* 智能代码生成 3
* @param message
* @param lang
* @return
*/
@GetMapping(value = "/generateStream3", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<AIResponse> generateStream3(@RequestParam(value = "message") String message,
@RequestParam(value = "lang") String lang) {
// 系统角色提示词模板
String systemPrompt = """
你是一位资深 {lang} 开发工程师, 已经从业数十年,经验非常丰富。
""";
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemPrompt);
// 填充提示词占位符,并转换为 Message 对象
Message systemMessage = systemPromptTemplate.createMessage(Map.of("lang", lang));
// 用户角色提示词模板
String userPrompt = """
请严格遵循以下要求编写代码:
1. 功能描述:{description}
2. 代码需包含详细注释
3. 使用业界最佳实践
""";
PromptTemplate promptTemplate = new PromptTemplate(userPrompt);
// 填充提示词占位符,并转换为 Message 对象
Message userMessage = promptTemplate.createMessage(Map.of("description", message));
// 组合多角色消息,构建提示词 Prompt
Prompt prompt = new Prompt(List.of(systemMessage, userMessage));
// 流式输出
return chatModel.stream(prompt)
.mapNotNull(chatResponse -> {
Generation generation = chatResponse.getResult();
String text = generation.getOutput().getText();
return AIResponse.builder().v(text).build();
});
}
}
解释一下上述代码:
- 首先,编写提示词模板,将 “角色设置” 部分拆开,放到 “系统角色消息” 中;
- 通过 new SystemPromptTemplate() 来创建一个 “系统角色消息” 的提示词模板,填充占位符后,转换为 Message 对象;
- 接着,和之前一样,创建 “用户角色消息” 提示词模板,填充占位符后,转换为 Message 对象;
- 创建一个 Prompt 提示词对象,组合多种角色消息;
- 调用 AI 大模型,响应结果;
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)