第1章:Spring AI 概述
1.1 什么是 Spring AI
| 概念名称 | 说明 | 注意事项 |
|---|
| Spring AI | Spring AI 是 Spring 生态系统中的一个项目,旨在为 Java 和 JVM 开发者提供一个简洁、一致的 API 来集成人工智能(AI)功能,特别是大语言模型(LLM)和生成式 AI 能力。 | Spring AI 并不训练模型,而是作为与各种 AI 平台(如 OpenAI、Azure、Bedrock 等)交互的抽象层。 |
| 核心定位 | 提供统一的编程模型,屏蔽底层 AI 服务的差异,使开发者能够以声明式、非侵入的方式集成 AI 功能。 | 开发者无需深入理解不同 AI 平台的 SDK 细节,即可实现跨平台迁移。 |
| 抽象层 | 通过接口(如 AiClient、ChatClient)封装 AI 调用,支持同步、异步、流式响应等多种模式。 | 所有调用最终由具体的平台实现(如 OpenAI 实现)完成。 |
1.2 Spring AI 的设计目标与核心理念
| 概念名称 | 说明 | 注意事项 |
|---|
| 简化集成 | 降低 Java 开发者使用 AI 服务的门槛,避免直接操作 REST API 或复杂 SDK。 | 设计目标是”开箱即用”,减少样板代码。 |
| 统一 API | 提供一致的接口抽象,无论后端是 OpenAI、Azure 还是本地 Ollama 模型。 | 更换 AI 提供商时,只需修改配置,无需重写业务逻辑。 |
| 模块化设计 | 功能按模块划分(如文本生成、聊天、嵌入、函数调用),便于按需引入。 | 可单独使用 spring-ai-chat-spring-boot-starter 而不引入文本生成模块。 |
| 与 Spring 生态融合 | 深度集成 Spring Boot、Spring Boot Actuator、Spring Security 等组件。 | 支持自动配置、依赖注入、外部化配置等 Spring 特性。 |
| 可观测性 | 内建对日志、监控、追踪的支持,便于生产环境调试与优化。 | 可结合 Micrometer、OpenTelemetry 实现调用链追踪。 |
1.3 Spring AI 与其他 AI 框架的对比
| 对比维度 | Spring AI | LangChain4j | Spring Integration AI | 原生 OpenAI SDK |
|---|
| 所属生态 | Spring 官方项目,深度集成 Spring Boot | 第三方 Java 库,受 LangChain 启发 | Spring 生态的扩展,偏消息集成 | OpenAI 官方提供的 Java 客户端 |
| 抽象层次 | 高层抽象,统一 API,支持多平台 | 中高层抽象,功能丰富,支持工具调用 | 中层集成,偏重消息流处理 | 低层 REST API 封装,直接映射 OpenAI 接口 |
| 多平台支持 | 原生支持 OpenAI、Azure、Bedrock、Vertex、Ollama 等 | 主要支持 OpenAI,扩展需手动实现 | 通过适配器支持多种服务 | 仅支持 OpenAI |
| 学习曲线 | 对 Spring 开发者极低,配置即用 | 中等,需理解其链式结构 | 中等,需熟悉 Spring Integration 模型 | 低,但需处理认证、重试、序列化等细节 |
| 社区与维护 | Spring 官方维护,长期支持预期高 | 社区驱动,更新活跃 | Spring 生态支持,但 AI 功能较新 | OpenAI 官方维护 |
| 适用场景 | Spring 项目中快速集成 AI 功能 | Java 项目中构建复杂 AI 应用链 | 已使用 Spring Integration 的系统集成 AI | 需要精细控制 OpenAI 调用的场景 |
1.4 Spring AI 的生态系统与模块结构
| 模块名称 | 说明 | 注意事项 |
|---|
| spring-ai-core | 核心模块,定义 AiClient、ChatClient、Prompt、Message 等通用接口与抽象类。 | 所有其他模块的依赖基础,必须引入。 |
| spring-ai-openai-spring-boot-starter | OpenAI 平台的自动配置与实现模块,支持文本生成、聊天、嵌入等。 | 需配置 spring.ai.openai.api-key。 |
| spring-ai-azure-openai-spring-boot-starter | Azure OpenAI 服务的集成模块,支持部署模型、密钥认证等企业特性。 | 适用于在 Azure 云环境中部署的应用。 |
| spring-ai-amazon-bedrock-spring-boot-starter | 集成 Amazon Bedrock,支持调用 Titan、Claude 等模型。 | 需 AWS 凭证配置,适合 AWS 生态用户。 |
| spring-ai-google-vertex-spring-boot-starter | 集成 Google Vertex AI,支持 PaLM 2、Gemini 等模型。 | 需 Google Cloud 凭证与项目配置。 |
| spring-ai-ollama-spring-boot-starter | 集成本地运行的 Ollama 服务,支持 Llama 3、Mistral 等开源模型。 | 适合本地开发、测试或隐私敏感场景。 |
| spring-ai-spring-boot-starter | 聚合启动器,包含核心功能与常用配置,简化依赖管理。 | 推荐初学者使用,可按需排除特定平台依赖。 |
第2章:环境搭建与快速入门
2.1 开发环境准备(JDK、Maven/Gradle、IDE)
| 环境项 | 要求说明 | 注意事项 |
|---|
| JDK 版本 | Spring AI 要求 JDK 17 或更高版本。 | 推荐使用 LTS 版本(如 JDK 17、21)。 |
| 构建工具 | 支持 Maven 3.5+ 或 Gradle 7.6+。 | Spring Boot 3.x 要求 Gradle 7.6+。 |
| IDE | 推荐使用 IntelliJ IDEA 或 Spring Tool Suite (STS)。 | 确保 IDE 支持 Spring Boot 和 Java 17+。 |
| 网络环境 | 需能访问所选 AI 平台(如 api.openai.com)。 | 若使用代理,需在 JVM 或系统中配置网络代理。 |
| AI 平台账号 | 如使用 OpenAI,需注册账号并获取 API Key。 | API Key 需妥善保管,避免硬编码在代码中。 |
2.2 创建第一个 Spring AI 项目(Spring Initializr 配置)
| 配置项 | 推荐值 | 注意事项 |
|---|
| Project | Maven / Gradle | 根据团队习惯选择。 |
| Language | Java | 当前主要支持语言。 |
| Spring Boot Version | 3.3.x 或更高 | Spring AI 需 Spring Boot 3.x。 |
| Group | com.example | 可自定义,遵循包命名规范。 |
| Artifact | spring-ai-demo | 项目名称。 |
| Packaging | Jar | 推荐使用可执行 JAR。 |
| Java Version | 17 | 必须 ≥17。 |
| Dependencies | Spring Web, Spring Configuration Processor | Web 用于构建 REST API,配置处理器提升 IDE 提示。 |
| 添加 Spring AI 支持 | 手动添加 spring-ai-spring-boot-starter 或具体平台 starter | Initializr 当前未直接提供 Spring AI 选项,需手动添加依赖。 |
2.3 引入 Spring AI 依赖(BOM 与 Starter)
| 依赖类型 | Maven 配置示例 | 说明 | 注意事项 |
|---|
| BOM(推荐) | 在 <dependencyManagement> 中添加 org.springframework.ai:spring-ai-bom:0.8.1(type=pom, scope=import) | 统一管理 Spring AI 所有模块的版本,避免版本冲突。 | 应放在 <dependencyManagement> 中。 |
| 核心启动器 | 添加 org.springframework.ai:spring-ai-spring-boot-starter 依赖 | 包含核心功能,自动配置基础 Bean。 | 适合快速启动。 |
| OpenAI 启动器 | 添加 org.springframework.ai:spring-ai-openai-spring-boot-starter 依赖 | 提供 OpenAI 模型的实现与自动配置。 | 需配置 API Key。 |
| Ollama 启动器(本地) | 添加 org.springframework.ai:spring-ai-ollama-spring-boot-starter 依赖 | 用于连接本地 Ollama 服务。 | 需提前启动 Ollama 并拉取模型(如 ollama run llama3)。 |
| 排除不需要的模块 | 在 <exclusions> 中排除特定 starter | 如仅用 Ollama,可排除 OpenAI starter 以减小包体积。 | 避免冲突或冗余依赖。 |
2.4 运行一个简单的 AI 调用示例
| 步骤 | 说明 | 示例代码 | 注意事项 |
|---|
| 1. 配置 AI 客户端 | 在 application.properties 中配置平台参数。 | spring.ai.openai.api-key=sk-xxx | API Key 不应提交到版本控制,使用环境变量替代。 |
| | spring.ai.openai.model=gpt-3.5-turbo | |
| 2. 注入 AiClient | 使用 @Autowired 注入 AiClient。 | @Autowired private AiClient aiClient; | AiClient 是 Spring 管理的 Bean。 |
| 3. 调用生成方法 | 调用 aiClient.generate() 方法。 | String response = aiClient.generate("Tell me a joke about programming."); | 默认返回纯文本响应。 |
| 4. 返回结果 | 获取生成的文本内容。 | System.out.println(response); | 实际应用中应通过 REST API 返回给前端。 |
| 完整示例类 | 创建一个简单的 REST 控制器。 | 见下方代码示例 | 启动应用后访问 /joke 即可看到 AI 生成的笑话。 |
完整示例代码:
@RestController
public class AiController {
@Autowired
private AiClient aiClient;
@GetMapping("/joke")
public String getJoke() {
return aiClient.generate("Tell me a joke about programming.");
}
}
第3章:核心抽象与编程模型
3.1 AiClient 接口概述
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| generate(String prompt) | String generate(String prompt) | 根据字符串提示词生成文本响应。 | String response = aiClient.generate("Explain quantum computing."); | 最简单调用方式,返回纯文本结果。 |
| generate(Prompt prompt) | Response<Generation> generate(Prompt prompt) | 使用 Prompt 对象进行更复杂的文本生成调用。 | Prompt prompt = new Prompt("Explain AI.", new Metadata());
Response<Generation> response = aiClient.generate(prompt); | 可携带元数据、消息历史等上下文信息。 |
| embed(String text) | List<Double> embed(String text) | 生成单个文本的嵌入向量(Embedding)。 | List<Double> embedding = aiClient.embed("Hello world"); | 返回浮点数列表,用于语义相似度计算。 |
| embed(List texts) | List<List<Double>> embed(List<String> texts) | 批量生成多个文本的嵌入向量。 | List<List<Double>> embeddings = aiClient.embed(Arrays.asList("Hi", "Bye")); | 提高批量处理效率,适用于文档索引场景。 |
说明:AiClient 是 Spring AI 中最基础的接口,用于执行文本生成、嵌入等通用 AI 操作。它屏蔽了底层模型平台的差异。
3.2 Prompt 与 PromptTemplate
| 概念/方法 | 语法 | 说明 | 代码示例 | 注意事项 |
|---|
| Prompt 构造函数 | new Prompt(String text) | 创建一个包含纯文本的提示对象。 | Prompt prompt = new Prompt("Tell me a story."); | 适用于简单场景。 |
| Prompt(带消息列表) | new Prompt(List<Message> messages) | 使用消息列表构建提示,支持多轮对话结构。 | List<Message> msgs = Arrays.asList(new UserMessage("Hello"), new SystemMessage("You are helpful."));
Prompt prompt = new Prompt(msgs); | 更接近聊天模型的输入格式。 |
| PromptTemplate 创建 | PromptTemplate.of(String template) | 从字符串模板创建提示模板。 | PromptTemplate pt = PromptTemplate.of("Tell me a joke about {topic}."); | {} 中为占位符,可被参数替换。 |
| 绑定参数 | promptTemplate.apply(Map<String, Object> variables) | 将变量映射应用到模板,生成 Prompt 对象。 | Map<String, Object> vars = Map.of("topic", "Java");
Prompt prompt = pt.apply(vars); | 支持复杂对象绑定,如 Map.of("user", userObj)。 |
| 消息类型支持 | new SystemMessage(...), new UserMessage(...), new AssistantMessage(...) | 定义不同角色的消息,影响模型行为。 | new SystemMessage("You are a poet.") | 系统消息通常用于设定角色或规则。 |
3.3 Response 与 Generation
| 类/属性 | 语法 | 说明 | 代码示例 | 注意事项 |
|---|
| Response<T> | interface Response<T> | 包装 AI 调用的响应结果,包含生成内容和元数据。 | Response<Generation> response = aiClient.generate(prompt); | 泛型 T 通常为 Generation 或其子类。 |
| Generation | class Generation | 表示一次生成的结果,包含文本内容。 | String content = response.getResult().getOutput().getContent(); | content 即模型返回的文本。 |
| 获取内容 | response.getResult().getOutput().getContent() | 从响应中提取生成的文本。 | 同上 | 链式调用需注意空指针风险。 |
| 元数据获取 | response.getMetadata() | 获取响应的附加信息,如 token 数量、模型名称等。 | Double tokenUsage = (Double) response.getMetadata().get("token.usage.total"); | 具体键名依赖于实现平台(如 OpenAI)。 |
| 多生成结果 | response.getResults() | 某些模型支持返回多个候选生成(n > 1)。 | List<Generation> gens = response.getResults(); | 需在 Prompt 中设置生成数量参数。 |
3.4 ChatClient 与 ChatResponse
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| call(String message) | <T> T call(String message) | 发送字符串消息并同步返回结果。 | String reply = chatClient.call("Hello!"); | 默认返回 String,可通过泛型指定返回类型。 |
| call(Message message) | <T> T call(Message message) | 发送 Message 对象(如 UserMessage)。 | UserMessage msg = new UserMessage("Explain LLMs.");
String reply = chatClient.call(msg); | 支持结构化消息传递。 |
| call(Prompt prompt) | <T> T call(Prompt prompt) | 发送完整的 Prompt(含多条消息)。 | Prompt prompt = new Prompt(Arrays.asList(new SystemMessage("..."), new UserMessage("...")));
ChatResponse response = chatClient.call(prompt); | 适用于复杂对话场景。 |
| stream(…) | Stream<T> stream(...) | 流式发送请求,返回 Stream 对象,支持逐块处理响应。 | chatClient.stream("Tell a long story").forEach(System.out::println); | 适用于长文本生成,提升用户体验。 |
| ChatResponse | interface ChatResponse | 聊天调用的响应接口,包含多个 ChatCompletionChoice。 | chatResponse.getResults().get(0).getOutput().getContent() | 结构与 Response<Generation> 类似但更丰富。 |
3.5 FunctionCalling 与工具调用支持
| 概念/方法 | 语法 | 说明 | 代码示例 | 注意事项 |
|---|
| @Tool 注解 | @Tool(description = "...") | 标记一个方法为可被 AI 调用的工具。 | @Tool(description = "Get current weather") public String getWeather(String city) { ... } | 方法必须是 Spring Bean 中的公共方法。 |
| 工具注册 | 自动扫描 @Tool 方法并注册 | Spring AI 自动将标注 @Tool 的 Bean 方法注册为可用工具。 | 无需手动注册,启动时自动完成。 | 确保类被 Spring 管理(如 @Component)。 |
| 函数调用触发 | AI 模型决定是否调用工具 | 当用户请求需要外部操作时,模型返回函数调用指令。 | 用户问:“北京天气如何?” → 模型返回调用 getWeather("Beijing") | 依赖模型支持函数调用能力(如 gpt-3.5-turbo-0613+)。 |
| 执行与回调 | 框架自动执行工具方法并将结果返回模型 | 工具执行后,返回值会作为上下文传回模型,由其生成最终回复。 | return "Sunny, 25°C"; → 模型生成:“北京今天晴朗,气温25°C。“ | 工具方法应快速响应,避免阻塞。 |
| 参数传递 | 支持基本类型、String、POJO | AI 解析用户意图并构造参数调用工具方法。 | public String getWeather(CityRequest cityReq) | POJO 需有默认构造函数和 getter/setter。 |
第4章:文本生成(Text Generation)
4.1 文本生成基本调用流程
| 步骤 | 说明 | 示例 | 注意事项 |
|---|
| 1. 创建 Prompt | 构建输入提示,可以是字符串或 Prompt 对象。 | "Summarize the following text: ..." | 提示质量直接影响输出效果。 |
| 2. 调用 AiClient | 使用 aiClient.generate() 方法发起请求。 | aiClient.generate(prompt) | 同步调用会阻塞线程。 |
| 3. 获取响应 | 从 Response<Generation> 中提取内容。 | response.getResult().getOutput().getContent() | 检查响应是否成功,避免空值。 |
| 4. 处理结果 | 将生成文本返回给前端或用于后续处理。 | return ResponseEntity.ok(content); | 可结合缓存、过滤等策略优化体验。 |
4.2 使用 AiClient 进行文本生成
| 方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| generate(String) | String generate(String prompt) | 快速生成文本,适合简单场景。 | String summary = aiClient.generate("Summarize: Spring AI simplifies AI integration."); | 不支持高级配置如温度、topP 等。 |
| generate(Prompt) | Response<Generation> generate(Prompt prompt) | 支持配置生成参数(通过 Metadata)。 | Prompt prompt = new Prompt("...", Metadata.builder().withTemperature(0.7).build()); | 可精细控制生成行为。 |
| 异常处理 | try-catch 包裹调用 | 处理网络错误、API 限流等异常。 | try { ... } catch (Exception e) { log.error("AI call failed", e); } | 生产环境必须添加容错机制。 |
4.3 提示词模板(PromptTemplate)的构建与参数绑定
| 方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| of(String template) | static PromptTemplate of(String template) | 从字符串创建模板。 | PromptTemplate pt = PromptTemplate.of("Write a {genre} story about {topic}."); | 占位符用 {} 包围。 |
| apply(Map vars) | Prompt apply(Map<String, Object> variables) | 应用变量生成 Prompt。 | Map<String, Object> vars = Map.of("genre", "sci-fi", "topic", "robots");
Prompt prompt = pt.apply(vars); | 变量名需与模板中一致。 |
| apply(Object pojo) | Prompt apply(Object object) | 使用 POJO 对象填充模板。 | prompt = pt.apply(new StoryRequest("fantasy", "dragons")); | POJO 属性名需匹配占位符。 |
| 消息模板 | new PromptTemplate(List<Message>) | 创建包含多角色消息的模板。 | new PromptTemplate(Arrays.asList(new SystemMessage("{role}"), new UserMessage("{query}"))) | 适用于聊天场景。 |
4.4 响应解析与元数据获取
| 属性/方法 | 语法 | 说明 | 代码示例 | 注意事项 |
|---|
| getContent() | generation.getContent() | 获取生成的文本内容。 | String text = response.getResult().getOutput().getContent(); | 主要输出结果。 |
| getFinishReason() | choice.getFinishReason() | 获取生成结束原因(如 “stop”, “length”)。 | (String) response.getMetadata().get("finish.reason") | 有助于判断是否完整生成。 |
| Token 使用量 | metadata.get("token.usage.total") | 获取总 token 消耗。 | Double totalTokens = (Double) response.getMetadata().get("token.usage.total"); | 用于成本监控和限流。 |
| 模型名称 | metadata.get("model.name") | 获取实际使用的模型名称。 | String model = (String) response.getMetadata().get("model.name"); | 验证配置是否生效。 |
| 原始响应 | 实现特定类提供 | 某些客户端支持获取原始 API 响应(如 JSON)。 | 需查看具体实现(如 OpenAiChatResponse) | 用于调试或特殊处理。 |
提示:元数据的键名可能因 AI 平台而异,建议查阅对应 starter 的文档。
第5章:聊天模型(Chat Models)
5.1 ChatClient 接口详解
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| call(String) | <T> T call(String message) | 发送字符串消息并同步获取响应,返回类型由泛型决定。 | String reply = chatClient.call("Hello"); | 默认返回 String,可指定为 ChatResponse。 |
| call(Message) | <T> T call(Message message) | 发送结构化消息对象(如 UserMessage)。 | UserMessage msg = new UserMessage("Explain AI.");
String reply = chatClient.call(msg); | 更精确控制消息角色。 |
| call(Prompt) | <T> T call(Prompt prompt) | 发送包含多条消息的 Prompt,支持复杂对话上下文。 | Prompt prompt = new Prompt(messages);
ChatResponse response = chatClient.call(prompt); | 推荐用于多轮对话。 |
| stream(String) | Stream<T> stream(String message) | 流式发送请求,返回 Stream 对象,逐块接收响应。 | chatClient.stream("Tell a story")
.forEach(System.out::println); | 避免长时间等待,提升用户体验。 |
| stream(Message) | Stream<T> stream(Message message) | 流式发送结构化消息。 | chatClient.stream(new UserMessage("..."))
.forEach(chunk -> {...}); | 支持实时显示生成内容。 |
| stream(Prompt) | Stream<T> stream(Prompt prompt) | 流式发送完整提示(含历史消息)。 | chatClient.stream(prompt).forEach(System.out::print); | 适用于长对话流式输出。 |
说明:ChatClient 是专为聊天模型设计的核心接口,支持同步与流式调用,是构建对话系统的基础。
5.2 消息类型:UserMessage、SystemMessage、AssistantMessage
| 消息类型 | 语法 | 说明 | 代码示例 | 注意事项 |
|---|
| SystemMessage | new SystemMessage(String content) | 设置系统级指令,影响模型行为(如角色设定)。 | new SystemMessage("You are a helpful assistant."); | 通常放在消息列表首位,仅发送一次。 |
| UserMessage | new UserMessage(String content) | 表示用户输入,最常见的消息类型。 | new UserMessage("What is Java?"); | 可多次发送,构成对话历史。 |
| AssistantMessage | new AssistantMessage(String content) | 表示模型之前的回复,用于维护对话上下文。 | new AssistantMessage("Java is a programming language."); | 必须准确记录模型输出,避免误导。 |
| 消息构造(带元数据) | new UserMessage(Content content) | 支持富内容(如文本+图像),但文本生成中较少用。 | new UserMessage(new TextContent("Describe this image")) | 当前主要用于多模态场景。 |
提示:消息顺序至关重要,通常为 [SystemMessage, UserMessage, AssistantMessage, ...],模型根据上下文生成回复。
5.3 多轮对话管理与消息历史维护
| 方法/策略 | 语法 | 说明 | 代码示例 | 注意事项 |
|---|
| 手动维护消息列表 | List<Message> conversation = new ArrayList<>() | 在服务类中维护会话消息列表。 | conversation.add(new UserMessage(userInput));
conversation.add(new AssistantMessage(aiReply)); | 简单场景可用,但需注意线程安全。 |
| 使用 Prompt 构造 | new Prompt(conversation) | 将完整消息历史传入 Prompt。 | Prompt prompt = new Prompt(conversation);
ChatResponse response = chatClient.call(prompt); | 确保模型”看到”全部上下文。 |
| 会话作用域 Bean | @Scope("session") | 为每个用户会话创建独立的消息历史实例。 | @Component @Scope("session") class ChatService { ... } | 适合 Web 应用,避免用户间消息混淆。 |
| 截断长历史 | 保留最近 N 条消息 | 防止 token 超限,提升性能。 | List<Message> recent = conversation.subList(Math.max(0, size - 10), size); | 平衡上下文完整性与成本。 |
| 外部存储(Redis) | 将消息列表序列化存储 | 实现跨实例会话持久化。 | 使用 StringRedisTemplate 存储 JSON 格式消息列表。 | 适用于分布式系统,需处理序列化性能。 |
注意:消息历史越长,消耗的 token 越多,可能导致请求失败或成本上升,建议合理管理。
5.4 流式响应(Streaming)支持
| 方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| stream(String) | Stream<String> stream(String message) | 流式接收字符串响应块。 | chatClient.stream("Long text...")
.forEach(System.out::print); | 响应块大小由模型决定,非固定。 |
| stream(Prompt) | Stream<ChatResponse> stream(Prompt prompt) | 流式接收 ChatResponse 对象流。 | chatClient.stream(prompt)
.map(r -> r.getResult().getOutput().getContent())
.forEach(System.out::print); | 可获取每块的元数据。 |
| WebFlux 响应 | return chatClient.stream(prompt); | 在 Spring WebFlux 中直接返回 Flux。 | @GetMapping(value="/chat", produces=TEXT_EVENT_STREAM)
public Flux<String> chat() { ... } | 需使用 text/event-stream 内容类型。 |
| 异常处理 | try-catch 或 onError | 处理流式过程中的网络或服务异常。 | stream.onErrorContinue((err, val) -> log.error(err)); | 流式调用需特别注意错误恢复。 |
| 资源释放 | stream.close() 或 try-with-resources | 确保流关闭,释放连接资源。 | 使用 Flux 时由框架自动管理。 | 避免连接泄漏。 |
提示:流式响应适用于聊天机器人、长文本生成等需要”边生成边显示”的场景。
第6章:函数调用与工具集成(Function Calling)
6.1 函数调用(Function Calling)概念解析
| 概念名称 | 说明 | 注意事项 |
|---|
| 函数调用(Function Calling) | 允许大模型在生成响应前,决定是否调用预定义的外部函数(工具)来获取信息或执行操作。 | 模型不直接执行代码,而是返回调用指令,由框架执行。 |
| 工具(Tool) | 一个 Java 方法,通过 @Tool 注解暴露给 AI 模型,可执行查询、计算、调用 API 等操作。 | 工具应具有明确的输入输出和副作用控制。 |
| 调用流程 | 用户提问 → 模型分析是否需要工具 → 若需要,返回函数调用请求 → 框架执行工具 → 将结果返回模型 → 模型生成最终回复。 | 是一个”AI 决策 + 外部执行 + AI 总结”的闭环。 |
| 支持的模型 | 并非所有模型都支持函数调用,如 OpenAI 的 gpt-3.5-turbo 需指定支持版本(如 0613)。 | 配置时需确认模型能力。 |
| 安全性 | 工具方法可能访问数据库或外部服务,需严格验证输入,防止注入攻击。 | 避免暴露敏感操作(如删除、支付)作为工具。 |
| 属性 | 语法 | 说明 | 代码示例 | 注意事项 |
|---|
| description | @Tool(description = "...") | 描述工具用途,供模型理解其功能。 | @Tool(description = "Get current weather by city name") | 描述越清晰,模型调用越准确。 |
| 方法参数 | 支持基本类型、String、POJO | 定义工具所需的输入参数。 | public String getWeather(String city) | POJO 需有默认构造函数和 getter/setter。 |
| 返回类型 | String 或 POJO | 工具执行结果,将作为上下文传回模型。 | return "Sunny, 20°C"; | 字符串最常用,POJO 会自动序列化为 JSON。 |
| 方法位置 | 必须在 Spring Bean 中 | @Component、@Service 等注解的类中。 | @Service class WeatherService { @Tool(...) public String getWeather(...) { ... } } | 非 Bean 中的方法不会被扫描注册。 |
| 多个工具 | 可在多个 Bean 中定义多个 @Tool 方法 | 实现功能解耦。 | 分别定义 WeatherTool、StockTool 等服务类。 | 避免单个类职责过重。 |
6.3 将工具注册到 AI 客户端
| 方式 | 说明 | 代码示例 | 注意事项 |
|---|
| 自动注册(推荐) | Spring AI 启动时自动扫描所有 @Tool 注解的方法并注册。 | 无需代码,只要 @Tool 方法在 Spring 管理的 Bean 中即可。 | 确保组件扫描路径包含工具类。 |
| 手动注册 | 通过 FunctionCallingHelper 或客户端配置手动添加工具。 | chatClient.withFunctions(Arrays.asList(weatherTool, stockTool)) | 适用于动态或条件注册场景。 |
| 配置文件控制 | 暂不支持通过 application.properties 开启/关闭工具。 | 需通过代码或条件 Bean 实现。 | 可结合 @Profile 注解按环境启用工具。 |
| 工具可见性 | 所有注册工具对模型”可见”,但是否调用由模型决策。 | 模型会根据描述和上下文判断。 | 避免注册过多无关工具,增加决策复杂度。 |
6.4 函数调用的执行流程与响应处理
| 步骤 | 说明 | 代码/行为示例 | 注意事项 |
|---|
| 1. 用户提问 | 用户输入需要外部数据的问题。 | “上海现在的天气怎么样?“ | 问题需明确指向可工具化操作。 |
| 2. 模型决策 | 模型识别到需要调用 getWeather 工具。 | 返回 {"tool_calls": [{"name": "getWeather", "arguments": {"city": "Shanghai"}}]} | 依赖模型的指令遵循能力。 |
| 3. 框架执行 | Spring AI 框架调用 getWeather("Shanghai") 方法。 | 框架通过反射调用并获取返回值。 | 方法执行异常将中断流程。 |
| 4. 结果回传 | 工具返回结果(如 “Cloudy, 18°C”)作为新消息传回模型。 | 模型收到系统消息:“工具 getWeather 执行结果:Cloudy, 18°C” | 模型知晓外部操作结果。 |
| 5. 生成最终回复 | 模型结合工具结果生成自然语言回复。 | “上海现在是多云,气温18°C。“ | 完成”AI + 外部能力”的协同。 |
| 异常处理 | 工具执行失败(如网络错误) | 框架可配置失败策略(如重试、忽略、返回错误) | 建议工具内部捕获异常并返回友好错误信息。 |
| 多工具调用 | 模型可同时请求调用多个工具。 | tool_calls 数组包含多个条目。 | 框架通常按顺序执行,可优化为并行。 |
提示:可通过日志观察 ToolExecutionRequest 和 ToolExecutionResult 来调试函数调用流程。
第7章:数据转换与内容处理
7.1 TextTransformer 接口与实现
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| transform(String text) | String transform(String text) | 对输入文本进行转换处理,返回处理后的文本。 | String cleaned = textTransformer.transform(" Hello! "); | 可用于清洗、格式化、摘要等操作。 |
| transform(Prompt prompt) | Prompt transform(Prompt prompt) | 转换整个 Prompt 对象,适用于复杂结构处理。 | Prompt enhanced = textTransformer.transform(prompt); | 可修改提示词内容或添加上下文。 |
| 内置实现:DefaultTextTransformer | new DefaultTextTransformer() | 提供默认文本处理能力,通常作为基类。 | 可继承并重写 transform 方法。 | 实际应用中常自定义实现。 |
| 自定义实现 | class MyTransformer implements TextTransformer | 实现特定业务逻辑的文本转换器。 | 见下方代码示例 | 需注册为 Spring Bean 才能注入使用。 |
| 链式处理 | 组合多个 TextTransformer | 构建文本处理流水线。 | transformer1.transform(transformer2.transform(text)) | 可结合策略模式或责任链模式实现。 |
自定义实现示例:
public class UppercaseTransformer implements TextTransformer {
public String transform(String text) {
return text.toUpperCase();
}
}
说明:TextTransformer 是一个通用接口,可用于预处理输入或后处理输出,增强 AI 应用的灵活性。
7.2 文本嵌入(Embedding)支持
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| embed(String text) | List<Double> embed(String text) | 生成单个文本的嵌入向量(浮点数列表)。 | List<Double> embedding = aiClient.embed("Hello world"); | 向量表示文本的语义特征。 |
| embed(List<String> texts) | List<List<Double>> embed(List<String> texts) | 批量生成多个文本的嵌入向量。 | List<List<Double>> embeddings = aiClient.embed(Arrays.asList("Hi", "Bye")); | 提高效率,适用于文档库处理。 |
| EmbeddingClient | interface EmbeddingClient | 专门用于嵌入操作的客户端接口(部分实现提供)。 | @Autowired EmbeddingClient embeddingClient; | 并非所有平台都单独暴露此接口。 |
| 嵌入维度 | 依赖模型(如 text-embedding-ada-002 为 1536 维) | 向量长度固定,需与模型匹配。 | assert embedding.size() == 1536; | 存储和计算时需注意维度一致性。 |
| 用途:语义搜索 | 计算向量相似度(如余弦相似度) | 找出与查询语义最接近的文本。 | 将用户问题嵌入,与文档库向量比对。 | 是构建 RAG(检索增强生成)的基础。 |
提示:嵌入操作通常消耗 token,但成本低于生成任务,适合高频调用。
7.3 向量存储集成基础
| 概念/方法 | 说明 | 示例 | 注意事项 |
|---|
| 向量数据库 | 专门存储和检索向量数据的数据库(如 Pinecone、Milvus、Weaviate、Chroma)。 | 用于持久化文本嵌入向量。 | 选择时考虑性能、成本、部署方式(云/本地)。 |
| 存储流程 | 文本 → 嵌入 → 向量存储 | 构建向量索引。 | for (doc : docs) { vec = aiClient.embed(doc); vectorStore.save(doc, vec); } |
| 检索流程 | 查询 → 嵌入 → 向量搜索 → 返回相似文本 | 实现语义检索。 | List<String> results = vectorStore.findSimilar(queryEmbedding, topK=3); |
| VectorStore 接口 | Spring AI 提供的向量存储抽象(如 SimpleVectorStore)。 | vectorStore.add(documents);
vectorStore.findSimilar(embedding); | SimpleVectorStore 为内存实现,适合测试。 |
| 集成第三方 | 通过具体实现类集成 Pinecone、Azure AI Search 等。 | 需引入对应 starter(如 spring-ai-pinecone-spring-boot-starter)。 | 配置认证信息和连接参数。 |
注意:生产环境应使用专业的向量数据库,内存存储仅用于演示。
7.4 内容提取与结构化输出
| 方法/技术 | 语法/说明 | 用途 | 代码示例 | 注意事项 |
|---|
| 提示词引导 | 在提示中要求 JSON 格式输出 | 强制模型返回结构化数据。 | "Return result as JSON: {name: string, age: number}" | 模型可能不严格遵守格式。 |
| @Schema 注解 | 结合 FunctionCalling 定义输出结构 | 更可靠地生成结构化响应。 | @Tool(outputSchema = @Schema(type = "object", properties = {...})) | 依赖模型支持结构化输出。 |
| 响应解析 | 使用 ObjectMapper 解析 JSON 响应 | 将字符串转换为 POJO。 | MyData data = objectMapper.readValue(jsonString, MyData.class); | 需捕获 JsonProcessingException。 |
| 错误重试 | 解析失败时重新调用 AI | 提高结构化输出可靠性。 | 循环调用直至获得有效 JSON。 | 设置最大重试次数避免无限循环。 |
| 输出验证 | 验证解析后的对象字段完整性 | 确保数据可用。 | if (data.getName() == null) throw new IllegalStateException(); | 防御性编程,避免空值异常。 |
提示:结合函数调用(Function Calling)是实现结构化输出最可靠的方式,模型会生成符合 schema 的 JSON。
第8章:提示工程(Prompt Engineering)
8.1 提示词设计原则
| 原则 | 说明 | 示例 | 注意事项 |
|---|
| 明确具体 | 避免模糊,清晰表达需求。 | ❌ “写点东西” → ✅ “写一篇关于气候变化的 200 字科普文章” | 越具体,输出越可控。 |
| 提供上下文 | 给出背景信息或角色设定。 | “你是一位资深 Java 工程师,请解释 Spring Bean 的生命周期。“ | 上下文影响回答的专业性和风格。 |
| 分步指令 | 复杂任务拆解为多个步骤。 | “1. 分析问题 2. 列出解决方案 3. 推荐最佳方案” | 提高复杂任务的完成质量。 |
| 示例引导(Few-shot) | 提供输入输出示例。 | “输入:‘happy’ → 输出:’😊‘;输入:‘sad’ → 输出:’😢‘;输入:‘angry‘“ | 模型模仿示例模式生成。 |
| 避免歧义 | 使用无歧义词汇,避免双重否定。 | 避免 “不要不写” → 改为 “请写” | 减少模型误解风险。 |
| 设置约束 | 限制长度、格式、风格等。 | “用不超过 50 字,以诗歌形式回答。“ | 确保输出符合下游处理要求。 |
8.2 动态提示模板与变量注入
| 方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| 占位符 {var} | PromptTemplate.of("Summarize: {text}") | 定义可变部分。 | PromptTemplate pt = PromptTemplate.of("Translate '{text}' to {lang}"); | 大括号包围变量名。 |
| apply(Map) | promptTemplate.apply(variables) | 注入变量生成 Prompt。 | Map<String, Object> vars = Map.of("text", "AI is great", "lang", "fr");
Prompt prompt = pt.apply(vars); | 变量名需与模板一致。 |
| apply(Object) | promptTemplate.apply(pojo) | 使用 POJO 注入多个字段。 | prompt = pt.apply(new TranslationRequest("Hello", "es")); | POJO 属性名匹配占位符。 |
| 条件逻辑 | 模板中使用 #if 等(需模板引擎) | 实现条件内容插入。 | 当前 Spring AI 原生不支持,需结合 Thymeleaf 等。 | 可在 Java 代码中实现条件逻辑。 |
| 消息模板 | new PromptTemplate(List<Message>) | 动态构建多角色提示。 | new PromptTemplate(Arrays.asList(new SystemMessage("{role}"), new UserMessage("{query}"))) | 适用于聊天机器人角色切换。 |
8.3 提示词版本管理与外部化配置
| 策略 | 说明 | 实现方式 | 注意事项 |
|---|
| 外部文件存储 | 将提示词存于 resources/prompts/ 目录下的 .txt 文件。 | String template = Files.readString(Path.of("classpath:prompts/summarize.txt"));
PromptTemplate pt = PromptTemplate.of(template); | 便于非开发人员(如产品经理)修改。 |
| 配置文件 | 使用 application.yml 存储提示词。 | app.prompts.summary: "Summarize the text: {text}" | 可结合 @Value 注入。 |
| 数据库存储 | 将提示词存入数据库,支持动态更新。 | PromptEntity entity = promptRepo.findByName("summary");
PromptTemplate pt = PromptTemplate.of(entity.getContent()); | 实现热更新,无需重启应用。 |
| 版本控制 | 为提示词添加版本号(如 summary_v2)。 | 文件命名 summarize_v2.txt 或数据库字段 version。 | 便于回滚和 A/B 测试。 |
| 环境区分 | 不同环境(dev/test/prod)使用不同提示词。 | 结合 @Profile 或配置中心实现。 | 避免测试提示影响生产。 |
8.4 提示词测试与评估
| 方法 | 说明 | 工具/示例 | 注意事项 |
|---|
| 单元测试 | 编写测试用例验证提示词输出。 | assertEquals(true, response.contains("Java")); | 测试关键字段是否存在或格式正确。 |
| 人工评估 | 由人工判断输出质量(相关性、准确性、流畅性)。 | 设计评分表(1-5 分)进行打分。 | 主观性强,但最直接。 |
| 自动化指标 | 使用 BLEU、ROUGE 等指标评估与标准答案的相似度。 | 需有”标准答案”作为基准。 | 仅适用于有固定答案的场景。 |
| A/B 测试 | 对比两个提示词版本的效果。 | 同一问题用 prompt_v1 和 prompt_v2 分别调用,比较结果。 | 需足够样本量才有统计意义。 |
| 日志分析 | 记录提示词和输出,分析失败案例。 | 将 Prompt 和 Response 存入日志或数据库。 | 用于持续优化提示词。 |
| 提示词调试工具 | 使用 LangSmith、PromptLayer 等第三方工具。 | 可视化调用链、评估性能。 | 需集成额外服务。 |
提示:提示词是 AI 应用的”程序代码”,应像代码一样进行测试、版本控制和持续优化。
第9章:支持的 AI 平台集成
9.1 集成 OpenAI(ChatGPT)
| 配置项 | 配置方式 | 示例值 | 说明 | 注意事项 |
|---|
| 依赖引入 | Maven | org.springframework.ai:spring-ai-openai-spring-boot-starter | 引入 OpenAI 自动配置模块。 | 确保 BOM 管理版本。 |
| API Key | application.yml | spring.ai.openai.api-key: sk-xxx | OpenAI 账户的 API 密钥。 | 切勿硬编码,推荐使用环境变量 OPENAI_API_KEY。 |
| 模型名称 | application.yml | spring.ai.openai.model: gpt-3.5-turbo | 指定调用的模型(如 gpt-4, gpt-4o)。 | 确保模型支持所需功能(如函数调用)。 |
| 基础 URL(可选) | application.yml | spring.ai.openai.base-url: https://api.openai.com/v1 | 默认为官方地址,可替换为代理或企业网关。 | 用于网络受限环境。 |
| 超时设置 | application.yml | spring.ai.openai.timeout: 30s | 连接与读取超时。 | 复杂请求建议适当延长。 |
| 使用示例 | Java | @Autowired ChatClient chatClient; String response = chatClient.call("Hello"); | 自动注入配置好的 ChatClient。 | 无需手动创建客户端。 |
注意:OpenAI 是最广泛支持的平台,功能完整,适合快速原型开发。
9.2 集成 Azure OpenAI
| 配置项 | 配置方式 | 示例值 | 说明 | 注意事项 |
|---|
| 依赖引入 | Maven | org.springframework.ai:spring-ai-azure-openai-spring-boot-starter | Azure 专用 starter。 | 与 OpenAI starter 不兼容,避免共存。 |
| API Key | application.yml | spring.ai.azure.openai.api-key: your-key | Azure 资源的密钥。 | 可在 Azure 门户的”密钥和终结点”中找到。 |
| 模型部署名称 | application.yml | spring.ai.azure.openai.deployment-name: gpt-35-turbo | Azure 中为模型创建的部署名称(非模型名)。 | 必须与 Azure 门户中部署的名称一致。 |
| 基础 URL | application.yml | spring.ai.azure.openai.base-url: https://your-resource.openai.azure.com | Azure OpenAI 服务的终结点 URL。 | 格式为 https://<资源名>.openai.azure.com。 |
| API 版本 | application.yml | spring.ai.azure.openai.api-version: 2024-02-15-preview | 指定使用的 API 版本。 | 需与 Azure 服务支持的版本匹配。 |
| 认证方式 | 可选 | 使用 azure-identity 实现 AAD 认证 | 更安全的企业级认证。 | 需额外依赖和配置,适合云原生部署。 |
提示:Azure OpenAI 适合企业用户,提供数据驻留、VNet 集成等高级功能。
9.3 集成 Amazon Bedrock
| 配置项 | 配置方式 | 示例值 | 说明 | 注意事项 |
|---|
| 依赖引入 | Maven | org.springframework.ai:spring-ai-amazon-bedrock-spring-boot-starter | Bedrock 集成模块。 | 需 AWS 凭证。 |
| AWS 凭证 | 环境变量/配置文件 | AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY | 标准 AWS 认证机制。 | 推荐使用 IAM 角色(EC2/ECS)或临时凭证。 |
| 区域 | application.yml | spring.ai.aws.region: us-east-1 | Bedrock 服务所在区域。 | 模型可用性因区域而异。 |
| 模型 ID | application.yml | spring.ai.bedrock.model-id: anthropic.claude-3-sonnet-20240229-v1:0 | Bedrock 中的完整模型标识符。 | 需从 Bedrock 控制台获取准确 ID。 |
| 基础 URL(可选) | application.yml | spring.ai.bedrock.base-url: https://bedrock-runtime.us-east-1.amazonaws.com | 自定义终结点。 | 通常使用默认。 |
| 使用模型 | 支持模型 | Claude、Titan、Llama 3、Jurassic 等 | Bedrock 提供多家厂商模型。 | 需确保 IAM 策略允许访问目标模型。 |
注意:Bedrock 适合 AWS 生态用户,支持多模型选择和企业级安全。
9.4 集成 Google Vertex AI
| 配置项 | 配置方式 | 示例值 | 说明 | 注意事项 |
|---|
| 依赖引入 | Maven | org.springframework.ai:spring-ai-google-vertex-spring-boot-starter | Vertex AI 集成模块。 | 需 Google Cloud 凭证。 |
| 凭证文件 | 环境变量 | GOOGLE_APPLICATION_CREDENTIALS=path/to/key.json | 指向服务账号密钥文件。 | 必须下载 JSON 密钥并安全存储。 |
| 项目 ID | application.yml | spring.ai.vertex.project-id: your-project-id | Google Cloud 项目 ID。 | 在 GCP 控制台查看。 |
| 区域 | application.yml | spring.ai.vertex.location: us-central1 | Vertex AI 服务区域。 | 模型部署需在同一区域。 |
| 模型名称 | application.yml | spring.ai.vertex.model: gemini-pro | 支持 gemini-pro, gemini-pro-vision, text-bison 等。 | Gemini 系列为最新模型。 |
| 端点(可选) | application.yml | spring.ai.vertex.base-url: https://us-central1-aiplatform.googleapis.com | 自定义 API 端点。 | 通常使用默认。 |
提示:Vertex AI 集成 Google 的先进模型(如 Gemini),适合 GCP 用户。
9.5 集成 Ollama(本地大模型)
| 配置项 | 配置方式 | 示例值 | 说明 | 注意事项 |
|---|
| 依赖引入 | Maven | org.springframework.ai:spring-ai-ollama-spring-boot-starter | Ollama 集成模块。 | 无需云服务,本地运行。 |
| Ollama 服务 | 本地运行 | ollama serve | 启动 Ollama 服务。 | 需先安装 Ollama(https://ollama.com)。 |
| 拉取模型 | CLI 命令 | ollama run llama3 | 下载并运行模型。 | 常用模型:llama3, mistral, phi3, gemma。 |
| 服务地址 | application.yml | spring.ai.ollama.base-url: http://localhost:11434 | Ollama 的 HTTP 服务地址。 | 默认端口 11434。 |
| 模型名称 | application.yml | spring.ai.ollama.model: llama3 | 指定本地运行的模型名称。 | 必须与 ollama run 的名称一致。 |
| GPU 支持 | Ollama 配置 | 依赖 CUDA/Metal | 利用 GPU 加速推理。 | 需安装对应驱动(NVIDIA/AMD/Mac)。 |
| 优势 | 说明 | 数据隐私、低成本、离线可用 | 适合开发、测试、隐私敏感场景。 | 性能依赖本地硬件。 |
注意:Ollama 是理想的本地开发和测试工具,可快速验证功能。
第10章:异步与流式处理
10.1 异步调用:AiClient 与 ChatClient 的异步方法
| 方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| callAsync(String) | CompletableFuture<String> callAsync(String message) | 异步发送消息,立即返回 CompletableFuture。 | CompletableFuture<String> future = chatClient.callAsync("Hello");
future.thenAccept(System.out::println); | 非阻塞主线程,提升吞吐量。 |
| callAsync(Message) | CompletableFuture<T> callAsync(Message message) | 异步发送结构化消息。 | future = chatClient.callAsync(new UserMessage("...")); | 与同步方法参数一致。 |
| callAsync(Prompt) | CompletableFuture<T> callAsync(Prompt prompt) | 异步发送完整提示(含历史)。 | CompletableFuture<ChatResponse> respFuture = chatClient.callAsync(prompt); | 适用于耗时较长的请求。 |
| 异常处理 | future.exceptionally(e -> {...}) | 处理异步调用中的异常。 | future.exceptionally(e -> "Error: " + e.getMessage()); | 必须处理,否则异常会静默丢失。 |
| 组合多个异步调用 | CompletableFuture.allOf(f1, f2) | 并行执行多个 AI 调用。 | CompletableFuture<Void> combined = CompletableFuture.allOf(future1, future2); | 提高整体效率。 |
提示:异步调用适合后台任务、批处理或高并发 Web 服务。
10.2 流式生成:stream 方法与响应处理
| 方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| stream(String) | Stream<String> stream(String message) | 流式接收响应块(字符串)。 | chatClient.stream("Long story")
.forEach(System.out::print); | 响应块大小由模型决定。 |
| stream(Message) | Stream<T> stream(Message message) | 流式发送结构化消息。 | chatClient.stream(new UserMessage("..."))
.forEach(chunk -> log.info("Chunk: {}", chunk)); | 支持实时日志记录。 |
| stream(Prompt) | Stream<ChatResponse> stream(Prompt prompt) | 流式接收 ChatResponse 对象流。 | stream.map(r -> r.getResult().getOutput().getContent())
.forEach(System.out::print); | 可获取每块的元数据。 |
| 异常处理 | try-catch 或 onError | 捕获流处理中的异常。 | 在 forEach 外层包裹 try-catch。 | 流式调用需谨慎处理网络中断。 |
| 资源管理 | try-with-resources 或显式关闭 | 确保流关闭,释放连接。 | 对于 InputStream 等需手动管理。 | Spring WebFlux 中由框架管理。 |
| Web 场景 | 结合 @ResponseBody | 在 REST API 中返回流式响应。 | 需使用 text/event-stream 内容类型。 | 详见 10.3 节。 |
注意:流式响应适用于聊天机器人、实时字幕、长文本生成等需要”渐进式”输出的场景。
10.3 响应式编程支持(Project Reactor)
| 方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| stream(String) 返回 Flux<String> | Flux<String> stream(String message) | 在 WebFlux 中直接返回响应流。 | 见下方代码示例 | 需使用 Spring WebFlux。 |
| stream(Prompt) 返回 Flux<ChatResponse> | Flux<ChatResponse> stream(Prompt prompt) | 流式接收结构化响应。 | Flux<ChatResponse> flux = chatClient.stream(prompt); | 可使用 map, filter 等操作符。 |
| 错误处理 | .onErrorReturn("Error") 或 .onErrorResume(...) | 处理流中的错误。 | flux.onErrorResume(e -> Flux.just(new ErrorResponse(e))); | 避免流因异常中断。 |
| 超时控制 | .timeout(Duration.ofSeconds(30)) | 设置流式响应超时。 | flux.timeout(Duration.ofSeconds(30)); | 防止客户端长时间等待。 |
| 背压支持 | Reactor 内建支持 | 处理高速生产与低速消费的场景。 | 使用 Flux.create() 时注意 Sink 的背压策略。 | Netty 服务器自动处理。 |
| 与 Mono 结合 | Mono<String> 用于单次响应 | 异步获取单个结果。 | Mono<String> result = Mono.fromFuture(chatClient.callAsync("Hello")); | 统一响应式编程模型。 |
WebFlux 流式响应示例:
@GetMapping(value="/chat", produces=MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chat(@RequestParam String msg) {
return chatClient.stream(msg);
}
提示:Project Reactor 是构建高性能、非阻塞微服务的理想选择,与 Spring AI 的流式 API 天然契合。
总结:通过响应式编程,可构建高并发、低延迟的 AI 服务,特别适合现代云原生架构。
第11章:配置管理与生产最佳实践
11.1 配置文件中的 AI 客户端配置
| 配置项 | 示例(application.yml) | 说明 | 注意事项 |
|---|
| 模型名称 | spring.ai.openai.model: gpt-4o | 指定使用的模型。 | 生产环境避免使用实验性模型。 |
| API 密钥 | spring.ai.openai.api-key: ${OPENAI_API_KEY} | 使用环境变量注入密钥。 | 严禁硬编码,使用 vault 或 KMS 更佳。 |
| 基础 URL | spring.ai.openai.base-url: https://api.openai.com/v1 | 可替换为代理或企业网关。 | 用于网络隔离或审计场景。 |
| 超时设置 | spring.ai.openai.timeout: 30s | 连接和读取超时。 | 复杂任务(如长文本生成)建议设为 60s+。 |
| 温度(temperature) | spring.ai.openai.options.temperature: 0.7 | 控制输出随机性(0-2)。 | 生产环境建议 0.3~0.7,避免过高随机性。 |
| 最大 Token 数 | spring.ai.openai.options.max-tokens: 1500 | 限制生成内容长度。 | 防止响应过长导致成本激增或超时。 |
| 系统消息 | spring.ai.openai.options.system-message: You are a helpful assistant. | 设置默认角色行为。 | 可统一服务风格,减少重复设置。 |
提示:使用 @ConfigurationProperties 可自定义配置绑定,实现更灵活的管理。
11.2 多客户端管理与命名实例
| 方法 | 说明 | 代码/配置示例 | 注意事项 |
|---|
| 命名配置 | 为不同用途的客户端使用不同配置前缀。 | spring.ai.openai.chat.model: gpt-3.5-turbo
spring.ai.openai.summary.model: gpt-4o | Spring AI 支持 chat, embedding 等命名空间。 |
| 自定义 Bean | 手动定义多个 ChatClient 实例。 | 见下方代码示例 | 使用 @Qualifier("gpt4Client") 注入。 |
| 场景分离 | 按功能划分客户端(如聊天、摘要、嵌入)。 | embeddingClient 用于向量生成,chatClient 用于对话。 | 提高可维护性,避免配置冲突。 |
| 条件注册 | 按环境或条件注册客户端。 | @Profile("prod") @Bean ChatClient prodClient() | 实现多环境差异化配置。 |
自定义 ChatClient Bean 示例:
@Bean("gpt4Client")
ChatClient gpt4Client() {
return ChatClient.builder(openAiApi)
.options(...)
.build();
}
注意:命名实例需明确用途,避免创建过多无意义的客户端。
11.3 超时、重试与容错机制
| 机制 | 配置/实现方式 | 说明 | 注意事项 |
|---|
| 超时设置 | spring.ai.openai.timeout: 30s | 防止请求无限等待。 | 设置应略大于模型平均响应时间。 |
| 重试机制 | 结合 Spring Retry 或 Resilience4j | 自动重试失败请求。 | 配置示例见下方代码。 |
| 断路器 | Resilience4j CircuitBreaker | 在服务不稳定时快速失败。 | 配置 failureRateThreshold 和 waitDurationInOpenState。 |
| 降级策略 | 提供备用响应或本地逻辑。 | 调用失败时返回缓存结果或默认提示。 | 确保核心功能可用。 |
| 异常分类 | 区分 ClientException(4xx)和 ServerException(5xx) | 5xx 错误适合重试,4xx 通常不重试。 | 提高重试策略精准度。 |
重试配置示例:
@Retryable(value = {ApiException.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000))
最佳实践:生产环境必须实现重试 + 断路器组合,确保服务韧性。
11.4 日志、监控与追踪(Observability)
| 类别 | 工具/方法 | 说明 | 注意事项 |
|---|
| 日志记录 | Logger 输出请求/响应 | 记录关键信息用于调试。 | 脱敏处理,避免记录 API 密钥或用户隐私数据。 |
| 指标监控 | Micrometer + Prometheus | 收集调用次数、延迟、错误率。 | 创建仪表盘监控 ai_client_request_duration_seconds。 |
| 分布式追踪 | OpenTelemetry / Zipkin | 追踪 AI 调用在微服务中的链路。 | 与 Jaeger 集成,可视化调用路径。 |
| 请求 ID 传递 | MDC 或 TraceId | 关联日志与追踪信息。 | 便于问题定位和审计。 |
| 成本监控 | 记录 token 使用量 | 监控输入/输出 token 消耗。 | 结合账单预警,防止费用超支。 |
| 健康检查 | /actuator/health | 检查 AI 客户端连接状态。 | 可自定义 HealthIndicator。 |
提示:可观测性是生产系统的基石,必须与 AI 集成紧密结合。
11.5 安全与密钥管理
| 措施 | 实现方式 | 说明 | 注意事项 |
|---|
| 密钥外置 | 环境变量、KMS、Vault | 避免密钥进入代码库。 | 使用 HashiCorp Vault 或云 KMS(如 AWS KMS、GCP KMS)。 |
| 最小权限原则 | 为 API Key 分配最小必要权限 | 如仅允许 chat 调用,禁用 delete 操作。 | 降低密钥泄露后的风险。 |
| 密钥轮换 | 定期更换 API Key | 减少长期密钥暴露风险。 | 自动化轮换流程,避免服务中断。 |
| 网络安全 | VPC、防火墙、IP 白名单 | 限制 AI 服务访问来源。 | Azure/AWS/GCP 均支持网络级控制。 |
| 输入输出过滤 | 内容安全扫描 | 防止生成或接收有害内容。 | 集成内容审核 API(如 Azure Content Moderator)。 |
| 审计日志 | 记录所有 AI 调用 | 用于安全审计和合规检查。 | 保留日志至少 90 天。 |
警告:AI 服务是新的攻击面,必须像对待数据库一样严格保护。
第12章:测试与调试
12.1 单元测试 AI 调用逻辑
| 方法 | 说明 | 代码示例 | 注意事项 |
|---|
| 测试服务层逻辑 | 验证业务逻辑是否正确调用 AI 客户端。 | @Test void shouldCallAiForSummary() { String result = service.summarize("Long text..."); assertThat(result).isNotEmpty(); } | 不真正调用 AI 服务,依赖 Mock。 |
| 验证参数传递 | 检查是否正确构造 Prompt 或消息。 | 使用 ArgumentCaptor 捕获 ChatClient.call() 的参数。 | 确保系统消息、温度等设置正确。 |
| 结构化输出测试 | 验证 JSON 解析或 POJO 映射。 | MyData data = objectMapper.readValue(json, MyData.class); assertThat(data.getName()).isEqualTo("Test"); | 覆盖边界情况(null、空字符串)。 |
提示:单元测试应快速、隔离,不依赖外部服务。
12.2 使用 MockAiClient 进行模拟测试
| 方法 | 说明 | 代码示例 | 注意事项 |
|---|
| Mockito Mock | 模拟 ChatClient 行为。 | @Mock ChatClient chatClient; @Test void testWithMock() { when(chatClient.call("Hello")).thenReturn("Hi!"); assertEquals("Hi!", service.greet("Hello")); } | 最常用方式,灵活控制返回值。 |
| MockAiClient | Spring AI 提供的专用测试工具。 | MockAiClient mockAiClient = new MockAiClient(); mockAiClient.chatClient().addResponse("Mock response"); | 更贴近真实 API,支持流式模拟。 |
| 模拟流式响应 | 返回 Flux 或 Stream。 | when(chatClient.stream("...")).thenReturn(Flux.just("part1", "part2")); | 测试流式处理逻辑。 |
| 模拟异常 | 测试错误处理路径。 | when(chatClient.call("error")).thenThrow(new ApiException("Test")); | 确保服务能优雅处理失败。 |
最佳实践:在 @SpringBootTest 中使用 @MockBean 替换真实客户端。
12.3 集成测试策略
| 策略 | 说明 | 实现方式 | 注意事项 |
|---|
| 真实服务调用 | 连接真实 AI 平台进行测试。 | 使用测试专用 API Key 和模型(如 gpt-3.5-turbo)。 | 成本低,但依赖网络,可能不稳定。 |
| 持续集成(CI) | 在 CI/CD 流水线中运行测试。 | GitHub Actions / GitLab CI 调用测试脚本。 | 设置超时和重试,避免因网络问题失败。 |
| 测试环境隔离 | 使用独立的测试配置。 | application-test.yml 配置测试密钥和模型。 | 避免影响生产或开发环境。 |
| 断言输出质量 | 验证生成内容的相关性和格式。 | 使用模糊匹配或语义相似度库(如 SimHash)。 | 不宜过度依赖精确字符串匹配。 |
| 性能测试 | 测试高并发下的表现。 | 使用 JMeter 或 Gatling 模拟多用户请求。 | 评估响应时间和错误率。 |
注意:集成测试是验证端到端流程的关键,建议定期运行。
12.4 调试技巧与常见错误分析
| 问题现象 | 可能原因 | 解决方法 | 调试技巧 |
|---|
| 401 Unauthorized | API Key 错误或缺失 | 检查密钥配置,确认环境变量加载。 | 打印密钥前几位(脱敏)确认是否为空。 |
| 429 Too Many Requests | 请求频率超限 | 实现重试(带退避)或降低调用频率。 | 查看平台限流文档,监控调用速率。 |
| 500 Internal Server Error | 模型服务端错误 | 重试请求,或切换模型/平台。 | 检查 AI 平台状态页面(如 status.openai.com)。 |
| 响应为空或截断 | max-tokens 过小或内容被截断 | 增加 max-tokens 或检查流式处理逻辑。 | 启用详细日志,观察完整响应。 |
| 函数调用未触发 | 模型未识别需要调用工具 | 优化工具描述,确保问题明确指向工具功能。 | 检查 tool_calls 是否在响应中出现。 |
| token 超限 | 消息历史过长 | 截断历史,仅保留最近 N 条消息。 | 计算输入 token 数(可用 tiktoken 等库)。 |
| 本地 Ollama 无法连接 | 服务未启动或端口错误 | 运行 ollama serve,确认 localhost:11434 可访问。 | 使用 curl http://localhost:11434/api/tags 测试。 |
调试建议:
- 启用 DEBUG 日志级别(
logging.level.org.springframework.ai=DEBUG)。
- 使用 ChatClient 的 Prompt 和 ChatResponse 对象打印完整输入输出。
- 借助 LangSmith、PromptLayer 等专业调试工具进行深度分析。
总结:AI 应用调试需结合日志、模拟、真实测试和专业工具,形成完整闭环。
第13章:实战案例
13.1 构建智能客服机器人
核心需求
- 用户通过文本提问(如”订单怎么退货?”)
- 系统自动检索知识库并生成自然语言回答
- 支持多轮对话,保持上下文
- 可识别意图并转接人工
技术架构
用户 → Web/APP → Spring Boot Controller → ChatClient + VectorStore → 知识库检索 → AI 生成回答 → 返回用户
关键代码实现
知识库准备与向量化:
// 将 FAQ 文档嵌入并存入向量数据库
List<Document> faqDocs = loadFaqDocuments(); // 加载文本
List<TextSegment> segments = faqDocs.stream()
.map(doc -> TextSegment.from(doc.getContent()))
.collect(Collectors.toList());
vectorStore.add(segments); // 存入 SimpleVectorStore 或 Pinecone
对话服务(支持上下文):
@Service
public class CustomerSupportService {
@Autowired
private ChatClient chatClient;
@Autowired
private VectorStore vectorStore;
// 使用 Map 存储用户会话(生产环境应使用 Redis)
private final Map<String, List<Message>> conversationHistory = new ConcurrentHashMap<>();
public String handleQuery(String userId, String userMessage) {
// 1. 检索相关知识
List<Document> relevantDocs = vectorStore.findSimilar(userMessage, 3);
String context = relevantDocs.stream()
.map(Document::getContent)
.collect(Collectors.joining("\n"));
// 2. 构建提示词
String prompt = """
你是一个电商客服助手。
根据以下知识回答问题,不要编造信息:
${context}
历史对话:
${getHistory(userId)}
用户:${userMessage}
客服:
""";
// 3. 调用 AI 生成回答
String response = chatClient.call(prompt);
// 4. 更新对话历史
addToHistory(userId, new UserMessage(userMessage));
addToHistory(userId, new AiMessage(response));
return response;
}
private String getHistory(String userId) {
return conversationHistory.getOrDefault(userId, List.of()).stream()
.map(msg -> msg.getClass().getSimpleName() + ": " + msg.getContent())
.collect(Collectors.joining("\n"));
}
private void addToHistory(String userId, Message message) {
conversationHistory.computeIfAbsent(userId, k -> new ArrayList<>()).add(message);
// 限制历史长度,避免 token 超限
if (conversationHistory.get(userId).size() > 10) {
conversationHistory.get(userId).remove(0);
}
}
}
最佳实践建议
- ✅ 使用 RAG(检索增强生成)提高回答准确性
- ✅ 限制对话历史长度,防止 token 超限
- ✅ 添加意图识别(如”转人工”),可使用
@Tool 定义转接逻辑
- ✅ 部署后持续收集用户反馈,优化知识库
13.2 实现文档摘要生成器
核心需求
- 用户上传长文本(如报告、论文)
- 系统生成简洁、准确的摘要
- 支持不同长度(短摘要/长摘要)
- 可提取关键词
技术架构
用户上传 → 文件解析 → 文本分块 → AI 摘要生成 → 结构化输出(JSON)→ 返回
关键代码实现
文档解析与分块:
@Service
public class DocumentSummarizer {
@Autowired
private ChatClient chatClient;
// 使用 Spring AI 的 DocumentReader(如 PdfDocumentReader)
public String summarizePdf(InputStream pdfStream) {
DocumentReader reader = new PdfDocumentReader(pdfStream,
PdfDocumentReader.Options.builder().build());
List<Document> documents = reader.get();
// 合并内容(或分块处理长文档)
String fullText = documents.stream()
.map(Document::getContent)
.collect(Collectors.joining(" "));
return generateSummary(fullText, "concise");
}
}
生成结构化摘要:
// 定义摘要结构
public class SummaryResult {
private String summary;
private List<String> keywords;
// getter/setter
}
public SummaryResult generateStructuredSummary(String text) {
String prompt = """
请对以下文本生成摘要和关键词:
${text}
要求:
- 摘要不超过 100 字
- 提取 3-5 个关键词
- 以 JSON 格式输出:{"summary": "...", "keywords": ["...", "..."]}
""";
String jsonResponse = chatClient.call(prompt);
try {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(jsonResponse, SummaryResult.class);
} catch (JsonProcessingException e) {
// 解析失败时重试或返回默认值
throw new RuntimeException("Failed to parse summary JSON", e);
}
}
最佳实践建议
- ✅ 对长文档进行分块处理,避免单次输入过长
- ✅ 使用
temperature=0.3 提高摘要一致性
- ✅ 添加超时和重试机制,防止大文档处理失败
- ✅ 提供摘要长度选项(如”一句话摘要”、“段落摘要”)
13.3 开发基于自然语言的数据库查询接口
核心需求
- 用户用自然语言提问(如”上个月销售额最高的产品”)
- 系统自动生成 SQL 并执行
- 返回结构化数据或自然语言描述
技术架构
用户提问 → NLP → AI 生成 SQL → 执行查询 → 格式化结果 → 返回
关键代码实现
定义 SQL 生成工具:
@Component
public class SqlGenerator {
@Autowired
private ChatClient chatClient;
@Value("${app.database.schema}")
private String dbSchema; // 数据库结构描述
public String generateSql(String naturalQuery) {
String prompt = """
你是一个 SQL 专家。
根据以下数据库结构,将自然语言转换为 SQL:
${dbSchema}
要求:
- 只返回 SQL 语句,不要解释
- 使用标准 SQL 语法
- 避免 SELECT *
问题:${naturalQuery}
SQL:
""";
return chatClient.call(prompt).trim();
}
}
安全执行 SQL:
@Service
public class NaturalQueryService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private SqlGenerator sqlGenerator;
public Object queryByNaturalLanguage(String question) {
// 1. 生成 SQL
String sql = sqlGenerator.generateSql(question);
// 2. 安全校验(防止恶意 SQL)
if (!isSafeSql(sql)) {
throw new IllegalArgumentException("Invalid SQL generated");
}
// 3. 执行查询
try {
return jdbcTemplate.queryForList(sql);
} catch (DataAccessException e) {
throw new RuntimeException("Query execution failed", e);
}
}
private boolean isSafeSql(String sql) {
// 简单校验:禁止 DELETE/UPDATE/DROP 等
String upperSql = sql.toUpperCase();
return !upperSql.contains("DELETE")
&& !upperSql.contains("UPDATE")
&& !upperSql.contains("DROP")
&& upperSql.startsWith("SELECT");
}
}
最佳实践建议
- ✅ 严格限制 SQL 类型,仅允许 SELECT
- ✅ 使用数据库视图暴露有限数据,避免直接访问基表
- ✅ 添加查询超时
- ✅ 对于复杂需求,可结合 LangChain Expression Language (LCEL) 实现多步推理
13.4 构建 AI 驱动的邮件助手
核心需求
- 用户输入邮件主题和要点
- AI 生成专业、得体的邮件草稿
- 支持不同风格(正式、简洁、友好)
- 可自动添加签名
技术架构
用户输入 → 风格选择 → AI 生成 → 内容过滤 → 返回邮件草稿
关键代码实现
邮件生成服务:
@Service
public class EmailAssistant {
@Autowired
private ChatClient chatClient;
public String draftEmail(String subject, String points, String tone) {
String prompt = """
请根据以下信息撰写一封邮件:
主题:${subject}
内容要点:
${points}
要求:
- 语气:${tone}(可选:正式、简洁、友好)
- 包含开头问候和结尾致意
- 长度适中
- 使用专业商务英语
邮件:
""";
return chatClient.call(prompt);
}
}
集成到 Web 控制器:
@RestController
@RequestMapping("/api/email")
public class EmailController {
@Autowired
private EmailAssistant emailAssistant;
@PostMapping("/draft")
public ResponseEntity<String> createDraft(@RequestBody EmailRequest request) {
String draft = emailAssistant.draftEmail(
request.getSubject(),
String.join("\n", request.getPoints()),
request.getTone()
);
return ResponseEntity.ok(draft);
}
}
// 请求 DTO
public class EmailRequest {
private String subject;
private List<String> points;
private String tone = "正式";
// getter/setter
}
最佳实践建议
- ✅ 提供预设模板(如”会议邀请”、“项目更新”)
- ✅ 支持多语言生成(添加”语言”参数)
- ✅ 集成拼写检查和敏感词过滤
- ✅ 允许用户编辑草稿后再发送,避免完全自动化
本章总结
| 案例 | 核心技术 | 适用场景 |
|---|
| 智能客服 | RAG + 对话历史 | 企业客服、知识问答 |
| 文档摘要 | 文本分块 + 结构化输出 | 报告处理、信息提取 |
| 自然语言查库 | SQL 生成 + 安全校验 | BI 分析、数据探索 |
| 邮件助手 | 风格化生成 + 模板 | 办公自动化、效率工具 |
通用建议:
- 所有案例均需考虑错误处理和用户体验
- 生产环境务必进行充分测试和性能评估
- 结合可观测性工具(日志、监控)持续优化
- 遵循安全最佳实践,防止数据泄露和滥用