精通Spring Boot 3 : 15. Spring Boot 新项目

在本章中,我们将回顾两个基于 Spring Boot 的新 Spring 项目,这些项目引入了新技术:Spring Modulith 和 Spring AI。Spring Modulith 可以帮助您使用 Spring Boot 构建模块化应用,而 Spring AI 则可以帮助您创建一个开箱即用的 OpenAI/ChatGPT 接口。

模块化

想象一下构建一个复杂的城市,不是逐砖逐瓦地建造,而是通过组装预设计的区域,每个区域都有其独特的功能和特点。这就是 Spring Modulith(
https://spring.io/projects/spring-modulith)构建软件的核心理念。就像 Spring Boot 是一个有明确观点的运行时(在第一章中讨论过),Spring Modulith 则是一套有明确观点的工具,旨在帮助您从技术和功能两个方面来组织您的应用程序。

Spring Modulith 就像 Spring Boot 为技术基础提供蓝图一样,帮助您将应用程序的核心功能结构化为独立且相互作用的模块。这种方法使您的应用程序更加模块化和灵活,能够根据业务需求的变化轻松更换或更新各个模块。

简而言之,Spring Modulith 帮助您构建更易于变更和与业务共同发展的软件,就像城市能够适应居民的需求一样。

Spring模块与微服务的比较

Spring Modulith 和微服务的目标都是高效地构建复杂应用,但它们的方法各不相同。要理解这些差异,可以将其比作建设一个大都市。

Spring Modulith 方法就像在一个城市中划分出明确的区域。每个区域都有其特定的功能(例如购物、住宅、工业),并通过明确的通道(如道路和桥梁)与其他区域进行互动。在 Spring Modulith 中,每个模块也有其独特的功能,并通过这些通道与其他模块进行交互。这种方法的优点包括

  • 更加简化的开发和部署:您可以构建并部署应用程序,从而简化初始设置和维护工作。
  • 简化复杂性:模块之间的通信在内部进行,避免了微服务中网络调用带来的额外开销。
  • 更快的迭代:可以在模块内进行更新,而不影响整个系统,这样可以更迅速地进行更改。

相比之下,微服务架构就像是建立完全独立的城市。每个城市(微服务)都是自给自足的,并通过类似于城市间交互的 API 与其他城市(微服务)进行沟通。这种方法的好处包括:

  • 高度可扩展:每个服务可以根据自身需求独立扩展,从而使整个系统更加灵活。
  • 技术独立:不同的服务可以采用不同的技术,从而促进创新和灵活性。
  • 韧性:一个服务的故障不会影响整个系统的运行,从而增强了系统的容错能力。

不过,微服务也存在以下一些缺点:

  • 复杂性增加:开发、部署以及服务之间的沟通变得更加繁琐。
  • 性能开销:服务之间的网络调用可能会导致延迟和增加复杂性。
  • 分布式复杂性:在多个服务中,调试和监控变得更加困难。

选择合适的方式

最佳方案取决于您的具体需求。Spring Modulith 非常适合:

  • 小型应用程序或开发初期阶段:由于其简单性和快速迭代,非常适合启动项目。
  • 功能紧密耦合的应用:当模块之间高度依赖时,Spring Modulith 的内部通信会更加高效。
  • 技术资源有限:Spring Modulith 的集中部署和配置在小团队中更易于管理。

微服务更适合于:

  • 大型复杂应用程序的独立功能:当每个服务能够独立运行时,微服务能够提供更好的可扩展性和韧性。
  • 拥有多样化技术专长的团队:微服务可以为不同的服务采用不同的技术,从而发挥团队的优势。
  • 对高可用性和容错性的需求:微服务的分布式架构能够有效减少单个服务故障带来的影响。

最终,最佳的方法取决于您项目的需求和限制。在构建软件系统之前,请考虑简单性与灵活性之间的权衡!

基础原理

要在您的项目中使用 Spring Modulith,您需要将以下内容添加到 build.gradle 文件中,并引入您将使用的一些 Spring Modulith 库:

dependencyManagement {
    imports {
        mavenBom 'org.springframework.modulith:spring-modulith-bom:1.1.2'
    }
}
dependencies {
//...
implementation 'org.springframework.modulith:spring-modulith-starter-core'
implementation 'org.springframework.modulith:spring-modulith-starter-jpa'
testImplementation 'org.springframework.modulith:spring-modulith-starter-test'
//...
}

Spring Modulith 帮助开发者将 Spring Boot 应用程序组织成称为模块的逻辑构建块,并提供相关工具。

  • 验证结构:确保模块组织合理,并遵循最佳实践。
  • 记录安排:编写清晰的文档,说明各模块之间的交互方式。
  • 独立测试模块:对各个模块进行集成测试,而无需依赖整个应用程序。
  • 监控模块之间的交互:观察模块在运行时的通信和行为。
  • 促进松散耦合:鼓励模块之间的互动,尽量避免紧密依赖。

Spring Boot 应用可以被组织成多个模块,每个模块专注于特定的功能。这些模块包含三个关键部分:

  • 公共接口:这类似于一个服务菜单,提供其他模块可以访问的功能(以 Spring bean 的形式实现)和事件。
  • 内部运作:这里是模块魔法发生的“厨房”,与其他模块隔离。
  • 依赖关系:模块就像食谱中的配料一样,依赖于其他模块提供的功能(豆子)、事件和配置设置。

Spring Modulith 提供了多种构建模块的方法,复杂性各不相同。这使得开发者可以从简单的功能开始,逐步根据需要添加更高级的特性。

理解 Spring Boot 模块包

让我们来看看 Spring Modulith 是如何处理您的代码包的:

  • 主包:这是您的主应用程序类所在的位置,通常会使用@SpringBootApplication 注解,并包含启动应用程序的主方法。
  • 子包:任何直接位于主包下的包都被视为应用模块包。如果没有子包,则主包本身将成为模块。
  • 代码可见性:通过使用 Java 的包作用域,模块包中的代码对其他包是不可见的。这意味着类无法直接注入到其他模块中,从而促进了松散耦合。
  • 模块 API:默认情况下,模块包中的公共类构成其 API,供其他模块使用。这个 API 定义了如何与模块进行交互。

作为例子,考虑一下用户应用的结构:

Users
└─  src/main/java
   ├─  com.apress.users
   |  └─  UsersApplication.java
   └─  com.apress.users.model
      ├─  User.java
      └─  UserRole.java

Spring Modulith 将 com.apress.users.* 视为一个用户应用模块。

让我们继续完整的解决方案,体验一下 Spring Modulith 的实际效果。

在我的复古项目中使用 Spring Modulith

在整本书中,我们一直在研究两个应用程序,用户应用和我的复古应用。现在是时候将它们整合成一个模块化应用,看看 Spring Modulith 如何帮助我们构建一个强大而模块化的单一应用。

我们的 My Retro App 解决方案将把两个项目合并为一个。完整的源代码可以在
15-new-techs/myretro-modulith 文件夹中找到,您可以将其导入到您喜欢的 IDE 中。

如果你想从头开始,你需要创建一个名为 myretro-modulith 的文件夹,将 myretro 和 users 包复制到同一个 src/ 文件夹中,并将 MyretroApplication 类移动或创建到 com.apress 包中。请删除任何其他包含 @SpringBootApplication 注解的类(例如 UsersApplication)。最终你应该得到一个如图 15-1 所示的结构。

图 15-1 我 retro-modulith 解决方案的结构示意图

图 15-1 展示了我们将要使用的最终结构。请打开或创建 build.gradle 文件。参见清单 15-1。

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.2'
    id 'io.spring.dependency-management' version '1.1.4'
    id 'org.hibernate.orm' version '6.4.1.Final'
}
group = 'com.apress'
version = '0.0.1-SNAPSHOT'
java {
    sourceCompatibility = '17'
}
configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}
repositories {
    mavenCentral()
    maven {
        url 'https://maven.pkg.github.com/felipeg48/myretro-spring-boot-starter'
        credentials {
            username = project.findProperty("GITHUB_USERNAME") ?: System.getenv("GITHUB_USERNAME")
            password = project.findProperty("GITHUB_TOKEN") ?: System.getenv("GITHUB_TOKEN")
        }
    }
}
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    // H2 runtime only
    runtimeOnly 'com.h2database:h2'
    // Modulith
    implementation 'org.springframework.modulith:spring-modulith-starter-core'
    implementation 'org.springframework.modulith:spring-modulith-starter-jpa'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
    // Web
    implementation 'org.webjars:bootstrap:5.2.3'
    // Test
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.modulith:spring-modulith-starter-test'
}
dependencyManagement {
    imports {
        mavenBom 'org.springframework.modulith:spring-modulith-bom:1.1.2'
    }
}
tasks.named('test') {
    useJUnitPlatform()
}
test {
    testLogging {
        events "passed", "skipped", "failed"
        showExceptions true
        exceptionFormat "full"
        showCauses true
        showStackTraces true
        // Change to `true` for more verbose test output
        showStandardStreams = true
    }
}

