基于 Spring‑AI‑Alibaba(DashScope 通义),面向业务 AI 应用:聊天对话、RAG 知识库、FunctionCall 工具调用、流式 SSE 输出、权限、链路追踪、向量库、文档解析、接口层。 技术基线:
- JDK17
- SpringBoot 3.2.x
- spring‑ai‑alibaba:
1.0.0‑M6.1- 向量库:PGVector(生产)/ InMemory(测试)
- Mysql:业务元数据;Redis:会话缓存;OpenTelemetry 链路追踪
- 能力:对话、知识库 RAG、工具调用、文档上传解析、SSE 流式输出、多模型切换
整体架构分为五层:接入层 → 应用服务层 → AI 能力层 → 存储层 → 外部大模型服务。
一、整体系统架构设计
架构分层
- 接入层
- 前端 Web / 小程序;网关 Spring Cloud Gateway;鉴权、限流、SSE 长连接透传;接口统一封装,不直接暴露 AI 原生接口。
- 应用服务层(业务层)
- 会话管理服务:用户对话历史管理、会话隔离、上下文窗口裁剪
- 知识库 RAG 服务:文档上传、解析(PDF/MD/TXT)、文本切分、向量化入库、检索召回
- Agent 服务:FunctionCall 工具调用编排、多轮工具循环调用
- AI 对话服务:普通对话、流式对话、多模态图文
- 模型管理:多模型动态切换(qwen‑turbo/qwen‑plus/qwen‑max)、模型参数动态配置
- AI 能力层(Spring AI Alibaba 核心)
封装 Spring AI 标准 API,底层对接 DashScope 通义
- ChatClient / DashScopeChatModel:大模型聊天
- DashScopeEmbeddingModel:文本向量化
- DocumentReader:文档解析器
- VectorStore:向量存储
- PromptTemplate:提示词模板
- FunctionCallback:函数调用注册
- 存储层
- MySQL:会话元数据、知识库元数据、文档元数据、用户权限
- PGVector:向量数据库(生产环境,替代内存向量库)
- Redis:会话缓存、SSE 会话标记、限流、热点 Prompt 缓存
- 文件存储:MinIO,存储原始上传文档 PDF 等
- 外部依赖
- 阿里云百炼 DashScope API 服务
- 可扩展:本地部署 Qwen 模型 (Ollama)
业务流程:RAG 问答完整链路
用户提问 → Gateway鉴权限流 → AI服务 → 1.问题向量化 → 2.PGVector检索相似文档片段 → 3.组装Prompt(系统提示词+检索上下文+用户问题) → 4.调用DashScope大模型 → 5.流式SSE返回结果,同时保存对话会话入库 → 前端渲染打字机效果Agent FunctionCall 流程
用户提问 → 判断是否需要调用工具 → 大模型输出工具调用参数 → 本地执行Java工具函数 → 将工具返回结果再次塞回上下文 → 再次交给大模型整合输出答案非功能设计
- 超时控制:大模型调用超时、SSE 超时
- 熔断降级:大模型接口不可用时熔断,返回友好提示
- 可观测:token 消耗统计、调用耗时、异常日志、otel 链路追踪
- 密钥安全:API‑key 配置环境变量,禁止配置文件硬编码;支持多密钥轮询
二、工程模块划分(Maven 多模块)
ai‑alibaba‑parent 父工程 ├── ai‑alibaba‑common 公共模块:常量、工具、异常、DTO、配置、链路追踪 ├── ai‑alibaba‑gateway SpringCloud Gateway网关,鉴权限流SSE透传 ├── ai‑alibaba‑service AI核心业务服务(主服务) │ └── src/main/java │ ├── config AI配置类:ChatClient、向量库、Embedding、FunctionCall注册 │ ├── controller 对外接口:聊天、流式、知识库、文档上传 │ ├── service │ │ ├── chat 对话会话服务 │ │ ├── rag RAG知识库服务(文档解析、切片、向量入库、检索) │ │ ├── agent Agent工具调用服务 │ │ └── session 会话管理服务 │ ├── repository Mysql、PGVector操作 │ └── dto 请求响应对象 └── ai‑alibaba‑api 对外API定义,feign DTO三、完整 pom 关键依赖(父 + service 模块)
父 pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.5</version> <relativePath/> </parent> <groupId>com.ai</groupId> <artifactId>ai‑alibaba‑parent</artifactId> <version>1.0.0‑SNAPSHOT</version> <packaging>pom</packaging> <modules> <module>ai‑alibaba‑common</module> <module>ai‑alibaba‑service</module> </modules> <properties> <java.version>17</java.version> <spring.cloud.version>2023.0.1</spring.cloud.version> <spring‑ai‑alibaba.version>1.0.0‑M6.1</spring‑ai‑alibaba.version> <pgvector.version>0.8.0</pgvector.version> </properties> <dependencyManagement> <dependencies> <!-- Spring AI Alibaba 版本管理 --> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring‑ai‑alibaba‑dependencies</artifactId> <version>${spring‑ai‑alibaba.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!-- Spring Cloud --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring‑cloud‑dependencies</artifactId> <version>${spring.cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> </project>ai‑alibaba‑service pom.xml
<dependencies> <dependency> <groupId>com.ai</groupId> <artifactId>ai‑alibaba‑common</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- Spring AI Alibaba starter --> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring‑ai‑alibaba‑spring‑boot‑starter</artifactId> </dependency> <!-- PGVector向量库 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring‑ai‑pgvector‑store</artifactId> </dependency> <!-- pdf文档解析 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring‑ai‑pdf‑reader</artifactId> </dependency> <!-- postgresql驱动 --> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>四、application.yml 完整配置(生产向)
server: port: 8080 spring: application: name: ai‑alibaba‑service # mysql业务库 datasource: url: jdbc:mysql://127.0.0.1:3306/ai_biz?useUnicode=true&characterEncoding=utf8 username: root password: 123456 # pgvector向量库 ai: alibaba: api-key: ${SPRING_AI_ALIBABA_API_KEY:} base-url: https://dashscope.aliyuncs.com/api/v1 chat: options: model: qwen‑plus temperature: 0.7 max‑tokens: 2048 # pgvector向量存储配置 vectorstore: pgvector: index-type: HNSW distance-type: COSINE_DISTANCE table‑name: ai_vector_store # redis data: redis: host: 127.0.0.1 port: 6379 # 自定义业务参数 ai: rag: # 文档切分大小 chunk‑size: 800 chunk‑overlap: 100 # RAG召回数量 top‑k: 4api‑key 优先使用环境变量,生产不要写配置文件。
PGVector 环境准备
- Postgres15+,安装 pgvector 扩展
CREATE EXTENSION IF NOT EXISTS vector;启动项目后会自动创建ai_vector_store向量表。
五、核心配置类实操代码
1. AI 配置 AiConfig.java
package com.ai.service.config; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.document.DocumentSplitter; import org.springframework.ai.document.TokenTextSplitter; import org.springframework.ai.vectorstore.PgVectorStore; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; @Configuration public class AiConfig { /** * ChatClient 构建器,SpringAI标准入口 */ @Bean public ChatClient chatClient(ChatClient.Builder builder) { return builder.build(); } /** * 文本切片器,RAG文档切分 */ @Bean public DocumentSplitter documentSplitter(@Value("${ai.rag.chunk-size}") int chunkSize, @Value("${ai.rag.chunk-overlap}") int chunkOverlap) { return new TokenTextSplitter(chunkSize, chunkOverlap); } /** * PGVector向量存储 */ @Bean public VectorStore vectorStore(JdbcTemplate jdbcTemplate, DashScopeEmbeddingModel embeddingModel) { return PgVectorStore.builder(jdbcTemplate, embeddingModel) .tableName("ai_vector_store") .dimensions(1536) .build(); } }2. FunctionCall 工具注册示例(Agent 能力)
自定义工具类,实现本地方法供大模型调用
package com.ai.service.agent.tool; import org.springframework.ai.tool.annotation.Tool; import org.springframework.stereotype.Component; @Component public class WeatherTool { /** * 工具方法,大模型会自动识别注解,调用该函数 */ @Tool(description = "查询指定城市当前天气情况") public String getWeather(String city) { // 这里写实际业务调用,http请求第三方天气接口 return city + " 当前天气:晴,26℃"; } }使用时,在 ChatClient 把工具注册进去即可开启 Agent 能力
chatClient.prompt() .tools(weatherTool) .user("查询北京天气") .call() .content();六、核心业务服务实操代码
1. RAG 知识库服务
@Service @RequiredArgsConstructor public class RagService { private final VectorStore vectorStore; private final DocumentSplitter documentSplitter; private final PdfDocumentReader pdfDocumentReader; /** * 上传PDF文档入库:解析→切片→向量化→存入PGVector */ public void uploadPdf(InputStream inputStream) { // 读取pdf List<Document> docs = pdfDocumentReader.read(inputStream); // 文本切分 List<Document> chunks = documentSplitter.split(docs); // 向量入库 vectorStore.add(chunks); } /** * 根据用户问题做向量检索,获取上下文片段 */ public List<Document> searchContext(String query, int topK) { SearchRequest searchRequest = SearchRequest.builder() .query(query) .topK(topK) .build(); return vectorStore.similaritySearch(searchRequest); } }2. 对话服务,RAG 增强 Prompt
@Service @RequiredArgsConstructor public class ChatAiService { private final ChatClient chatClient; private final RagService ragService; @Value("${ai.rag.top-k}") private Integer topK; private static final String RAG_SYSTEM_PROMPT = """ 你是知识库问答助手,请基于下面检索到的上下文回答用户问题。 如果上下文中没有答案,如实告知不知道,不要编造内容。 上下文: {context} """; /** * RAG普通对话 */ public String chatWithRag(String userQuestion) { // 向量检索 List<Document> docs = ragService.searchContext(userQuestion, topK); String contextStr = docs.stream().map(Document::getText).collect(Collectors.joining("\n")); PromptTemplate promptTemplate = new PromptTemplate(RAG_SYSTEM_PROMPT); promptTemplate.add("context", contextStr); promptTemplate.add("question", userQuestion); return chatClient.prompt(promptTemplate.create()) .call() .content(); } /** * RAG流式SSE输出 */ public Flux<String> streamChatWithRag(String userQuestion) { List<Document> docs = ragService.searchContext(userQuestion, topK); String contextStr = docs.stream().map(Document::getText).collect(Collectors.joining("\n")); PromptTemplate promptTemplate = new PromptTemplate(RAG_SYSTEM_PROMPT); promptTemplate.add("context", contextStr); promptTemplate.add("question", userQuestion); return chatClient.prompt(promptTemplate.create()) .stream() .content(); } }3. Controller 对外接口
@RestController @RequestMapping("/ai") @RequiredArgsConstructor public class AiController { private final ChatAiService chatAiService; private final RagService ragService; /** * 普通RAG问答 */ @PostMapping("/chat") public String chat(@RequestBody ChatReq req) { return chatAiService.chatWithRag(req.getQuestion()); } /** * SSE流式输出 */ @GetMapping(value = "/stream/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> streamChat(@RequestParam String question) { return chatAiService.streamChatWithRag(question); } /** * 上传PDF知识库文档 */ @PostMapping("/rag/upload") public void uploadPdf(@RequestPart("file") MultipartFile file) throws IOException { ragService.uploadPdf(file.getInputStream()); } }DTO 对象 ChatReq.java
@Data public class ChatReq { private String question; }七、会话管理实现关键点
真实业务不能只靠大模型上下文,需要自己做会话持久化:
- 数据库表保存会话 id、用户 id、消息角色 (user/assistant)、消息内容、创建时间
- 每次对话把历史消息组装到 Prompt,同时做窗口裁剪,防止 token 超限
- Redis 缓存最近 N 条会话,减少 DB 查询
- ChatClient 可以传入历史 Message 列表,实现多轮对话
示例组装历史消息:
List<Message> historyMessageList = loadHistoryMessage(sessionId); Prompt prompt = new Prompt(historyMessageList);八、生产环境重要优化点
- 超时与熔断
DashScope 调用是 http 接口,设置 http 连接超时;使用 Resilience4j 做熔断,大模型不可用时降级。
- SSE 注意事项网关不能缓存 SSE 响应;不要使用 @ResponseBodyAdvice 统一包装 SSE 返回;流式返回直接返回 Flux。
- Token 统计 监听 ChatResponse,统计 input/output token,做计费、日志埋点。
- 提示词模板统一管理 放到数据库,不要硬编码,支持后台动态修改 system prompt。
- 文档解析 PDF、Word、Markdown,大文件异步解析,MQ 异步处理,避免接口超时。
- 多模型动态选择 不写死 yml,数据库维护模型配置,运行时动态构建 DashScopeChatModel。
九、部署方案
- 开发环境:本地 IDE 直接启动;向量库可以切换为内存
InMemoryVectorStore,不需要 PG。
# 测试环境使用内存向量库,注释pgvector配置 #spring.ai.vectorstore.pgvector...- 生产部署:Docker + K8s;PGVector 独立 Postgres 服务;Redis、Mysql;服务多实例;API‑key 从 k8s secret 注入。
十、常见坑
- JDK 版本必须 17,SpringBoot3.2,SpringAI Alibaba 版本对齐;M6.1 不要和更高版本混用。
- SSE 被网关包装后前端拿不到流,网关配置不要对 text/event‑stream 做 body 重写。
- PGVector 的 embedding 维度,通义 embedding 输出 1536 维,表维度要匹配。
- FunctionCall 工具,方法必须 public,@Tool 注解正确,否则无法识别工具。
- RAG 召回 chunk 不能过大,否则 prompt 超长,触发 max‑tokens 超限。
Spring AI Alibaba Mysql 业务建表 SQL
数据库:
ai_biz,存储会话、知识库文档元数据;向量数据由 PGVector 自行管理,不在 Mysql。 字段设计:会话多轮对话、知识库文档元信息、文档切片关联、软删除、时间戳。
CREATE DATABASE IF NOT EXISTS ai_biz DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE ai_biz; -- 1. AI会话主表:一个会话代表一次对话窗口 DROP TABLE IF EXISTS ai_chat_session; CREATE TABLE ai_chat_session ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', session_id VARCHAR(64) NOT NULL COMMENT '会话唯一UUID,前端传递', user_id VARCHAR(64) NOT NULL COMMENT '用户ID', title VARCHAR(256) DEFAULT '' COMMENT '会话标题,AI自动生成', model_name VARCHAR(64) DEFAULT 'qwen-plus' COMMENT '使用模型 qwen‑turbo/qwen‑plus/qwen‑max', status TINYINT DEFAULT 1 COMMENT '状态 1正常 0禁用', del_flag TINYINT DEFAULT 0 COMMENT '0未删除 1已删除', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_session_id (session_id), KEY idx_user_id (user_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI对话会话主表'; -- 2. AI会话消息明细表:保存每一轮 user / assistant 消息 DROP TABLE IF EXISTS ai_chat_message; CREATE TABLE ai_chat_message ( id BIGINT AUTO_INCREMENT PRIMARY KEY, session_id VARCHAR(64) NOT NULL COMMENT '关联会话ID', user_id VARCHAR(64) NOT NULL COMMENT '用户ID', role VARCHAR(32) NOT NULL COMMENT '消息角色:user / assistant / system / tool', content TEXT NOT NULL COMMENT '消息内容', tool_call_json TEXT COMMENT 'FunctionCall工具调用原始JSON', input_tokens INT DEFAULT 0 COMMENT '输入token消耗', output_tokens INT DEFAULT 0 COMMENT '输出token消耗', del_flag TINYINT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_session_id (session_id), KEY idx_user_id (user_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI对话消息明细'; -- 3. 知识库主表:知识库分组(支持多套知识库) DROP TABLE IF EXISTS ai_knowledge_base; CREATE TABLE ai_knowledge_base ( id BIGINT AUTO_INCREMENT PRIMARY KEY, kb_code VARCHAR(64) NOT NULL COMMENT '知识库编码,业务唯一', kb_name VARCHAR(128) NOT NULL COMMENT '知识库名称', description VARCHAR(512) DEFAULT '' COMMENT '知识库描述', status TINYINT DEFAULT 1 COMMENT '1启用 0停用', del_flag TINYINT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_kb_code (kb_code) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识库分组'; -- 4. 上传文档元数据表:记录上传的PDF/TXT等原始文档 DROP TABLE IF EXISTS ai_kb_document; CREATE TABLE ai_kb_document ( id BIGINT AUTO_INCREMENT PRIMARY KEY, kb_code VARCHAR(64) NOT NULL COMMENT '归属知识库编码', doc_name VARCHAR(256) NOT NULL COMMENT '文档原始文件名', doc_type VARCHAR(32) NOT NULL COMMENT '文档类型 pdf / txt / md / docx', file_key VARCHAR(256) DEFAULT '' COMMENT 'MinIO文件存储key', file_size BIGINT DEFAULT 0 COMMENT '文件大小字节', status TINYINT DEFAULT 0 COMMENT '0待解析 1解析成功 2解析失败', fail_msg TEXT COMMENT '解析失败原因', total_chunk INT DEFAULT 0 COMMENT '切分的chunk总数量', del_flag TINYINT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_kb_code (kb_code) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识库上传文档元数据'; -- 5. Prompt模板配置表:动态管理system提示词,避免硬编码 DROP TABLE IF EXISTS ai_prompt_template; CREATE TABLE ai_prompt_template ( id BIGINT AUTO_INCREMENT PRIMARY KEY, template_code VARCHAR(64) NOT NULL COMMENT '模板编码,如RAG_CHAT、AGENT_CHAT', template_name VARCHAR(128) NOT NULL COMMENT '模板名称', template_content TEXT NOT NULL COMMENT '提示词模板内容,支持{xxx}占位符', description VARCHAR(512) DEFAULT '', status TINYINT DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_template_code (template_code) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='动态Prompt模板表'; -- 初始化一条RAG问答模板示例 INSERT INTO ai_prompt_template(template_code,template_name,template_content,description) VALUES ( 'RAG_CHAT', 'RAG知识库问答模板', '你是知识库问答助手,请基于下面检索到的上下文回答用户问题。 如果上下文中没有答案,如实告知不知道,不要编造内容。 上下文: {context} 用户问题:{question}', 'RAG场景system prompt模板' );配套实体简要说明(JPA)
AiChatSession.java
@Data @Entity @Table(name = "ai_chat_session") public class AiChatSession { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String sessionId; private String userId; private String title; private String modelName; private Integer status; private Integer delFlag; private LocalDateTime createTime; private LocalDateTime updateTime; }AiChatMessage.java
@Data @Entity @Table(name = "ai_chat_message") public class AiChatMessage { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String sessionId; private String userId; private String role; @Column(columnDefinition = "TEXT") private String content; @Column(columnDefinition = "TEXT") private String toolCallJson; private Integer inputTokens; private Integer outputTokens; private Integer delFlag; private LocalDateTime createTime; private LocalDateTime updateTime; }