Spring AI 实战:聊天、提示词、记忆三件套

从"说一句话"开始

Spring AI 里和大模型对话的最小单元叫 ChatClient。上一篇文章讲过,它屏蔽了底层模型差异,你只跟它打交道:

java 复制代码
@RestController
public class ChatController {

    private final ChatClient chatClient;

    public ChatController(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    @PostMapping("/chat")
    public Map<String, String> chat(@RequestBody ChatRequest request) {
        String reply = chatClient.prompt()
                .user(request.getMessage())
                .call()
                .content();
        return Map.of("reply", reply);
    }
}

发个 POST:

json 复制代码
{ "message": "用一句话解释什么是 Spring AI" }

返回:

json 复制代码
{ "reply": "Spring AI 是 Spring 生态中统一接入大语言模型能力的框架。" }

到这你已经能跟大模型对话了。但光这样不够------大模型没有"人设",也没有记忆力。

提示词:给模型一个人设

没有提示词的大模型像个百科全书机器人,什么问题都能答,但毫无性格。提示词(Prompt)就是给模型划一个"角色框"------你是什么身份、什么语气、什么不该说。

Spring AI 提供了 PromptTemplate 来管理提示词,支持占位符替换:

java 复制代码
@Service
public class AssistantService {

    private final ChatClient chatClient;

    // 提示词模板,用 Resource 从外部文件加载
    @Value("classpath:/prompts/assistant.st")
    private Resource promptResource;

    public AssistantService(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    public String chat(String userName, String message) {
        return chatClient.prompt()
                .system(spec -> spec
                    .text(promptResource)
                    .param("name", userName)
                )
                .user(message)
                .call()
                .content();
    }
}

prompts/assistant.st 文件内容(StringTemplate 语法):

复制代码
你是一位名叫小海的技术助手,性格温和、解释耐心。
当前对话的用户叫 {name},请用对方的名字称呼。
回答时遵循以下规则:
1. 涉及代码的问题,给出完整可运行的示例
2. 不猜测用户的技术水平,用对方能听懂的方式解释
3. 如果遇到不确定的问题,诚实说明而不是编造答案

提示词单独放文件而不是硬编码在代码里,好处很明显:改人设不用重新编译部署。

记忆:让模型"记住"你们聊过什么

大模型本身是无状态的。你让它"继续刚才的话题",它一脸懵,因为它不记得上一轮说了什么。

解决办法是把历史对话作为上下文每次一起发给模型。Spring AI 用 ChatMemory 接口来管理这件事:

java 复制代码
@Configuration
public class ChatConfig {

    @Bean
    public ChatMemory chatMemory() {
        // InMemory 存内存,生产环境可以换成 Redis/JDBC
        return new InMemoryChatMemory();
    }
}

然后在对话时注入历史消息:

java 复制代码
@Service
public class MemoryChatService {

    private final ChatClient chatClient;
    private final ChatMemory chatMemory;

    public MemoryChatService(ChatClient.Builder builder, ChatMemory chatMemory) {
        this.chatClient = builder.build();
        this.chatMemory = chatMemory;
    }