列表 15-1 的 build.gradle 文件

列表 15-1 显示我们正在使用依赖管理部分,并声明了
spring-modulith-starter-core、
spring-modulith-starter-jpa 和
spring-modulith-starter-test 这几个依赖项,以确保我们的应用程序遵循所需的模块化结构。

再次查看图 15-1,请注意 com.apress.myretro 和 com.apress.users 包顶部的 package-info java。这些信息对于基于 Java 9 的模块化非常有用,可以将其作为模块化应用程序的一部分使用。请参见清单 15-2 和 15-3。

@org.springframework.lang.NonNullApi
package com.apress.myretro;

15-2 src/main/java/com/apress/myretro/package-info.java

@org.springframework.lang.NonNullApi
package com.apress.users;

15-3 src/main/java/com/apress/users/package-info.java

通常,package-info.java 可以帮助指定包级的可见性修饰符,或为您自己的框架或库声明自定义注解,同时也可以为 FindBugs、Lombok、Spring Security 和 Spring Modulith 等工具设置默认注解。

接下来,在您的 Spring Boot 应用中使用 Spring Modulith 的一个好处是它提供了模块验证测试。让我们来看看。请创建或打开 ModularityTests 类。请参阅列表 15-4。

package com.apress;
import org.junit.jupiter.api.Test;
import org.springframework.modulith.core.ApplicationModules;
import org.springframework.modulith.docs.Documenter;
public class ModularityTests {
    ApplicationModules modules = ApplicationModules.of(MyretroApplication.class);
    @Test
    void verifiesModularStructure() {
        modules.verify();
    }
    @Test
    void createApplicationModuleModel() {
        ApplicationModules modules = ApplicationModules.of(MyretroApplication.class);
        modules.forEach(System.out::println);
    }
    @Test
    void createModuleDocumentation() {
        new Documenter(modules).writeDocumentation();
    }
}

