一、简介

Spring Boot 4 + Spring 7 + IDEA 2025 + JDK17 ,Spring AI 的定位是:大模型基础对话 + RAG + MCP + Tool + Memory等基础功能。

大模型和程序的关系:大模型适合做模糊的事情,而程序必须是要做非常确定的事。

LangChain4j vs SpringAl

在这里插入图片描述

Spring AI Alibaba

Spring AI 是更底层的基座能力,Spring AI Alibaba 在基础能力之上提供更多的企业功能,包括智能体编码Graph、支持多个Agent框架等。

在这里插入图片描述
在这里插入图片描述

大模型选择

  • 自研
  • 云端大模型:如阿里百云炼平,功能完善成熟
  • 本地部署开源大模型:数据比较私密,不能对外暴漏,Ollama本地部署。
    • 根据行业(如教育、医疗、金融、法律)选择大模型 大模型评测
    • 根据模型成本选择
    • 最后需要人工实际评估得出最终选择
    • 或者直接选择通用能力比较好的综合模型

Spring AI 2.0 新特性

  1. 传输协议发生了变化:去掉了Spring AI 1.0中的SSE模式(Server Send Events 单向长连接),使用新的模式(MCP Streamable HTTP)。
  2. 在上层ChatClient增加日志拦截器
  3. 增加了Agent的模式:观察思考 - 执行工具 - 反思迭代 - 验证确认
    在这里插入图片描述

应用场景

  1. 智能客服:在项目中嵌入一个聊天窗,然后通过Tool调用系统内部的Java方法以及通过MCP调用系统外部的功能(如网络搜索、地图、天气等第三方发布的API),原来通过用户点点点来实现的,现在通过聊天完成。
  2. RAG知识库。

二、流式输出(打字机效果)

1. 添加依赖 pom.xml

SpringWeb + Spring AI + 第三方厂商(如 DeepSeek 、OpenAI、 Ollama):要考虑性能、成本、以及行业评分。
在这里插入图片描述

2. 配置application.properties

阿里云百炼支持多模态:如 文生图、文生语言、文生视频等。

获取API Key(创建API Key): https://bailian.console.aliyun.com/cn-beijing?tab=model#/api-key
在这里插入图片描述

获取API Host: https://bailian.console.aliyun.com/cn-beijing?tab=api#/api/?type=app&url=2782167
在这里插入图片描述
示例代码(获取模型名称、base_url) https://bailian.console.aliyun.com/cn-beijing?tab=model#/model-market/detail/qwen3.7-plus?serviceSite=asia-pacific-china&ref=suggest

在这里插入图片描述
API Key配置在环境变量中。
在这里插入图片描述

# 阿里百炼平台
spring.ai.deepseek.api-key=${DASHSCOPE_API_KEY}
spring.ai.deepseek.chat.model= qwen3.7-plus
spring.ai.deepseek.chat.base-url=https://llm-x2l5ucz6vk2xupzh.cn-beijing.maas.aliyuncs.com/compatible-mode/v1

# 可选参数(不是必须的)
spring.ai.deepseek.chat.options.temperature=1.0
spring.ai.deepseek.chat.options.maxTokens=2048

logging.level.org.springframework.ai=DEBUG

环境变量配置完成后需要重启一下IDEA,否则环境变量加载不到。

3. Bean Configuration

@Configuration
public class SpringAiConfig {

    @Bean
    public ChatClient chatClient(DeepSeekChatModel deepSeekChatModel) {
        return ChatClient.builder(deepSeekChatModel)
        	.defaultSystem("""
        	# 角色
        	你是一个教育行业的只能小助手
			# 要求
			1. 永远讲中文
			""")
        	.build();
    }
}

4. Controller测试

  • call():同步调用,模型全部返回后一次性返回结果。
  • stream():流式返回,异步调用,模型输出一次chunk就返回一次。
@RestController
public class SpringAIController {

    @Autowired
    private ChatClient chatClient;

    @GetMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> chat(@RequestParam("message") String message) {
        Flux<String> content = chatClient.prompt()
                .user(message)
                .stream()
                .content()
                .withConcat(Flux.just("[complete]"));  // 自定义一个结束标记,前端解析到就不会再请求,表示本轮会话全部结束
        return content;
    }
}

在这里插入图片描述
在Chrome浏览器中直接调用,可能会出现乱码,可以通过设置charset=UTF-8来解决。

@GetMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE + ";charset=UTF-8")
  • content():返回String
  • chatResponse():返回ChatResponse对象
Flux<ChatResponse> chatResponse = chatClient.prompt()
        .user(message)
        .stream()
        .chatResponse();

// 消息id:好像只有STOP的时候才有值
var messageId = chatResponse.getMetadata().getId();

var finishReason = chatResponse.getResult().getMetadata().getFinishReason();

// 获取大模型的输出的内容
var text = chatResponse.getResult().getOutput().getText();

三、同步输出

