基于 Spring AI 与 DeepSeek 的儿童故事生成服务构建
项目概述与核心架构
本方案旨在构建一个后端服务,利用大语言模型能力实现基于主题关键词的儿童故事自动生成。系统核心涉及数据持久化模型设计、大模型 API 集成以及流式响应处理。技术栈选用 Java 17 配合 Spring AI 框架,对接 DeepSeek 开放平台。
环境配置与依赖管理
首先需要在项目中引入 Spring AI 的相关依赖。由于 DeepSeek 兼容 OpenAI 协议,可使用 spring-ai-starter-model-openai 进行集成。以下是 pom.xml 的关键配置片段:
<properties>
<java.version>17</java.version>
<spring-ai.version>1.0.1</spring-ai.version>
</properties>
<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>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
应用配置文件 application.yml 需设置数据库连接及 AI 服务凭证。注意 DeepSeek 需要有效的 API Key 并保证账户余额充足:
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/story_db
username: root
password: your_password
ai:
openai:
api-key: sk-your-deepseek-key
base-url: https://api.deepseek.com
chat:
options:
model: deepseek-chat
大模型客户端配置
创建配置类以初始化 ChatClient,预设系统提示词以规范 AI 的输出风格,并启用内存组件以支持多轮对话上下文。
package com.storylab.config;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
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 LLMProviderConfig {
@Bean
public ChatMemory chatMemory() {
// 配置上下文窗口大小,保留最近 20 条消息
return MessageWindowChatMemory.builder()
.chatMemoryRepository(new InMemoryChatMemoryRepository())
.maxMessages(20)
.build();
}
@Bean
public ChatClient chatClient(ChatClient.Builder builder, ChatMemory memory) {
return builder
.defaultSystem("你是一位擅长讲述童话的作家。请根据用户提供的主题,创作一个温馨、有趣且富有教育意义的儿童故事。语言需生动活泼,适合 5-10 岁儿童阅读,体现友谊、勇气等积极价值观。")
.defaultAdvisors(new MessageChatMemoryAdvisor(memory))
.build();
}
}
数据模型设计
故事实体类 Narrative 需包含主题、标题、摘要、正文内容及时间戳字段,对应数据库表结构以便持久化生成结果。
控制器与服务层实现
控制层提供流式接口,允许前端逐字接收生成内容。服务层负责调用模型、解析响应结构并异步存储数据。
控制器代码:
package com.storylab.controller;
import com.storylab.service.NarrativeService;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
@RestController
@RequestMapping("/v1/narratives")
public class NarrativeController {
private final NarrativeService narrativeService;
public NarrativeController(NarrativeService narrativeService) {
this.narrativeService = narrativeService;
}
@GetMapping(value = "/stream", produces = "text/plain;charset=UTF-8")
public Flux<String> streamGenerate(@RequestParam String themes, @RequestParam String sessionId) {
if (themes == null || themes.isBlank()) {
return Flux.just("错误:主题不能为空");
}
return narrativeService.streamGenerate(themes, sessionId);
}
}
服务接口定义:
package com.storylab.service;
import reactor.core.publisher.Flux;
public interface NarrativeService {
Flux<String> streamGenerate(String themes, String sessionId);
}
服务实现逻辑:
实现类中通过构建提示词引导 AI 按特定 Markdown 格式输出,利用流式操作收集内容,并在传输完成后异步解析并保存至数据库。
package com.storylab.service.impl;
import com.storylab.entity.Narrative;
import com.storylab.mapper.NarrativeMapper;
import com.storylab.service.NarrativeService;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.scheduler.Schedulers;
import java.time.LocalDateTime;
@Service
public class NarrativeServiceImpl implements NarrativeService {
private final NarrativeMapper narrativeMapper;
private final ChatClient chatClient;
public NarrativeServiceImpl(NarrativeMapper narrativeMapper, ChatClient chatClient) {
this.narrativeMapper = narrativeMapper;
this.chatClient = chatClient;
}
@Override
public Flux<String> streamGenerate(String themes, String sessionId) {
String prompt = "请创作一个儿童短故事,包含主题:%s。\n" +
"请严格遵循以下 Markdown 格式返回:\n" +
"### 标题\n" +
"故事标题\n" +
"### 摘要\n" +
"100 字以内的概述\n" +
"### 正文\n" +
"500-1000 字的故事内容";
String formattedPrompt = String.format(prompt, themes);
StringBuilder contentBuffer = new StringBuilder();
return chatClient.prompt()
.user(formattedPrompt)
.advisors(a -> a.param("conversation_id", sessionId))
.stream()
.content()
.doOnNext(chunk -> contentBuffer.append(chunk))
.doOnComplete(() -> persistNarrative(contentBuffer.toString(), themes));
}
private void persistNarrative(String fullText, String themes) {
Schedulers.boundedElastic().schedule(() -> {
try {
Narrative story = parseContent(fullText, themes);
story.setCreatedAt(LocalDateTime.now());
story.setUpdatedAt(LocalDateTime.now());
narrativeMapper.insert(story);
} catch (Exception e) {
System.err.println("故事保存失败:" + e.getMessage());
}
});
}
private Narrative parseContent(String response, String themes) {
Narrative story = new Narrative();
story.setThemes(themes);
// 基于 Markdown 标记解析内容
String[] sections = response.split("###");
if (sections.length > 1) {
// 简单处理,实际生产环境建议增强解析逻辑
for (String section : sections) {
if (section.contains("标题")) {
story.setTitle(section.split("\n")[1].trim());
} else if (section.contains("摘要")) {
story.setSummary(section.split("\n")[1].trim());
} else if (section.contains("正文")) {
story.setContent(section.split("\n", 2)[1].trim());
}
}
}
// 兜底处理
if (story.getTitle() == null) story.setTitle("主题故事:" + themes);
if (story.getContent() == null) story.setContent(response);
return story;
}
}
通过上述架构,系统能够接收用户输入的主题,实时流式返回生成的故事内容,并在后台自动完成数据归档,实现了 AI 能力与传统业务逻辑的有效融合。