列表 15-4 源代码:
src/main/test/com/apress/ModularityTests.java

在清单 15-4 中,首先要审查的是 ApplicationModules 类,它将设置我们需要了解的关于应用程序及其模块化程度的所有内容。我们需要传递主应用程序的名称,在这种情况下是 MyretroApplication 类(@SpringBootApplication 声明的位置)。

让我们逐个运行测试,看看结果。我们先从 verifiesModularStructure 测试开始。

./gradlew test --tests ModularityTests.verifiesModularStructure
> Task :test
ModularityTests > verifiesModularStructure() PASSED
BUILD SUCCESSFUL in 2s
5 actionable tasks: 5 executed

请记住,通常我们可以通过请求 getAllUsers 来从 My Retro App 与 Users App 进行通信,方法是访问 /users 端点。不过,现在我们可以直接从 My Retro App 与 Users App 进行通信,而不需要依赖 /users 端点。问题是,这样做可以吗?

让我们设想一下,每当有新的用户被保存到数据库中时,我们就会发出一个事件,表示该用户已被保存,对吗?

因此,在
com.apress.users.service.UserService 类的 saveUpdateUser 方法中,请使用以下代码:

private ApplicationEventPublisher events;
@Transactional
public User saveUpdateUser(User user) {
   User userResult = this.userRepository.save(user);
   // Only when the user is saved do we publish the event
   events.publishEvent(user);
   return userResult;
}

