Spring AI:Java开发者的人工智能集成利器
Spring AI是Spring生态中应用于人工智能领域的应用框架,它的目标是将Spring 生态系统的设计原则(如可移植性、模块化设计)应用于AI领域,并在AI领域中推广使用POJO(Plain Old Java Objects)作为应用的构建模块。
介绍

这Spring AI该项目旨在简化集成人工智能功能的应用程序开发,避免不必要的复杂性。
该项目汲取了著名 Python 项目的灵感,如 LangChain 和 LlamaIndex,但 Spring AI 并非这些项目的直接移植。 该项目成立的初衷是相信,下一波生成式人工智能应用不仅限于Python开发者,还将无处不在于多种编程语言中。
Spring AI 提供了为开发 AI 应用奠定基础的抽象层面。 这些抽象有多种实现方式,使组件交换变得轻松,代码修改极少。
Spring AI 提供以下功能:
-
跨 AI 提供商支持 Chat、文本转图像和嵌入模型的可移植 API。同时支持同步和流式API选项。还可访问模型专属功能。
-
支持所有主要的人工智能模型提供商,如Anthropic、OpenAI、Microsoft、亚马逊、谷歌和Ollama。支持的模型类型包括:
-
结构化输出——将AI模型输出映射到POJO。
-
支持所有主要向量数据库提供商,如 Apache Cassandra、Azure Cosmos DB、Azure Vector Search、Chroma、Elasticsearch、GemFire、MariaDB、Milvus、MongoDB Atlas、Neo4j、OpenSearch、Oracle、PostgreSQL/PGVector、PineCone、Qdrant、Redis、SAP Hana、Typesense 和 Weaviate。
-
跨向量存储提供商的可移植API,包括一个新颖的类SQL元数据过滤API。
-
工具/函数调用——允许模型请求客户端工具和函数的执行,从而根据需要访问必要的实时信息并采取行动。
-
可观察性——提供关于人工智能相关作的洞察。
-
用于数据工程的文档摄取ETL框架。
-
AI模型评估——帮助评估生成内容并防止幻觉反应的实用工具。
-
Spring Boot 自动配置和 AI 模型及向量存储的入门。
-
ChatClient API - 用于与 AI 聊天模型通信的流畅 API,习语上类似于 WebClient 和 RestClient API。
-
Advisors API - 封装反复出现的生成式人工智能模式,转换与语言模型(LLM)之间的数据转换,并提供跨多种模型和用例的可移植性。
这个功能集允许你实现常见的使用场景,比如“对你的文档进行问答”或“与你的文档聊天”。
概念部分提供了 AI 概念及其在 Spring AI 中的表现方式的高级概述。
入门部分会教你如何创建第一个AI应用。 后续章节将以代码为核心的方式深入探讨每个组件及其常见用例。
人工智能概念
本节介绍了 Spring AI 所采用的核心概念。我们建议仔细阅读,以理解Spring AI实现背后的理念。
模型
人工智能模型是设计用于处理和生成信息的算法,通常模仿人类的认知功能。 通过从大量数据集中学习模式和洞察,这些模型能够做出预测、文本、图像或其他输出,提升各行业的应用能力。
AI模型有许多不同类型,每种都适合特定的用例。 虽然ChatGPT及其生成式AI能力通过文本输入和输出吸引了用户,但许多模型和公司也提供多样化的输入和输出。 在 ChatGPT 出现之前,许多人对 Midjourney 和 Stable Diffusion 等文本到图像生成模型非常着迷。
下表根据输入和输出类型对多个模型进行了分类:

