實驗一:AI故事生成平台

核心代碼

1. Story數據模型 (models/story.py)

import os
from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime
from sqlalchemy.ext.declarative import declarative_base
from datetime import datetime

# 創建基類
Base = declarative_base()

class Story(Base):
    """
    故事數據模型
    用於存儲生成的兒童故事信息
    """
    __tablename__ = 'stories'
    
    # 主鍵
    id = Column(Integer, primary_key=True, index=True)
    
    # 故事標題
    title = Column(String(255), nullable=False)
    
    # 故事內容
    content = Column(Text, nullable=False)
    
    # 關鍵詞
    keywords = Column(String(500))
    
    # 目標年齡段
    age_group = Column(String(50))
    
    # 故事主題
    theme = Column(String(100))
    
    # 插圖URL
    image_url = Column(String(500))
    
    # 音頻URL
    audio_url = Column(String(500))
    
    # 是否收藏
    is_favorite = Column(Boolean, default=False)
    
    # 創建時間
    created_at = Column(DateTime, default=datetime.utcnow)
    
    # 更新時間
    updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
    
    def to_dict(self):
        """
        將故事對象轉換為字典格式
        便於JSON序列化和API響應
        """
        return {
            'id': self.id,
            'title': self.title,
            'content': self.content,
            'keywords': self.keywords,
            'age_group': self.age_group,
            'theme': self.theme,
            'image_url': self.image_url,
            'audio_url': self.audio_url,
            'is_favorite': self.is_favorite,
            'created_at': self.created_at.isoformat() if self.created_at else None,
            'updated_at': self.updated_at.isoformat() if self.updated_at else None
        }

2. 故事生成服務 (services/story_service.py)

import os
from dotenv import load_dotenv
from utils.aliyun_client import AliyunClient

# 加載環境變量
load_dotenv()

def generate_story(keywords, age_group, story_length, theme, voice_type=None):
    """
    使用阿里雲百鍊大模型生成兒童故事
    
    Args:
        keywords: 關鍵詞列表,用於生成故事
        age_group: 目標年齡段
        story_length: 故事長度 (short, medium, long)
        theme: 故事主題
        voice_type: 聲音類型 (child, female, male)
    
    Returns:
        dict: 包含故事內容的字典
    """
    # 構建提示詞
    prompt = f"請為{age_group}歲的兒童創作一個有趣的童話故事。"
    prompt += f"關鍵詞: {', '.join(keywords)}"
    
    if theme:
        prompt += f"主題: {theme}"
    
    # 根據聲音類型調整故事風格
    voice_style_map = {
        'child': '用天真活潑、充滿童趣的語氣來講述,讓故事聽起來像小朋友在分享自己的經歷',
        'female': '用温柔親切、富有母愛的語調來講述,讓故事充滿温暖和關懷',
        'male': '用穩重有趣、略帶磁性的聲音來講述,讓故事既有趣味性又有安全感'
    }
    
    if voice_type and voice_type in voice_style_map:
        prompt += f"\n{voice_style_map[voice_type]}"
    
    # 根據故事長度設置字數要求
    length_map = {
        'short': '200-300字',
        'medium': '400-600字',
        'long': '800-1000字'
    }
    prompt += f"\n請確保故事字數控制在{length_map.get(story_length, '400-600字')}左右。"
    prompt += "\n故事要有積極向上的價值觀,語言生動有趣,適合兒童閲讀。"
    prompt += "\n請將故事內容分成適當的段落,每個段落圍繞一個小情節展開,讓閲讀體驗更好。"
    prompt += "\n請提供一個吸引人的標題。"
    
    print(f"📖 故事生成提示詞: {prompt}")
    
    # 創建阿里雲客户端並調用API
    client = AliyunClient()
    
    try:
        # 調用文本生成API
        result = client.generate_text(prompt, model="qwen-max", temperature=0.7, max_tokens=1000)
        
        print(f"📄 文本生成API響應: {result}")
        
        # 提取故事內容
        story_text = result.get('output', {}).get('text', '')
        
        print(f"📖 提取的故事文本: '{story_text}'")
        print(f"📏 故事文本長度: {len(story_text)}")
        
        # 如果故事文本為空,返回錯誤
        if not story_text or len(story_text.strip()) == 0:
            raise Exception("文本生成API返回了空內容")
        
        # 處理標題和正文,確保都不包含井號
        lines = story_text.strip().split('\n')
        title = lines[0].lstrip('# ') if lines else "童話故事"
        # 處理內容,移除所有井號字符並在每段首行添加兩字符縮進
        content_lines = lines[1:] if len(lines) > 1 else []

        # 分組內容行到段落(空行分隔段落)
        paragraphs = []
        current_paragraph = []
        for line in content_lines:
            clean_line = line.replace('#', '')
            if clean_line.strip():
                current_paragraph.append(clean_line)
            else:
                if current_paragraph:
                    paragraphs.append('\n'.join(current_paragraph))
                    current_paragraph = []
        # 添加最後一個段落
        if current_paragraph:
            paragraphs.append('\n'.join(current_paragraph))

        # 為每個段落的首行添加兩字符縮進
        indented_paragraphs = []
        for paragraph in paragraphs:
            if not paragraph:
                continue
            lines_in_paragraph = paragraph.split('\n')
            if lines_in_paragraph:
                # 首行添加縮進
                lines_in_paragraph[0] = '  ' + lines_in_paragraph[0]
                indented_paragraphs.append('\n'.join(lines_in_paragraph))

        # 段落間用空行分隔
        content = '\n\n'.join(indented_paragraphs)

        # 處理特殊情況:如果沒有生成內容,使用備用方案
        if not content.strip():
            content = '  ' + story_text.replace('#', '') if story_text.strip() else ''
        
        print(f"📖 最終標題: '{title}'")
        print(f"📖 最終內容長度: {len(content)}")
        
        return {
            'title': title,
            'content': content,
            'keywords': keywords,
            'age_group': age_group,
            'story_length': story_length,
            'theme': theme
        }
        
    except Exception as e:
        print(f"❌ 故事生成異常: {e}")
        raise Exception(f"生成故事時發生錯誤: {str(e)}")