你已经了解了 ApplicationEventPublisher 类及其发布事件的方式,这里我们只是发布用户信息。

然后,在
com.apress.myretro.service.RetroBoardAndCardService 类中添加以下代码:

@Async
@EventListener
public void newSavedUser (User user){
    log.info("New user saved: {} {}",user.getEmail(), LocalDateTime.now());
}

你已经知道如何使用 EventListener 了。现在,如果我们重新运行测试,将会得到以下输出:

./gradlew tests --tests ModularityTests.verifiesModularStructure
> Task :compileJava
> Task :test FAILED
ModularityTests > verifiesModularStructure() FAILED
    org.springframework.modulith.core.Violations: - Module 'myretro' depends on non-exposed type com.apress.users.model.User within module 'users'!
    User declares parameter User.newSavedRetroBoard(User) in (RetroBoardAndCardService.java:0)
    - Module 'myretro' depends on non-exposed type com.apress.users.model.User within module 'users'!
    Method  calls method  in (RetroBoardAndCardService.java:64)
    - Module 'myretro' depends on non-exposed type com.apress.users.model.User within module 'users'!
    Method  has parameter of type  in (RetroBoardAndCardService.java:0)

我们有一个传说,因以下违规行为导致失败:模块'myretro'依赖于模块'users'中未公开的类型
com.apress.users.model.User!

我们遇到了一个模块错误,因为用户位于一个内部和私有的包中,这不应该成为其他类的依赖关系。那么,我们该如何解决这个问题呢?有多种方法可以避免这些冲突,因此我们可以使用 Spring Modulith 来帮助解决这些问题。

首先,我们创建一个 UserEvent 类,用于保存一些重要信息;最后,我们可能不想传递所有用户的信息(如密码、头像、激活状态等),对吗?请参见列表 15-5。

package com.apress.users;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class UserEvent {
    private String email;
    private String action;
}

文件路径:
src/main/java/com/apress/users/UsersEvent.java(列表 15-5)

列表 15-5 显示我们正在 com.apress.users 包中创建 UserEvent 类,并确保所有其他子级都保持私有。

接下来,在 UsersService#saveUpdateUser 中,将事件更改为发送 UserEvent,而不是 User:

events.publishEvent(new UserEvent(user.getEmail(), "save"));

在 RetroBoardAndCardService#newSavedUser 中,请使用以下代码进行更改:

@Async
@TransactionalEventListener
public void newSavedUser(UserEvent userEvent){
  log.info("New user saved: {} {} {}",userEvent.getEmail(), userEvent.getAction(), LocalDateTime.now());
}

我们现在使用一个 Spring Modulith 注解,@
TransactionalEventListener。这个注解继承自 @EventListner,并在 Spring Modulith 中包含更多逻辑,使我们能够在添加依赖时将事件持久化到数据库中。

现在更改已经完成,我们来重新进行测试:

./gradlew clean test --tests ModularityTests.verifiesModularStructure
> Task :compileJava
> Task :test
ModularityTests > verifiesModularStructure() PASSED
BUILD SUCCESSFUL in 2s
5 actionable tasks: 5 executed

是的,我们的解决方案是模块化的!那么,如果我们有一些无法避免的重依赖,该怎么办呢?我们可以在 package-info.java 中进行以下操作:

@org.springframework.lang.NonNullApi
@org.springframework.modulith.ApplicationModule(
  allowedDependencies = "users"
)
package com.apress.myretro;