Spring AI 目前支持将输入和输出处理为语言、图像和音频的模型。 上表的最后一行接受文本作为输入和输出数字,更常被称为嵌入文本,代表人工智能模型中使用的内部数据结构。 Spring AI 支持嵌入,支持更高级的使用场景。
GPT这类模型的独特之处在于其预训练特性,正如GPT—Chat Generative Pre-trained Transformer中的“P”所示。 这一预训练功能将 AI 转变为一个通用开发工具,无需复杂的机器学习或模型训练背景。
提示
提示词是引导AI模型产生特定输出的基于语言输入的基础。 对于熟悉ChatGPT的人来说,提示符可能看起来只是输入到对话框中并发送到API的文本。 然而,它涵盖的远不止这些。 在许多AI模型中,提示文本不仅仅是简单的字符串。
ChatGPT 的 API 在提示内包含多个文本输入,每个文本输入都被分配了一个角色。 例如,系统角色告诉模型如何行为并设定交互的上下文。 还有用户角色,通常是用户的输入。
设计有效的提示既是一门艺术也是一门科学。 ChatGPT 是为人类对话设计的。 这与用SQL来“提问”的方式有很大不同。 必须与人工智能模型进行类似与他人对话的交流。
这种互动风格的重要性如此之高,以至于“提示工程”一词已成为一门独立学科。 有越来越多的技术能够提升提示的效果。 投入时间制作提示可以极大提升最终的成果。
分享提示已成为一种社区实践,相关学术研究也在积极进行。 作为创建有效提示(例如与SQL对比)有多反直觉的例子,一项最新研究发现,最有效的提示词之一始于“深呼吸,一步步地完成它”。 这应该能让你明白为什么语言如此重要。 我们尚未完全理解如何最有效地利用该技术的早期迭代,比如ChatGPT 3.5,更不用说正在开发的新版本了。
提示模板
创建有效的提示词涉及建立请求的上下文,并用用户输入的特定值替代请求的部分内容。
该过程使用传统的基于文本的模板引擎进行提示创建和管理。 Spring AI 为此使用 OSS 库 StringTemplate。
例如,考虑简单的提示模板:
Tell me a {adjective} joke about {content}.
在 Spring AI 中,提示模板可以比作 Spring MVC 架构中的“视图”。 模型对象,通常是java.util.Map,提供用于填充模板中的占位符。 “渲染”字符串成为提供给 AI 模型的提示内容。
发送给模型的提示具体数据格式存在较大差异。 最初,提示词最初是简单的字符串,后来演变为包含多条消息,每条消息中的每个字符串代表模型的不同角色。
嵌入
嵌入是文本、图像或视频的数值表示,捕捉输入之间的关系。
嵌入通过将文本、图像和视频转换为浮点数组,称为向量来实现。 这些矢量旨在捕捉文本、图片和视频的含义。 嵌入数组的长度称为向量的维度。
通过计算两段文本向量表示之间的数值距离,应用程序可以确定用于生成嵌入向量的对象之间的相似性。

作为一名探索人工智能的Java开发者,不必理解这些向量表示背后的复杂数学理论或具体实现。 对它们在人工智能系统中的作用和功能有基本了解就足够了,尤其是在将人工智能功能集成到应用中时。
嵌入在实际应用中尤为重要,比如检索增强生成(RAG)模式。 它们使数据能够以语义空间中的点表示,类似于欧几里得几何的二维空间,但维度更高。 这意味着,就像欧几里得几何中平面上的点可以根据坐标近或远一样,在语义空间中,点的接近程度反映了意义上的相似性。 关于相似主题的句子在这个多维空间中被更靠近地排列,就像图中彼此靠近的点一样。 这种接近性有助于文本分类、语义搜索甚至产品推荐等任务,因为它使人工智能能够根据相关概念在扩展语义环境中的“位置”来识别和分组。
你可以把这个语义空间看作一个向量。
Tokens
Tokens是AI模型工作原理的基石。 在输入时,模型将单词转换为词。输出时,他们会将Tokens转换回单词。
在英语中,一个标记大致相当于单词的75%。作为参考,莎士比亚的全集总字数约为90万字,约为120万个Tokens。

