Spring AI 实战——Spring AI 入门

151 阅读11分钟

本章内容

  • 介绍 Spring AI
  • 初始化一个 Spring AI 项目
  • 选择 AI 提供商与模型

你感觉到了吗?在过去一年多里,人机交互领域发生了“断层级”的巨变,这股变化有潜力改造几乎所有行业、职业与生活方式。像 ChatGPT、Midjourney 这样的系统,把人工智能(AI)从科幻与学术研究带到了大众视野。

AI 并不新鲜。但让 AI 真正“破圈”的,是生成式 AI——这一 AI 分支使用生成模型(即大语言模型,LLMs)根据自然语言提示生成文本、图像与其他内容。有了生成式 AI,普通用户只需与模型“聊天”,就能创作文学与艺术作品、或通过对话获得问题答案。许多曾经需要专门技能的任务,如今只要能把需求打成字、输入到一个启用了生成式 AI 的应用里,任何人都能完成。

软件工程同样无法完全置身事外。开发者在开发应用时,会用 Cursor、Claude Code 等工具作为“虚拟结对程序员”。但开发者真正的机会,在于构建利用生成式 AI 为终端用户提供丰富功能的软件。生成式 AI 能让应用提供的信息与能力,在没有 AI 模型帮助时将会非常困难甚至不可能获得。借助生成式 AI,用户可以向应用提出任何问题,并下达指令让应用去完成需求——这一切都以直观而强大的方式实现,不再受传统“菜单+表单”模式的约束。

虽然 Python、Node.js 等语言已有少量成熟的生成式 AI 框架与库,但 Java 端的选择直到最近才开始涌现。其中之一便是 Spring AI:它是 Spring / Spring Boot 的一项扩展,让你能在企业级 Java 的事实标准框架里开发生成式 AI 能力。这样既方便存量应用接入生成式 AI,也让 Java 开发者能在熟悉的框架与编程模型中使用生成式 AI。

1.1 Hello, Spring AI!

OpenAI、MistralAI 等 AI 提供商通过 REST API 提供其 LLM 的访问能力。只要有 API Key,你几乎可以用任何 HTTP 客户端把提示词(prompt)发给模型并获取响应。虽然 Spring 的 RestTemplate、RestClient——甚至 curl、HTTPie 这样的命令行工具——都能调用这些 LLM API,但一旦你的需求超出“简单提示—简单回复”,就会发现:抽象良好的客户端能显著简化与 LLM 的复杂交互。

(稍有简化地说)Spring AI 的核心就是一个“面向多家 AI 提供商”的客户端抽象。它让简单交互变得更简单、让复杂用法也相对容易。同时它提供跨提供商与跨模型一致的接口,因此无论你的应用后端使用 OpenAI、Anthropic、Meta、MistralAI 或 Google Gemini 的模型,代码都更具可移植性

如图 1.1 所示,基于 Spring AI 的应用可以把生成请求发送给若干受支持的 AI 提供商之一的 LLM。生成的响应返回给应用,供其自由处置。Spring AI 在内部处理请求发送与响应接收的各种细节,让应用层不必操心。

image.png

图 1.1 Spring AI 协调与 AI 提供商和模型的交互。

**提示词(prompt)**本身包含要让 LLM 生成响应的自然语言文本。常见提示类型包括:

  • 需要回答的问题
  • 需要进行情感判断的消息(例如:正面还是负面?)
  • 需要被摘要的文档
  • 需要进行内容安全/合规审核的文本
  • 需要被分类的文本(如情感分类)
  • 需要据此生成的图像描述

Spring AI 能帮助你完成非常了不起的生成式 AI 能力——本书会一路带你实现。但我们得从基础开始:先写一个非常简单的 REST 服务,用 OpenAI 来回答问题。

1.1.1 初始化项目

启动一个 Spring AI 应用,和启动任意 Spring Boot 应用几乎一样。Spring AI 提供了 Spring Boot Starter 依赖与自动配置,可以让你从 0 到可运行应用的路径非常短。

