Spring AI Alibaba入门教程
一、什么是 Spring AI Alibaba
Spring AI Alibaba
是阿里巴巴推出的企业级 AI 应用开发框架,致力于为 Java 开发者提供构建 AI 应用的完整解决方案。它主要解决两大核心问题:
- 统一接口:消除不同 AI 服务的 API 差异,支持阿里云通义千问、智谱 AI 等多种模型。
- 企业级生态整合:提供企业级的观测、评估、MCP 集成等功能。
🛠️ 核心功能
Spring AI Alibaba 具备以下核心能力:
1. 基于图的多智能体框架
- 通过 Spring AI Alibaba Graph 构建工作流和多智能体应用
- 支持可视化调试和 Dify DSL 集成
2. 企业级 AI 生态集成
- 支持阿里云百炼平台、ARMS 和 Langfuse 等观测产品
- 提供 Nacos MCP Registry 等企业级组件
3. Plan-Act 智能体产品
- JManus:企业级智能体实现
- DeepResearch:研究和报告智能体
二、快速入门
效果展示
我们要完成的案例:智能天气预报助手,提供准确、便捷的天气信息服务。
环境搭建
创建项目
使用 IDEA 创建 Spring Boot 项目:
- JDK 17+
- Spring Boot 3.2.x 或 3.3.x
- 勾选
Spring Web
和Spring Reactive Web
添加依赖
在 pom.xml
中添加:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-bom</artifactId>
<version>1.0.0.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
</dependency>
</dependencies>
配置文件
创建 application.yml
:
spring:
ai:
dashscope:
api-key: ${DASHSCOPE_API_KEY}
chat:
options:
model: qwen-turbo
环境变量配置:
DASHSCOPE_API_KEY=你的阿里云API_KEY
后端代码编写
创建 WeatherController
:
package com.example.weather.controller;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
@RestController
@RequestMapping("/weather")
public class WeatherController {
private final DashScopeChatModel chatModel;
private static final String SYSTEM_PROMPT = """
你是一个专业的天气预报机器人,擅长:
1. 解答天气相关的问题
2. 提供天气预报建议
3. 解释天气现象
4. 提供合适的穿衣建议
5. 分析天气对出行的影响
请始终以专业、友好的口吻回答问题。如果问题与天气无关,请礼貌地提醒用户你是一个天气预报助手。
""";
public WeatherController(DashScopeChatModel chatModel) {
this.chatModel = chatModel;
}
/**
* 生成单次天气相关回复
*/
@GetMapping("/generate")
public String generate(@RequestParam(value = "message") String message) {
String prompt = SYSTEM_PROMPT + "\n用户问题:" + message;
return this.chatModel.call(prompt);
}
/**
* 生成流式天气相关回复
*/
@GetMapping(value = "/generateStream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> generateStream(@RequestParam(value = "message") String message) {
String promptText = SYSTEM_PROMPT + "\n用户问题:" + message;
var prompt = new Prompt(new UserMessage(promptText));
Flux<ChatResponse> stream = this.chatModel.stream(prompt);
return stream.map(response -> {
if (response.getResult() != null && response.getResult().getOutput() != null) {
return response.getResult().getOutput().getContent();
}
return "";
});
}
}
前端代码编写
创建 src/main/resources/static/weather.html
:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>智能天气预报助手</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: linear-gradient(120deg, #89f7fe 0%, #66a6ff 100%);
height: 100vh;
}
.chat-container {
max-width: 800px;
margin: 20px auto;
height: calc(100vh - 40px);
display: flex;
flex-direction: column;
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15);
padding: 20px;
}
.chat-header {
text-align: center;
padding: 20px 0;
border-bottom: 1px solid #eee;
margin-bottom: 20px;
}
.chat-header h1 {
color: #1a73e8;
font-size: 24px;
margin-bottom: 10px;
}
.messages-container {
flex: 1;
overflow-y: auto;
padding: 20px;
background: rgba(255, 255, 255, 0.8);
border-radius: 8px;
margin-bottom: 20px;
}
.message {
margin-bottom: 20px;
padding: 15px;
border-radius: 8px;
max-width: 80%;
}
.user-message {
background: #e3f2fd;
margin-left: auto;
color: #1565c0;
}
.assistant-message {
background: #f5f5f5;
margin-right: auto;
color: #333;
}
.input-container {
position: relative;
padding: 20px;
background: rgba(255, 255, 255, 0.9);
border-radius: 8px;
}
#message-input {
width: 100%;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 16px;
resize: none;
height: 50px;
}
#send-button {
position: absolute;
right: 30px;
bottom: 30px;
padding: 8px 20px;
background: #1a73e8;
color: white;
border: none;
border-radius: 20px;
cursor: pointer;
}
.example-questions {
margin-top: 10px;
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.example-question {
background: #e3f2fd;
color: #1565c0;
padding: 8px 16px;
border-radius: 16px;
font-size: 14px;
cursor: pointer;
}
.typing {
display: inline-block;
}
.typing span {
display: inline-block;
width: 6px;
height: 6px;
background: #666;
border-radius: 50%;
margin: 0 2px;
animation: typing 1s infinite;
}
@keyframes typing {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-4px); }
}
</style>
</head>
<body>
<div class="chat-container">
<div class="chat-header">
<h1>🌤️ 智能天气预报助手</h1>
<p>基于 Spring AI Alibaba 构建</p>
</div>
<div class="messages-container" id="messages">
<div class="message assistant-message">
您好!我是智能天气预报助手,可以为您提供天气预报、穿衣建议和出行建议。
</div>
</div>
<div class="example-questions">
<div class="example-question" onclick="askExample(this)">北京今天天气怎么样?</div>
<div class="example-question" onclick="askExample(this)">今天适合户外运动吗?</div>
<div class="example-question" onclick="askExample(this)">明天要出门,需要带伞吗?</div>
</div>
<div class="input-container">
<textarea id="message-input" placeholder="请输入您的天气相关问题..."
onkeydown="if(event.keyCode === 13 && !event.shiftKey) { event.preventDefault(); sendMessage(); }"></textarea>
<button id="send-button" onclick="sendMessage()">发送</button>
</div>
</div>
<script>
const messagesContainer = document.getElementById('messages');
const messageInput = document.getElementById('message-input');
const sendButton = document.getElementById('send-button');
function askExample(element) {
messageInput.value = element.textContent;
sendMessage();
}
function createMessageElement(content, isUser) {
const messageDiv = document.createElement('div');
messageDiv.className = `message ${isUser ? 'user-message' : 'assistant-message'}`;
messageDiv.textContent = content;
return messageDiv;
}
function createTypingIndicator() {
const typingDiv = document.createElement('div');
typingDiv.className = 'message assistant-message';
typingDiv.innerHTML = '正在查询天气信息<div class="typing"><span></span><span></span><span></span></div>';
return typingDiv;
}
async function sendMessage() {
const message = messageInput.value.trim();
if (!message) return;
messageInput.disabled = true;
sendButton.disabled = true;
messagesContainer.appendChild(createMessageElement(message, true));
messageInput.value = '';
const typingIndicator = createTypingIndicator();
messagesContainer.appendChild(typingIndicator);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
try {
const response = await fetch(`/weather/generate?message=${encodeURIComponent(message)}`);
const data = await response.text();
typingIndicator.remove();
messagesContainer.appendChild(createMessageElement(data, false));
messagesContainer.scrollTop = messagesContainer.scrollHeight;
} catch (error) {
console.error('API调用错误:', error);
typingIndicator.remove();
const errorMessage = document.createElement('div');
errorMessage.className = 'message assistant-message';
errorMessage.textContent = '抱歉,发生了错误,请稍后重试。';
messagesContainer.appendChild(errorMessage);
} finally {
messageInput.disabled = false;
sendButton.disabled = false;
messageInput.focus();
}
}
window.onload = () => {
messageInput.focus();
};
</script>
</body>
</html>
三、打字机效果实现
流式响应版本
创建 src/main/resources/static/stream.html
:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>智能天气预报助手 - 流式响应版</title>
<style>
/* 样式与普通版本相同 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: linear-gradient(120deg, #89f7fe 0%, #66a6ff 100%);
height: 100vh;
}
.chat-container {
max-width: 800px;
margin: 20px auto;
height: calc(100vh - 40px);
display: flex;
flex-direction: column;
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15);
padding: 20px;
}
.chat-header {
text-align: center;
padding: 20px 0;
border-bottom: 1px solid #eee;
margin-bottom: 20px;
}
.chat-header h1 {
color: #1a73e8;
font-size: 24px;
margin-bottom: 10px;
}
.messages-container {
flex: 1;
overflow-y: auto;
padding: 20px;
background: rgba(255, 255, 255, 0.8);
border-radius: 8px;
margin-bottom: 20px;
}
.message {
margin-bottom: 20px;
padding: 15px;
border-radius: 8px;
max-width: 80%;
white-space: pre-wrap;
word-wrap: break-word;
}
.user-message {
background: #e3f2fd;
margin-left: auto;
color: #1565c0;
}
.assistant-message {
background: #f5f5f5;
margin-right: auto;
color: #333;
}
.input-container {
position: relative;
padding: 20px;
background: rgba(255, 255, 255, 0.9);
border-radius: 8px;
}
#message-input {
width: 100%;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 16px;
resize: none;
height: 50px;
}
#send-button {
position: absolute;
right: 30px;
bottom: 30px;
padding: 8px 20px;
background: #1a73e8;
color: white;
border: none;
border-radius: 20px;
cursor: pointer;
}
.example-questions {
margin-top: 10px;
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.example-question {
background: #e3f2fd;
color: #1565c0;
padding: 8px 16px;
border-radius: 16px;
font-size: 14px;
cursor: pointer;
}
.typing {
display: inline-block;
}
.typing span {
display: inline-block;
width: 6px;
height: 6px;
background: #666;
border-radius: 50%;
margin: 0 2px;
animation: typing 1s infinite;
}
@keyframes typing {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-4px); }
}
</style>
</head>
<body>
<div class="chat-container">
<div class="chat-header">
<h1>🌤️ 智能天气预报助手</h1>
<p>基于 Spring AI Alibaba 构建 - 流式响应版</p>
</div>
<div class="messages-container" id="messages">
<div class="message assistant-message">
您好!我是智能天气预报助手(流式响应版),可以为您提供天气预报、穿衣建议和出行建议。
</div>
</div>
<div class="example-questions">
<div class="example-question" onclick="askExample(this)">北京今天天气怎么样?</div>
<div class="example-question" onclick="askExample(this)">今天适合户外运动吗?</div>
<div class="example-question" onclick="askExample(this)">明天要出门,需要带伞吗?</div>
</div>
<div class="input-container">
<textarea id="message-input" placeholder="请输入您的天气相关问题..."
onkeydown="if(event.keyCode === 13 && !event.shiftKey) { event.preventDefault(); sendMessage(); }"></textarea>
<button id="send-button" onclick="sendMessage()">发送</button>
</div>
</div>
<script>
const messagesContainer = document.getElementById('messages');
const messageInput = document.getElementById('message-input');
const sendButton = document.getElementById('send-button');
function askExample(element) {
messageInput.value = element.textContent;
sendMessage();
}
function createMessageElement(content, isUser) {
const messageDiv = document.createElement('div');
messageDiv.className = `message ${isUser ? 'user-message' : 'assistant-message'}`;
messageDiv.textContent = content;
return messageDiv;
}
function createTypingIndicator() {
const typingDiv = document.createElement('div');
typingDiv.className = 'message assistant-message';
typingDiv.innerHTML = '正在查询天气信息<div class="typing"><span></span><span></span><span></span></div>';
return typingDiv;
}
async function sendMessage() {
const message = messageInput.value.trim();
if (!message) return;
messageInput.disabled = true;
sendButton.disabled = true;
messagesContainer.appendChild(createMessageElement(message, true));
messageInput.value = '';
const typingIndicator = createTypingIndicator();
messagesContainer.appendChild(typingIndicator);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
try {
const assistantMessage = document.createElement('div');
assistantMessage.className = 'message assistant-message';
const eventSource = new EventSource(`/weather/generateStream?message=${encodeURIComponent(message)}`);
typingIndicator.remove();
messagesContainer.appendChild(assistantMessage);
eventSource.onmessage = function (event) {
assistantMessage.textContent += event.data;
messagesContainer.scrollTop = messagesContainer.scrollHeight;
};
eventSource.onerror = function (error) {
console.error('EventSource错误:', error);
eventSource.close();
if (!assistantMessage.textContent) {
assistantMessage.textContent = '抱歉,发生了错误,请稍后重试。';
}
messageInput.disabled = false;
sendButton.disabled = false;
messageInput.focus();
};
} catch (error) {
console.error('API调用错误:', error);
const errorMessage = document.createElement('div');
errorMessage.className = 'message assistant-message';
errorMessage.textContent = '抱歉,发生了错误,请稍后重试。';
messagesContainer.appendChild(errorMessage);
messageInput.disabled = false;
sendButton.disabled = false;
messageInput.focus();
}
}
window.onload = () => {
messageInput.focus();
};
</script>
</body>
</html>
四、常用参数说明
Spring AI Alibaba 提供了丰富的配置选项:
属性 | 描述 | 默认值 |
---|---|---|
spring.ai.dashscope.api-key | 阿里云API密钥 | - |
spring.ai.dashscope.base-url | API基础URL | https://dashscope.aliyuncs.com |
spring.ai.dashscope.chat.enabled | 是否启用聊天模型 | true |
spring.ai.dashscope.chat.options.model | 模型名称 | qwen-turbo |
spring.ai.dashscope.chat.options.temperature | 温度参数 | 0.7 |
spring.ai.dashscope.chat.options.top-p | 核采样参数 | 1.0 |
参数配置示例
spring:
ai:
dashscope:
api-key: ${DASHSCOPE_API_KEY}
chat:
enabled: true
options:
model: qwen-turbo # 可选:qwen-turbo, qwen-plus, qwen-max
temperature: 0.7 # 控制创造性
top-p: 0.8 # 核采样参数
温度和 topP 到底有什么区别?
举例子来对比一下温度和 topP。
温度:
当温度=0.5 时,AI 写诗只会用经典押韵格式;
当温度=2.0 时,AI 可能把"月亮"写成"会飞的咸蛋黄"
这个参数本质是在"稳定性"和"创造性"之间找平衡,温度越低越像教科书,温度越高越像科幻小说。
topP:
想象你在点外卖,模型要推荐菜品:
此时就算温度很高,只要 topP 压得低(比如 0.9),模型实际只能从"前三热门"里选,不会跳出厨房小白的常规选项。
为什么同时用这两个参数?
就像开车时既要控制油门(温度)又要控制方向盘(topP)。
五、总结
通过 Spring AI Alibaba,我们成功构建了一个智能天气预报助手,实现了:
- 快速集成:简单依赖配置即可接入阿里云通义千问
- 流式响应:使用 WebFlux 实现打字机效果
- 企业级特性:支持参数调优、错误处理
- 灵活配置:支持多种模型和参数配置
Spring AI Alibaba 提供了完整的企业级 AI 应用开发解决方案,是构建智能应用的理想选择。