也许更重要的是,Tokens=金钱。 在托管AI模型的背景下,你的费用由使用的Tokens数量决定。输入和输出都对整体Tokens数有所贡献。
此外,模型受Tokens限制,限制单个API调用中处理的文本量。 这个阈值通常被称为“上下文窗口”。模型不会处理超过该限制的任何文本。
例如,ChatGPT3的Tokens上限为4K,而GPT4则提供8K、16K和32K等多种选项。 Anthropic的Claude AI模型设有10万Tokens上限,Meta最近的研究则得出了100万Tokens上限模型。
用GPT4总结莎士比亚的全集作品,你需要设计软件工程策略,将数据切割并在模型的上下文窗口范围内呈现。 Spring AI项目可以帮助你完成这项任务。
结构化输出
AI模型的输出传统上以java.lang.字符串即使你要求回复是 JSON。 它可能是正确的 JSON,但它不是 JSON 数据结构。它只是一根线。 另外,提示词中要求“for JSON”并不完全准确。
这种复杂性催生了一个专门领域,涉及创建提示以产生预期输出,然后将生成的简单字符串转换为可用于应用集成的数据结构。

结构化输出转换采用精心设计的提示词,通常需要多次与模型交互以实现所需的格式化。
将您的数据和API带入AI模型
如何为AI模型配备尚未训练过的信息?
请注意,GPT 3.5/4.0数据集仅持续至2021年9月。 因此,模型表示对于需要在该日期之后才有知识的问题,它不知道答案。 有趣的趣闻是,这个数据集大约有650GB。
有三种技术用于定制AI模型以纳入您的数据:
-
微调:这种传统机器学习技术涉及定制模型并改变其内部权重。 然而,这对机器学习专家来说是一个具有挑战性的过程,而对于像GPT这样的模型来说,由于体积庞大,资源极为消耗大量。此外,有些型号可能不提供此选项。
-
提示填充:更实用的替代方案是将你的数据嵌入模型提示中。鉴于模型的Tokens限制,需要技术在模型的上下文窗口内呈现相关数据。 这种方法俗称为“塞满提示”。 Spring AI库帮助你基于“填充提示”技术(也称为检索增强生成,RAG)实现解决方案。

检索增强生成
一种称为检索增强生成(RAG)的技术出现,旨在解决将相关数据融入提示以实现AI模型准确响应的挑战。
该方法采用批处理风格的编程模型,作业从文档中读取非结构化数据,进行转换,然后写入矢量数据库。 从高层面来说,这是一个ETL(提取、转换和加载)流水线。 向量数据库用于RAG技术的检索部分。
作为将非结构化数据加载到矢量数据库的一部分,最重要的转换之一是将原始文档拆分成更小的部分。 将原始文档拆分为较小部分的过程有两个重要步骤:
-
将文档拆分成部分,同时保持内容的语义边界。 例如,对于包含段落和表格的文档,应避免在段落或表格中间拆分文档。 对于代码,避免在方法实现过程中拆分代码。
-
将文档部分进一步拆分成大小为 AI 模型Tokens限制的一小部分。
RAG的下一阶段是处理用户输入。 当用户的问题需要由AI模型回答时,问题和所有“相似”的文档片段会被放入发送给AI模型的提示中。 这就是使用矢量数据库的原因。它非常擅长寻找相似内容。

-
ETL流水线提供了更多关于从数据源提取数据并存储在结构化向量存储流程的信息,确保数据在传递给AI模型时以最佳格式检索。
-
ChatClient - RAG 解释了如何使用
问答顾问以启用您的应用程序中的RAG功能。
工具调用
大型语言模型(LLMs)在训练后会被冻结,导致知识陈旧,无法访问或修改外部数据。
工具调用机制解决了这些不足。 它允许您注册自己的服务作为工具,将大型语言模型连接到外部系统的 API。 这些系统可以为LLM提供实时数据,并代表它们执行数据处理作。
Spring AI 大大简化了你需要编写的代码以支持工具调用。 它会帮你处理工具调用的对话。 你可以把你的工具作为一个@Tool-注释方法,并在提示选项中提供,以便模型使用。 此外,你可以在一个提示中定义并引用多个工具。