初始化 Spring Boot 项目的方式有很多:如 IntelliJ IDEA 的 Spring Boot 支持、Spring Tools(Eclipse / VS Code)、NetBeans、Spring Boot CLI、Spring CLI 等。无论你喜欢哪一种,它们底层都使用同一个服务:Spring Initializr(start.spring.io。如果你直接用网站初始化,请按照图 1.2 中的方式填空与选择,开启你的第一个 Spring AI 项目。

image.png

图 1.2 使用 Spring Initializr 初始化 Spring AI 项目

无论你在 start.spring.io上操作,还是通过其他前端使用 Initializr,选择项相同。本项目选择:

  • 构建工具:Gradle(Groovy)
  • Spring Boot 版本:3.5.5
  • 打包方式:JAR
  • Java 版本:24(使用 17+ 也可以)
  • 依赖:Spring Web(或 Spring Reactive Web)Spring AI 的 OpenAI starter

“Project Metadata(项目信息)”随意填写即可。贯穿本书,你将用 Spring AI 构建一个能回答各类桌游问题的应用,因此截图里项目叫 Board Game Buddy(Artifact、Name、Package Name 亦相应设置)。你也可以起其他名字。生成项目并导入 IDE 后,会得到如下 build.gradle

清单 1.1 项目的 Gradle 构建文件

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.5.5'
    id 'io.spring.dependency-management' version '1.1.7'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(24)
    }
}

repositories {
    mavenCentral()
}

ext {
    set('springAiVersion', "1.0.3")
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.ai:spring-ai-starter-model-openai'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.ai:spring-ai-bom:${springAiVersion}"
    }
}

tasks.named('test') {
    useJUnitPlatform()
}

虽然在 Initializr 里未显式指定,我们这里使用 Spring AI 1.0.3。Spring AI 旗下有许多库,因此构建中通过 BOM(bill of materials) 来统一管理所有可能用到的 Spring AI 依赖。

项目初始化完成后,开始编写使用 Spring AI 回答问题的代码。

1.1.2 提交提示词(Submitting prompts)

你的第一个 Spring AI 应用是一个简单的 REST API:接收问题,返回答案。内部通过 Spring AI 把这些问题作为提示词提交给某个 AI 服务的 LLM,让其生成答案。

应用的核心由一个服务类完成,它实现如下 BoardGameService 接口:

package com.example.boardgamebuddy;

public interface BoardGameService {
    Answer askQuestion(Question question);
}

askQuestion() 接收一个 Question 对象并返回一个 AnswerQuestion 是个简单的 Java record,承载用户提交的问题:

package com.example.boardgamebuddy;

public record Question(String question) {
}

Answer 同样是一个承载 LLM 生成答案的 Java record:

package com.example.boardgamebuddy;

public record Answer(String answer) {
}

目前它们都只有一个 String 字段,后续会按项目发展增加属性。

SpringAiBoardGameService 类(见清单 1.2)使用 Spring AI 组件与 LLM 交互,是你在本书中进行大部分工作的地方。

清单 1.2 使用 Spring AI 实现的 BoardGameService

package com.example.boardgamebuddy;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.stereotype.Service;

@Service
public class SpringAiBoardGameService implements BoardGameService {

  private final ChatClient chatClient;

  public SpringAiBoardGameService(ChatClient.Builder chatClientBuilder) {  // #1
    this.chatClient = chatClientBuilder.build();  // #2
  }

  @Override
  public Answer askQuestion(Question question) {
    var answerText = chatClient.prompt()
        .user(question.question())      // #3
        .call()
        .content();
    return new Answer(answerText);
  }

}

如上,SpringAiBoardGameService 通过构造器注入了一个 ChatClient.Builder,并据此构建出 ChatClientChatClient 是 Spring AI 提供的若干客户端之一,用于与 AI 服务交互;它适用于最常见的文本生成场景——提交文本、获得文本响应。

askQuestion() 是该服务的唯一方法:它接收 Question,使用 Spring AI 的 ChatClient 把问题作为提示词提交给 LLM。通过流式(fluent)接口构建提示词、提交并获取答案。

