2025/12/8 每日总结 三大核心模块实现:故事生成+插图匹配+语音合成
三大核心模块实现:故事生成+插图匹配+语音合成
在上一篇博客中,我们完成了AI儿童故事平台的基础搭建,确定了数据模型与三大核心大模型选型。这一篇,我们聚焦技术落地——详细拆解故事生成、插图匹配、语音合成三大模块的具体实现,包括API调用、核心代码逻辑、异常处理与问题解决,让每个模块都能稳定跑通并精准联动。
一、故事生成模块:基于Kimi API的儿童故事创作
故事生成是平台的核心基础,目标是接收用户关键词,调用Kimi API生成符合6-12岁儿童认知的结构化故事(标题+梗概+正文)。
1. 核心准备:API配置与环境搭建
-
API密钥申请:访问Kimi API官方文档,注册账号后创建应用,获取API Key,并配置到
.env文件中(KIMI_API_KEY=your_api_key)。 -
依赖安装:核心依赖为
requests(网络请求)、json(数据解析),直接通过pip install requests安装即可。 -
配置文件关联:在
config.py中读取环境变量,供kimi_api.py调用,确保密钥不硬编码,提升安全性。2. 核心代码实现:
kimi_api.py(1)类初始化与基础配置
import requests import json import os from config import KIMI_API_KEY class KimiAPI: def __init__(self): self.api_key = KIMI_API_KEY self.base_url = "https://api.moonshot.cn/v1" # Kimi API基础地址(2)提示词设计:精准约束儿童故事风格
提示词是故事质量的关键,需明确风格、结构、长度与输出格式:
def generate_story(self, keywords: str) -> dict: url = f"{self.base_url}/chat/completions" # 提示词:明确儿童故事要求+结构化输出约束 prompt = f""" 请根据以下关键词创作一个适合6-12岁儿童的精彩故事: 关键词:{keywords} 要求:
-
语言简单易懂,避免复杂词汇;
-
故事有完整起承转合,情节生动有趣;
-
长度控制在500-800字;
-
包含积极教育意义(如勇气、友谊、互助);
-
严格按照以下JSON格式返回,不添加任何额外文本:
{{
"title": "故事标题",
"summary": "100字内梗概",
"content": "完整故事内容"
}}
"""
#### (3)API请求参数配置
```python
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {self.api_key}" # 身份认证
}
payload = {
"model": "moonshot-v1-8k", # 选用8k上下文模型,满足故事长度需求
"messages": [
{"role": "system", "content": "你是专业儿童故事作家,擅长想象力与教育意义结合的创作"},
{"role": "user", "content": prompt}
],
"temperature": 0.7, # 保留一定随机性,避免故事同质化
"max_tokens": 2000, # 足够覆盖800字故事+结构化字段
"response_format": {"type": "json_object"} # 强制返回JSON
}
(4)响应处理与异常兜底
针对网络超时、JSON解析失败等常见问题,设计容错机制:
try:
response = requests.post(url, headers=headers, json=payload, timeout=30)
response.raise_for_status() # 抛出HTTP请求错误
result = response.json()
story_content = result.get("choices", [{}])[0].get("message", {}).get("content", "")
return self._parse_story_response(story_content, keywords)
except requests.exceptions.Timeout:
raise Exception("请求超时,请稍后重试")
except requests.exceptions.RequestException as e:
raise Exception(f"网络请求失败: {str(e)}")
except Exception as e:
raise Exception(f"故事生成失败: {str(e)}")
# 解析响应:JSON失败时手动提取信息
def _parse_story_response(self, story_content: str, keywords: str) -> dict:
try:
# 清理可能的Markdown格式(如```json包裹)
cleaned_content = story_content.strip().lstrip('```json').rstrip('```')
story_data = json.loads(cleaned_content)
# 验证必需字段
for field in ["title", "summary", "content"]:
if field not in story_data:
raise ValueError(f"缺少字段: {field}")
return story_data
except json.JSONDecodeError:
# 兜底方案:默认标题+截取梗概+原文内容
title = f"关于{keywords}的奇妙冒险"
content = story_content
summary = content[:100] + "..." if len(content) > 100 else content
return {"title": title, "summary": summary, "content": content}
3. 测试验证
调用generate_story("宇航员、小狗、月球"),成功返回结构化数据,故事符合儿童认知,长度与教育意义均达标,异常场景下能返回兜底结果,模块稳定性验证通过。
二、插图匹配模块:阿里云百炼API生成故事配图
插图模块需基于故事文本自动生成贴合意境的儿童插画,并与故事关联存储,核心挑战是“提示词精准度”与“资源管理”。
1. 核心准备:API配置与依赖
-
API密钥申请:登录阿里云百炼平台,创建API-Key,配置到
.env(ALIYUN_ACCESS_KEY=your_key)。 -
依赖安装:
pip install dashscope requests pillow(dashscope是阿里云AI服务SDK)。 -
存储目录创建:在项目根目录创建
images文件夹,用于存储本地图片。2. 核心代码实现:
image_api.py(1)类初始化与配置
import requests import os import uuid import time import logging from dotenv import load_dotenv from dashscope import ImageSynthesis from http import HTTPStatus load_dotenv() logger = logging.getLogger(__name__) class ImageGenerationAPI: def __init__(self): self.images_dir = "images" os.makedirs(self.images_dir, exist_ok=True) # 确保目录存在 self.api_config = { "aliyun": { "access_key": os.getenv("ALIYUN_ACCESS_KEY"), "model": "qwen-image-plus", # 儿童插画适配模型 "size": "1024x1024" # 正方形配图,适配前端展示 } }(2)提示词优化:从故事中提取核心元素
为避免插图与故事脱节,设计自动提取关键元素的提示词生成逻辑:
def generate_image_prompt(self, story_title: str, story_content: str, keywords: str) -> str: key_elements = [] # 从关键词和正文中提取场景、角色、氛围 if "魔法" in keywords or "魔法" in story_content: key_elements.append("魔法元素") if "森林" in keywords or "森林" in story_content: key_elements.append("森林场景") if any(animal in story_content for animal in ["小狗", "小猫", "小鹿"]): key_elements.append("可爱动物") if "冒险" in keywords or "冒险" in story_content: key_elements.append("冒险氛围") # 构建提示词 prompt = f"儿童插画风格,{story_title},{','.join(key_elements)},色彩鲜艳,画面生动,适合儿童观看" return prompt if len(prompt) > 30 else prompt + ",充满童趣和想象力"(3)图片生成与本地存储
def generate_image_from_text(self, prompt: str, story_title: str) -> dict: try: # 配置阿里云API密钥 dashscope.api_key = self.api_config["aliyun"]["access_key"] # 调用文生图API response = ImageSynthesis.call( model=self.api_config["aliyun"]["model"], prompt=prompt, n=1, size=self.api_config["aliyun"]["size"] ) if response.status_code != HTTPStatus.OK: raise Exception(f"API调用失败: {response.message}") # 下载图片 image_url = response.output.results[0].url image_response = requests.get(image_url) image_response.raise_for_status() # 生成唯一文件名(避免重复) filename = self._generate_filename(story_title) image_path = os.path.join(self.images_dir, filename) # 保存图片到本地 with open(image_path, "wb") as f: f.write(image_response.content) # 返回图片访问URL(适配前端静态文件服务) image_url = f"/images/{filename}" return {"image_url": image_url, "image_path": image_path, "status": "success"} except Exception as e: logger.error(f"图片生成失败: {str(e)}") return {"image_url": None, "status": "failed", "error": str(e)} # 生成唯一文件名:故事标题+时间戳+UUID def _generate_filename(self, story_title: str) -> str: safe_title = "".join(c for c in story_title if c.isalnum() or c in (' ', '-', '_')).replace(' ', '_')[:50] safe_title = safe_title if safe_title else "story" unique_id = str(uuid.uuid4())[:8] timestamp = int(time.time()) return f"{safe_title}_{timestamp}_{unique_id}.png"3. 常见问题解决
-
插图不贴合故事:通过
generate_image_prompt自动提取故事核心元素,避免纯关键词提示的片面性; -
图片路径错误:采用相对路径存储,前端通过
/images/文件名直接访问,确保前后端路径一致; -
API调用失败:添加日志记录与状态返回,便于排查网络或密钥配置问题。
三、语音合成模块:百度TTS生成故事朗读音频
语音模块需将故事正文合成为MP3音频,支持长短文本适配,核心挑战是“文本长度限制”与“音频资源关联”。
1. 核心准备:API配置与依赖
-
API密钥申请:登录百度AI开放平台,创建语音合成应用,获取
API Key与Secret Key,配置到.env。 -
依赖安装:
pip install requests(百度TTS无需额外SDK,直接通过HTTP调用)。 -
存储目录创建:在项目根目录创建
audio文件夹,用于存储音频文件。2. 核心代码实现:
tts_api.py(1)类初始化与Token管理
百度TTS需要通过
API Key和Secret Key获取临时访问Token,且Token有有效期,需自动刷新:import requests import os import uuid import time import logging from dotenv import load_dotenv load_dotenv() logger = logging.getLogger(__name__) class TextToSpeechAPI: def __init__(self): self.audio_dir = "audio" os.makedirs(self.audio_dir, exist_ok=True) self.api_config = { "baidu": { "api_key": os.getenv("BAIDU_API_KEY"), "secret_key": os.getenv("BAIDU_SECRET_KEY"), "access_token": None, "token_expire_time": 0 # Token过期时间 } }(2)Token获取与刷新
def _get_baidu_token(self, config: dict) -> str: now = time.time() # 若Token未过期,直接返回;否则重新获取 if now < config["token_expire_time"] and config["access_token"]: return config["access_token"] # 调用Token获取接口 token_url = f"https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id={config['api_key']}&client_secret={config['secret_key']}" response = requests.get(token_url, timeout=30) response.raise_for_status() token_data = response.json() config["access_token"] = token_data["access_token"] # 提前5分钟刷新Token(避免过期) config["token_expire_time"] = now + token_data["expires_in"] - 300 return config["access_token"](3)音频生成与存储
支持长短文本适配,短文本直接调用,长文本自动拆分(百度TTS短文本限制1024GBK字节):
def generate_speech_from_text(self, text: str, story_title: str) -> dict: config = self.api_config["baidu"] token = self._get_baidu_token(config) tts_url = "https://tsn.baidu.com/text2audio"
配置音频参数(儿童友好音色)
params = {
"tex": text,
"tok": token,
"cuid": "story_platform", # 用户唯一标识
"ctp": "1", # 客户端类型(web端固定为1)
"lan": "zh", # 语言(中文)
"per": "0", # 音色(0=度小美,女声,适合儿童)
"spd": "5", # 语速(5为中速)
"vol": "5", # 音量(5为中音量)
"aue": "3" # 音频格式(3=MP3)
}
try:
调用语音合成API
response = requests.post(tts_url, data=params, timeout=60)
if response.status_code == 200 and 'audio' in response.headers.get('Content-Type', ''):
生成唯一文件名
filename = self._generate_filename(story_title)
audio_path = os.path.join(self.audio_dir, filename)
保存音频文件
with open(audio_path, "wb") as f:
f.write(response.content)
返回音频访问URL
audio_url = f"/audio/{filename}"
return {"audio_url": audio_url, "audio_path": audio_path, "status": "success"}
else:
error_msg = response.text if response.text else "未知错误"
raise Exception(f"音频生成失败: {error_msg}")
except Exception as e:
logger.error(f"语音合成失败: {str(e)}")
return {"audio_url": None, "status": "failed", "error": str(e)}
生成唯一音频文件名(同图片命名逻辑,保持一致性)
def generate_filename(self, story_title: str) -> str:
safe_title = "".join(c for c in story_title if c.isalnum() or c in (' ', '-', '')).replace(' ', '')[:50]
safe_title = safe_title if safe_title else "audio"
unique_id = str(uuid.uuid4())[:8]
timestamp = int(time.time())
return f"{safe_title}_{unique_id}.mp3"
### 3. 长短文本适配优化
针对百度TTS短文本长度限制,新增文本拆分逻辑(核心思路:按标点符号拆分,每段不超过120字):
```python
def _split_long_text(self, text: str) -> list:
import re
# 按句号、感叹号、问号拆分,避免拆分到完整语义
sentences = re.split(r'([。!?])', text)
chunks = []
current_chunk = ""
for sent in sentences:
if len(current_chunk + sent) < 120: # 每段不超过120字(约1024GBK字节)
current_chunk += sent
else:
if current_chunk:
chunks.append(current_chunk)
current_chunk = sent
if current_chunk:
chunks.append(current_chunk)
return chunks
在generate_speech_from_text中调用拆分逻辑,长文本分段合成后拼接(需引入pydub库处理音频拼接,pip install pydub)。
四、三大模块联动逻辑
单个模块跑通后,需实现“故事生成→插图生成→音频生成”的自动联动,核心逻辑如下:
# 主流程联动示例(后续整合到app.py)
def generate_story_package(keywords: str) -> dict:
# 1. 生成故事文本
kimi_api = KimiAPI()
story_data = kimi_api.generate_story(keywords)
# 2. 生成配套插图(使用故事标题、正文、关键词)
image_api = ImageGenerationAPI()
image_prompt = image_api.generate_image_prompt(
story_title=story_data["title"],
story_content=story_data["content"],
keywords=keywords
)
image_data = image_api.generate_image_from_text(image_prompt, story_data["title"])
# 3. 生成朗读音频
tts_api = TextToSpeechAPI()
audio_data = tts_api.generate_speech_from_text(story_data["content"], story_data["title"])
# 4. 整合结果(关联故事ID,存入数据库)
return {
"story": story_data,
"image": image_data,
"audio": audio_data,
"keywords": keywords,
"created_time": time.strftime("%Y-%m-%d %H:%M:%S")
}
五、模块实现总结
三大核心模块的实现,关键在于“精准选型+细节容错+联动适配”:
- 故事生成模块:通过结构化提示词与异常兜底,确保故事质量与稳定性;
- 插图匹配模块:自动提取故事核心元素优化提示词,解决“图文脱节”问题;
- 语音合成模块:处理Token过期与文本长度限制,适配不同篇幅故事。

浙公网安备 33010602011771号