实验一: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;
    }
}

实现效果:

image

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

posted @ 2025-10-30 22:25  雨花阁  阅读(5)  评论(0)    收藏  举报