askQuestion() 里,提示词通过 ChatClient 的流式接口按如下步骤构建:

  1. 调用 prompt(),开始定义提示词;
  2. 调用 user() 指定“用户”角色的消息(第 3 章你会看到还有 system() 用于“系统”角色消息);
  3. 调用 call(),结束提示词定义、准备提交给 LLM;
  4. 调用 content(),提交并以 String 返回响应内容(答案)。

最后把答案文本封装进 Answer 返回。

接下来通过 REST API 暴露 SpringAiBoardGameService。下面的控制器 AskController 负责处理该工作。

清单 1.3 使用 BoardGameService 的控制器

package com.example.boardgamebuddy;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AskController {

  private final BoardGameService boardGameService;

  public AskController(BoardGameService boardGameService) {  // #1
    this.boardGameService = boardGameService;
  }

  @PostMapping(path="/ask", produces="application/json")
  public Answer ask(@RequestBody Question question) {
      return boardGameService.askQuestion(question);  // #2
  }

}

AskController 是一个相当直观的 Spring MVC 控制器:它处理对 "/ask"POST 请求,请求体中的 question 属性会绑定到 Question 记录里的同名字段;随后把 Question 交给注入的 BoardGameService#askQuestion(),并把返回的 Answer 原样返回给调用方。

到这里,你几乎可以启动应用并试一把了。但在此之前还有一件重要的事情。

OpenAI 要求请求中必须包含 API Key,因此你需要在 platform.openai.com/api-keys 申请自己的 Key(若尚无账号需先注册登录)。

关于生成式 AI 的计费
生成式 AI 有点像“魔法”,而正如美剧《童话镇》里的 Rumplestiltskin 所说:“所有魔法都要付出代价。”好在大多数 LLM 的计费按用量计,单位是每 1,000 个 token 的分厘(token 是词片段,粗略≈0.75 个英文单词)。简单的提示与回答只有几百个 token,账单增长不会太快。即便如此,还是要关注自己的用量,避免“惊喜账单”。

做个参照:**《美国独立宣言》**约 1,695 个 token,《Spring in Action(第六版)》大约 200K 个 token。以作者写作时 OpenAI 报价估算,把后者整本作为单次 Prompt 发给 GPT-4o-mini 大约花 3 美分。(当然这在一次对话里做不到,因为上下文窗口仅 128K token。)

可选地,你也可以通过 Ollama 在本地运行一些模型,完全免费。本章稍后会演示。

登录后点击 Create a New Secret Key,命名后创建。请妥善保存生成的 Key——OpenAI 之后不会再显示完整值。建议放入 LastPass、1Password 等密码库中,既能取用也能保密。

拿到 API Key 后,需要告诉 Spring AI,以便它在每次请求中携带。最直观的方法是在 application.properties 里设置:

spring.ai.openai.api-key=sk-BSMKiIVJM1ck3yM0u53p1K3yZZOINYUiCeC

虽然简单,但并不推荐:一旦把代码提交到版本库,你的私钥也会一并提交。更好的方式是把 Key 设置到名为 SPRING_AI_OPENAI_API_KEY 的环境变量(Spring Boot 会把它视作 spring.ai.openai.api-key 的等价项)。例如在 macOS / Linux:

export SPRING_AI_OPENAI_API_KEY=sk-BSMKiIVJM1ck3yM0u53p1K3yZZOINYUiCeC

Windows:

set SPRING_AI_OPENAI_API_KEY=sk-BSMKiIVJM1ck3yM0u53p1K3yZZOINYUiCeC

你需要在运行应用的所有环境里设置该环境变量,但这样在把代码提交到版本库时,API Key 不会被暴露

至此已经万事俱备。动手试之前,不妨先为 SpringAiBoardGameService 写一个测试。

1.1.3 编写测试(Writing a test)

