Spring AI
主要从chatclient、chat、struct、tools、vector、advisor、rag、mcp几个方面。
提示:以下是本篇文章正文内容,下面案例可供参考
一、ChatClient的认识
1. 常用的属性
- api-key:用于身份验证的唯一密钥,调用API时必须通过请求头(如Authorization: Bearer )传递。需严格保密,避免泄露或硬编码38。
- base-url:指定API服务端点地址。例如:
OpenAI默认URL:https://api.openai.com/v1
DeepSeek自定义URL:https://api.deepseek.com/v1/chat/completions2
企业部署时可指向私有化服务地址。 - model:指定调用的模型名称,决定模型能力和成本。常见选项:
- gpt-4o:最新多模态模型(文本/图像)
- gpt-4:高性能通用模型
- gpt-3.5-turbo:经济型轻量模型
不同模型的并发度、计费规则不同,需按需选择。
- temperature:控制输出随机性的核心参数(范围0~2):
- 低值(0~0.3):保守输出,选择概率最高的词,适合代码生成、事实问题
- 中等温度(0.5-0.9):平衡确定性与多样性,适合日常对话或商业写作。
- 高温度(1.0-2.0):提升低概率词汇的权重,增强创造性和随机性,适合诗歌、故事生成等创意任务,但可能降低连贯性
- system::对话消息数组,每条消息需包含 role 和 content 字段:
- user:作用:用户输入的问题或指令
- assistant :作用:模型历史回复,用于维持多轮对话上下文。
- message :一个完整的User message是一个json对象,包含以下字段:
- content:必须提供的string或array类型,二选一,表示user的消息内容
- string类型时,表示消息的文本内容
- 为array类型时,一般用于调用多模态模型,用来包含多个内容部分的数组,一般是一个文本内容的json对象和一个或多个图片内容的json对象。仅当使用 gpt-4-visual-preview 这样的多模态模型时才支持图像输入。具体字段如下:
- 文本内容部分,是一个json对象:
- type:必须提供的string类型,表示内容部分的类型,一般是“text”
- text:必须提供的string类型,文字内容
- 图片内容部分,是一个json对象:
- type:必须提供的string类型,表示内容部分的类型,一般是"image_url"
- image_url:必须提供的json对象类型,字段有:
url:必须提供的string类型,图像的 URL 或 Base64 编码的图像数据
detail:可选的string类型,一般默认是“auto”
- 文本内容部分,是一个json对象:
- role:必须提供的string类型,表示消息作者的角色,对于user message应该是"user"
- name:可选的string类型,表示对话参与者的名称
- content:必须提供的string或array类型,二选一,表示user的消息内容
关键注意:多轮对话需严格按 system → user → assistant → user 顺序组织消息列表。
2. Assistant
Assistant 这个是维持多轮会话和上下文一个关键的角色。但是Spring AI是没有设置的入口的。
原因 | 说明 |
---|---|
抽象、屏蔽复杂性 | Spring AI 更关注用户输入(user)和系统提示(system),以保持接口简洁 |
兼容性考虑 | 不同 LLM 对 assistant 的实现略有差异(如 Claude 用 assistant,Anthropic 用 human / assistant) |
主要使用场景限制 | ChatClient 更适用于一次交互式问答,而非多轮训练数据构造。 |
但是当使用了MessageWindowChatMemory,框架会默认把历史记录的信息放到AssistantMessage中的。
3. 案例展示
{
"model": "gpt-4o",
"temperature": 0.5,
"messages": [
{"role": "system", "content": "你是一位历史学家"},
{"role": "user", "content": "罗马帝国灭亡的原因是什么?"}
{"role": "assistant", "content": "罗马帝国是一个强大的国家"}
]
}
curl https://api.openai.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "gpt-4-turbo",
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "What'\''s in this image?"
},
{
"type": "image_url",
"image_url": {
"url": "https://pic4.zhimg.com/v2-4d9e9f936b9968f53be22b594aafa74f_r.jpg"
}
}
]
}
],
"max_tokens": 300
}
// java 调用 open AI
OpenAIClient client = OpenAIOkHttpClient.builder()
.apiKey("sk-rNst1bBrT4P7rzD6OLpqgbwNedeQLw9KtwfS0DGpUDCe3GtY")
.baseUrl("https://www.dmxapi.cn/v1")
.build();
//usermessage 消息组装
List<ChatCompletionContentPart> list = new ArrayList<>();
ChatCompletionContentPart text =
ChatCompletionContentPart.ofText(
ChatCompletionContentPartText
.builder()
.text(ScreenPromptConfig.SCREENSHOT_TO_CODE_PROMPT)
.build());
ChatCompletionContentPart imageUrl =
ChatCompletionContentPart.ofImageUrl(
ChatCompletionContentPartImage
.builder()
.imageUrl(
ChatCompletionContentPartImage
.ImageUrl
.builder()
.url(url)
.build())
.build());
list.add(text);
list.add(imageUrl);
//助理:历史记录消息
ChatCompletionAssistantMessageParam assistantMessageParam =
ChatCompletionAssistantMessageParam
.builder()
.content(ChatCompletionAssistantMessageParam.Content.ofText("我是一个助理消息")).build();
ChatCompletionCreateParams params =
ChatCompletionCreateParams
.builder()
.model("llama-3.3-70b-instruct")
.addUserMessage(
ChatCompletionUserMessageParam
.Content.ofArrayOfContentParts(list))
//设置助理消息
.addMessage(assistantMessageParam)
.temperature(0.2)
.maxCompletionTokens(1000)
.build();
// 发送请求并接收响应
ChatCompletion chatCompletion = client.chat().completions().create(params);
Optional<String> content = chatCompletion.choices().get(0).message().content();
if (content.isEmpty()) {
return "none";
}
return content.get();
});
二、Chat对话
1.引入库
代码如下(示例):
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-autoconfigure-model-openai</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-autoconfigure-model-chat-client</artifactId>
</dependency>
2. 配置文件
spring.ai.openai.api-key=YOUR_API_KEY
spring.ai.openai.base-url=YOUR_BASE_URL
spring.ai.openai.chat.options.model=YOUR_MODEL_NAME
3. 普通调用
@GetMapping("/call")
public String call(@RequestParam(value = "msg")String msg) {
return chatClient.prompt(msg).call().content();
}
4. 流式调用
@GetMapping("/stream")
public Flux<String> stream(@RequestParam(value = "msg")String msg, HttpServletResponse response) {
response.setCharacterEncoding("UTF-8");
return chatClient.prompt(msg).stream().content();
}
5. Option使用
@GetMapping("/call/temperature")
public String callOption(@RequestParam(value = "msg")String msg, HttpServletResponse response) {
response.setCharacterEncoding("UTF-8");
return chatClient.prompt(msg)
.options(
OpenAiChatOptions.builder()
.temperature(0.2)
.build()
)
.call().content();
}
三、struct结构化数据
1. Bean结构化
@JsonPropertyOrder({ "title", "date", "author", "content"})
public record BeanEntity (String title, String date, String author, String content){
}
private final BeanOutputConverter<BeanEntity> converter;
public BeanController(ChatClient.Builder builder) {
this.converter = new BeanOutputConverter<>(
new ParameterizedTypeReference<BeanEntity>() {
}
);
this.chatClient = builder.build();
}
1. 格式化前的结果
@GetMapping("/call")
public String call(@RequestParam(value = "msg") String msg){
String result = chatClient.prompt(msg).call().content();
log.info("result: {}", result);
assert result != null;
try {
BeanEntity convert = converter.convert(result);
log.info("反序列成功, convert: {}", convert);
} catch (Exception e){
log.error("反序列失败");
}
return result;
}
2. 格式化后的结果
@GetMapping("/call/format")
public String callFormat(@RequestParam(value = "msg") String msg){
String template = msg + "{format}";
PromptTemplate promptTemplate = PromptTemplate.builder()
.template(template)
.variables(Map.of("format", converter.getFormat()))
.renderer(StTemplateRenderer.builder().build())
.build();
Prompt prompt = promptTemplate.create();
String result = chatClient.prompt(prompt).call().content();
log.info("result: {}", result);
assert result != null;
try {
BeanEntity convert = converter.convert(result);
log.info("反序列成功, convert: {}", convert);
} catch (Exception e){
log.error("反序列失败");
}
return result;
}
2. Map-List结构化
1. 格式化前
2. 格式化后的map
private final MapOutputConverter mapConverter;
private final ListOutputConverter listConverter;
public MapListController(ChatClient.Builder builder) {
// map转换器
this.mapConverter = new MapOutputConverter();
// list转换器
this.listConverter = new ListOutputConverter(new DefaultConversionService());
this.chatClient = builder
.build();
}
@GetMapping("/map")
public Map<String, Object> map(@RequestParam(value = "msg", defaultValue = "请为我描述下春天的特性") String msg) {
String promptUserSpec = """
format: key为描述的东西,value为对应的值
outputExample: {format};
""";
String format = mapConverter.getFormat();
logger.info("map format: {}",format);
String result = chatClient.prompt(msg)
.user(u -> u.text(promptUserSpec)
.param("format", format))
.call().content();
logger.info("result: {}", result);
assert result != null;
Map<String, Object> convert = null;
try {
convert = mapConverter.convert(result);
logger.info("反序列成功,convert: {}", convert);
} catch (Exception e) {
logger.error("反序列化失败");
}
return convert;
}
3. 格式化后的list
@GetMapping("/list")
public List<String> list(@RequestParam(value = "msg", defaultValue = "请为我描述下春天的特性") String msg) {
String promptUserSpec = """
format: value为对应的值
outputExample: {format};
""";
String format = listConverter.getFormat();
logger.info("list format: {}",format);
String result = chatClient.prompt(msg)
.user(u -> u.text(promptUserSpec)
.param("format", format))
.call().content();
logger.info("result: {}", result);
assert result != null;
List<String> convert = null;
try {
convert = listConverter.convert(result);
logger.info("反序列成功,convert: {}", convert);
} catch (Exception e) {
logger.error("反序列化失败");
}
return convert;
}
四、tools工具
调用本地的方法和工具类
假如我想要查询现在几点,如果不联网的话,大模型是不知道的。
假如我想知道现在今天的天气,或者大模型执行到某个节点的时候,我想把这个时间的状态记录下载,存储到mysql或者redis,如何做。
1. 引入包
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-autoconfigure-model-tool</artifactId>
</dependency>
2. 调用前
3. 调用后
@Tool(description = "Get the time of a specified city.")
public String getCityTimeMethod(@ToolParam(description = "Time zone id, such as Asia/Shanghai") String timeZoneId) {
logger.info("The current time zone is {}", timeZoneId);
return String.format("The current time zone is %s and the current time is " + "%s", timeZoneId,
TimeUtils.getTimeByZoneId(timeZoneId));
}
public static String getTimeByZoneId(String zoneId) {
// Get the time zone using ZoneId
ZoneId zid = ZoneId.of(zoneId);
// Get the current time in this time zone
ZonedDateTime zonedDateTime = ZonedDateTime.now(zid);
// Defining a formatter
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");
// Format ZonedDateTime as a string
String formattedDateTime = zonedDateTime.format(formatter);
return formattedDateTime;
}
/**
* 调用工具版 - method
*/
@GetMapping("/call/tool-method")
public String callToolMethod(@RequestParam(value = "msg", defaultValue = "请告诉我现在北京时间几点了") String msg) {
return chatClient.prompt(msg).tools(new TimeTools()).call().content();
}
4. 其他-调用天气前
5. 其他-调用天气后
五、vector向量库
文本数据存入向量库,然后通过向量库进行检索出来。
1. 引入包
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-elasticsearch</artifactId>
</dependency>
<!-- es版本匹配-->
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>8.17.4</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.10.1</version>
</dependency>
@GetMapping("/import")
public void importData() {
logger.info("start import data");
HashMap<String, Object> map = new HashMap<>();
map.put("id", "12345");
map.put("year", 2025);
map.put("name", "lyl");
List<Document> documents = List.of(
new Document("当第一缕晨光刺穿云层时,整个校园突然被镀上蜂蜜色的光泽。我站在三楼走廊,看见悬铃木的新叶在风中翻飞,那些半透明的嫩绿像极了实验室里的硫酸铜溶液,在光线折射下荡漾出翡翠般的波纹。"),
new Document("操场东南角的樱花树总是最早感知春讯。淡粉色的花瓣边缘带着细微的锯齿,每片都承托着晨露制成的棱镜。物理老师曾说露珠的曲率半径约0.2毫米,这个数字此刻具象为无数个颠倒的微型世界——蚂蚁在露珠里变成庞然大物,而远处的教学楼则扭曲成梵高笔下的星空。", Map.of("year", 2024)),
new Document("正午的食堂后墙爬满蔷薇的枝条。那些猩红的花苞尚未绽放,却已吸引早醒的蜜蜂前来勘探。它们毛茸茸的后腿沾满金色花粉,飞行轨迹画出布朗运动般的折线。生物课本里\"虫媒花蜜腺分泌速率0.03ml/h\"的知识点,在此刻化作瓷砖地上滴落的黏稠光斑。", map));
new Document("黄昏时分的篮球场最具戏剧性。西晒的阳光下,飞扬的尘埃变成金色的蜉蝣,而褪色的篮板在夕照中泛起铁锈红。最动人的是墙角那排冬青树新抽的嫩芽,在暮色里依然倔强地闪烁着磷火般的微光,仿佛要把白天储存的阳光在夜间慢慢释放。");
new Document("晚自习的窗口飘进混合着青草与合欢花香的夜风。对面居民楼晾晒的床单在月光下起伏,像极了解剖图里舒展的肺泡。而真正让人怔忡的,是偶然抬头看见玉兰花瓣飘落的轨迹——那些旋转下坠的白色弧线,完美复现了数学课上抛物线方程的图像。");
new Document("当保安大叔开始巡视空荡的走廊,春日就在荧光笔标记的重点里悄悄更迭。这个发现让我突然理解:原来最动人的春色,从来不在远方,而在我们习以为常的日常褶皱里。");
elasticsearchVectorStore.add(documents);
}
六、Advisor
Spring AI Advisor 是 Spring 框架生态系统中的一个工具或组件,用于在应用程序中引入人工智能(AI)功能。它通常用于提供智能化的建议、决策支持或行为预测等功能。
1. 引入包
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-memory</artifactId>
<version>${spring-ai-alibaba.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-memory-jdbc</artifactId>
<version>${spring-ai-alibaba.version}</version>
</dependency>
2. 代码哪里
@Bean
public MysqlChatMemoryRepository mysqlChatMemoryRepository(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(mysqlDriverClassName);
dataSource.setUrl(mysqlJdbcUrl);
dataSource.setUsername(mysqlUsername);
dataSource.setPassword(mysqlPassword);
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
return MysqlChatMemoryRepository.mysqlBuilder()
.jdbcTemplate(jdbcTemplate)
.build();
}
private final ChatClient chatClient;
private final int MAX_MESSAGES = 100;
private final MessageWindowChatMemory messageWindowChatMemory;
public MysqlAdvisorController(ChatClient.Builder builder, MysqlChatMemoryRepository mysqlChatMemoryRepository) {
this.messageWindowChatMemory = MessageWindowChatMemory.builder()
.chatMemoryRepository(mysqlChatMemoryRepository)
.maxMessages(MAX_MESSAGES)
.build();
this.chatClient = builder
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(messageWindowChatMemory)
.build()
)
.build();
}
3. 效果演示
七、RAG检索生成增强
1. 引入包
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-milvus-store</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-autoconfigure-vector-store-milvus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency>
2. 实例化bean
@Bean
public RestClient restClient(){
logger.info("create elasticsearch rest client");
// 构建RestClient
return RestClient.builder(HttpHost.create(url))
.setDefaultHeaders(new Header[]{
new BasicHeader("Authorization", "ApiKey " + apikey)
}).build();
}
@Bean
@Qualifier("elasticsearchVectorStore")
public ElasticsearchVectorStore vectorStore(RestClient restClient, EmbeddingModel embeddingModel) {
logger.info("create elasticsearch vector store");
ElasticsearchVectorStoreOptions options = new ElasticsearchVectorStoreOptions();
options.setIndexName(indexName); // Optional: defaults to "spring-ai-document-index"
options.setSimilarity(similarityFunction); // Optional: defaults to COSINE
options.setDimensions(dimensions); // Optional: defaults to model dimensions or 1536
return ElasticsearchVectorStore.builder(restClient, embeddingModel)
.options(options) // Optional: use custom options
.initializeSchema(true) // Optional: defaults to false
.batchingStrategy(new TokenCountBatchingStrategy())// Optional: defaults to TokenCountBatchingStrategy
.build();
}
3. 方法调用
@GetMapping("/add")
public void add() {
logger.info("start add data");
HashMap<String, Object> map = new HashMap<>();
map.put("year", 2025);
map.put("name", "lyl");
List<Document> documents = List.of(
new Document("你的姓名是春天,湖南邵阳人,25年硕士毕业于北京科技大学,曾先后在百度、理想、快手实习,曾发表过一篇自然语言处理的sci,现在是一名AI研发工程师"),
new Document("你的姓名是春天,专业领域包含的数学、前后端、大数据、自然语言处理", Map.of("year", 2024)),
new Document("你姓名是春天,爱好是发呆、思考、运动", map));
elasticsearchVectorStore.add(documents);
}
@GetMapping("/chat-rag-advisor")
public String chatRagAdvisor(@RequestParam(value = "msg", defaultValue = "你好,请告诉我春天这个人的身份信息") String msg) {
logger.info("start chat with rag-advisor");
RetrievalAugmentationAdvisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(VectorStoreDocumentRetriever.builder()
.vectorStore(elasticsearchVectorStore)
.build())
.queryAugmenter(ContextualQueryAugmenter.builder()
.allowEmptyContext(true)
.build())
.build();
return chatClient.prompt(msg)
.advisors(retrievalAugmentationAdvisor)
.call().content();
}
4. 效果展示
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。