一个覆盖 Spring AI Alibaba 大模型 / Agent 开发核心技能 的学习项目,自带 FreeMarker 单页面,
包含 10 个可动手测试的示例:ChatClient、ChatModel、嵌入模型、工具调用、对话记忆、提示词模板、
结构化输出、向量存储、RAG 博客笔记问答、Graph 多节点 Agent。
官网文档:https://sca.aliyun.com/en/docs/ai/get-started/ (https://java2ai.com)
技术栈
| 组件 | 版本 |
|---|---|
| JDK | 17 |
| Spring Boot | 3.4.5 |
| Spring AI Alibaba | 1.0.0.2(BOM 统一管理) |
| Spring AI | 1.0.0 |
| 页面模板 | FreeMarker |
| 数据库 | H2(内存模拟)/ MySQL(可切换) |
| 向量数据库 | 可切换:SimpleVectorStore 内存版(默认)/ 阿里云 Tair / Redis Stack / Milvus / Chroma |
| 大模型 | 可切换:阿里云百炼 DashScope(默认)/ DeepSeek / 腾讯混元 / 字节豆包 / Kimi / GLM |
快速开始(IDEA 直接启动)
- 配置 API Key(二选一,获取地址:https://bailian.console.aliyun.com/)
- 环境变量:
AI_DASHSCOPE_API_KEY=sk-xxxx - 或直接写在
src/main/resources/application.yml的spring.ai.dashscope.api-key
- 环境变量:
- 用 IDEA 打开项目(JDK 选择 17,如
F:\Software\Java\jdk-17.0.1) - 运行
StudyApplication,访问 http://localhost:8080/ - 默认
study.db-type=h2:H2 内存库自动建表、自动插入 6 篇示例博文、启动时自动完成 RAG 初始化,
开箱即可在页面上测试所有功能。
若启动时没配 API Key:应用仍能正常启动(RAG 自动初始化会跳过并记录日志),配置好 Key 后在页面上点「初始化知识库」即可。
模型来源切换(多厂商支持)
项目不绑定阿里云:通过 study.model.* 一套配置即可切换任意主流厂商。
原理:所有厂商的模型在 Spring AI 中都实现统一的 ChatModel / EmbeddingModel 接口,
业务代码零改动(详见 config/ModelProviderConfig,含 Spring 高级技巧:BeanFactoryPostProcessor 动态降级 primary Bean)。
study:model:provider: dashscope # dashscope=阿里云百炼(默认) / openai=任意 OpenAI 兼容接口embedding-provider: auto # 嵌入模型来源:auto(跟随 provider) / dashscope / openaiopenai: # provider=openai 时生效base-url: https://api.deepseek.comapi-key: ${OPENAI_COMPATIBLE_API_KEY:}chat-model: deepseek-chatembedding-model: "" # DeepSeek 留空(无 embedding 接口,自动回退 DashScope)temperature: 0.7
各厂商速查表(改 base-url / api-key / chat-model 三项即可):
| 厂商 | base-url | chat-model 示例 | embedding 模型示例 |
|---|---|---|---|
| DeepSeek | https://api.deepseek.com |
deepseek-chat / deepseek-reasoner |
无(自动回退 DashScope) |
| 腾讯混元 | https://api.hunyuan.cloud.tencent.com/v1 |
hunyuan-turbos-latest |
hunyuan-embedding |
| 字节豆包(火山方舟) | https://ark.cn-beijing.volces.com/api/v3 |
doubao-seed-1-6-250615 |
doubao-embedding-large |
| Kimi(月之暗面) | https://api.moonshot.cn/v1 |
moonshot-v1-8k |
- |
| 智谱 GLM | https://open.bigmodel.cn/api/paas/v4 |
glm-4-flash |
embedding-3 |
注意:对话模型与嵌入模型可以来自不同厂商。典型组合:对话用 DeepSeek + 嵌入用百炼
(DeepSeek 不提供 embedding 接口,此组合只需配置 DashScope 的 key,嵌入自动回退)。
数据库切换开关
application.yml 中:
study:db-type: h2 # h2:内存数据库模拟(默认,零配置)# db-type: mysql # 切换为真实博客库时改成 mysql,并配置下面的连接mysql:url: jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=trueusername: rootpassword: root
- h2 模式:自动创建
content表并插入示例博文(仅当表为空时),SPRING_AI_CHAT_MEMORY对话记忆表也自动创建。 - mysql 模式:连接你真实的博客数据库,直接读取已有
content表数据。
建表/示例数据脚本见sql/init-mysql.sql(content表已存在则跳过)。
content 表结构(兼容常见博客系统的最小字段):
CREATE TABLE IF NOT EXISTS content (id BIGINT AUTO_INCREMENT PRIMARY KEY,title VARCHAR(255) NOT NULL,content TEXT NOT NULL);
RAG 实现说明(对应需求逻辑)
1. 初始化(POST /api/rag/init,也可配置启动自动执行 study.rag.auto-init: true)
- 连接数据库(h2/mysql 由
study.db-type决定) - 查询
content表全部博文笔记 - 按段落(空行分隔)分片,每段生成带元数据(contentId/title)的
Document - 调用 DashScope 嵌入模型
text-embedding-v3向量化,批量写入向量存储
2. 查询(POST /api/rag/query)
- 前端输入的问题先向量化
- 去向量库相似检索 Top-K(
study.rag.top-k,默认 4) - 检索到的笔记片段 + 用户问题一起提交给大模型,返回带引用来源的回答
3. 向量数据库选型与切换(study.vectorstore.type)
study:vectorstore:type: simple # simple / tair / redis / milvus / chromaembedding-dimensions: 1024 # 必须与嵌入模型一致(text-embedding-v3=1024)
| type | 说明 | 前置条件 |
|---|---|---|
simple(默认) |
Spring AI 内置内存向量库,零部署开箱即用 | 无;重启后需重新初始化 RAG |
tair |
阿里云 Tair(Redis 协议兼容 + TairVector 向量检索,Spring AI Alibaba 官方适配) | 需开通阿里云 Tair 实例,连接信息填 study.vectorstore.tair.* |
redis |
Redis Stack(本地最轻量持久化方案) | docker run -p 6379:6379 redis/redis-stack-server |
milvus |
Milvus 开源向量数据库 | docker run -d -p 19530:19530 milvusdb/milvus |
chroma |
Chroma 轻量本地向量库 | docker run -d -p 8000:8000 chromadb/chroma |
注意事项:
- 切换向量库后必须重新执行「初始化知识库」(重建索引);
- redis/milvus/chroma 在启动/初始化时会连接对应服务,请先把服务跑起来再切换;
- 嵌入向量维度
embedding-dimensions必须与嵌入模型一致,否则检索会报维度错误; - 业务代码(RagService 等)只依赖
VectorStore接口,切换实现零改动 —— 实现见VectorStoreConfig; - 附:
spring.ai.vectorstore.type: simple这个配置是关键开关,用于禁用 redis/milvus/chroma
starter 的官方自动装配(它们在属性缺失时默认激活并互相冲突),本项目统一由study.vectorstore.type控制。
功能菜单与后端代码对照
| 页面菜单 | 后端入口 | 学习要点 |
|---|---|---|
| ChatClient 对话 | ChatClientController |
prompt().user().call().content()、system 角色设定 |
| ChatModel 底层调用 | ChatModelController |
直接用 ChatModel + 通用 ChatOptions(跨厂商)、token 用量 |
| 嵌入模型 | EmbeddingController |
embedForResponse、余弦相似度 |
| 工具调用 | ToolController + tools/StudyTools |
@Tool/@ToolParam 注解、.tools() 绑定 |
| 对话记忆 | MemoryController + AiConfig |
MessageWindowChatMemory、MessageChatMemoryAdvisor、JDBC 持久化 |
| 提示词模板 | PromptController |
PromptTemplate 变量占位与渲染 |
| 结构化输出 | StructuredOutputController |
.entity(record.class) 自动 JSON 转对象 |
| 向量存储 | VectorStoreController |
Document、vectorStore.add/similaritySearch |
| RAG 知识库问答 | RagController + service/RagService |
分片、向量化入库、检索 + 生成完整链路 |
| Graph Agent | AgentController + agent/StudyAgentConfig |
StateGraph、OverAllState、节点、条件边路由 |
Agent(Graph)说明
agent/StudyAgentConfig 用 Spring AI Alibaba Graph 编排了一个学习助手智能体:
START → classify(LLM 意图分类:concept/code/chat)→ 条件边按分类结果路由→ concept_expert / code_expert / chat_expert(三个不同提示词的专家节点)→ END
一次 compiledGraph.invoke(Map.of("input", ...)) 即完成「分类 → 路由 → 专家回答」的完整工作流,
页面上会展示实际执行轨迹。
常见问题
- 报错「url error, please check url」(HTTP 400 InvalidParameter):配置的模型名在对应厂商平台上不存在。DashScope 通道请用
qwen-plus/qwen-max/qwen-turbo等真实模型名;GLM、DeepSeek 官方 key 等非阿里模型请走study.model.provider=openai通道。 - 报错「api-key 不可为空」:未配置
AI_DASHSCOPE_API_KEY环境变量或 yml 中的 key。 - 切换厂商后 401:检查
study.model.openai.api-key(或环境变量OPENAI_COMPATIBLE_API_KEY)。 - RAG 查询提示未初始化:点页面上的「初始化知识库」按钮。
- MySQL 连接失败:确认
study.mysql.url/username/password与数据库已启动、已执行sql/init-mysql.sql(或已有 content 表)。 - 切换数据库后:重启应用即可,H2 与 MySQL 使用同一套
content表结构与 RAG 代码。 - 切换向量库(study.vectorstore.type)后启动/初始化报连接错误:redis/milvus/chroma/tair 需要先把对应向量数据库服务跑起来(见上文切换表中的 docker 命令 / 阿里云控制台),切换后记得重新点「初始化知识库」。
- 向量检索报维度不匹配:
study.vectorstore.embedding-dimensions必须与嵌入模型输出维度一致(text-embedding-v3=1024),换嵌入模型时同步修改。 - RAG 初始化报「Range of input length should be [1, 8192]」:博文中有超长段落(中间无空行)超过了嵌入模型单条输入上限。已内置自动切分:超过 1000 字符的段落会按句子边界切成多个分片(相邻分片保留 100 字符重叠),无需手工处理,日志会打印「超长段落已切分」。