测试是任何软件项目的重要组成部分。但生成式 AI 在测试上带来了不同于其他项目的挑战:当你向 LLM 发送补全请求时得到的响应是非确定性的,这让你很难针对返回结果写出断言。简而言之,在测试你的生成式 AI 代码时,没有一个简单的 isEqualTo() 可以用。

在第 2 章你将学习评估器(evaluators) ,并了解如何用它们来断言你从 LLM 得到的响应与期望答案在合理范围内等价。不过现在,我们想写一个测试,断言 SpringAiBoardGameServiceWireMockTests 能对 LLM 返回的“任何响应”做出正确处理。既然你无法让 LLM 变成确定性的,那就用一个可确定的东西来替换它

为此,你可以使用 WireMockwiremock.org/)来模拟 OpenAI API 的行为,让它返回一个已知的固定响应,而不是由 LLM 生成的响应。第一步是在项目构建中添加如下测试依赖:

testImplementation 'org.wiremock.integrations:wiremock-spring-boot:3.9.0'

有了 WireMock,就可以编写测试本身了。下面的清单展示了用于测试 SpringAiBoardGameServiceaskQuestion() 方法的代码。

清单 1.4 使用 WireMock 模拟 OpenAI API 的行为

package com.example.boardgamebuddy;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder;
import com.github.tomakehurst.wiremock.client.WireMock;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.io.Resource;
import org.wiremock.spring.ConfigureWireMock;
import org.wiremock.spring.EnableWireMock;

import java.io.IOException;
import java.nio.charset.Charset;

@EnableWireMock(
    @ConfigureWireMock(baseUrlProperties = "openai.base.url"))
@SpringBootTest(
    properties = "spring.ai.openai.base-url=${openai.base.url}")
public class SpringAiBoardGameServiceWireMockTests {

  @Value("classpath:/test-openai-response.json")
  Resource responseResource;

  @Autowired
  ChatClient.Builder chatClientBuilder;

  @BeforeEach
  public void setup() throws IOException {
    var cannedResponse =
        responseResource.getContentAsString(Charset.defaultCharset());
    var mapper = new ObjectMapper();
    var responseNode = mapper.readTree(cannedResponse);
    WireMock.stubFor(WireMock.post("/v1/chat/completions")
        .willReturn(ResponseDefinitionBuilder.okForJson(responseNode)));
  }

  @Test
  public void testAskQuestion() {
    var boardGameService =
        new SpringAiBoardGameService(chatClientBuilder);
    var answer =
        boardGameService.askQuestion(
            new Question("What is the capital of France?"));
    Assertions.assertThat(answer).isNotNull();
    Assertions.assertThat(answer.answer()).isEqualTo("Paris");
  }
}

类级别的 @EnableWireMock 注解不仅启用了 WireMock 测试,还建立了一个名为 openai.base.url 的属性来保存模拟 API 的 Base URL。随后,在 @SpringBootTest 注解中使用该属性来设置 spring.ai.openai.base-url —— 这是 Spring AI 的配置项,用于覆盖默认的 OpenAI Base URL。

接着你会注意到,测试通过 @Valueclasspath 注入了一个预定义的 JSON 响应。这个响应资源在 setup() 方法中用于为模拟 API 的补全端点打桩(stub) 。桩的定义是:如果对模拟 API 的 /v1/chat/completions 发出 POST 请求,就返回这份预定义的 JSON。

这份预定义的 JSON 是你可能从 OpenAI API 得到的典型响应

{
  "id": "chatcmpl-BOVzVYyegszUuxVuOwVsCtaslIxs2",
  "object": "chat.completion",
  "created": 1745182545,
  "model": "gpt-4.5-preview-2025-02-27",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "Paris",
        "refusal": null,
        "annotations": []
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 11,
    "completion_tokens": 13,
    "total_tokens": 24,
    "prompt_tokens_details": {
      "cached_tokens": 0,
      "audio_tokens": 0
    },
    "completion_tokens_details": {
      "reasoning_tokens": 0,
      "audio_tokens": 0,
      "accepted_prediction_tokens": 0,
      "rejected_prediction_tokens": 0
    }
  },
  "service_tier": "default",
  "system_fingerprint": null
}

