实验一:AI故事生成平台 调用deepseek大模型
实验一:AI故事生成平台
实验名称: AI故事生成平台 - 核心数据模型与文本生成
核心任务: 构建平台的后端核心,实现基于关键词的自动故事生成。
任务要求:
设计并实现 Story 数据模型,至少包含标题、故事梗概、正文、创建时间等字段。
集成百度文心一言或其他大语言模型API,开发一个后端服务。该服务接收用户提供的故事关键词(如“宇航员、小狗、月球”),调用AI生成一个完整的儿童故事,并保存至数据库。
API平台:DeepSeek 开放平台
注意要充值!不过并不贵。
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306
username:
password:
ai:
openai:
api-key:
base-url: https://api.deepseek.com
chat:
options:
model: deepseek-chat
导入依赖:
pom.xml
<properties>
<java.version>17</java.version>
<spring-ai.version>1.0.1</spring-ai.version>
</properties>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
配置一下AI
AiConfig
package com.example.config;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.ChatMemoryRepository;
import org.springframework.ai.chat.memory.InMemoryChatMemoryRepository;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AiConfig {
@Bean
public ChatMemoryRepository chatMemoryRepository() {
//聊天记忆库实现,来替换为Redis
return new InMemoryChatMemoryRepository();
}
@Bean
public ChatMemory chatMemory(ChatMemoryRepository chatMemoryRepository) {
//注册 聊天上下文机制
return MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository)
.maxMessages(20) //聊天记忆条数
.build();
}
//角色预设 - 儿童故事生成助手
@Bean
public ChatClient chatClient(ChatClient.Builder chatClientBuilder,ChatMemory chatMemory) {
return chatClientBuilder
.defaultSystem("你是一个专业的儿童故事生成专家。请根据用户提供的关键词,创作一个温馨、有趣、富有教育意义的儿童故事。故事应该简短易懂,语言生动活泼,适合5-10岁的儿童阅读。故事中应包含积极的价值观,如友谊、勇气、好奇心等。")
.defaultAdvisors(new SimpleLoggerAdvisor(),
// 添加会话记忆
MessageChatMemoryAdvisor.builder(chatMemory).build()) //配置日志Advisor
.build();
}
}
输出内容的形式包括非流式和流式输出。流式输出简单来说就想我们和ai对话时一样,它不是一下输出全部回答,而是逐字输出。
StoryController
package com.example.controller;
import com.example.service.StoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
/**
* 故事控制器
*/
@RestController
@RequestMapping("/api/story")
public class StoryController {
@Autowired
private StoryService storyService;
/**
* 流式生成故事
*/
@GetMapping(value = "/generate/stream", produces = "text/plain;charset=UTF-8")
public Flux<String> generateStoryStream(@RequestParam String keywords, @RequestParam String chatId) {
if (keywords == null || keywords.trim().isEmpty()) {
return Flux.just("错误:关键词不能为空");
}
return storyService.generateStoryStream(keywords, chatId);
}
}
StoryService
package com.example.service;
import reactor.core.publisher.Flux;
/**
* 故事服务接口
*/
public interface StoryService {
/**
* 流式生成故事
* @param keywords 故事关键词
* @param chatId 会话ID
* @return 故事内容的流式输出
*/
Flux<String> generateStoryStream(String keywords, String chatId);
}
StoryServiceImpl
package com.example.service.impl;
import com.example.entity.Story;
import com.example.mapper.StoryMapper;
import com.example.service.StoryService;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.scheduler.Schedulers;
import java.time.LocalDateTime;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.springframework.ai.chat.memory.ChatMemory.CONVERSATION_ID;
/**
* 故事服务实现类
*/
@Service
public class StoryServiceImpl implements StoryService {
@Autowired
private StoryMapper storyMapper;
@Autowired
private ChatClient chatClient;
@Override
public Flux<String> generateStoryStream(String keywords, String chatId) {
// 创建提示词,指导AI生成儿童故事
String prompt = String.format("请创作一个适合儿童的短故事,包含以下关键词:%s。\n" +
"请按照以下格式返回:\n" +
"【标题】故事的标题\n" +
"【梗概】故事的简要内容概述(100字以内)\n" +
"【正文】完整的故事内容(500-1000字)\n" +
"故事应该积极向上,富有想象力,适合儿童阅读。", keywords);
try {
// 使用AtomicReference收集完整的故事内容
AtomicReference<StringBuilder> fullContent = new AtomicReference<>(new StringBuilder());
// 使用ChatClient进行流式调用
return chatClient.prompt()
.user(prompt)
.advisors(a -> a.param(CONVERSATION_ID, chatId))
.stream()
.content()
// 收集内容并在完成时保存到数据库
.doOnNext(chunk -> {
fullContent.get().append(chunk);
})
.doFinally(type -> {
// 在流结束后异步保存到数据库
String completeStory = fullContent.get().toString();
saveStoryToDatabase(completeStory, keywords);
});
} catch (Exception e) {
// 错误处理,返回简单的故事内容
String fallbackStory = "【标题】关键词的故事\n" +
"【梗概】这是一个关于关键词的有趣故事。\n" +
"【正文】很久很久以前,在一个神奇的王国里,关键词们快乐地生活着...";
// 保存错误情况下的故事到数据库
saveStoryToDatabase(fallbackStory, keywords);
return Flux.just(fallbackStory);
}
}
/**
* 将故事保存到数据库
*/
private void saveStoryToDatabase(String storyContent, String keywords) {
// 在独立线程中异步保存,不阻塞流式响应
Schedulers.boundedElastic().schedule(() -> {
try {
// 解析AI返回的结果
Story story = parseStoryResponse(storyContent, keywords);
// 设置创建时间
story.setCreateTime(LocalDateTime.now());
story.setUpdateTime(LocalDateTime.now());
// 保存到数据库
storyMapper.insert(story);
} catch (Exception e) {
// 记录保存失败的错误,不影响用户体验
e.printStackTrace();
}
});
}
/**
* 解析AI返回的故事内容
*/
private Story parseStoryResponse(String response, String keywords) {
Story story = new Story();
story.setKeywords(keywords);
// 使用正则表达式提取标题、梗概和正文
// 标题提取
Pattern titlePattern = Pattern.compile("【标题】(.*?)(?=【梗概】|$)", Pattern.DOTALL);
Matcher titleMatcher = titlePattern.matcher(response);
if (titleMatcher.find()) {
story.setTitle(titleMatcher.group(1).trim());
} else {
story.setTitle("AI生成的故事:" + keywords);
}
// 梗概提取
Pattern summaryPattern = Pattern.compile("【梗概】(.*?)(?=【正文】|$)", Pattern.DOTALL);
Matcher summaryMatcher = summaryPattern.matcher(response);
if (summaryMatcher.find()) {
story.setSummary(summaryMatcher.group(1).trim());
} else {
story.setSummary("一个关于" + keywords + "的故事。");
}
// 正文提取
Pattern contentPattern = Pattern.compile("【正文】(.*)", Pattern.DOTALL);
Matcher contentMatcher = contentPattern.matcher(response);
if (contentMatcher.find()) {
story.setContent(contentMatcher.group(1).trim());
} else {
// 如果没有按照格式返回,就使用整个响应作为正文
story.setContent(response.trim());
}
return story;
}
}
实现效果:

本文参考:黑马程序员SpringAI+DeepSeek大模型应用开发实战视频教程,传统Java项目AI化转型必学课程_哔哩哔哩_bilibili
浙公网安备 33010602011771号