    public String chat(String conversationId, String userMessage) {
        // MessageChatMemoryAdvisor 自动管理消息存取,无需手动 add
        return chatClient.prompt()
                .advisors(new MessageChatMemoryAdvisor(chatMemory, conversationId, 20))
                .user(userMessage)
                .call()
                .content();
    }
}

关键角色是 MessageChatMemoryAdvisor。它会在每次请求前自动从 ChatMemory 拉这个会话的历史消息,拼到 prompt 里一起发给模型。

效果好到什么程度?看这段多轮对话:

复制代码
用户:我叫张三,我是 Java 后端开发
助手:好的张三,记下了。有什么 Java 相关的问题随时问我。

用户:今天工作中遇到一个 NullPointerException,排查了很久
助手:张三,NPE 确实是 Java 开发的老朋友了。能说说具体是哪个对象为空吗?

用户:就是上次那个订单查询的接口
助手:明白了,之前你提到的订单查询接口。建议从 Service 层返回的 Optional 开始排查......

第三轮"上次那个"------模型知道你在说什么,因为它看到了前两轮的对话上下文。这就是记忆的价值。

一个注意点:记忆不是无限的

大模型有上下文窗口限制(比如 128K tokens),你把 10 轮、20 轮、100 轮历史全塞进去会爆。MessageChatMemoryAdvisor 默认保留最近 20 条消息,可以根据场景缩小:

java 复制代码
new MessageChatMemoryAdvisor(chatMemory, "default", 10); // 缩小到最近 10 条

更高级的做法是"摘要记忆"------当对话太长时,用模型自己对历史做摘要压缩,然后只存摘要。Spring AI 目前对这块的支持还在完善中,但基本思路是公开的了。

另外注意,InMemoryChatMemory 是在内存中无限累积的,生产环境长时间运行会撑爆 JVM。底层存储换成 Redis 或 JDBC 实现,再配合定时任务清理过期会话即可。

三者拼起来:一个完整的聊天服务

把提示词、记忆、对话串起来,就是一个能用的 AI 聊天系统:

java 复制代码
@RestController
@RequestMapping("/api/chat")
public class SmartChatController {

    private final ChatClient chatClient;
    private final ChatMemory chatMemory;
    private final Resource systemPrompt;

    public SmartChatController(
            ChatClient.Builder builder,
            ChatMemory chatMemory,
            @Value("classpath:/prompts/assistant.st") Resource systemPrompt) {
        this.chatClient = builder.build();
        this.chatMemory = chatMemory;
        this.systemPrompt = systemPrompt;
    }

    @PostMapping("/{sessionId}")
    public ChatResponse chat(
            @PathVariable String sessionId,
            @RequestBody ChatRequest request) {

        String reply = chatClient.prompt()
                .system(s -> s.text(systemPrompt))
                .advisors(new MessageChatMemoryAdvisor(chatMemory, sessionId, 20))
                .user(request.getMessage())
                .call()
                .content();

        return new ChatResponse(reply, sessionId);
    }
}

sessionId 区分不同用户/会话,每人一段独立的记忆空间。

收尾

聊天、提示词、记忆------这三个是构建 AI 对话系统的骨架。Spring AI 把它们包装得足够简单,你不用关心 token 怎么算、上下文怎么拼接、对话怎么管理。你只管定义人设、写业务逻辑,剩下的框架兜底。

下一步可以玩的方向:把记忆换成 Redis 实现(多实例共享)、接入 Tool Calling 让 AI 能查数据库、或者加入 RAG 让 AI 读你的文档回答。每个方向都不需要推翻现有代码------这就是 Portable API 的好处。

相关推荐
树谷-胡老师几秒前
MaxENT生态位模型的物种适生区预测(全球尺度+中国尺度+省级尺度+市县尺度+保护区尺度)超详细教程!
人工智能
笨蛋不要掉眼泪4 分钟前
Java虚拟机:堆的参数配置
java·开发语言·jvm
SEO_juper5 分钟前
2026_GEO_AI搜索技术实战_CSDN
人工智能·chrome·目标检测·seo·geo·谷歌优化
霸道流氓气质6 分钟前
SpringBoot中使用JasperReports 报表引擎 — 介绍、原理与使用实践
java·spring boot·后端
暴躁的大熊6 分钟前
机器学习中的知识图谱:概念、核心技术与应用场景
人工智能·机器学习·知识图谱
Pokerhead8 分钟前
如何评价 DeepSeek-V4 的价格?
人工智能·大模型·ai编程·deepseek
aiqianji11 分钟前
有哪些稳定的AI短篇小说写作软件可以推荐?
人工智能·python
花生了什么事o12 分钟前
DDD 分层架构:六层分层架构
java·架构·ddd
蓝速科技12 分钟前
蓝速科技丨AI双屏翻译机:重塑线下接待的从容沟通体验
人工智能·科技
Nomarsgo13 分钟前
研华17寸工业显示器 FPM-217在智能制造产线中的应用方案
人工智能·科技·计算机视觉·视觉检测·电脑