3. 故事生成API路由 (routes/story_routes.py)

from flask import Blueprint, request, jsonify
from services.story_service import generate_story

# 創建故事相關的藍圖
bp = Blueprint('story', __name__)

# 生成故事的路由
@bp.route('/generate', methods=['POST'])
def generate_story_route():
    try:
        data = request.json
        
        # 驗證必要的參數
        if not data or 'keywords' not in data:
            return jsonify({'error': '缺少必要的關鍵詞參數'}), 400
        
        keywords = data['keywords']
        age_group = data.get('age_group', '5-8')  # 默認年齡段
        story_length = data.get('story_length', 'medium')  # 默認故事長度
        theme = data.get('theme', '')  # 可選主題
        voice_type = data.get('voice_type', 'child')  # 聲音類型
        
        # 調用故事生成服務,傳入聲音類型
        story_data = generate_story(keywords, age_group, story_length, theme, voice_type)
        
        return jsonify(story_data), 200
        
    except Exception as e:
        return jsonify({'error': f'生成故事時發生錯誤: {str(e)}'}), 500

# 獲取所有故事的路由
@bp.route('/', methods=['GET'])
def get_all_stories():
    try:
        # 這裏將在實現故事管理功能時補充
        return jsonify({'stories': []}), 200
    except Exception as e:
        return jsonify({'error': f'獲取故事列表時發生錯誤: {str(e)}'}), 500

# 獲取單個故事的路由
@bp.route('/<story_id>', methods=['GET'])
def get_story(story_id):
    try:
        # 這裏將在實現故事管理功能時補充
        return jsonify({'error': '故事不存在'}), 404
    except Exception as e:
        return jsonify({'error': f'獲取故事時發生錯誤: {str(e)}'}), 500

功能説明

Story數據模型

  • 核心字段:id、title、content、keywords、age_group、theme、created_at等
  • 擴展字段:image_url、audio_url(用於與後續實驗集成)
  • 方法:to_dict() 用於對象序列化

故事生成服務

  • 核心功能:使用阿里雲百鍊大模型API生成兒童故事
  • 參數處理:接收關鍵詞、年齡段、故事長度、主題等參數
  • 提示詞工程:根據參數動態構建提示詞,支持不同聲音類型的風格調整
  • 文本處理:處理API返回結果,提取標題和內容,移除特殊字符,添加段落縮進
  • 錯誤處理:完善的異常捕獲和錯誤信息返回

API路由

  • 生成故事:POST /story/generate,接收故事生成參數
  • 參數驗證:驗證必要參數,設置合理默認值
  • 錯誤響應:統一的錯誤處理和狀態碼返回

技術棧

  • Python 3.12
  • Flask Web框架
  • SQLAlchemy ORM
  • 阿里雲百鍊大模型API

使用方法

  1. 確保配置了阿里雲API密鑰(在.env文件中)
  2. 調用 /story/generate 端點,傳入必要的故事生成參數
  3. API返回生成的故事標題、內容和相關信息

後續擴展

  • 故事保存功能
  • 故事列表和詳情查詢
  • 與插圖生成和語音合成服務集成