002-Spring AI Alibaba Prompt 功能完整案例

本案例将引导您一步步构建一个 Spring Boot 应用,演示如何利用 Spring AI Alibaba 的 Prompt 模板功能,实现角色扮演和基于上下文的智能问答。
1. 案例目标
我们将创建一个包含两个核心功能的 Web 应用:
- AI 角色扮演 (
/example/ai/roles):通过系统提示词模板,让 AI 扮演一个具有特定名字和声音风格的角色,并以该角色的口吻回答问题。 - 上下文问答 (
/prompt/ai/stuff):根据请求参数,决定是否将一篇关于"冰壶"的维基百科文档作为上下文"填充"到提示词中,让 AI 基于该文档内容回答相关问题。
2. 技术栈与核心依赖
- Spring Boot 3.x
- Spring AI Alibaba (用于对接阿里云 DashScope 通义大模型)
- Maven (项目构建工具)
在 pom.xml 中,你需要引入以下核心依赖:
<dependencies><!-- Spring AI Alibaba 核心启动器,集成 DashScope --><dependency><groupId>com.alibaba.cloud.ai</groupId><artifactId>spring-ai-alibaba-starter-dashscope</artifactId><version>1.0.0-M2</version> <!-- 请使用最新版本 --></dependency><!-- Spring Web 用于构建 RESTful API --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
</dependencies>
<!-- 添加 Spring Boot 和 Spring Cloud 的版本管理 -->
<dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>3.3.1</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>2023.0.1.2</version><type>pom</type><scope>import</scope></dependency></dependencies>
</dependencyManagement>3. 项目配置
在 src/main/resources/application.yml 文件中,配置你的 DashScope API Key。
spring:ai:dashscope:api-key: ${DASHSCOPE_API_KEY} # 建议使用环境变量,更安全# 可选:指定使用的模型,默认为 qwen-turbochat:options:model: qwen-plus重要提示:请将 DASHSCOPE_API_KEY 环境变量设置为你从阿里云获取的有效 API Key。你也可以直接将其写在配置文件中,但这不推荐用于生产环境。
4. 准备 Prompt 模板文件
在 src/main/resources 目录下创建以下文件结构:
src/main/resources/
├── docs/
│ └── wikipedia-curling.md
└── prompts/├── system-message.st└── qa-prompt.st4.1 docs/wikipedia-curling.md
这是我们将用作上下文的文档内容。
# Curling
Curling is a sport in which players slide stones on a sheet of ice toward a target area which is segmented into four concentric circles. It is related to bowls, boules and shuffleboard. Two teams, each with four players, take turns sliding heavy, polished granite stones, also called rocks, across the ice curling sheet toward the house, a circular target marked on the ice. Each team has eight stones. The purpose is to accumulate the highest score for a game; points are scored for the stones resting closest to the centre of the house at the conclusion of each end, which is completed when both teams have thrown all of their stones. A game usually consists of eight or ten ends.
The curler can induce a curved path by causing the stone to slowly turn as it slides, and the path of the rock may be further influenced by two sweepers with brooms who accompany it as it slides down the sheet, using the brooms to alter the state of the ice in front of the stone. A great deal of strategy and teamwork goes into choosing the ideal path and placement of a stone for each situation, and the skills of the curlers determine how close to the desired result the stone will achieve. This gives curling its nickname of "chess on ice".4.2 prompts/system-message.st
系统提示词模板,用于定义 AI 的角色。
你是一个乐于助人的 AI 助手。你的名字是 {name},你的说话风格是 {voice}。
请始终保持这个角色设定与用户进行对话。4.3 prompts/qa-prompt.st
问答提示词模板,用于注入上下文和问题。
请根据提供的上下文信息来回答用户的问题。如果上下文中没有相关信息,请说你不知道。
上下文信息:
---
{context}
---
用户问题: {question}5. 编写 Java 代码
5.1 RoleController.java
实现 AI 角色扮演功能。
package com.example.demo.controller;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/example/ai")
public class RoleController {private final ChatClient chatClient;// 注入系统提示词模板文件@Value("classpath:/prompts/system-message.st")private Resource systemResource;// 构造函数注入 ChatClientpublic RoleController(ChatClient.Builder chatClientBuilder) {this.chatClient = chatClientBuilder.build();}/*** 生成角色扮演对话* @param message 用户消息* @param name AI 角色名字* @param voice AI 说话风格* @return 流式响应*/@GetMapping("/roles")public Flux<String> generate(String message, String name, String voice) {// 1. 创建用户消息UserMessage userMessage = new UserMessage(message);// 2. 使用系统提示词模板并填充变量SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemResource);Message systemMessage = systemPromptTemplate.createMessage(Map.of("name", name, "voice", voice));// 3. 调用大模型,传入系统消息和用户消息return chatClient.prompt(new Prompt(List.of(systemMessage, userMessage))).stream().content();}
}5.2 StuffController.java
实现基于上下文的问答功能。
package com.example.demo.controller;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/prompt/ai")
public class StuffController {private final ChatClient chatClient;// 注入要填充的文档资源@Value("classpath:/docs/wikipedia-curling.md")private Resource docsToStuffResource;// 注入问答提示词模板@Value("classpath:/prompts/qa-prompt.st")private Resource qaPromptResource;public StuffController(ChatClient.Builder chatClientBuilder) {this.chatClient = chatClientBuilder.build();}/*** 根据上下文回答问题* @param message 用户问题* @param stuffit 是否填充上下文* @return 流式响应*/@GetMapping(value = "/stuff")public Flux<String> completion(String message, boolean stuffit) {// 1. 创建提示词模板PromptTemplate promptTemplate = new PromptTemplate(qaPromptResource);Map<String, Object> map = new HashMap<>();map.put("question", message);// 2. 根据参数决定是否填充上下文信息if (stuffit) {map.put("context", docsToStuffResource);} else {map.put("context", ""); // 不填充时,传入空字符串}// 3. 创建最终的 Prompt 并调用模型return chatClient.prompt(promptTemplate.create(map)).stream().content();}
}6. 运行与测试
- 启动应用:运行你的 Spring Boot 主程序。
- 使用浏览器或 API 工具(如 Postman, curl)进行测试。
测试 1:AI 角色扮演
访问以下 URL,让 AI 扮演一个名叫"阿尔法"、声音"沉稳而富有磁性"的助手,询问它今天天气如何。
http://localhost:8080/example/ai/roles?message=今天天气怎么样?&name=阿尔法&voice=沉稳而富有磁性预期响应(流式输出):
你好,我是阿尔法。关于今天的天气,我无法直接获取实时信息,建议您查看当地的天气预报应用。不过,无论天气如何,都希望您有愉快的一天。
测试 2:带上下文的问答
访问以下 URL,开启上下文填充,并提问"冰壶运动有几个队员?"。
http://localhost:8080/prompt/ai/stuff?message=冰壶运动有几个队员?&stuffit=true预期响应(流式输出):
根据提供的上下文信息,冰壶运动有两支队伍,每支队伍有四名队员。
测试 3:不带上下文的问答
访问相同的 URL,但将 stuffit 设为 false,再问同样的问题。
http://localhost:8080/prompt/ai/stuff?message=冰壶运动有几个队员?&stuffit=false预期响应(流式输出):
根据提供的上下文信息,我没有找到关于"冰壶运动有几个队员"的相关信息,所以我无法回答这个问题。
7. 实现思路与扩展建议
实现思路
本案例的核心思想是"模板与数据分离"。我们将固定的提示词结构(模板)与动态变化的变量(如角色名、上下文文档)分离开来。这使得:
- 可维护性高:修改提示词风格只需编辑
.st文件,无需改动 Java 代码。 - 复用性强:同一个模板可以用于不同的数据填充场景。
- 功能强大:通过动态注入上下文,为构建检索增强生成(RAG)应用奠定了基础。
扩展建议
- 多模板管理:可以设计一个更复杂的模板管理器,根据业务场景动态选择不同的模板。
- 模板缓存:对于频繁使用的模板,可以增加缓存层,避免每次请求都从磁盘加载,提升性能。
- A/B 测试:通过配置或请求头,动态切换不同版本的提示词模板,用于效果对比和优化。
- 集成向量数据库:将
StuffController中的静态文档替换为从向量数据库(如 Milvus, Chroma)中检索到的相关文本片段,构建真正的 RAG 系统。