@Bean
public ChatClient openAiChatClient(@Qualifier("openAiChatModel") ChatModel openAiChatModel,
                                   Advisor loggerAdvisor  // 日志记录器
) {
    return ChatClient.builder(openAiChatModel)
            .defaultAdvisors(loggerAdvisor)
            .build();
}


// chatText 文本同步返回结果,不需要记忆也不需要工具,只需要设置提示词简单的调用大模型
public String chatText(String question) {
    return this.openAiChatClient.prompt()
            .system("系统提示词")
            .user(question)
            .call()
            .content();
}

四、结构化输出 entity

直接将结果转为实体类。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Address {
    /**
     * 收件人姓名
     */
    private String name;
    /**
     * 联系电话
     */
    private String phone;
    /**
     * 省
     */
    private String province;
    /**
     * 市
     */
    private String city;
    /**
     * 区/县
     */
    private String district;
    /**
     * 详细地址
     */
    private String detail;
}
Address address = chatClient.prompt()
        .system("""
			请提取用户输入的收货地址信息,输出严格JSON,禁止输出任何解释、markdown标记。
			字段说明:
			name:收件人姓名
			phone:联系电话
			province:省份,例如浙江省
			city:城市,例如杭州市
			district:区/县,例如西湖区
			detail:门牌号、街道等剩余详细地址;若无,则返回空字符串""
			
			规则:
			1. 严格拆分省、市、区,不要把省市区全部塞到detail字段;
			2. 不允许编造不存在信息,原文没有的内容填空字符串,不要瞎编;
			3. JSON只返回这6个字段,不要多出其他字段。
			""")
        .user("收货人:李四,电话13611112222,地址:广东省深圳市南山区科技园南路88号创新大厦A座1201")
        .call()
        .entity(Address.class);

System.out.println(address);

实际返回可能会统一包装成ChatEventVO

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ChatEventVO {

    /**
     * 文本内容
     */
    private Object eventData;

    /**
     * 事件类型,1001-数据事件,1002-停止事件,1003-参数事件
     */
    private int eventType;

}

五、多模型

<!--deepseek-->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-deepseek</artifactId>
</dependency>

<!--百炼-->
<dependency>
    <groupId>com.alibaba.cloud.ai</groupId>
    <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
@Configuration
public class SpringAiConfig {

    @Bean("deepSeekChatClient")
    public ChatClient chatClient(DeepSeekChatModel deepSeekChatModel) {
        return ChatClient.builder(deepSeekChatModel)
        	.defaultSystem("You are a helpful assistant.")
        	.build();
    }

	@Bean("dashScopeChatClient")
    public ChatClient chatClient(DashScopechatModel dashScopechatModel) {
        return ChatClient.builder(dashScopechatModel)
        	.defaultSystem("You are a helpful assistant.")
        	.build();
    }

		@Bean
    public ChatClient openAiChatClient(@Qualifier("openAiChatModel") ChatModel openAiChatModel,
                                       Advisor loggerAdvisor  // 日志记录器
    ) {
        return ChatClient.builder(openAiChatModel)
                .defaultAdvisors(loggerAdvisor)
                .build();
    }
}
@RestController
public class SpringAIController {

    @Autowired
    @Qualifier("dashScopeChatClient")
    private ChatClient chatClient;

    @GetMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> chat(@RequestParam("message") String message) {
        Flux<String> content = chatClient.prompt()
                .user(message)
                .stream()
                .content()
                .withConcat(Flux.just("[complete]"));  // 自定义一个结束标记,前端解析到就不会再请求,表示本轮会话全部结束
        return content;
    }
}

五、其它

Spring AI的本质还是以http的方式调用(restClient)大模型API,将请求参数封装成Prompt对象的JSON,接口返回值也是JSON。

HTTP请求。

curl --location "https://llm-x2l5ucz6vk2xupzh.cn-beijing.maas.aliyuncs.com/compatible-mode/v1" \
--header "Authorization: Bearer $DASHSCOPE_API_KEY" \
--header "Content-Type: application/json" \
--data '{
    "model": "qwen3.8-max",
    "input":{
        "messages":[
            {
                "role": "system",
                "content": [{"text": "You are a helpful assistant."}]
            },
            {
                "role": "user",
                "content": [{"text": "你是谁?"}]
            }
        ]
    },
    "parameters": {
        "result_format": "message"
    }
}'

响应。

{
  "status_code": 200,
  "request_id": "902fee3b-f7f0-9a8c-96a1-6b4ea25af114",
  "code": "",
  "message": "",
  "output": {
    "text": null,
    "finish_reason": null,
    "choices": [
      {
        "finish_reason": "stop",
        "message": {
          "role": "assistant",
          "content": "我是阿里云开发的一款超大规模语言模型,我叫千问。"
        }
      }
    ]
  },
  "usage": {
    "input_tokens": 22,
    "output_tokens": 17,
    "total_tokens": 39
  }
}
Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