在这种情况下,myretro 模块中的代码只能引用 users 模块中的代码(以及未分配给任何模块的代码)。

Spring Modulith 可以打印出您的模块及其依赖关系,不仅如此,您还可以生成文档。因此,接下来,请运行 ModularityTests#
createApplicationModuleModel 测试:

./gradlew clean test --tests ModularityTests.createApplicationModuleModel
> Task :compileJava
> Task :test
ModularityTests STANDARD_OUT
    15:07:18.494 [Test worker] INFO com.tngtech.archunit.core.PluginLoader -- Detected Java version 17.0.9
ModularityTests > createApplicationModuleModel() STANDARD_OUT
    # Myretro
    > Logical name: myretro
    > Base package: com.apress.myretro
    > Spring beans:
      o ....config.RetroBoardConfig
      o ....events.RetroBoardLog
      o ....metrics.RetroBoardMetrics
      o ....persistence.RetroBoardRepository
      o ....service.RetroBoardAndCardService
      o ....web.RetroBoardController
      o io.micrometer.core.instrument.Counter
      o io.micrometer.observation.aop.ObservedAspect
      o org.springframework.boot.CommandLineRunner
      o org.springframework.web.servlet.handler.MappedInterceptor
    # Users
    > Logical name: users
    > Base package: com.apress.users
    > Spring beans:
      o ....actuator.EventsHealthIndicator
      o ....actuator.LogEventEndpoint
      o ....config.UserConfiguration
      o ....config.UserProperties
      o ....repository.UserRepository
      o ....service.UserService
      o ....stream.UserProcessor
      o ....stream.UserSource
      o ....web.UsersController
      o java.util.function.Function
      o java.util.function.Supplier
      o org.springframework.boot.CommandLineRunner
ModularityTests > createApplicationModuleModel() PASSED
BUILD SUCCESSFUL in 1s
5 actionable tasks: 5 executed

输出展示了模块及其子模块。通过这个输出,您可以更清楚地了解在需要依赖时该如何处理。

接下来,我们来运行 ModularityTests#createModuleDocumentation 测试:

./gradlew clean test --tests ModularityTests.createModuleDocumentation
> Task :compileJava
> Task :test
ModularityTests STANDARD_OUT
    15:09:46.442 [Test worker] INFO com.tngtech.archunit.core.PluginLoader -- Detected Java version 17.0.9
ModularityTests > createModuleDocumentation() PASSED
BUILD SUCCESSFUL in 1s
5 actionable tasks: 5 executed