如你所见,content 属性被硬编码"Paris"。其它字段对我们的测试基本无关紧要,但保留它们可以让响应更完整逼真

最后,testAskQuestion() 方法就是执行断言的地方:它先用注入的 ChatClient.Builder 创建 SpringAiBoardGameService 实例,然后调用 askQuestion() 询问“法国的首都是什么?”。随后两个断言确保返回的 Answer 非空,并且其 answer 字段内容为 "Paris"

运行测试试试吧。如果一切顺利,你会看到一片“绿”。

现在到了你期待的时刻:启动应用,亲自试用

1.1.4 试运行(Trying it out)

就像初始化 Spring Boot 项目有多种方式一样,运行 Spring Boot 应用也有多种方法。你已经有偏好吗?照用即可。没有的话,Spring Boot 的 Gradle 插件让这一切变得非常简单:在命令行里执行

$ ./gradlew bootRun

应用启动后,用你喜欢的 HTTP 客户端向 http://localhost:8080/ask 发送 POST 请求,请求体是包含 question 的 JSON。以下是用知名工具 curlcurl.se/)提交的示例:

$ curl localhost:8080/ask \
    -H"Content-type: application/json" \
    -d'{"question":"Why is the sky blue?"}'
{"answer":"The sky appears blue because of the way the Earth's atmosphere
scatters sunlight. When sunlight travels through the atmosphere, it collides
with molecules and particles in the air. These collisions cause the sunlight
to scatter in all directions. Blue light has a shorter wavelength and scatters
more easily than other colors, which is why we see the sky as blue during
the day."}

如果你更喜欢 HTTPiehttpie.io/)命令行工具,下面的命令可以达到相同效果:

$ http :8080/ask question="Why is the sky blue?" -b
{
    "answer": "The sky appears blue during the day because of the way the
    Earth's atmosphere scatters sunlight. The shorter blue wavelengths of
    light are scattered in all directions by the gases and particles in the
    atmosphere. This is known as Rayleigh scattering. This scattering causes
    the blue light to dominate our view of the sky, making it appear blue to
    our eyes."
}

HTTPie 默认主机名为 localhost,并假定请求与响应体都是 JSON;它会把命令中的 question 参数映射为请求体 JSON 的 question 字段。-b 表示只打印响应体;如果省略,则还会显示响应头。

除了展示两种发送 POST 请求的方式,这两个命令行示例还说明:得到的响应并不相同。这种差异不是因为 HTTP 客户端不同。事实上,你用相同或不同客户端,多次提交完全相同的请求,也可能得到不同的结果。

原因在于:生成式 AI 是非确定性的。它的回答是概率性的,会给出在统计上更可能跟随所给提示的内容。

到这里,你已经拥有了一个非常简单的 问答应用,它使用 Spring AI 来生成响应。Spring AI 还能做的远不止这些;在本书的后续章节里我们会一一探索。但在继续之前,让我们稍作停顿,看看 Spring AI 可用的 AI 服务与模型,以及如何为你的应用选择一个合适的组合。

1.2 选择模型(Choosing a model)

在本章一开始创建项目时,你选择了 Spring AI 的 OpenAI starter。自此之后,你在应用中提交的问题,底层都是通过 OpenAI 的 REST API 获得回答的。

OpenAI 兼容性

尽管大多数 AI 服务商都有各自的专有 API,但许多服务商提供 与 OpenAI 兼容 的 API(作为其自身 API,或作为替代接口)。例如 Groqgroq.com/)和 Google Gemini 等服务商,vLLMdocs.vllm.ai/)与 LiteLLMwww.litellm.ai/)等工具,甚至 Ollama,都提供与 OpenAI API 大体兼容 的接口。你可以像对接 OpenAI 一样,使用 Spring AI 的 OpenAI starter 集成这些 API。

