Java转AI应用开发速成(3)—— 第一个 SpringAI 聊天应用

准备工作:

创建api-key,以deepSeek为例:DeepSeek开放平台

一、创建项目

Spring Boot 3.3+,JDK 21,添加依赖:

xml 复制代码
<!-- Spring AI  -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
        </dependency>

application.yml

yaml 复制代码
spring:
  application:
    name: java-tech-assistant

  ai:
    openai:
      api-key: ${DEEPSEEK_API_KEY:换成自己的key}
      base-url: https://api.deepseek.com
      chat:
        options:
          model: deepseek-chat
          temperature: 0.7

server:
  port: 8080
  servlet:
#    否则流式输出乱码
    encoding:
      charset: UTF-8
      enabled: true
      force: true

二、最简单的聊天接口

java 复制代码
@RestController
public class ChatController {

    private final ChatClient chatClient;

    public ChatController(ChatClient.Builder builder) {
        this.chatClient = builder
                .defaultSystem("你是一个专业的Java技术助手,回答简洁准确。")
                .build();
    }

    /**
     * 聊天接口 - 完整输出
     * GET /chat?message=你好
     */
    @GetMapping("/chat")
    public String chat(@RequestParam String message) {
        return chatClient.prompt()
                .user(message)
                .call().content();
    }
}

接口测试 测试:

AI 返回一连串 Java 八股文------你的第一个 AI 应用就跑通了。


三、加上流式输出(打字机效果)

上面那个是一口气吐完整段回复,没有 ChatGPT 那种逐字输出的效果。加流式输出:

java 复制代码
 /**
     * 流式聊天接口 - 逐字输出
     * GET /chat/stream?message=你好
     */
    @GetMapping(value = "/chat/stream", produces = "text/event-stream;charset=UTF-8")
    public Flux<String> streamChat(@RequestParam String message) {
        return chatClient.prompt()
                /**
                 * 创建一个 Prompt 对象,并设置用户输入的 message
                 */
                .user(message)
                .stream()
                .content();

    }

四、加上历史对话记忆

聊到第三句 AI 忘记了第一句?加上记忆:

java 复制代码
	public ChatController(ChatClient.Builder builder, ChatMemory chatMemory) {
        this.chatClient = builder
                .defaultSystem("你是一个专业的Java技术助手,回答简洁准确。")
                .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory))
                .build();
    }

    @GetMapping(value = "/chat/stream", produces = "text/event-stream;charset=UTF-8")
    public Flux<String> streamChat(
            @RequestParam String message,
            @RequestParam(defaultValue = "default") String sessionId) {
        return chatClient.prompt()
                .user(message)
                // 历史对话 
                .advisors(a -> a.param("chat_memory_conversation_id", sessionId))
                .stream()
                .content();
    }

核心机制是 Spring AI 的 MessageChatMemoryAdvisor:

数据流:

  • → Controller
  • → MessageChatMemoryAdvisor.aroundCall/aroundStream
    1. 从 InMemoryChatMemory 查 sessionId 对应的历史消息
    2. 将历史消息注入到 Prompt 的 messages 列表中
    3. 调用 LLM
    4. LLM 回复后,将本轮 User 消息 + Assistant 回复存入 InMemoryChatMemory
  • → 返回结果

一句话总结:

Advisor 拦截请求 → 从 Map<sessionId, 历史消息> 取出前文拼进 prompt → LLM 回复后把本轮问答追加回去。纯内存,默认保留最近 100 条。

五、加上页面测试

六. ChatClient API 速查表

1. 构建输入 --- .prompt()

方法 作用
.prompt() 创建请求入口,返回 PromptRequestSpec
.prompt(String text) 快捷方式,等价于 .prompt().user(text)

2. 组装消息 --- PromptRequestSpec

方法 说明
.user(String text) 用户消息
.system(String text) 系统角色设定
.messages(Message... msgs) 自定义消息列表
.messages(Consumer) 编程式追加消息
.options(ChatOptions ops) 模型参数(temperature、topP 等)
.advisors(Advisor... ads) 请求拦截器链(RAG、日志、对话记忆)
.advisors(Consumer) 编程式组装 advisor 链
.toolContext(Map) 传上下文给 advisor
.tools(Object... beans) 注册 Function Calling 工具

3. 发送 & 返回形式 --- PromptRequestSpec

方法 返回类型 用途
.call() ChatClientCallResponseSpec 同步、一次性返回完整结果
.stream() ChatClientStreamResponseSpec 流式、逐 token 输出
.entity(Class<T>) UserInputAdvisor 链 结构化提取(提前终止,不调 LLM)

4. 取结果

call() 的结果:

方法 返回
.chatResponse() ChatResponse(含 metadata、token 用量)
.content() String(纯文本,最常用)
.entity(Class<T>) 反序列化到实体
.entities(ParameterizedTypeRef<T>) 反序列化到泛型集合

stream() 的结果:

方法 返回
.chatResponse() Flux
.content() Flux(逐字推送文本)
.entity(Class<T>) Flux

付源码地址: https://gitee.com/menglinjie/hello-ai

相关推荐
罗超驿5 小时前
9.LeetCode 209. 长度最小的子数组 | 滑动窗口专题详解
java·算法·leetcode·面试
Simon523146 小时前
Spring AOP 五大通知类型
java·前端·spring
小村儿6 小时前
连载11- Claude code 的 Agent Teams——当子 Agent 开始互相说话
前端·后端·ai编程
早睡身体真不戳6 小时前
【无标题】
java·服务器·windows
布吉岛的石头6 小时前
Java 程序员第 38 阶段:Embedding 向量缓存实战,减少重复向量化计算开销
java·缓存·embedding
Circ.6 小时前
Java 远程调用 NX 11 完整实战:参数读取、修改、STP 文件导出(附环境配置 + 源码)
java·开发语言·nx11
2401_833269306 小时前
【无标题】
java·开发语言
宇宙realman_9996 小时前
420B污染度等级查询代码
java·开发语言·算法
折哥的程序人生 · 物流技术专研6 小时前
《Java 100 天进阶之路》第35篇:Java异常处理最佳实践
java·开发语言·后端·面试·求职招聘