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

    • 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 实践
    • 最佳实践与面试考点汇总

小崔敲下 mvn spring-boot:run,浏览器打开 localhost:8080/chat?message=你好,屏幕上出现了 AI 的回复。"成了!"他兴奋地喊道。

第一个 AI 对话应用

定义与作用

本节带你从零构建第一个 Spring AI 对话应用,展示两种调用方式:

  • 非流式 call():等待完整回复后一次性返回
  • 流式 stream():逐 Token 实时推送,适合聊天场景

核心原理:调用链路

图释:用户请求经过 Controller → ChatClient → ChatModel → LLM 四层传递。ChatClient 负责 Fluent API 和 Advisor 链,ChatModel 负责与 LLM 通信。

完整示例一:非流式调用

场景说明

飞翔科技学生管理系统的第一个 AI 功能——课程智能问答。学生输入问题,AI 给出回答。小崔先做一个最简单的非流式版本。

依赖引入与配置

pom.xml 已按第 1 章配置,application.yml:

spring:
  ai:
    ollama:
      base-url: http://localhost:11434
      chat:
        options:
          model: llama3
          temperature: 0.7

关键代码

package com.feixiang.student;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
public class StudentAiApplication {

    public static void main(String[] args) {
        SpringApplication.run(StudentAiApplication.class, args);
    }

    // 创建 ChatClient Bean
    @Bean
    public ChatClient chatClient(ChatClient.Builder builder) {
        return builder
                .defaultSystem("你是飞翔科技大学的学生助手,回答简洁、专业。")
                .build();
    }
}

@RestController
class ChatController {

    private final ChatClient chatClient;

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

    @GetMapping("/chat")
    public String chat(@RequestParam(defaultValue = "你好") String message) {
        return chatClient.prompt()
                .user(message)
                .call()
                .content();   // 直接获取文本内容
    }
}

运行结果及分析

# 启动应用
mvn spring-boot:run

# 终端调用
curl "http://localhost:8080/chat?message=计算机科学专业有哪些必修课"

AI 回复示例:

飞翔科技大学计算机科学专业的必修课通常包括:数据结构与算法、操作系统、
计算机网络、数据库原理、编译原理、计算机组成原理等核心课程。
具体课程安排请参考各年级培养方案。

完整示例二:流式调用(SSE)

场景说明

孔蓝(产品经理)提需求:"AI 回答太慢了,学生等得不耐烦。要像 ChatGPT 那样一个字一个字往外蹦!"

关键代码

@RestController
class StreamChatController {

    private final ChatClient chatClient;

    public StreamChatController(ChatClient chatClient) {
        this.chatClient = chatClient;
    }

    // 流式调用:返回 Server-Sent Events
    @GetMapping(value = "/chat/stream", produces = "text/event-stream")
    public Flux<String> streamChat(@RequestParam(defaultValue = "你好") String message) {
        return chatClient.prompt()
                .user(message)
                .stream()                         // 流式调用
                .content();                       // 逐 Token 推送
    }
}

运行结果及分析

curl -N "http://localhost:8080/chat/stream?message=介绍飞翔科技大学"

SSE 输出(逐 Token):

data:飞翔
data:科技
data:大学
data:成立于
data:2018
data:年
data:...

流式 vs 非流式对比:

维度call()stream()
返回类型StringFlux<String>
响应方式一次性返回全文逐 Token 推送
首字延迟高(等全文生成完)低(首个 Token 即返回)
适用场景API 调用、批量处理聊天界面、实时交互
Content-Type任意text/event-stream

操作前后对比:直接 curl OpenAI API vs Spring AI ChatClient

操作前:直接调用 OpenAI API

# 手动构造 JSON、手动管理 HTTP 连接
curl https://api.openai.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -d '{
    "model": "gpt-4o",
    "messages": [
      {"role": "system", "content": "你是飞翔科技大学的学生助手"},
      {"role": "user", "content": "计算机科学专业有哪些必修课"}
    ]
  }'

返回原始 JSON:

{
  "id": "chatcmpl-xxx",
  "object": "chat.completion",
  "choices": [{
    "message": {
      "role": "assistant",
      "content": "飞翔科技大学计算机科学专业的必修课包括..."
    }
  }],
  "usage": {
    "prompt_tokens": 45,
    "completion_tokens": 120,
    "total_tokens": 165
  }
}

操作后:Spring AI ChatClient

// 一行 Fluent API,无需手动管理 JSON 和 HTTP
String answer = chatClient.prompt()
        .user("计算机科学专业有哪些必修课")
        .call()
        .content();

改进总结:

维度直接 curl APISpring AI ChatClient
HTTP 管理手动构造 URL 和 Header自动配置
JSON 拼装手动拼接 JSON 字符串自动序列化
响应解析从 JSON 中提取 choices[0].message.content.content() 一行搞定
模型切换改 URL 和参数改 YAML 配置
异常处理手动判断 HTTP 状态码自动抛出 Spring 异常体系
流式调用手动解析 SSE.stream().content() 返回 Flux

易错场景与面试考点

易错场景一:忘记调用 .call() 或 .content() 导致空指针

// ❌ 错误:prompt().user() 返回的是 ChatClientRequestSpec,不是 String
@GetMapping("/chat")
public String chat(@RequestParam String message) {
    return chatClient.prompt()
            .user(message)
            .toString();  // toString() 返回对象描述,不是 AI 回复!
}

问题分析

Fluent API 每个阶段返回不同类型:

  • .prompt() → ChatClientRequestSpec
  • .user() → ChatClientRequestSpec(继续配置)
  • .call() → ChatClientCallResponseSpec
  • .content() → String
// ✅ 正确:必须完整走完链
return chatClient.prompt()
        .user(message)
        .call()
        .content();

易错场景二:流式调用未设置 produces

// ❌ 错误:流式调用但未设置 Content-Type
@GetMapping("/chat/stream")
public Flux<String> streamChat(@RequestParam String message) {
    return chatClient.prompt().user(message).stream().content();
}

问题分析

浏览器和前端框架依赖 Content-Type: text/event-stream 来识别 SSE 流。不设置 produces 时默认返回 application/json,前端无法正确解析流式数据。

// ✅ 正确:显式设置 produces
@GetMapping(value = "/chat/stream", produces = "text/event-stream")
public Flux<String> streamChat(@RequestParam String message) {
    return chatClient.prompt().user(message).stream().content();
}

面试高频题

Q1:ChatClient 的 call() 和 stream() 有什么区别?

call() 阻塞等待完整回复后返回 ChatClientCallResponseSpec,stream() 返回 Flux<ChatResponse> 逐 Token 推送。前者适合 API 调用场景,后者适合聊天 UI 实时展示。

Q2:Spring AI 相比直接调用 OpenAI API 的优势是什么?

①统一抽象:切换模型只需改 YAML,代码不变;②Fluent API:链式调用简洁直观;③自动配置:无需手动管理 HTTP 连接;④Advisor 链:可插入日志、记忆、RAG 等拦截逻辑;⑤Spring 生态集成:天然支持依赖注入、事务管理、可观察性。

本章小结

  • 最小应用只需 3 步:创建 ChatClient Bean → Controller 注入 → prompt().user().call().content()
  • 非流式 call() 返回完整文本,流式 stream() 返回 Flux<String>
  • 流式调用必须设置 produces = "text/event-stream"
  • Spring AI 最直接的价值:用 1 行 Fluent API 替代 20 行手动 HTTP + JSON 拼装
上一页
环境准备与配置