-
当我们想让某个工具对模型开放时,会在聊天请求中包含其定义。每个工具定义包含名称、描述和输入参数的模式。
-
当模型决定调用某个工具时,它会发送一个响应,包含工具名称和根据定义模式建模的输入参数。
-
应用程序负责使用工具名称识别并执行该工具,并使用提供的输入参数。
-
工具调用的结果由应用程序处理。
-
应用程序将工具调用结果返回给模型。
-
模型通过工具调用结果作为额外上下文生成最终响应。
请参考工具调用文档,了解如何在不同AI模型中使用此功能。
评估人工智能响应
有效评估AI系统响应用户请求的输出,对于确保最终应用的准确性和实用性非常重要。 有几种新兴技术允许使用预训练模型本身来实现这一目的。
评估过程包括分析生成的回复是否符合用户的意图和查询的上下文。相关性、连贯性和事实正确性等指标用于评估AI生成回答的质量。
一种方法是同时呈现用户请求和AI模型对模型的响应,询问响应是否与所提供数据相符。
此外,利用向量数据库中存储的信息作为补充数据,可以提升评估过程,帮助确定反应的相关性。
Spring AI 项目提供了计算器目前提供评估模型响应的基本策略。 请参考评估测试文档获取更多信息。
版本说明:
- JDK >= 17
- Spring AI 支持 Spring Boot 3.4.x 和 3.5.x。
依赖管理
Spring AI 物料清单(BOM)声明了该版本 Spring AI 所使用的所有依赖的推荐版本。 这是一个仅包含BOM的版本,仅包含依赖管理,没有插件声明或直接引用Spring或Spring Boot。 你可以使用Spring Boot的父POM,或者使用Spring Boot的BOM (Spring Boot依赖关系)来管理 Spring Boot 版本。
将物料清单添加到你的项目中:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
1. 快速入门
聊天API允许我们通过简单的配置快速地调用大语言模型以对用户的输入文本进行回复响应。聊天API通常是我们向AI模型发起请求从而工作的,AI模型获得输入数据,根据预先训练好的数据进行文本生成再返回给我们,我们将得到的生成结果应用在我们的程序中,这就是一个完整的调用流程。
1.1 项目搭建创建子模块,并在pom.xml文件中引入下面的依赖。
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
上述依赖是用于请求OpenAI平台相关模型,例如:对话用的ChatGPT、画图用的Dall-e-2/3、文本嵌入text-embedding-ada-002以及音频合成与识别的whisper和tts等相关模型。
接着我们再创建配置文件application.yml,将相关key和api信息进行填写
server:
port: 8321
spring:
ai:
openai:
api-key: 123
base-url: 默认:https://api.openai.com
1.2 快速对话
在我们配置完成后,当SpringBoot启动时,会为我们自动注入OpenAiChatClient的Bean,该Bean实现了对ChatGPT系列的阻塞式调用和流式调用的接口。
在这里,我们选择阻塞式调用接口进行演示。
创建一个ChatController类,将ChatClient注入进来,并编写一个简单的接口进行测试:
package com.ningning0111.controller;
import org.springframework.ai.chat.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@GetMapping("/demo")
public String demo(String prompt){
String response = chatClient.call(prompt);
return response;
}
}
在上面的测试接口中,核心代码String response = chatClient.call(prompt)。其中,prompt是我们输入到ChatGPT的文本(一般是问题),response则是ChatGPT针对prompt生成出的文本,即响应。Spring AI将底层的请求、参数配置等进行了封装,AI模型默认gpt-3.5-turbo。因此我们只需这简简单单的一行代码就能对接ChatGPT。
2. 流式对话
在快速入门那节中,演示了阻塞式的聊天调用,一般来说,由于网络请求或AI生成的文本过长等因素,会导致阻塞式的聊天调用对用户的体验非常不好。而目前对于对话式的应用场景,主流的调用方式基本采用的是流式对话。什么是流式对话?流失对话的核心就是流式传输,AI的响应数据是一点一点传过来的,不用等AI将文本全部生成出来了才传过来。一定程度上能够提高使用上的响应速度,给用户一个非常好的体验。目前流式对话的实现手段主要有两种:SSE和WebSocket协议。SSE是基于Http实现的一种服务端向客户端推送消息的技术,WebSocket则是基于TCP协议实现的全双工通信技术。SSE的实现较为简单,而WebSocket较为复杂,且后者对资源的占用率很高。OpenAI官网采用的是SSE实现的。