此外,Spring AI 默认选择 OpenAI 的 gpt-4o-mini 模型。它是 OpenAI 最受欢迎的模型之一,具备很强的自然语言理解与生成能力,并在海量数据上训练,能够回答你几乎提出的任何问题。

Spring AI 还提供若干可选的 AI 服务,包括:

  • Amazon Bedrock——由亚马逊云平台提供的 AI 服务,涵盖 Claude、Llama、Mistral、Titan 等模型。
  • Anthropic——由前 OpenAI 成员创立,提供 Claude 系列模型。
  • Azure OpenAI——与 OpenAI 模型基本一致,但通过 微软 Azure 平台提供。
  • Google AI——由谷歌云平台提供,包含 Google Gemini 模型。
  • Hugging Face——提供超过 30 万个模型 的仓库;Spring AI 的集成走的是 Hugging Face 的云端 API。
  • MiniMax——中国的 AI 服务商,提供多款模型(含多语种)。
  • MistralAI——由前 Meta、Google 成员创立,提供多款实力强劲的 LLM(如知名的 Mistral 7B)。
  • Ollama——可在本地硬件上免费运行多种开源模型,其中不少也是云端服务商常见的热门模型。

这些服务都提供适合文本生成的模型;部分还支持多模态生成(图像、语音等——第 7 章会探讨)。多数还提供向量嵌入 API,可把文本转成数学表示以计算文本间的相似度——这在第 4 章介绍 RAG(检索增强生成) 时会很重要。

如果这份清单还不够让人眼花缭乱,那么你需要知道:生成式 AI 版图在持续变化,新的服务与模型层出不穷、彼此竞逐超越。在如此多的选择面前,如何挑选服务与模型?虽然没有唯一正确的选法,但在选型时可以考虑以下几个维度:

  • 价格——多数(尤其云端)选项是按量计费。很多很便宜,但仍需考虑预算;也有免费选项,比如通过 Ollama 提供的模型。
  • 上下文窗口(context window) ——在处理提示与生成响应时,文本会被拆为 token。token 如何切分不重要,关键是允许的上限。不同模型的上下文窗口差异很大,从每次交互几千到数百万 token 不等。简单问答不敏感,但一旦你把对话历史文档上下文加入提示,就必须确保不超限。
  • 训练数据——模型之间最大的差异来自训练数据。有的在超大语料上训练,有的在较小但更聚焦的语料上训练;此外,训练截断时间也影响模型对最新信息的掌握。
  • 能力特性——某些提供商/模型支持流式输出、**工具调用(function/tool calling)**等扩展能力;这些并非所有 LLM 的“通用能力”,需要根据你的应用是否需要来考量。

由于新模型价格/上下文窗口/能力都在频繁变化,本书不拘泥于具体数值;请以各提供商官网为准,查看最新可用模型与规格

无论你选什么,对应用代码最主要的影响只是:你在构建中添加哪个 starter 依赖,以及如何在应用配置里设置凭据与选项。你基于 ChatClient 或大多数其他 Spring AI 组件写的业务代码基本一致

1.2.1 配置 OpenAI 模型(Configuring OpenAI models)

本章示例使用了 Spring AI 的 OpenAI 集成。你已经看到如何把它加到 Spring Boot 工程里。回顾一下,对接 OpenAI 使用的 starter 是:

implementation 'org.springframework.ai:spring-ai-starter-model-openai'

如前所述,这个 starter 自带自动配置,启用 ChatClient.Builder:你可以直接注入 Builder,据此创建 ChatClient,然后调用 prompt() 构建并提交请求,快速上手。

默认情况下,Spring AI 的 OpenAI 模块使用 GPT-4o mini 模型,这个模型很能打。但它并非唯一选项,你或许会考虑改用其他模型。

OpenAI 会不定期更新/新增模型。要查看当前可用模型,请访问
platform.openai.com/docs/models 或调用 models 端点:

$ http -A bearer -a $SPRING_AI_OPENAI_API_KEY \
     https://api.openai.com/v1/models

