SpringAI系列(基础概念&springai系列 API)
参照该课程
基础概念
token
token是语言模型的 计费/生成 基本单位,大概理解成每token生成0.75个中文。
API KEY
由于大部分人不会部署模型到本地,所以需要消耗token来调用模型,而调用模型的时候需要使用API Key进行身份验证,所以需要准备一个厂商的API Key。
prompt
prompt是输入给模型的文本,模型会根据这个文本来生成相应的输出。
狭义上,prompt就是我们对大模型说了什么,广义上,还包括了其他属性,比如当前设置的 温度Temperature、top_p等参数,这些参数会影响模型的输出结果。
例如:
{
"model": "deepseek-v4-pro",
"messages": [
{
"role": "system",
"content": "这里输入一些人物设定、场景设定、对话风格等信息,来引导模型生成符合预期的内容,如:你是一个段子高手。"
},
{
"role": "user",
"content": "这里是用户输入的内容,如:开个玩笑"
}
],
"temperature": 1.0,
}
接口调用
当拥有一个API Key后,就可以通过HTTP的post请求来调用大模型的接口,向厂家提供的接口发送一个包含prompt与api key的请求,模型会根据这个prompt来生成相应的输出。
例如:
curl https://api.deepseek.com/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${DEEPSEEK_API_KEY}" \
-d '{
"model": "deepseek-v4-pro",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"}
],
"thinking": {"type": "enabled"},
"reasoning_effort": "high",
"stream": false
}'
一些参数
上面prompt除了我们发送给大模型的messages,还有一些参数会影响模型的输出结果,常见的参数有:
temperature:控制生成文本的随机程度,值越高,生成的文本越随机,值越低,生成的文本越确定。top_p:与Temperature类似,值越高,生成的文本越多样化,值越低,生成的文本越集中。
大模型本质是一个概率模型,输入一个字后面有不同概率接不同的字,假如输入“我”,后面可能接40%接“是”、30%接“喜欢”、20%接“想”等等,top_p假如说是100%,就会考虑所有的可能性,top_p如果是40%,就只会考虑这里的"是"了。max_tokens:控制生成文本的最大长度,单位是token。stream:控制是否开启流式输出,开启后模型会在生成文本的过程中逐步返回结果,而不是等到生成完成后一次性。
上面的messages列表中可以发现出现了不同的role,常见的有:
system:系统角色,设定AI的行为和角色。user:用户角色,表示用户的输入。assistant:助手角色,表示AI的回复。tool:工具调用的消息。
SpringAI
Spring AI 作为 Spring 官方推出的 AI 应用开发框架,其核心设计哲学是将 AI 能力无缝集成到 Spring 生态系统中,为 Java 开发者提供熟悉、一致的编程模型。
Spring AI 不是要重新发明轮子,而是基于 Spring 的核心原则——依赖注入、面向切面编程和模板模式,为各种 AI 服务提供统一的抽象层。
准备
参考:https://springdoc.cn/spring-ai/index.html
版本/依赖:
JDK:17 (官方最低要求17)
SpringAI :1.0.0
SpringBoot : 3.4.0
一个大模型的starter: 我用的deepseek
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-deepseek</artifactId>
</dependency>
配置yml文件:
spring:
ai:
deepseek:
api-key: 你的apikey
API - Message
Spring AI 提供消息类型对应prompt中messages列表的不同message。
常见的消息类型有:
SystemMessage:系统消息,用于设定AI的行为和角色。UserMessage:用户消息,表示用户的输入。AssistantMessage:助手消息,表示AI的回复。ToolMessage:工具消息,表示工具调用的消息。
他们都继承自AbstractMessage,包含textContent和messageType两个属性,对应’content’和’role’。
各message之间关系如图:
一个简单的示例展示构建一个聊天接口:
package com.example.springai.controller;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.deepseek.DeepSeekChatModel;
import org.springframework.ai.deepseek.DeepSeekChatOptions;
import org.springframework.ai.deepseek.api.DeepSeekApi;
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 reactor.core.publisher.Flux;
import java.util.List;
import java.util.Map;
@RestController
public class ChatController {
// 注入一个DeepSeekChatModel,这个模型是Spring AI提供的一个接口,封装了调用DeepSeek模型的逻辑,我们可以通过它来调用DeepSeek模型生成文本。
private final DeepSeekChatModel chatModel;
@Autowired
public ChatController(DeepSeekChatModel chatModel) {
this.chatModel = chatModel;
}
@GetMapping("/ai/generate")
public Map generate(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
// 构建messages列表
UserMessage userMessage = new UserMessage(message);
SystemMessage systemMessage = new SystemMessage("你是一个段子高手");
// 准备构建Prompt(消息列表和相关参数),参数通过DeepSeekChatOptions来设置
DeepSeekChatOptions deepSeekChatOptions = new DeepSeekChatOptions();
deepSeekChatOptions.setTemperature(1.4);
deepSeekChatOptions.setModel("deepseek-v4-flash");
deepSeekChatOptions.setMaxTokens(2048);
// 通过builder构建DeepSeekChatOptions
DeepSeekChatOptions chatoptions = DeepSeekChatOptions.builder()
.model("deepseek-v4-flash")
.temperature(1.4)
.maxTokens(2048)
.build();
// .call()方法通过ctrl+P查看参数,他可以传入三种类型,分别是String、Message、Prompt。
// 例如:
// return Map.of("generation", chatModel.call("Tell me a joke") ); // 传入string
// return Map.of("generation", chatModel.call(systemMessage, userMessage) ); // 传入message
return Map.of("generation", chatModel.call(new Prompt(List.of(systemMessage, userMessage), deepSeekChatOptions)) ); // 传入prompt
}
}
API - Prompt
刚才的案例中已经初步展示了Prompt的使用,Prompt包括消息列表和相关参数。可以new一个Prompt,包含List.of<Message>和DeepSeekChatOptions,也可以通过builder来构建chatoptions。
API - ChatResponse
.call()方法的返回值是一个ChatResponse对象,包含generation、chatResponseMetadata。
其中generation是模型生成的信息,包含assisantMessage和chatGenerationMetadata;chatResponseMetadata包含一些元信息,如生成的token数量、使用的模型等。assisantMessage里面包含了生成的文本内容、消息类型等信息。
遇到陌生API快速学习的小tip:
- 可以打个断点,运行的时候看一下
- 尝试调用对象的方法(对象.方法)
- 右键对象,可以
对表达式求值,看看每一个getxxx方法都出来些什么东西
API - ChatModel
结构如图:
其实流程和prompt/chatresponse的结构上文已经有所提及了,chatmodel就是把我们输入的prompt转换成厂商接口需要的requset格式,调用厂商接口,拿到response后再把结果转换成chatresponse。
需要使用的时候这样注入一下就行了。
private final DeepSeekChatModel chatModel;
@Autowired
public ChatController(DeepSeekChatModel chatModel) { this.chatModel = chatModel; }
此外,前面用的都是一次性的返回,chatmodel还支持流式响应。使用chatModel.stream()方法,返回一个流Flux。Flux<>中的内容取决于入参,如果入参是String或者Message,那么流中的内容就是String,直接返回就行;
如果入参是Prompt,流中的内容是ChatResponse,需要chatResponse.getResult().getOutput().getText()获取文本。
如果是用中文输入,会返回乱码,解决方案是调整编码:
server:
servlet:
encoding:
charset: UTF-8
enabled: true
force: true
API - ChatClient
ChatClient可以更简单的调用模型,也提供了更多的高级功能,如:
- 聊天记忆(Chat Memory)
- 工具/函数调用(Function Calling)
- RAG
- …
创建
直接注入:
@RestController
public class ChatController {
private final ChatClient chatClient;
@Autowired
public ChatController(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
}
可以选择模型的方式(传入chatModel):
@RestController
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatModel chatModel) {
this.chatClient = ChatClient.builder(chatModel)
.build();
}
}
调用模型
使用chatClient.prompt()方法,传入Prompt对象或者直接传入消息和参数,最后调用.call().content()方法获取String或者.call().chatResponse()获取更多信息。
和chatModel不太一样,这里call返回的是一个CallResponseSpec,chatModel的call直接返回ChatResponse。
案例:
package com.example.springai.controller;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.deepseek.DeepSeekChatOptions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/chat-client")
public class ChatClientController {
private final ChatClient chatClient;
// 可以自己选择不同的model
public ChatClientController(ChatClient.Builder builder) {
this.chatClient = builder.build();
// this.chatClient = ChatClient.builder(构造时传入chatModel).build();
}
@GetMapping("/test-client")
public String testClient(String message) {
DeepSeekChatOptions chatoptions = DeepSeekChatOptions.builder()
.model("deepseek-v4-flash")
.temperature(1.4)
.maxTokens(2048)
.build();
// -----------------------------------------------------------------------------------------
// SystemMessage systemMessage = new SystemMessage("You are a helpful assistant.");
// UserMessage userMessage = new UserMessage(message);
// Prompt prompt = new Prompt(List.of(systemMessage, userMessage), chatoptions);
// return chatClient.prompt(prompt)
// .call()
// .content();
// 或者直接在这里组装prompt
return chatClient.prompt()
.system("You are a helpful assistant.")
.user(message)
.options(chatoptions)
.call() // 也可以.stream()返回流式响应
.content(); // 也可 .chatResponse 返回ChatResponse
}
}
返回实体
ChatClient还支持返回一个实体对象,比如让大模型返回一本书(对象),属性有名称和作者。
首先定义类:
@Data // lombok注解,自动生成getter/setter等方法
public class Book {
private String name;
private String author;
}
然后在调用的时候指定返回类型并传入该类的字节码对象即可:
@GetMapping("/response")
public Book response() {
return chatClient.prompt()
.user("给我随机生成一本书,要求书名和作者都是中文")
.call().entity(Book.class);
}
模型就可以返回:
{
"name": "时间的玫瑰",
"author": "林清玄"
}
这个功能的本质是springAI自动加了一段提示词,告诉大模型要返回一个符合Book类结构的json字符串,去掉```以后(因为大模型生成的文本通常像markdown会有···),把这个json字符串转换成Book对象返回。
Advisor(增强器)
Advisor(增强器)是Spring AI提供的一种机制,可以在调用模型之前或之后对输入或输出进行增强处理。
如图:
ChatClientRequest: 输入+上下文
ChatClientResponse: 响应+上下文
继承体系:
对同步调用增强—CallAdvisor。对流式调用增强—StreamAdvisor。
都增强可以俩都继承一下,BaseAdvisor就是都增强的,一般只实现BaseAdvisor的before和after方法就行了。
使用
新建MyCallAdvisor
package com.example.springai.advisor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.CallAdvisor;
import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;
import org.springframework.ai.chat.client.advisor.api.StreamAdvisor;
import org.springframework.ai.chat.client.advisor.api.StreamAdvisorChain;
import reactor.core.publisher.Flux;
@Slf4j
public class MyCallAdvisor implements CallAdvisor {
@Override
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
log.info("Before call 1"); // 在调用大模型/进入下一个advisor之前先做一些事情
ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest);
log.info("After call 1"); // 在调用大模型/进入下一个advisor之后再做一些事情
return chatClientResponse;
}
@Override
public String getName() {
return "MyCallAdvisor";
}
@Override
public int getOrder() {
// 作用:调整advisor的调用顺序。order值越小,优先级越高。
return 0;
}
}
然后在chatClient中调用.advisors()方法,传入这个advisor的实例即可:
@GetMapping("/advisor")
public String advisorChat() {
return chatClient.prompt()
.user("")
.advisors(new MyCallAdvisor()) // order越小越先被调用,后被响应
.call().content();
}
如果有多个advisor,ad1,ad2的order分别是0和1,那么进出顺序就是:prompt->ad1.before->ad2.before->调用模型->ad2.after->ad1.after->返回结果。
基于Advisor实现简单的对话记忆功能
便于学习先存进内存
前:获取对话id,获取历史记录,把历史记录并入当前请求送给模型
后:获取对话id,把返回的结果存入内存
advisor类:
package com.example.springai.advisor;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.AdvisorChain;
import org.springframework.ai.chat.client.advisor.api.BaseAdvisor;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import java.util.*;
public class SimpleMemoryAdvisor implements BaseAdvisor {
// 建立静态map来存储对话历史记录,key为对话id,value为历史记录列表(仅供示例,这肯定要oom)。
private static final Map<String, List<Message>> chatMemory = new HashMap<>();
@Override
public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {
// 通过传入的chatClientRequest获取上下文获取对话id
String conversationId = chatClientRequest.context().get("conversationId").toString();
// 获取历史记录,如果为空则创建一个新的列表
List<Message> messages = chatMemory.get(conversationId);
if (messages == null) {
messages = new ArrayList<>();
}
// 把当前请求中的消息添加到历史记录中
messages.addAll(chatClientRequest.prompt().getInstructions());
// 把历史消息也放入请求中
ChatClientRequest processedChatClientRequest =
chatClientRequest.mutate()
.prompt(chatClientRequest.prompt().mutate().messages(messages).build())
.build();
// 获取请求中的消息 保存
chatMemory.put(conversationId, messages);
return processedChatClientRequest;
}
@Override
public ChatClientResponse after(ChatClientResponse chatClientResponse,
AdvisorChain advisorChain) {
// 获取会话id
String conversationId =
chatClientResponse.context().get("conversationId").toString();
// 通过会话id查询之前的对话记录
List<Message> hisMessages = chatMemory.get(conversationId);
if (hisMessages == null) {
hisMessages = new ArrayList<>();
}
// 获取response中ai的消息 添加到对话记录中
if(Objects.isNull(chatClientResponse)){
return chatClientResponse;
}
AssistantMessage assistantMessage = chatClientResponse.chatResponse()
.getResult()
.getOutput();
hisMessages.add(assistantMessage);
chatMemory.put(conversationId, hisMessages);
return chatClientResponse;
}
@Override
public int getOrder() {
return 0;
}
}
chatClient接口:
.advisors()方法可以传入advisor实例,也可以传入一个Consumer(在里面设置参数),consumer可以进一步省略成lambda表达式。
@GetMapping("/simpleChatMemory")
public String simpleChatMemory(String msg, String conversationId) {
return chatClient.prompt()
.user(msg)
.advisors(new Consumer<ChatClient.AdvisorSpec>() {
@Override
public void accept(ChatClient.AdvisorSpec advisorSpec) {
advisorSpec.param("conversationId", conversationId);
}
}) // 通过Consumer的方式传入参数,advisor里通过chatClientRequest.context().get("conversationId")获取
.call().content();
}
官方提供的Advisor实现对话记录存储
实际上官方的和刚才实现的差不多。client中方法如下:
// 官方方式
@GetMapping("/chatMemoryAdvisor")
public String chatMemory(String msg, String conversationId) {
return chatClient.prompt()
.user(msg)
.advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID, conversationId)) // 传入对话id
.advisors(MessageChatMemoryAdvisor.
builder(MessageWindowChatMemory
.builder().build())
.conversationId(conversationId).build()
) // 官方提供的记忆advisor,window表示有最大窗口限制,默认20
.call().content();
}
Prompt模板
当需要可变的输入时,可以使用prompt template。
硬拼接案例:
String name = "小王";
String voice = "幽默";
String userQuestion = "推荐上海的三个景点";
// 硬编码拼接提示词
String promptText = "你是一个有用的人工智能助手,名字是" + name + ",请用" + voice + "的风格回答以下问题:" + userQuestion;
使用prompt template的好处:动态静态内容分离,提高可维护性。
Prompt模板是预定义的文本结构,可以更高效地构建Prompt,Spring AI提供了PromptTemplate接口来定义Prompt模板,使用时可以通过占位符来动态替换内容。
例如:
// 用户提示词
PromptTemplate userPrompt = new PromptTemplate("你是一个有用的人工智能助手,名字是{name}请用{voice}的风格回答以下问题:{userQuestion}");
Message message = userPrompt.createMessage(Map.of("name", "小白", "voice", "幽默", "userQuestion", "推荐上海的三个景点"));
System.out.println(message);
// 系统提示词
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate("你是一个有用的人工智能助手,名字是{name}请用{voice}的风格回答以下问题:{userQuestion}");
Message message2 = systemPromptTemplate.createMessage(Map.of("name", "小白","voice", "幽默", "userQuestion", "推荐上海的三个景点"));
System.out.println(message2);
但是正常情况应该是提示词单独写一个配置文件,然后加载。(待实现)
ToolCalling(工具调用) —— 给agent装手脚
工具调用是指在生成过程中调用外部工具来获取信息或执行操作,以增强模型的能力。
SpringAI中工具调用的流程:

发送请求时携带了工具定义(包括名字、描述、input schema等信息),模型生成的文本中会包含工具调用的指令(如调用哪个工具,传入什么参数等),
SpringAI会解析这些指令,调用相应的工具,并将工具的输出结果返回给模型,模型可以根据工具的输出继续生成文本。
案例
- 定义工具类
定义好以后在工具类上添加@Tool和@ToolParam注解即可:
public class TimeTools {
@Tool(description = "通过时区id获取当前时间")
public String getTimeByZoneId(@ToolParam(description = "时区id, 比如Asia/Shanghai") String zoneId) {
ZoneId zid = ZoneId.of(zoneId);
ZonedDateTime zonedDateTime = ZonedDateTime.now(zid);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");
return zonedDateTime.format(formatter);
}
}
- 在ChatClient中使用工具
使用.tools()方法,传入工具类的实例即可:
@GetMapping("/ask")
public String askQuestion(@RequestParam("question") String question) {
return chatClient.prompt()
.user(question) // 比如询问现在的时间是什么?
.tools(new TimeTools())
.call()
.content();
}
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)