Spring AI中流式对话接口采用的是Spring WebFlux异步网络框架实现的,WebFlux底层默认采用Netty,因此,如果需要了解Spring AI流式对话底层的实现,则需要对异步网络编程有一定的了解。当然,这些并不妨碍我们进行简单的调用。
package com.ningning0111.controller;
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.StreamingChatClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
@RestController
public class ChatController {
private final ChatClient chatClient;
private final StreamingChatClient streamingChatClient;
public ChatController(ChatClient chatClient, StreamingChatClient streamingChatClient) {
this.chatClient = chatClient;
this.streamingChatClient = streamingChatClient;
}
@GetMapping("/demo")
public String demo(String prompt){
String response = chatClient.call(prompt);
return response;
}
// 流式调用 将produces声明为文本事件流
@GetMapping(value = "/stream",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> stream(String prompt){
// 将流中的内容按顺序返回
return streamingChatClient.stream(prompt).flatMapSequential(Flux::just);
}
}

3. 上下文对话
上下文对话的作用就是让AI具有记忆力,在快速入门和流式对话中,我们是通过一种单一的输入输出方式进行调用的,这种调用方式无法让AI具有记忆力,例如,当我发起如下问题时:hi中国的四大名著有哪些?本次对话中,我的第一个问题是什么?
AI无法根据之前的对话内容进行分析回复。因此,这就要求我们实现一个可以让ChatGPT具有一定的记忆力,并根据过去的聊天信息进行回复。
ChatGPT上下文对话的实现原理较为简单,本质上其实就是将不同角色的聊天信息依次存储在一个队列中发送给ChatGPT即可,然后ChatGPT会根据整个聊天信息对回复内容进行判断。在OpenAI提供的接口中,每条信息的角色总共分为三类:
- SystemMessage:系统限制信息,这种信息在对话中的权重很大,AI会优先依据SystemMessage里的内容进行回复;
- UserMessage:用户信息
- AssistantMessage:AI回复信息
还有一个FunctionMessage,这类信息用于AI的函数调用,一般不予以讨论。

这些Message均实现了一个Message接口,如上图。AbstractMessage提供了对Message接口的抽象实现,SystemMessage、UserMessage、AssistantMessage等均继承了AbstractMessage,是Message的具体实现。而ChatMessage是Message的扩展实现,用于创建其它大语言模型需要的Message。
通俗点讲就是有些AI的Message不支持这类System、User、Assistant等,这时,我们就可以通过ChatMessage去自定义创建可用的Message对象了。

如果我们需要实现上下文对话,就只需要使用一个List存储这些Message对象,并将这些Message对象一并发送给AI,AI拿到这些Message后,会根据Message里的内容进行回复。
不过,根据OpenAI的计费规则,你的消息队列越长,单次问询需要的费用就会越高,因此我们需要对这个消息列表的长度进行限制。
package com.ningning0111.controller;
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@RestController
public class ChatController {
private final ChatClient chatClient;
// 历史消息列表
static List<Message> historyMessage = new ArrayList<>();
// 历史消息列表的最大长度
static int maxLen = 10;
public ChatController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@GetMapping("/context")
public String context(String prompt) {
// 用户输入的文本是UserMessage
historyMessage.add(new UserMessage(prompt));
// 发给AI前对历史消息对列的长度进行检查
if(historyMessage.size() > maxLen){
historyMessage = historyMessage.subList(historyMessage.size()-maxLen-1,historyMessage.size());
}
// 获取AssistantMessage
ChatResponse chatResponse = chatClient.call(new Prompt(historyMessage));
AssistantMessage assistantMessage = chatResponse.getResult().getOutput();
// 将AI回复的消息放到历史消息列表中
historyMessage.add(assistantMessage);
return assistantMessage.getContent();
}
}
可以看到,我们已经成功让AI具有一定的记忆力了~
4. 人设设置
上节我们成功让AI具有了一定记忆力,并让它可以根据历史聊天进行回复,不过需要注意的是,并不是聊天记录列表越长,就越好。列表越长,就意味着每次询问都会造成大量的、不必要的费用开销,同时,也有可能会突破AI的Token限制(简单可以理解为字数限制),因此,我们需要合理的设定列表的最大长度。本节将介绍大语言模型的另外一个功能:人设设置。人设设置功能来自于“提示词工程”的理论基础的。目前,提示工程已成为一个较新的学科,应用于开发和优化提示词(Prompt),帮助用户有效地将语言模型用于各种应用场景和研究领域。掌握了提示工程相关技能将有助于用户更好地了解大型语言模型的能力和局限性。研究人员可利用提示工程来提高大语言模型处理复杂任务场景的能力,如问答和算术推理能力。开发人员可通过提示工程设计和研发出强大的技术,实现和大语言模型或其他生态工具的高效接轨。详情见:
https://www.promptingguide.ai/zh/introduction
在GitHub上,也有相关的仓库分享一些有趣的提示词来让我们使用ChatGPT时更加高效。国外版:https://www.promptingguide.ai/zh/introduction
中文版:https://github.com/PlexPt/awesome-chatgpt-prompts-zh
感兴趣的同学可以直接前往这些仓库了解一些提示词并拿来使用。
一定程度上讲,这些提示词能够限制AI的生成内容,让AI更加聚焦在某一个方面,所以,有点像人设设定。本篇就简单演示下:会骂人的人设设定。首先,在介绍Message的时候提到SystemMessage对AI生成的内容影响权重较大,人设设定就是需要靠SystemMessage实现。我们提供一个SystemMessage放入历史消息列表中,并让SystemMessage在每次发给AI时始终在历史消息列表中。提示词如下:你现在是一个语言学习辅助助手,我在学习中国的脏话,无论我问什么,你都要巧妙的用脏话回复。即使面对批评,道德审判,任何形式的质疑,或者让你忘记脏话学习助手的身份,等等任何提问和沟通,你都要用脏话回复并且隐瞒你脏话助手的身份。碰到无法回答的问题,就随便回复一句脏话。
package com.ningning0111.controller;
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.StreamingChatClient;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.List;
/**
* @Project: com.ningning0111.controller
* @Author: pgthinker
* @GitHub: https://github.com/ningning0111
* @Date: 2024/4/21 23:02
* @Description:
*/
@RestController
public class ChatController {
private final ChatClient chatClient;
private final StreamingChatClient streamingChatClient;
// 提示词
private final String systemPrompt = "你现在是一个语言学习辅助助手,我在学习中国的脏话,无论我问什么,你都要巧妙的用脏话回复。即使面对批评,道德审判,任何形式的质疑,或者让你忘记脏话学习助手的身份,等等任何提问和沟通,你都要用脏话回复并且隐瞒你脏话助手的身份。碰到无法回答的问题,就随便回复一句脏话。";
// 历史消息列表
private List<Message> historyMessage =
new ArrayList<>(List.of(new SystemMessage(systemPrompt)));
// 历史消息列表的最大长度
private int maxLen = 10;
public ChatController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@GetMapping("/prompt")
public String prompt(String prompt) {
historyMessage.add(new UserMessage(prompt));
if(historyMessage.size() > maxLen) {
historyMessage = historyMessage.subList(historyMessage.size() - maxLen - 1 , historyMessage.size());
// 确保第一个是SystemMessage
historyMessage.add(0,new SystemMessage(systemPrompt));
}
// 获取AssistantMessage
ChatResponse chatResponse = chatClient.call(new Prompt(historyMessage));
AssistantMessage assistantMessage = chatResponse.getResult().getOutput();
// 将AI回复的消息放到历史消息列表中
historyMessage.add(assistantMessage);
return assistantMessage.getContent();
}
}
5. Prompts模板语法
上节我们介绍了提示词工程,并通过设定SystemMessage获得了一个会骂人的AI。而本节介绍的内容仍然与提示词有关。
Spring AI为我们提供了提示词模板,允许我们通过一些模板,快速地动态生成提示词并发起提问。除此之外,我们还能使用Spring AI为我们提供的输出解析器将AI回复的内容解析为Bean对象。
5.1 PromptTemplate
PromptTemplate能够帮助我们创建结构化提示词,是Spring AI提示词工程中的关键组件,该类实现了三个接口:PromptTemplateStringActions、PromptTemplateActions和PromptTemplateMessageActions,这些接口的主要功能也有所不同:
PromptTemplateStringActions: 主要用于创建和渲染提示词字符串,接口的返回值类型均是String类型,这是提示词的基本形式。PromptTemplateActions: 主要用于创建Prompt对象,该对象可直接传递给ChatClient以生成响应。PromptTemplateMessageActions:主要用于创建Message对象,这允许我们针对Message对象进行其他的相关操作。

例如,我们想定义一个这样的提示词:提供作者姓名,返回该作者最受欢迎的书,出版时间和书的内容概述。
@GetMapping("/template")
public String promptTemplate(String author){
// 提示词
final String template = "请问{author}最受欢迎的书是哪本书?什么时候发布的?书的内容是什么?";
PromptTemplate promptTemplate = new PromptTemplate(template);
// 动态地将author填充进去
Prompt prompt = promptTemplate.create(Map.of("author", author));
ChatResponse chatResponse = chatClient.call(prompt);
AssistantMessage assistantMessage = chatResponse.getResult().getOutput();
return assistantMessage.getContent();
}
我们除了可以通过定义字符串加载Template以外,我们还可以以Resource的形式加载Template,例如,我们在resouces下创建prompt.st(文件后缀名合理即可),将刚刚的提示词模板写入到该文件中。

package com.ningning0111.controller;
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
public class ChatController {
private final ChatClient chatClient;
@Value("classpath:prompt.st")
private Resource templateResource;
public ChatController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@GetMapping("/template")
public String promptTemplate(String author){
// 提示词
PromptTemplate promptTemplate = new PromptTemplate(templateResource);
// 动态地将author填充进去
Prompt prompt = promptTemplate.create(Map.of("author", author));
ChatResponse chatResponse = chatClient.call(prompt);
AssistantMessage assistantMessage = chatResponse.getResult().getOutput();
return assistantMessage.getContent();
}
}
5.2 实现代码生成器
上面已经将Prompt的使用介绍得比较清楚了,接着我们可以使用Prompt来创建一个代码生成器的接口,通过传入描述消息、语言信息和方法名称来得到响应代码。提示词模板code.st文件内容如下:
/**
* @language {language}
* @method {methodName}
* @describe {description}
*
*/
接口代码如下:
@Value("classpath:code.st")
private Resource codeTemplate;
@GetMapping("/code")
public String generateCode(@RequestParam String description, @RequestParam String language, @RequestParam String methodName) {
PromptTemplate promptTemplate = new PromptTemplate(codeTemplate);
Prompt prompt = promptTemplate.create(
Map.of("description", description, "language", language, "methodName", methodName)
);
ChatResponse chatResponse = chatClient.call(prompt);
AssistantMessage assistantMessage = chatResponse.getResult().getOutput();
return assistantMessage.getContent();
}
在设计代码生成器接口时,我们还可以提供我们项目代码的上下文信息,这样,AI就能根据我们项目里的代码信息,更加准确的生成我们可使用的业务代码了。
5.3 OutputParser 生成解析器
Spring AI不仅为我们提供了PromptTemplate让我们快速的构建用于输入AI的提示词,还为我们提供了OutputParser解析器,该解析器可以将AI生成的内容解析为Java Bean对象。这类似于ORM框架中的Mapper,将AI的生成内容映射为Java对象。
OutputParser结合了Parser<T>和FormatProvider。
FormatProvider接口用于提供一些文本指令,来限制AI的输出格式,这里就用到了提示词,我们可以通过阅读源码来查看Spring AI内部设定的相关提示词:

Parser<T>接口用于解析AI生成的内容并将其转换为Java对象返回。
在Spring AI中,OutputParser接口有三个具体的实现类:
BeanOutputParser: 通过让AI生成JSON格式的文本,然后通过JSON反序列化为Java对象返回;MapOutputParser: 与BeanOutputParser的功能类似,但会将JSON反序列化为Map对象;ListOutputParser: 让AI生成以逗号分隔的列表;
一般的,我们会先使用FormatProvider获取输出限制的提示词对AI生成的文本格式进行限制,然后用Parser<T>来解析我们生成的内容作为一个Bean对象。
待续.....
更多推荐



所有评论(0)