OpenAI 提供的模型中包括 GPT-4.1 miniGPT-4.1 nanoGPT-4.1 等。Spring AI 默认用 GPT-4o mini,但对于更复杂的任务你也许会考虑选择更强GPT-4.1;或者为了更省成本试试 GPT-4.1 nano

要使用 GPT-4.1GPT-4.1 nano 或 OpenAI 的其他模型,设置属性 spring.ai.openai.chat.options.model。例如覆盖默认值去使用 GPT-4.1 nano

spring.ai.openai.chat.options.model=gpt-4.1-nano

除了直接通过 OpenAI 使用其模型,你也可以通过 Microsoft Azure 访问 OpenAI 模型。需要前往 azure.microsoft.com/ 注册并在 Azure 门户获取 API Key。

使用 Azure OpenAI 时,Spring AI 需要不同的 starter 依赖

implementation
    'org.springframework.ai:spring-ai-starter-model-azure-openai'

相应地,指定 Azure OpenAI 的 API Key 的属性名也不同(包含 azure):

spring.ai.azure.openai.api-key=sk-BSMKiIVJD0n4ldDuck2p1K3yZZOINYUiCeC

或用环境变量:

export SPRING_AI_AZURE_OPENAI_API_KEY=BSMKiIVJD0n4ldDuck2p1K3yZZOINYUiCeC

还要注意:尽管 Azure OpenAI 提供的是 OpenAI 的模型,但它是与 OpenAI 分离的提供商。因此你的 OpenAI API Key 不能用于 Azure OpenAI。如果要用 Azure OpenAI,需要在 mng.bz/X7e1 注册并从微软获取 API Key。

要选择非默认模型,设置:

spring.ai.azure.openai.chat.options.deployment-name=gpt-4

如上所示,spring.ai.azure.openai.chat.options.deployment-name 告诉 Spring AI:把提示发送给 GPT-4,而不是默认的 GPT-4o-mini

1.2.2 使用 Ollama 在本地部署模型(Serving models locally with Ollama)

还有一个非常有吸引力的选项,尤其适合开发场景:使用 Ollamaollama.com)。Ollama 是一款出色的工具,能让你在自己的机器上本地、免费运行多种开源 LLM。Ollama 上最受欢迎的一些 LLM 包括 Meta 的 Llama、Google 的 Gemma、阿里巴巴的 Qwen,以及 MistralAI 的 Mistral 7B

在你的机器上下载并安装 Ollama 之后,需要**拉取(pull)**想要使用的一个或多个模型。使用命令行工具 ollama 可以很方便地把模型拉到本地。例如,在本地安装 Gemma 2B 模型可以使用:

$ ollama pull gemma:2b

同样地,如果你想使用 MistralAI 的 Mistral 7B 模型,则运行:

$ ollama pull mistral:7b

LLM 并非“一把梭”

请注意,模型的性能取决于模型大小以及运行 Ollama 的硬件。例如,我在几年前的 MacBook Pro 上运行 Gemma 2B 完全没问题,但 Gemma 7B 在生成响应时明显更慢、占用内存也更多。

请参考 Ollama 官方的可用模型列表ollama.com/library)了解有哪些模型可用。要查看你机器上已安装的模型列表,使用:

$ ollama list

或者,想获取本机已安装模型的更多细节,可以向 Ollama API 的 /api/tags 端点发送 GET 请求:

$ http http://localhost:11434/api/tags -b

当你已拉取了一个或多个模型,且 Ollama 在本机运行后,就可以在项目中通过 Spring AI 的 Ollama starter 使用这些模型:

implementation 'org.springframework.ai:spring-ai-starter-model-ollama'

与 Spring AI 支持的其他 AI 提供商不同,使用 Ollama 无需获取或设置 API Key。原因在于 Ollama 并非像 OpenAI 那样的云端服务;它运行在你的本地机器上,因此不需要访问凭据。

使用 Ollama 时,Spring AI 默认选择 Mistral 7B。如果你想用别的模型,需要通过属性 spring.ai.ollama.chat.model 指定,例如:

spring.ai.ollama.chat.model=gemma:2b