这个测试会生成 AsciiDoc 格式的文档(https://asciidoctor.org/),类似于 markdown 语言。同时,它还会生成 PlantUML(https://plantuml.com/)代码,以便您可以绘制模块的图表。生成的文档和代码存放在
build/spring-modulith-docs 文件夹中。请参见图 15-2。

图 15-2 Spring Modulith 文档

在 JetBrains IntelliJ IDEA 中,有一个 AsciiDoc 和 PlantUML 的插件。您还需要安装 GraphViz (
https://plantuml.com/graphviz-dot),以便能够可视化图表。

运行我的复古方案

可以通过您的 IDE 或使用以下命令来运行应用程序:

./gradlew bootRun

现在检查 /h2-console 端点。使用 URL jdbc:h2:mem:myretro_db 并点击连接,您将看到不仅有 PEOPLE、CARD 和 RETRO_BOARD 表,还有 EVENT_PUBLICATION 表,其中包含 Spring Modulith 在事件发生时执行的所有操作。请参见图 15-3。


图 15-3
http://localhost:8080/h2-console - URL: jdbc:h2:mem:myretro_db

本节介绍了 Spring Modulith 的概况,以及它如何帮助您使用 Spring Boot 创建模块化应用程序。Spring Modulith 现已正式发布(GA),截至本文撰写时,版本为 1.1.2。如果您想了解更多关于 Spring Modulith 的信息,请访问
https://docs.spring.io/spring-modulith/reference/index.html。

Spring 人工智能

Spring AI (
https://spring.io/projects/spring-ai) 帮助开发者构建 AI 驱动的应用程序,避免陷入复杂性。受到 LangChain 和 LlamaIndex 等 Python 项目的启发,Spring AI 专为使用多种编程语言的开发者设计,而不仅限于 Python。

从本质上讲,Spring AI 为各种 AI 任务提供了构建模块(抽象)。这些模块可以轻松替换,让您能够在不同的工具(如 OpenAI、Azure OpenAI、Hugging Face 等)之间切换,所需的代码更改非常少。这使得您的应用程序更加灵活和适应性强。

超越基本模块,Spring AI 提供了针对常见任务的预构建解决方案。想象一下,您可以向自己的文档提问,或者拥有一个由文档驱动的聊天机器人!随着需求的增长,Spring AI 可以与其他 Spring 工具(如 Spring Integration 和 Spring Data)集成,以处理复杂的工作流程。

Spring AI 专注于开发能够处理语言输入并生成语言输出的 AI 模型。它使用的模型包括 GPT-3.5、GPT-4 等。

在撰写本文时,该项目仍处于早期阶段,版本为 0.9.0-SNAPSHOT,但已经足够成熟,可以进行尝试。

人工智能的基本概念

本节简要概述了 Spring AI 所包含的一些人工智能概念。

模型数据

可以将人工智能模型看作是从海量数据中学习的强大工具。它们能够分析信息、识别模式,甚至模仿我们的思维方式。这使得它们能够生成文本、图像和预测等内容,帮助人类在各个领域中以多种方式受益。

人工智能模型有很多种类,每种都有其独特的专长。比如 ChatGPT,它根据你输入的内容生成文本;还有一些模型可以将文本转换为图像,例如 Midjourney 和 Stable Diffusion!

Spring AI 目前专注于能够理解和用语言回应的模型。可以想象成和一个非常擅长理解和回复的朋友交谈。我们从 OpenAI 和 Azure OpenAI 开始,但未来还有更多内容!

像 ChatGPT 这样的模型有什么特别之处?它们已经经过预训练,就像在学习中有了一个起点。这使得它们更易于使用,即使你不是 AI 专家!

提示信息

想象一下和一个非常聪明的朋友交谈,但他们需要具体的指示才能理解你。这就是提示的作用!它们告诉像 ChatGPT 这样的 AI 模型该如何处理你的话。

  • 不仅仅是文本:与询问“嘿,谷歌,法国的首都是什么?”不同,ChatGPT 中的提示可以包含不同的部分,例如“给我讲个故事”(用户角色)和“从前有一个...” (系统角色),以此来设定场景。
  • 制作提示是一门艺术:这不仅仅是打字。就像与朋友交谈一样,你需要清晰表达,并引导 AI 模型朝着正确的方向发展。
  • 学习与“人工智能”交流:与人工智能模型的互动方式不同于 SQL 提问,专家们正在探索如何更有效地与这些模型沟通。这种方法被称为提示工程,它帮助我们获得更好的结果!
  • 分享技巧和窍门:人们甚至在分享他们最好的提示,研究人员也在研究如何让这些提示更加有效。
  • 这并不总是容易:就像学习一门新语言一样,掌握提示需要不断练习。即使是像 ChatGPT 3.5 这样的优秀模型也可能无法完全理解我们,但我们每天都在不断进步!
  • 请记住:提示是释放人工智能模型全部潜力的关键。通过更深入地理解这些提示,我们可以共同创造出更加惊人的成果!

提示模板集合

为人工智能模型编写有效的提示就像为一场戏剧搭建舞台一样。你需要

  1. 1.
  2. 设置场景:通过提供背景信息来说明您希望 AI 模型执行的任务。
  3. 2.
  4. 填写空白:用用户输入的具体信息替换请求中的部分内容。

接下来…

  • 可以把模板看作脚本:我们使用一个名为 StringTemplate 的工具来创建带有占位符的模板,以便填入相关细节。

例子:想象一个模板,上面写着“告诉我一个关于{猫}的{搞笑}笑话。”当有人请求笑话时,我们会用他们的输入替换占位符(例如,“告诉我一个关于小狗的搞笑笑话”)。

  • 模板就像应用程序中的视图:它们提供了一个结构,我们用数据(类似于地图)来填充这个结构,以生成最终的 AI 模型提示。
  • 提示变得越来越复杂:以前它们只是简单的文本,现在可以包含多个部分,AI 模型可以承担不同的角色。

嵌入表示

想象一下,你是一名 Java 开发者,想要为你的应用添加 AI 功能。你可能会遇到“嵌入”这个术语,听起来很复杂。但别担心!你不需要成为数学天才就能使用嵌入技术。

这里是要点:嵌入技术将文本(例如句子)转换为数字(例如数组),这帮助人工智能模型“理解”文本的含义。可以把它看作是将单词翻译成人工智能模型所能理解的语言。

这种转换在以下任务中尤其有用:

  • 寻找相似的事物:想象一下你有一个产品推荐系统。嵌入技术可以帮助人工智能模型根据文本描述中的“含义”将相似的产品归类在一起。
  • 文本分类:想要自动将电子邮件分为垃圾邮件和重要邮件吗?嵌入技术可以帮助人工智能模型理解电子邮件内容的含义,从而进行准确分类。

可以把它想象成一张地图:AI 模型使用这些数字(嵌入)来探索意义的地图,而不是使用单词。相似的单词和句子在这张地图上更为接近,这使得 AI 模型更容易识别它们之间的联系和关系。

因此,尽管嵌入的“如何”很复杂,但理解它们的“是什么”和“为什么”对于在您的 Java 应用程序中有效使用它们至关重要。它们可以成为为您的项目增添智能和功能的强大工具!

令牌

AI 模型有点类似于文字处理软件:

  • 输入:AI 模型将句子拆分成称为“标记”的较小单元,类似于单词。一个标记大约相当于一个单词的四分之三。
  • 处理:AI 模型在内部使用这些标记来理解您的文本含义。
  • 最后,它将这些令牌转换回单词,从而给出答案。

将令牌看作是 AI 模型的“货币”:

  • 您使用 AI 模型时,根据输入和输出的令牌数量支付费用。
  • 每个模型都有一个限制,称为上下文窗口,表示它一次可以处理的令牌数量。可以把它想象成一个短消息框!
  • 不同的模型有不同的限制:ChatGPT3 的令牌限制为 4000,而其他模型则提供 8000 甚至 100000 的选项!
  • 这个令牌限制意味着,如果你想分析大量文本,比如莎士比亚的作品,你需要将其拆分成更小的部分,以便符合模型的限制。
  • Spring AI 可以帮助您!它提供了工具,可以将您的数据进行处理,并以合适的方式呈现给模型,从而最大限度地提高效率,避免达到限制。

教 AI 新技能:超越训练数据集

想象一下,你有一个在庞大数据集上训练的 AI 模型,比如 GPT-3.5/4.0。这个模型非常出色,但它的知识截止于 2021 年 9 月!如果你需要关于更新事件的答案,该怎么办呢?以下是一些“增强”模型以获取超出训练数据的选项:

  • 微调(专家模式):可以将其视为重新连接 AI 模型的大脑。你将特定的数据输入模型,并调整其内部运作。这是一项强大的技术,但也非常复杂,需要机器学习的专业知识和大量资源,尤其是对于像 GPT 这样的大型模型。此外,并非所有模型都提供此选项。
  • 提示填充(更实用):这就像是对 AI 模型悄悄地提供线索。你不是重新编程它,而是将你的数据嵌入到你给它的问题或提示中。但有一个问题:AI 模型的注意力有限(就像一个短消息框)。需要一些技巧来将你的数据适应模型的上下文窗口。可以把它想象成用相关信息“填充提示”。
  • 春季人工智能来拯救:别担心填充技巧!春季人工智能提供工具,帮助您在上下文窗口中有效展示数据,最大限度地提高人工智能模型从您的提示中学习的能力。就像有一个助手在合适的时机悄悄告诉您正确的信息。

还有更多 Spring AI 所使用的 AI 概念,但目前我认为这些内容足以继续下面的例子。

创建一个聊天 GPT 客户端应用

Spring AI 仍在开发中,尚未正式发布,但已经足够成熟,可以支持许多 AI 应用。其中一个简单且开箱即用的解决方案是 Spring AI 提供了一个可直接使用的 Chat GPT 客户端。那么,让我们从这里开始。

目前还没有 Spring Initializr 来创建或包含 Spring AI。Spring AI 团队依赖于(同样是预发布版本的)Spring CLI(
https://spring.io/projects/spring-cli)项目,该项目可以轻松创建各种 Spring Boot 应用程序。让我们从以下简单步骤开始:

  1. 安装 Spring 命令行工具:
  2. 如果你使用的是 Mac,可以安装 Spring CLI

brew tap spring-cli-projects/spring-cli
brew install spring-cli

如果您使用的是 Windows,请访问此链接:
https://github.com/spring-projects/spring-cli/releases/tag/early-access。确保按照以下方式执行 Spring CLI:java -jar /spring-cli-0.8.1.jar'

在 Windows 系统上,您可以创建一个 spring.cmd 文件来执行 JAR。这样做的目的是为了使用 spring 命令。

使用以下命令来创建 Spring AI 应用

spring boot new --from ai --name myretro-ai
  1. 这将默认使用 Maven 作为构建工具生成 myretro-ai 文件夹结构。现在,您可以在自己喜欢的 IDE 中导入项目。

在撰写本文时,Spring CLI 仅支持单模块的 Maven 项目。计划在 1.0 版本中支持单模块的 Gradle 项目,但尚未确定支持多模块项目的时间表。

默认情况下,上述命令(spring boot new)会生成多个文件;首先查看 README.md 文件,其中包含所有运行说明。它还会创建一个包:
org.springframework.ai.openai.samples.helloworld,并生成 Application 类和 SimpleAiController。您可以按照清单 15-6 的示例打开 SimpleAiController 类。

package org.springframework.ai.openai.samples.helloworld.simple;
import org.springframework.ai.chat.ChatClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
public class SimpleAiController {
    private final ChatClient chatClient;
    @Autowired
    public SimpleAiController(ChatClient chatClient) {
        this.chatClient = chatClient;
    }
    @GetMapping("/ai/simple")
    public Map completion(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
        return Map.of("generation", chatClient.call(message));
    }
}

示例 15-6
src/main/java/org/springframework/ai/openai/samples/helloworld/simple/SimpleAiController.java

在 SimpleAiController 类中,唯一需要注意的重要部分是我们使用了 ChatClient 类。这个类包含了调用 ChatGPT 所需的所有逻辑。

启动应用程序

要运行该应用程序,您需要用自己的密钥设置 SPRING_AI_OPENAI_API_KEY。您可以在
https://platform.openai.com/api-keys 上获取该密钥(当然,您需要先注册;这是免费的)。然后,您就可以使用它来运行应用程序。

SPRING_AI_OPENAI_API_KEY= ./gradlew bootRun

您可以通过执行 cURL 命令来获取在 Spring Boot 应用中使用 ChatGPT 的结果

curl localhost:8080/ai/simple
Why did the cow go to space?
Because it wanted to see the mooooon!

恭喜你!你成功创建了一个简单的 ChatGPT 应用!

概要

在本章中,我们回顾了 Spring 团队两项备受期待的技术:Spring Modulith,它可以帮助您使用 Spring Boot 创建模块化应用程序,并确保架构合理且布局得当;以及 Spring AI,它可以帮助您快速创建与 OpenAI/ChatGPT 的接口。