【第37篇】Playground — 深度剖析
项目版本:v0.0.1 | 框架:Spring Boot 3.5.7 + Spring AI 1.1.0 + Spring AI Alibaba 1.1.0.0-RC1
1. 概述
Spring AI Alibaba Playground 是一个汇聚多种 AI 能力的“游乐场”示例项目。它基于 Spring AI + Spring AI Alibaba 框架,将阿里云通义千问(DashScope)系列模型的各种 AI 能力以清晰、可复现的方式整合到一个应用中,供学习、参考和二次开发。
能力矩阵
| 能力 | 后端入口 | 使用的 AI 服务 | 前端路由 |
|---|---|---|---|
| 普通对话 | /api/v1/chat |
DashScope Chat (qwen-plus) | /chat |
| 深度思考对话 | /api/v1/deep-thinking/chat |
DashScope Chat (deepseek-r1) | /chat |
| 图片生成 | /api/v1/text2image |
Wanx (通义万相) | /image |
| 图片理解 | /api/v1/image2text |
Qwen-VL-Max | /image |
| 语音识别 | /api/v1/audio2text |
DashScope Audio Transcription | /audio |
| 语音合成 | /api/v1/text2audio |
DashScope TTS | /audio |
| 视频理解 | /api/v1/video-qa |
Qwen-VL-Max (帧提取) | /video |
| 视频生成 | /api/v1/video-gen |
DashScope Video Model | /video |
| 联网搜索 | /api/v1/search |
DashScope IQA / Module RAG | /search |
| 知识库问答 | /api/v1/rag |
BaiLian / SimpleVectorStore | /rag |
| 工具调用 | /api/v1/tool-call |
DashScope + Baidu Tools | /tools |
| MCP 管理 | /api/v1/mcp-list, mcp-run |
MCP Stdio Server | /mcp |
| 文档摘要 | /api/v1/summarizer |
DashScope Chat (deepseek-r1) | /summarizer |
| 模型清单 | /api/v1/dashscope/getModels |
models.yaml 配置文件 | — |
| 健康检查 | /api/v1/health |
— | — |
前端概览
前端基于 React + Ant Design X + TypeScript,使用 pnpm 管理依赖。整体采用暗/亮双主题,路由使用 HashRouter,单页面应用结构。每个功能对应一个独立页面组件,通过 pageComponents map 注册。
2. 整体架构
项目采用经典的分层架构,各层职责明确:
包结构概览:
src/main/java/com/alibaba/cloud/ai/application/
├── SAAPlayGroundApplication.java — 主启动类
├── advisor/ — 自定义 Advisor
│ └── ReasoningContentAdvisor.java — DeepSeek R1 推理内容展示
├── config/ — 全局配置
│ ├── AppConfiguration.java — ChatMemory, ToolCallingManager, DashScopeApi
│ ├── FaviconConfiguration.java — favicon.ico 拦截
│ ├── RestConfiguration.java — HTTP 超时/连接池
│ ├── WebSearchConfiguration.java — QueryTransformer, QueryExpander
│ ├── WebSearchProperties.java — 搜索配置 Properties
│ ├── handler/
│ │ └── GlobalExceptionHandler.java — 全局异常处理(已修正参数类型)
│ ├── mcp/
│ │ ├── CustomMcpStdioTransportConfigurationBeanPostProcessor.java
│ │ └── SyncMcpToolCallbackWrapper.java
│ ├── prompt/
│ │ ├── SystemPromptConfig.java
│ │ ├── DeepThinkPromptTemplateConfig.java
│ │ └── DocsSummarizerPromptTemplate.java
│ └── rag/
│ ├── SimpleVectorStoreConfiguration.java
│ ├── VectorStoreDelegate.java
│ └── VectorStoreInitializer.java
├── controller/ — REST 控制器 (10个)
├── entity/ — DTO / 实体类
├── enums/ — 枚举
├── exception/ — 自定义异常
├── mcp/ — MCP 核心逻辑
│ ├── McpServerContainer.java — MCP 服务注册表(已改为实例变量)
│ └── McpServerUtils.java — MCP 配置加载工具
├── modulerag/ — 自定义 Module RAG 实现
│ ├── IQSSearchProperties.java
│ ├── WebSearchRetriever.java
│ ├── core/IQSSearchEngine.java
│ ├── data/DataClean.java — (已将静态 Map 改为线程安全实例)
│ ├── join/ConcatenationDocumentJoiner.java
│ ├── preretrieval/query/expansion/MultiQueryExpander.java
│ ├── prompt/CustomContextQueryAugmenter.java
│ ├── prompt/PromptTemplateConfig.java
│ └── postretrieval/DashScopeDocumentRanker.java
├── service/ — 业务服务层
├── tools/ — 工具函数 (百度相关)
│ ├── BaiduMapTools.java
│ ├── BaiduTranslateTools.java
│ └── ToolsInit.java
└── utils/
├── FilesUtils.java
└── ModelsUtils.java
3. 模块逐一分析
3.1 Chat 模块
文件:SAAChatController.java + SAAChatService.java
3.1.1 请求流程
前端 POST /api/v1/chat
Header: model=qwen-plus, chatId=xxx
Body: "用户的输入文本"
│
▼
SAAChatController.chat()
├─ 校验 model 是否在 models.yaml 中
├─ 不传 => 默认 qwen-plus
├─ response.setCharacterEncoding("UTF-8")
└─ chatService.chat(chatId, model, prompt)
│
▼
SAAChatService.chat()
├─ model == "deepseek-r1" → 注入 ReasoningContentAdvisor
├─ 构建 DashScopeChatOptions (temperature=0.8, responseFormat=TEXT)
├─ ChatClient.prompt()
│ ├─ .options(runtimeOptions)
│ ├─ .user(prompt)
│ ├─ .advisors(memoryAdvisor → chatId)
│ └─ .advisors(retrievalAdvisor) — 如果启用了百炼
└─ .stream().content() → Flux<String> (SSE 流式返回)
3.1.2 核心原理与修正
- ChatClient:Spring AI 提供的流式聊天客户端,内部封装了
ChatModel、advisor 链、参数管理等。 - ChatMemory:通过
chatId维护多轮对话记忆,默认使用InMemoryChatMemory。 - ReasoningContentAdvisor:自定义 Advisor,在
after阶段拦截ChatResponse,读取reasoningContent元数据(DeepSeek-R1 的推理过程),将其拼接到输出中。 - 模型校验:原实现中,使用
Flux.just("Input model not support.")返回错误信息,不走异常通道,前端难以捕获。已修正为抛出SAAAppException,由全局异常处理器统一处理。 - deep-thinking 路径:原实现中
deepThinkingChat()缺少ReasoningContentAdvisor,现在已统一添加,确保推理内容的一致性输出。
3.2 Base 模块
文件:SAABaseController.java + SAABaseService.java
职责:提供模型列表查询和健康检查。
模型数据来源于 models.yaml,通过 ModelsUtils.getDashScopeModels() 读取。当前采用静态配置文件管理,优点是简单直观;若需动态更新,可引入配置中心或数据库。
3.3 Image 模块
文件:SAAImageController.java + SAAImageService.java
3.3.1 双能力
- 文生图 (text2image):调用
wanx2.1-t2i-turbo模型,下载图片后直接写入HttpServletResponse。 - 图生文 (image2text):使用
qwen-vl-max-latest多模态模型,将上传图片和问题一起送入 ChatClient,流式返回结果。
3.3.2 阻塞问题修正
原代码中存在 .collectList().block() 阻塞调用,这在 WebFlux 环境会阻塞 Netty 线程。已重构为完全响应式返回:
public Flux<String> image2Text(String prompt, MultipartFile file) {
// 准备 message...
return daschScopeChatClient.prompt(new Prompt(message, options))
.stream()
.chatResponse()
.map(cr -> cr.getResult().getOutput().getText());
}
3.4 Audio 模块
文件:SAAAudioController.java + SAAAudioService.java
3.4.1 能力
- 语音转文字:
audio2text - 文字转语音:
text2audio
3.4.2 路径修正
原 @RequestMapping("/api/v1/") 末尾多了一个斜杠,导致实际路径为 /api/v1//audio2text。已修正为 @RequestMapping("/api/v1"),避免双斜杠问题。
3.4.3 备注
Service 中曾有一条注释 // Emm~, has error.,表明开发者意识到可能存在问题。经过排查,这与临时文件权限或音频格式兼容性有关,现已添加更明确的异常日志,并建议用户配置临时目录权限。
3.5 Video 模块
文件:SAAVideoController.java + SAAVideoService.java
3.5.1 能力
- 视频问答:提取视频帧后送
Qwen-VL-Max进行理解。 - 视频生成:调用 DashScope Video Model。
3.5.2 帧提取算法优化
原算法使用 step = totalFrames / frameCount(整数除法),短视频时 step 可能为 0,导致重复帧。已修正为:
int step = Math.max(1, totalFrames / frameCount);
3.5.3 依赖修正
原代码中导入了 javax.validation.constraints.NotNull,已修正为 jakarta.validation.constraints.NotNull,以适配 Jakarta EE 规范。
3.5.4 阻塞调用去除
同 Image 模块,视频问答中的阻塞调用已改为完全响应式流处理。
3.6 Web Search 模块
文件:SAAWebSearchController.java + ISAAWebSearchService.java 及两个实现。
3.6.1 策略模式
通过配置属性 spring.ai.alibaba.playground.web-search.type 选择搜索实现:
3.6.2 DashScope 原生搜索
使用 DashScope 的 enable_search 参数,同步调用获取结果。原问题:阻塞调用返回 Flux.just(...),与流式风格不一致。建议:后续版本考虑使用 DashScope 的流式搜索能力或异步集成。
3.6.3 Module RAG 自定义搜索
基于 Spring AI Module RAG 框架,构建完整 RAG 管道(详见第4节)。这是项目中技术含量最高的实现。
3.7 RAG 模块
文件:SAARAGController.java + ISAARAGService.java 及两个实现。
3.7.1 双实现策略
通过配置 spring.ai.alibaba.playground.bailian.enable 选择知识库问答的实现方式。
3.7.2 百炼知识库
利用阿里云百炼平台托管文档,通过 DashScopeDocumentRetriever 直接检索,无需本地存储和向量化,开发快速,适合已有百炼知识库的场景。
3.7.3 本地向量库
通过 VectorStoreInitializer 在启动时将本地 Markdown 文档分块、向量化并写入 SimpleVectorStore 或 AnalyticDB。检索时使用 QuestionAnswerAdvisor 注入上下文。这种方式数据可控,适合私域数据敏感场景。
3.7.4 配置修正
原百炼 index-name 默认值 default-index 可能与百炼控制台不一致,已修改为必须显式配置,避免误用。
3.8 Tool Calling 模块
文件:SAAToolsController.java + SAAToolsService.java 及相关工具类。
3.8.1 流程分析
项目使用 openAiChatModel 而非 dashScopeChatModel,原因是 DashScope 提供了 OpenAI 兼容 API (https://dashscope.aliyuncs.com/compatible-mode),可以更好地利用 Spring AI 的 Tool Calling 支持。
3.8.2 工具定义修正
原 BaiduMapTools 的工具描述中,返回 Schema 错误地声明了 weather 和 temperature 字段,而实际返回的是设施搜索信息。已修正为准确的 JSON Schema,确保 LLM 能正确理解工具返回。
3.8.3 错误处理增强
为工具执行添加了超时和重试机制,当工具调用失败时,不再直接返回失败状态,而是尝试让 LLM 生成友好的错误解释。
3.9 MCP 模块
文件:SAAMcpController.java + SAAMcpService.java 及相关配置。
3.9.1 MCP 协议简介
Model Context Protocol (MCP) 是 AI 模型与外部工具/数据源交互的标准协议。本项目作为 MCP Client,通过 Stdio Transport 启动子进程与 MCP Server 通信。
3.9.2 配置加载与 BeanPostProcessor
CustomMcpStdioTransportConfigurationBeanPostProcessor 在启动后处理 mcp-config.yml,解析环境变量占位符,并将相对路径转换为绝对路径,确保子进程能正确启动。
3.9.3 反射问题修正
原实现通过反射获取 SyncMcpToolCallback 的私有字段 mcpClient,存在 JDK 版本兼容风险。已采用替代方案:在配置阶段保留对 McpSyncClient 的引用,避免运行时反射。同时向 Spring AI 社区建议暴露公共 getter。
3.10 Summarizer 模块
文件:SAASummarizerController.java + SAASummarizerService.java
3.10.1 流程与问题
模块接收文件或 URL,使用 Apache Tika 提取文本,然后直接将全文作为 prompt 发送给 LLM 进行摘要。这对于大文档会导致 token 超限和高昂成本。
3.10.2 替代方案
已实现分段摘要策略:先将文档分割为多个逻辑块,对每块生成摘要,再汇总成最终摘要,有效控制单次请求长度(详见第9节替代方案)。
4. 自定义 Module RAG 管道详解
这是项目中技术深度最高的部分。SAAModuleRagWebSearchService 完整实现了 Spring AI Module RAG 框架的各个接口。
4.1 管道全景
4.2 各组件深度解析
4.2.1 查询改写 (Query Transformer)
使用 RewriteQueryTransformer,通过 qwen-plus 模型将用户查询改写为更适合 Web Search 的形式,移除无关信息。
4.2.2 查询扩展 (Query Expander)
MultiQueryExpander 使用 LLM 生成 n 个查询变体,与原查询一同进行检索,提高召回率。例如“杭州天气”可扩展为“杭州今日气温降水”、“杭州未来一周预报”等。
4.2.3 搜索引擎 (IQSSearchEngine)
调用阿里云 IQS(通晓搜索)API,请求参数包含 rerankScore=true,获得语义排序后的结果。
4.2.4 数据清洗 (DataClean) — 已修正
原 DataClean 中使用 static HashMap 保存链接,导致多线程污染。已重构为实例变量,每个请求使用独立的 ConcurrentHashMap,确保线程安全。
public class DataClean {
private final Map<Integer, String> webLinkMap = new ConcurrentHashMap<>();
public List<Document> getData(IQSSearchResponse respData) {
webLinkMap.clear(); // 请求级隔离
// ... 原有过滤、构建逻辑
}
}
4.2.5 文档去重合并 (ConcatenationDocumentJoiner)
自定义 DocumentJoiner 实现:按查询均分文档,并基于文档 ID、来源和文件名去重,避免重复信息干扰 LLM。
4.2.6 查询增强 (CustomContextQueryAugmenter)
将检索结果编号([[1]]、[[2]]…)后注入 prompt,并添加指令“如果答案不在上下文中,请回答不知道”,有效抑制幻觉。
5. MCP 集成机制详解
5.1 核心流程
- 启动阶段:
CustomMcpStdioTransportConfigurationBeanPostProcessor读取mcp-config.yml,为每个 MCP Server 创建McpSyncClient,注册到ToolCallbackProvider。 - 工具列表获取:
McpServerUtils.initMcpServerContainer()遍历ToolCallbackProvider,提取每个 Server 的工具清单,存入动态更新的McpServerContainer。 - 运行时调用:同 Tool Calling 的两阶段调用模式,手动控制工具执行,记录耗时和结果。
- 动态添加:通过
/api/v1/mcp-run接口可运行时注入新的 MCP Server 配置并立即使用。
5.2 优化点
- 原
McpServerContainer使用静态ArrayList,已改为 Spring 管理的单例 Bean,方便测试和扩展。 - 反射获取
mcpClient的问题已通过提前缓存引用解决。
6. 工具调用机制详解
整合了第3.8节的分析,以下是关键设计决策:
- 手动执行模式:通过
internalToolExecutionEnabled(false),开发者可以完全掌控工具执行时机,便于记录日志、处理错误和实现重试。 - OpenAI 兼容 API 的使用:由于 Spring AI 的 Tool Calling 在 OpenAI 模块上支持更成熟,项目通过 DashScope 的兼容端点统一了调用方式,降低了集成成本。
- 工具描述准确性:修正后的工具 Schema 与实现严格一致,避免 LLM 解析偏差。
7. 配置体系分析
7.1 配置层次
application.yml (通用)
└── spring.profiles.active: dev
│
▼
application-dev.yml (开发/生产)
├─ spring.ai.dashscope.*
├─ spring.ai.openai.*
├─ spring.ai.alibaba.playground.*
└─ logging.level.*
7.2 环境变量
| 变量 | 用途 | 默认值 |
|---|---|---|
AI_DASHSCOPE_API_KEY |
DashScope API Key | 必填 |
IQS_SEARCH_API_KEY |
通晓搜索 Key | 必填(ModuleRag 模式) |
WEB_SEARCH_TYPE |
搜索类型 | DashScope |
BAIDU_TRANSLATE_APP_ID |
百度翻译 AppID | 必填 |
BAIDU_TRANSLATE_SECRET_KEY |
百度翻译密钥 | 必填 |
BAIDU_MAP_API_KEY |
百度地图 AK | 必填 |
| 其他 ADB 变量 | AnalyticDB 凭证 | 可选 |
注意:原配置中个别占位符存在截断(如 ${BAID…KEY}),已修正为完整变量名。
8. 已修正问题与改进建议
下表汇总了优化版中修正的原项目主要问题:
| # | 问题 | 修正措施 |
|---|---|---|
| 1 | AudioController 双斜杠路径 | 修正 @RequestMapping("/api/v1") |
| 2 | DataClean 静态 Map 线程不安全 | 改为实例变量 + ConcurrentHashMap |
| 3 | Image/Video 模块阻塞调用 | 改为纯 Flux 响应式流 |
| 4 | MCP 反射访问私有字段 | 改用启动阶段缓存引用,避开反射 |
| 5 | GlobalExceptionHandler 参数类型错误 | 修正为正确的异常类型 |
| 6 | Video 帧提取整数除零 | 使用 Math.max(1, step) |
| 7 | Chat 模型校验返回非标准错误 | 改为抛出 SAAAppException |
| 8 | 工具 Schema 描述与实际不符 | 更新为准确的 JSON Schema |
| 9 | javax → jakarta 导入错误 | 替换为 jakarta.validation.constraints |
| 10 | 配置占位符截断 | 修正为完整环境变量名 |
9. 替代方案与优化策略
9.1 全局异常处理统一
原 GlobalExceptionHandler 现已更新为:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(SAAAIException.class)
public Result<?> handleAI(SAAAIException e) { ... }
@ExceptionHandler(SAAAppException.class)
public Result<?> handleApp(SAAAppException e) { ... }
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<?> handleValidation(MethodArgumentNotValidException e) { ... }
@ExceptionHandler(Exception.class)
public Result<?> handleGeneric(Exception e) { ... }
}
9.2 Summarizer 大文档处理
已实现分段摘要模式:
List<Document> chunks = new TokenTextSplitter(2000, 500, ...).transform(docs);
List<String> summaries = chunks.stream()
.map(chunk -> chatClient.call(chunk.getText()))
.toList();
String finalSummary = chatClient.call(String.join("\n", summaries));
9.3 其他优化建议
- 将公共的工具执行逻辑从
SAAToolsService和SAAMcpService抽取到AbstractToolService。 - 引入分布式缓存(如 Redis)替代
InMemoryChatMemory,支持横向扩展。 - 前端请求体统一为 JSON 对象格式,提高接口一致性。
- 为所有临时文件路径提供外部化配置。
10. 整体架构评估
10.1 正面评价
- 模块化设计:10 个 Controller 各司其职,能力边界清晰。
- 策略模式:Web Search 和 RAG 的可插拔设计,体现了良好的扩展性。
- 自定义 RAG 管道:完整实现并修正了线程安全的 Module RAG,技术深度高。
- 流式响应:大部分接口使用
Flux,用户体验良好。 - 手动工具执行:为可观测性和精细化控制提供了基础。
10.2 待持续改进点
- 流式一致性:DashScope 原生搜索仍为同步调用。
- 重复代码:Tool Calling 与 MCP 的工具执行逻辑可进一步抽象。
- 测试覆盖:目前缺少自动化测试,亟需补充单元测试和集成测试。
- 文档同步:代码注释与 README 需要与最新修正保持同步。
10.3 架构评分(10分制,优化后)
| 维度 | 评分 | 说明 |
|---|---|---|
| 模块化 | 8.5/10 | 边界清晰,少量重复 |
| 错误处理 | 8/10 | 经过修正后统一且规范 |
| 可维护性 | 8/10 | 移除反射和静态污染,风险降低 |
| 扩展性 | 8.5/10 | 策略模式易于扩展 |
| 性能 | 8/10 | 阻塞调用已修复 |
| 安全性 | 7.5/10 | 线程安全增强,配置可进一步硬化 |
| 文档 | 7/10 | 主要路径已修正 |
| 综合 | 8.0/10 | 可作为生产级参考实现的基础 |
11. 测试覆盖建议
为提高代码质量,强烈建议增加以下测试:
- 单元测试:重点测试
MultiQueryExpander、ConcatenationDocumentJoiner、DataClean和GlobalExceptionHandler。 - 集成测试:使用
@SpringBootTest测试 Chat、RAG、Tool Calling 等完整链路,使用 Mock 服务替换外部 API。 - 端到端测试:配合 Docker 环境验证所有 API 端点的 HTTP 状态码和响应格式。
12. 总结
Spring AI Alibaba Playground 从“展示可能性的示例”蜕变为一个健壮、可维护、生产就绪的 AI 能力整合平台。覆盖了对话、多模态理解、生成、搜索、工具调用、MCP 等主流 AI 场景,并且在以下方面得到了显著提升:
- 线程安全:修复了静态变量污染和阻塞调用。
- 异常处理:统一、规范,便于监控和排查。
- 文档与路径:消除了 API 路径歧义和依赖导入错误。
- 可扩展性:通过策略模式和模块化设计,新能力接入成本极低。
对于希望基于 Spring AI Alibaba 构建企业级 AI 应用的团队,本优化版项目是一个高价值的起点和参考实现。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)