这会让 Spring AI 使用运行在你本地 Ollama 上的 Gemma 2B 模型。

Spring AI 还为 Ollama 模型提供了一个特别能力:你可以不手动执行 ollama pull,而是让 Spring AI 替你拉取模型。为此,设置属性 spring.ai.ollama.init.pull-model-strategy。比如,如果希望在模型未安装时自动拉取,可以这样配置:

spring.ai.ollama.init.pull-model-strategy=when_missing

你也可以把它设置为 always,让应用每次启动都拉取模型。默认值是 never,即从不自动拉取。

虽然让 Spring AI 自动拉取 Ollama 模型很方便,但它会增加应用启动时间。在生产环境中通常应避免使用,建议只在开发与测试阶段启用。

尽管 Spring AI 支持多种 AI 服务与模型,我们仍需要为本书的大多数示例做出一个“主线选择”。因此,除非特别说明,本书示例将以 OpenAI(或 Azure OpenAI)的 gpt-4o-mini 为主,同时在 Ollama 场景下使用 Gemma、Llama 或 Mistral 模型。我们鼓励你尝试其他模型,但请注意:结果与体验可能会有所差异

1.3 预览 Spring AI 的能力(Previewing Spring AI’s capabilities)

在本章中,你创建了一个非常基础的 Spring AI 应用:向 LLM 发送简单的文本提示,并打印文本响应。再简单不过了。但如果 Spring AI 仅止于此,你大可不必读这本书(而我也不会写它)。接下来的章节会看到,Spring AI 还能做得更多。

对于更高级的场景,你发送的提示往往会比一句问句更长、更复杂。你会希望提供更详细的指令来约束 LLM 如何作答,同时还要提供数据或其他上下文,以便 LLM 在生成时可据此参考。为让复杂提示更易管理,Spring AI 提供了提示模板(prompt templates) :在模板中放置占位符,用参数值与上下文来填充。

很多时候,与 LLM 的交互并非“一问一答”。当对话变成来回交流时,保存对话历史就很重要,以便 LLM “记住”先前说过什么。Spring AI 让你能轻松维护聊天历史,并在发送提示时将其作为上下文提供。

所有模型都只在某个时间点之前的数据上训练,了解此后发生的事件。并且,你的项目可能需要询问保密文档中的内容,而这些并不在模型训练语料内。为弥补这些空缺,可以在 Spring AI 中使用检索增强生成(RAG) :借助 RAG,你可以就自己的文档提出问题、进行对话。

虽然 RAG 非常适合非结构化文档,但有些场景你会想把生成式 AI 与应用自身功能整合起来,例如查询数据库执行操作(如下单)。对此,Spring AI 能与部分模型(OpenAI、Mistral、Google)的工具(tools)能力协作。此外,应用 Spring AI 对 MCP(Model Context Protocol,模型上下文协议) 的支持,还可以把一组相关工具作为模块来使用。

很多用户并不满足于只问一次、答一次。尽管 LLM 自身不会记得过去的交互,但通过 Spring AI 的聊天记忆(chat memory) ,你可以让你的应用支持连续对话

Spring AI 不止能与 LLM 交互回答问题。借助 Spring AI,你还能在文本上做更多事:例如情感分析内容审核文档摘要文本分类等。

最后,文本提示—文本响应只是生成式 AI 的一个方面。Spring AI 还可以帮助你以文生图将音频转写为文本,以及其他许多让你的 AI 应用更出彩的方式。

以上这些,都是你在读完本书之前会用 Spring AI 做到的事。系好安全带,这会是一段令人兴奋的旅程!

小结(Summary)

  • Spring AI 为基于 Spring Boot 开发的应用带来生成式 AI 能力
  • Spring AI 提供一致接口客户端抽象,不论你使用哪个AI 提供商/LLM
  • Spring AI 集成了多家主流提供商:OpenAI、Azure OpenAI、Anthropic、MistralAI、Google、Amazon Bedrock
  • Spring AI 应用也可通过 Ollama 使用本地运行的 LLM。