1. 基于“助手角色”实现聊天记忆功能
核心原理:定义一个以会话 ID 为 key,List<Message>为值的 Map, 将用户输入作为“用户消息”存入该 Map 中,将模型响应作为“助手消息”存入该 Map 中。
@RestController
@RequestMapping("/v6/ai")
public class AliyunBailianController {
// 存储聊天对话
private Map<String, List<Message>> chatMemoryStore = new ConcurrentHashMap<>();
/**
* 普通对话
*/
@GetMapping("/generate")
public String generate(@RequestParam(value = "message", defaultValue = "你是谁?") String message,
@RequestParam(value = "chatId") String chatId) {
// 根据 chatId 获取对话记录
List<Message> messages = chatMemoryStore.get(chatId);
// 若不存在,则初始化一份
if (CollectionUtils.isEmpty(messages)) {
messages = new ArrayList<>();
chatMemoryStore.put(chatId, messages);
}
// 添加 “用户角色消息” 到聊天记录中
messages.add(new UserMessage(message));
// 构建提示词
Prompt prompt = new Prompt(messages);
// 一次性返回结果
String responseText = chatModel.call(prompt).getResult().getOutput().getText();
// 添加 “助手角色消息” 到聊天记录中
messages.add(new AssistantMessage(responseText));
return responseText;
}
}2. 提示词模版
2.1. 提示词模版的组成
2.1.1. 固定部分
角色定义: 明确要求 AI 扮演什么角色
核心任务: 清晰说明需要 AI 做什么
输出格式要求: 严格规定输出的结构和样式
风格与语气: 指定所需的写作风格
背景信息/上下文: 提供必要的背景知识或限制条件
思考过程/步骤(可选): 引导 AI 按照特定逻辑或步骤思考
示例(Few-Shot Learning,可选): 提供几个输入-输出的例子,让 AI 更清楚地理解任务模式。
2.1.2. 用户填充部分
用特定的符号(如
{},[],{{变量名}},$变量名)标记出来代表每次使用时需要用户或系统动态填入的具体内容
例如:
[主题]、[目标语言]、[产品名称]、[客户姓名]、[具体数据]等
3. Demo
3.1. 在代码中定义提示词模版
@RestController
@RequestMapping("/v7/ai")
public class PromptTemplateController {
@Resource
private OpenAiChatModel chatModel;
/**
* 智能代码生成
* @param message
* @param lang
* @return
*/
@GetMapping(value = "/generateStream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<AIResponse> generateStream(@RequestParam(value = "message") String message,
@RequestParam(value = "lang") String lang) {
// 提示词模板
String template = """
你是一位资深 {lang} 开发工程师。请严格遵循以下要求编写代码:
1. 功能描述:{description}
2. 代码需包含详细注释
3. 使用业界最佳实践
""";
PromptTemplate promptTemplate = new PromptTemplate(template);
// 填充提示词占位符,转换为 Prompt 提示词对象
Prompt prompt = promptTemplate.create(Map.of("description", message, "lang", lang));
// 流式输出
return chatModel.stream(prompt)
.mapNotNull(chatResponse -> {
Generation generation = chatResponse.getResult();
String text = generation.getOutput().getText();
return AIResponse.builder().v(text).build();
});
}
}3.2. 从文件中读取提示词
@RestController
@RequestMapping("/v7/ai")
public class PromptTemplateController {
@Resource
private OpenAiChatModel chatModel;
@Value("classpath:/prompts/code-assistant.st")
private org.springframework.core.io.Resource templateResource;
/**
* 智能代码生成
* @param message
* @param lang
* @return
*/
@GetMapping(value = "/generateStream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<AIResponse> generateStream(@RequestParam(value = "message") String message,
@RequestParam(value = "lang") String lang) {
// 提示词模板
PromptTemplate promptTemplate = new PromptTemplate(templateResource);
// 省略...
}
}3.3. 自定义占位符
默认情况下,Spring AI 中提示词模板中的占位符为 {} 花括号。
// 提示词模板
PromptTemplate promptTemplate = PromptTemplate.builder()
.renderer(StTemplateRenderer.builder()
.startDelimiterToken('<')
.endDelimiterToken('>').build()) // 自定义占位符
.template("""
你是一位资深 <lang> 开发工程师。请严格遵循以下要求编写代码:
1. 功能描述:<description>
2. 代码需包含详细注释
3. 使用业界最佳实践
""")
.build();3.4. 设置多角色
@RestController
@RequestMapping("/v7/ai")
public class PromptTemplateController {
@Resource
private OpenAiChatModel chatModel;
// 省略...
/**
* 智能代码生成 3
* @param message
* @param lang
* @return
*/
@GetMapping(value = "/generateStream3", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<AIResponse> generateStream3(@RequestParam(value = "message") String message,
@RequestParam(value = "lang") String lang) {
// 系统角色提示词模板
String systemPrompt = """
你是一位资深 {lang} 开发工程师, 已经从业数十年,经验非常丰富。
""";
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemPrompt);
// 填充提示词占位符,并转换为 Message 对象
Message systemMessage = systemPromptTemplate.createMessage(Map.of("lang", lang));
// 用户角色提示词模板
String userPrompt = """
请严格遵循以下要求编写代码:
1. 功能描述:{description}
2. 代码需包含详细注释
3. 使用业界最佳实践
""";
PromptTemplate promptTemplate = new PromptTemplate(userPrompt);
// 填充提示词占位符,并转换为 Message 对象
Message userMessage = promptTemplate.createMessage(Map.of("description", message));
// 组合多角色消息,构建提示词 Prompt
Prompt prompt = new Prompt(List.of(systemMessage, userMessage));
// 流式输出
return chatModel.stream(prompt)
.mapNotNull(chatResponse -> {
Generation generation = chatResponse.getResult();
String text = generation.getOutput().getText();
return AIResponse.builder().v(text).build();
});
}
}3.5. 结构化输出
3.5.1. 演员代表作
转 Java 对象
@JsonPropertyOrder({"actor", "movies"})
public record ActorFilmography(String actor, List<String> movies) {
}@RestController
@RequestMapping("/v8/ai")
public class StructuredOutputController {
@Resource
private ChatClient chatClient;
/**
* 示例1: BeanOutputConverter - 获取演员电影作品集
* @param name
* @return
*/
@GetMapping("/actor/films")
public ActorFilmography generate(@RequestParam(value = "name") String name) {
// 一次性返回结果
return chatClient.prompt()
.user(u -> u.text("""
请为演员 {actor} 生成包含5部代表作的电影作品集,
只包含 {actor} 担任主演的电影,不要包含任何解释说明。
""")
.param("actor", name))
.call()
.entity(ActorFilmography.class);
}
}3.5.2. 获取编程语言信息
转 Map
@RestController
@RequestMapping("/v8/ai")
public class StructuredOutputController {
@Resource
private ChatClient chatClient;
/**
* 示例2: MapOutputConverter - 获取编程语言信息
* @param language
* @return
*/
@GetMapping("/language-info")
public Map<String, Object> getLanguageInfo(@RequestParam(value = "lang") String language) {
String userText = """
请提供关于编程语言 {language} 的结构化信息,包含以下字段:"
name (语言名称), "
popularity (流行度排名,整数), "
features (主要特性,字符串数组), "
releaseYear (首次发布年份). "
不要包含任何解释说明,直接输出 JSON 格式数据。
""";
return chatClient.prompt()
.user(u -> u.text(userText).param("language", language))
.call()
.entity(new MapOutputConverter());
}
}
3.5.3. 获取城市列表
转 List
@RestController
@RequestMapping("/v8/ai")
public class StructuredOutputController {
@Resource
private ChatClient chatClient;
/**
* 示例3: ListOutputConverter - 获取城市列表
* @param country
* @return
*/
@GetMapping("/city-list")
public List<String> getCityList(@RequestParam(value = "country") String country) {
return chatClient.prompt()
.user(u -> u.text(
"""
列出 {country} 的8个主要城市名称。
不要包含任何编号、解释或其他文本,直接输出城市名称列表。
""")
.param("country", country))
.call()
.entity(new ListOutputConverter(new DefaultConversionService()));
}
}3.5.4. 获取书籍信息
转复杂 Java 对象
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book {
/**
* 书名
*/
private String title;
/**
* 作者
*/
private String author;
/**
* 发布年份
*/
private Integer publishYear;
/**
* 类型
*/
private List<String> genres;
/**
* 简介
*/
private String description;
}@RestController
@RequestMapping("/v8/ai")
public class StructuredOutputController {
@Resource
private ChatClient chatClient;
/**
* 使用低级 API 的 BeanOutputConverter - 获取书籍信息
* @param bookTitle
* @return
*/
@GetMapping("/book-info")
public Book getBookInfo(@RequestParam(value = "name") String bookTitle) {
// 使用 BeanOutputConverter 定义输出格式
BeanOutputConverter<Book> converter = new BeanOutputConverter<>(Book.class);
// 提示词模板
String template = """
请提供关于书籍《{bookTitle}》的详细信息:
1. 作者姓名
2. 出版年份
3. 主要类型(数组)
4. 书籍描述(不少于50字)
不要包含任何解释说明,直接按指定格式输出。
{format}
""";
// 创建 Prompt
PromptTemplate promptTemplate = new PromptTemplate(template);
Prompt prompt = promptTemplate.create(Map.of(
"bookTitle", bookTitle,
"format", converter.getFormat()
));
// 调用模型并转换结果
String result = chatClient.prompt(prompt)
.call()
.content();
// 结构化转换
return converter.convert(result);
}
}
评论区