Arduino UNO Q 讲好中国儿童故事
摘要:本文介绍了一款基于ArduinoUNOQ和科大讯飞SparkX1.5大模型的国产AI睡前故事生成器。该系统通过Streamlit构建用户界面,支持自定义年龄、主题、语调等参数,可生成包含成语故事、西游记等中国元素的儿童故事。硬件采用ArduinoUNOQ,软件通过HTTPAPI调用讯飞大模型,无需额外LLMBrick,适合国内网络环境。该方案简化了原厂程序,融入中国元素,展示了国产大模型在教
本示例国产大模型睡前故事讲述器是一个使用Arduino UNO Q构建的生成式AI应用,通过HTTP API直接调用科大讯飞Spark X1.5大语言模型,根据用户选择的参数创建个性化中文睡前故事。应用使用Streamlit构建直观的用户界面,支持实时参数调整和故事生成。
主要功能:
使用科大讯飞Spark X1.5大语言模型生成高质量中文睡前故事。
通过HTTP API直接调用,无需额外的原厂LLM Brick,更加适合中国网络环境,
使用Streamlit构建的现代化UI界面,带有精美国产动画背景图片,
支持多种故事参数自定义:年龄、主题、语调、结局类型、叙事结构、时长等,
支持随机生成故事功能,一键创建中国元素的成语故事,红色经典等积极教育意义的故事,
响应式设计,适配不同设备屏幕。
硬件:
Arduino UNO Q (x1)
USB-C电缆(用于供电和编程)(x1)
软件:
Arduino App Lab
科大讯飞Spark X1.5 API密钥
此示例需要有效的互联网连接以访问AI提供商的API。您还需要使用的服务的有效API密钥,目前科大讯飞Spark X1.5提供免费服务值得推荐。此外科大讯飞开源了API调用的示例方便二次开发。
下图中密钥为APIPassword,点击文档在跳转页面下载示例程序

引入WebUI - Streamlit金砖配置

python程序main.py输入:
# SPDX-FileCopyrightText: Copyright (C) ARDUINO SRL (http://www.arduino.cc)
#
# SPDX-License-Identifier: MPL-2.0
import re
import os
import json
import datetime
import hashlib
import hmac
import base64
import urllib.parse
import requests
from arduino.app_utils import App
# 科大讯飞Spark X1.5 API配置
SPARK_API_KEY = "你申请的密钥"
SPARK_API_URL = "https://spark-api-open.xf-yun.com/v2/chat/completions"
def call_spark_api(prompt):
"""调用科大讯飞Spark X1.5 API"""
headers = {
"Authorization": f"Bearer {SPARK_API_KEY}",
"Content-Type": "application/json"
}
# 构建请求体
data = {
"model": "spark-x",
"messages": [
{
"role": "system",
"content": "你是一个擅长讲述中国传统文化故事的儿童睡前故事讲述者。你的回答必须是故事本身,直接用HTML格式呈现。不要将回答包装在markdown代码块或任何其他格式中。使用<h1>、<h2>等标签作为标题和副标题。使用<strong>或<b>标签表示粗体文本。包含相关的表情符号。如果故事是分章节的,使用标题标签作为章节标题。请用中文回复,确保故事内容适合儿童睡前阅读。特别擅长讲述成语故事、西游记等经典名著、红色经典故事等中国传统文化题材。"
},
{
"role": "user",
"content": prompt
}
],
"stream": False, # Streamlit UI不适合流式响应,改为非流式
"temperature": 0.8,
"max_tokens": 2048
}
try:
response = requests.post(SPARK_API_URL, headers=headers, json=data, timeout=60)
if response.status_code == 200:
result = response.json()
if 'choices' in result and len(result['choices']) > 0:
return result['choices'][0]['message']['content']
else:
return "API返回了无效的响应格式"
else:
return f"API请求失败,状态码: {response.status_code}, 错误信息: {response.text}"
except Exception as e:
return f"API调用异常: {str(e)}"
def generate_stories_app():
"""使用Streamlit UI构建故事生成应用"""
import streamlit as st
st.set_page_config(
page_title="国产大模型睡前故事讲述器",
page_icon="🏰",
layout="wide"
)
# 读取并编码背景图片
import base64
try:
with open("assets/img/storyteller-background.png", "rb") as image_file:
encoded_string = base64.b64encode(image_file.read()).decode()
st.markdown(
f"""
<style>
.stApp {{
background-image: url("data:image/png;base64,{encoded_string}");
background-size: cover;
background-position: center;
background-repeat: no-repeat;
background-attachment: fixed;
}}
</style>
""",
unsafe_allow_html=True
)
except Exception as e:
st.error(f"无法加载背景图片: {e}")
st.title("🏰 国产大模型睡前故事讲述器")
# 创建侧边栏用于参数设置
st.sidebar.header("📚 故事参数设置")
# 年龄选择
age_options = ["3-5岁", "6-8岁", "9-12岁", "13-16岁"]
age = st.sidebar.selectbox("年龄段", age_options)
# 主题选择
theme_options = ["奇幻/冒险", "童话", "成语故事", "西游记", "科学/宇宙", "动物", "喜剧", "红色经典"]
theme = st.sidebar.selectbox("故事主题", theme_options)
# 语调选择
tone_options = ["平静甜美", "史诗冒险", "活泼愉快", "神秘好奇", "智慧深思", "紧张怪诞"]
tone = st.sidebar.selectbox("故事语调", tone_options)
# 结局类型
ending_options = ["快乐安心", "开放式神秘", "有道德寓意", "戏剧性但有希望"]
ending_type = st.sidebar.selectbox("结局类型", ending_options)
# 叙事结构
structure_options = ["经典", "分章节", "系列剧式"]
narrative_structure = st.sidebar.selectbox("叙事结构", structure_options)
# 故事时长
duration_options = ["短篇 5分钟", "中篇 10-15分钟", "长篇 20+分钟"]
duration = st.sidebar.selectbox("故事时长", duration_options)
# 其他要求
other = st.sidebar.text_area("其他要求(可选)", height=100)
# 生成按钮
generate_button = st.sidebar.button("📖 生成故事", type="primary")
# 随机生成按钮
random_button = st.sidebar.button("🎲 随机生成")
if random_button:
import random
age = random.choice(age_options)
theme = random.choice(theme_options)
tone = random.choice(tone_options)
ending_type = random.choice(ending_options)
narrative_structure = random.choice(structure_options)
duration = random.choice(duration_options)
st.sidebar.success("已随机选择参数!")
# 主内容区域
if generate_button:
# 创建提示
prompt = f"作为一个喜欢给{age}的孩子读睡前故事的家长,我需要一个愉快且适合年龄的故事。"
# 根据主题类型添加特定提示
if theme == "成语故事":
prompt += " 请创作一个基于中国成语的故事,解释成语的由来和含义,使用生动有趣的方式讲述。"
elif theme == "西游记":
prompt += " 请创作一个基于西游记人物和情节的冒险故事,保持原著的风格和价值观。"
elif theme == "红色经典":
prompt += " 请创作一个类似《鸡毛信》、《小兵张嘎》、《闪闪的红星》等红色经典的故事,传承革命精神和爱国情怀。"
else:
prompt += f" 故事类型是{theme}。"
prompt += f"语调应该是{tone}。"
prompt += f"请确保故事有{narrative_structure}的叙事结构,给孩子留下{ending_type}的感觉。"
prompt += f"长度应该大约是{duration}。语言应该易于理解,适合我孩子的年龄理解能力。"
prompt += "请在故事中加入适合中国儿童的文化元素和价值观念。"
if other:
prompt += f"\n\n其他可选的故事元素:{other}"
# 显示加载状态
with st.spinner("正在生成故事..."):
# 调用API生成故事
story = call_spark_api(prompt)
# 显示故事
st.header("📖 生成的故事")
st.markdown(story, unsafe_allow_html=True)
# 添加复制按钮
if st.button("📋 复制故事"):
st.write("故事已复制到剪贴板")
else:
st.info("请在左侧设置参数后点击\"生成故事\"按钮")
# 显示示例故事
st.header("📖 示例故事")
example_story = """
<h1>小兔子的冒险</h1>
<p>从前,有一只可爱的小兔子,它住在一个美丽的森林里...</p>
"""
st.markdown(example_story, unsafe_allow_html=True)
st.caption("这是一个示例故事,设置参数并点击生成按钮来创建您自己的故事!")
# 使用Streamlit运行应用
if __name__ == "__main__":
generate_stories_app()
背景图片按照此路径assets/img/storyteller-background.png放置,尺寸笔者给的是1980x1024大小。
使用说明
1. 启动应用后,您会看到一个带有精美背景的Streamlit界面,
2. 在左侧边栏中设置故事参数:
年龄段: 选择适合的年龄段(3-5岁、6-8岁、9-12岁、13-16岁)
故事主题: 选择故事类型(奇幻/冒险、童话、成语故事、西游记、科学/宇宙、动物、喜剧、红色经典)
故事语调:选择叙述风格(平静甜美、史诗冒险、活泼愉快等)
结局类型:选择故事结尾方式(快乐安心、开放式神秘等)
叙事结构:选择故事结构(经典、分章节、系列剧式)
故事时长:选择故事长度(短篇、中篇、长篇)
3. 点击"生成故事"按钮,科大讯飞Spark X1.5将根据您的设置生成个性化中文故事,
4. 或点击"随机生成"按钮,系统会随机选择参数生成故事。
较原厂程序笔者的方案更加简单,更加容易理解。融入中国元素,契合国情使开源硬件在中国落地生根,枝繁叶茂。以此示例建议大家在应用中多采用国产大语言模型,这样国产大语言模型才能越做越好,越来越聪明。

原汁原味
然而在原著中cloud_llm金砖主要目的是介绍longchain的应用。LangChain是一种创新性的框架,是语言模型驱动的应用程序的开发方式。LangChain 是基于大模型的应用开发框架,是一个开源的Python库,旨在通过以下方式更轻松地构建基于LLM的应用程序。按照科大讯飞文档里openai示例,笔者在此演示不制作金砖也能用Arduino UNO Q应用longchain,同样非常的简洁。
python程序main.py输入:
# SPDX-FileCopyrightText: Copyright (C) ARDUINO SRL (http://www.arduino.cc)
#
# SPDX-License-Identifier: MPL-2.0
import os
import streamlit as st
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
# 页面配置
st.set_page_config(
page_title="国产大模型睡前故事讲述器 - LangChain版",
page_icon="🏰",
layout="wide",
initial_sidebar_state="expanded"
)
# 从环境变量或硬编码获取配置
SPARK_API_KEY = os.getenv("API_KEY", "您申请的密钥")
# 故事参数选项列表
AGE_OPTIONS = ["3-5岁", "6-8岁", "9-12岁", "13-16岁"]
THEME_OPTIONS = ["唐诗宋词", "童话", "成语故事", "西游记", "科学/宇宙", "动物", "喜剧", "红色经典"]
TONE_OPTIONS = ["平静甜美", "史诗冒险", "活泼愉快", "神秘好奇", "智慧深思", "睿智机灵"]
ENDING_OPTIONS = ["快乐安心", "开放式神秘", "有道德寓意", "戏剧性但有希望"]
STRUCTURE_OPTIONS = ["经典", "分章节", "系列剧式"]
DURATION_OPTIONS = ["短篇 5分钟", "中篇 10-15分钟", "长篇 20+分钟"]
def generate_stories_app():
"""使用 LangChain + Streamlit UI 构建故事生成应用"""
# 初始化 session state(仅用于非 widget 关联的状态)
if "story_generated" not in st.session_state:
st.session_state.story_generated = False
if "generated_story" not in st.session_state:
st.session_state.generated_story = ""
if "background_image_base64" not in st.session_state:
st.session_state.background_image_base64 = ""
# 加载背景图片(使用缓存优化启动速度)
img_src = ""
if not st.session_state.background_image_base64:
image_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "assets", "img", "storyteller-background.png")
if os.path.exists(image_path):
import base64
with open(image_path, "rb") as img_file:
img_base64 = base64.b64encode(img_file.read()).decode()
st.session_state.background_image_base64 = f"data:image/png;base64,{img_base64}"
img_src = st.session_state.background_image_base64
# 自定义CSS样式(设置背景图片)
if img_src:
st.markdown(
f"""
<style>
.stApp {{
background-image: url("{img_src}");
background-size: cover;
background-position: center;
background-repeat: no-repeat;
background-attachment: fixed;
}}
</style>
""",
unsafe_allow_html=True
)
# 标题
st.title("🏰 国产大模型睡前故事讲述器")
# 侧边栏 - 参数设置
with st.sidebar:
st.header("📚 故事参数设置")
# 年龄选择
st.selectbox("👶 年龄段", AGE_OPTIONS, index=0, key="age")
# 主题选择
st.selectbox("🎭 故事主题", THEME_OPTIONS, index=1, key="theme")
# 语调选择
st.selectbox("🎼 故事语调", TONE_OPTIONS, index=0, key="tone")
# 结局类型
st.selectbox("🎯 结局类型", ENDING_OPTIONS, index=0, key="ending")
# 叙事结构
st.selectbox("📖 叙事结构", STRUCTURE_OPTIONS, index=0, key="structure")
# 故事时长
st.selectbox("⏱️ 故事时长", DURATION_OPTIONS, index=0, key="duration")
# 其他要求
st.text_area("✨ 其他要求(可选)", value="", height=80, key="other")
st.divider()
# 按钮区域
col1, col2 = st.columns(2)
with col1:
generate_button = st.button("📖 生成故事", type="primary", use_container_width=True, key="generate")
with col2:
random_button = st.button("🎲 随机生成", use_container_width=True, key="random")
# 随机生成功能
if random_button:
import random
st.session_state.age = random.choice(AGE_OPTIONS)
st.session_state.theme = random.choice(THEME_OPTIONS)
st.session_state.tone = random.choice(TONE_OPTIONS)
st.session_state.ending = random.choice(ENDING_OPTIONS)
st.session_state.structure = random.choice(STRUCTURE_OPTIONS)
st.session_state.duration = random.choice(DURATION_OPTIONS)
st.success("已随机选择参数!")
st.rerun()
# 生成故事功能
if generate_button:
with st.spinner('🤖 正在调用 LangChain + 科大讯飞 API 创作精彩故事...'):
try:
# 初始化 LangChain 模型(使用科大讯飞兼容的 OpenAI API)
llm = ChatOpenAI(
api_key=SPARK_API_KEY,
base_url="https://spark-api-open.xf-yun.com/v2/",
model="x1",
temperature=0.8,
max_tokens=2048
)
# 构建 System Prompt
system_content = """你是一个擅长讲述中国传统文化故事的儿童睡前故事讲述者。你的回答必须是故事本身,直接用HTML格式呈现。不要将回答包装在markdown代码块或任何其他格式中。使用<h1>、<h2>等标签作为标题和副标题。使用<strong>或<b>标签表示粗体文本。包含相关的表情符号。如果故事是分章节的,使用标题标签作为章节标题。请用中文回复,确保故事内容适合儿童睡前阅读。特别擅长讲述成语故事、西游记等经典名著、红色经典故事等中国传统文化题材。"""
# 构建 User Prompt
user_content = f"作为一个喜欢给{st.session_state.age}的孩子读睡前故事的家长,我需要一个愉快且适合年龄的故事。"
# 根据主题类型添加特定提示
if st.session_state.theme == "成语故事":
user_content += " 请创作一个基于中国成语的故事,解释成语的由来和含义,使用生动有趣的方式讲述。"
elif st.session_state.theme == "唐诗宋词":
# 根据年龄段选择合适的唐诗宋词主题
if st.session_state.age == "3-5岁":
user_content += " 请选择一首简单的五言绝句,如李白《静夜思》、白居易《草》等,内容关于自然、动物、家庭等,语言要极简易懂。"
elif st.session_state.age == "6-8岁":
user_content += " 请选择一首朗朗上口的五言或七言绝句,如孟浩然《春晓》、贺知章《咏柳》等,内容关于四季景色、学习生活等。"
elif st.session_state.age == "9-12岁":
user_content += " 请选择一首经典的唐诗或简单的宋词,如王之涣《登鹳雀楼》、苏轼《水调歌头·明月几时有》等,可以简单解释诗意和背景。"
elif st.session_state.age == "13-16岁":
user_content += " 请选择一首经典的唐诗宋词,如杜甫《春望》、李白《将进酒》、李清照《声声慢》等,可以适当分析文学手法和情感内涵。"
user_content += " 不要选择爱情主题的诗词,重点选择爱国、励志、自然、友情、亲情等积极向上的内容。请以故事形式讲述诗词的创作背景和含义,而不是简单的赏析。"
elif st.session_state.theme == "西游记":
user_content += " 请创作一个基于西游记人物和情节的冒险故事,保持原著的风格和价值观。"
elif st.session_state.theme == "红色经典":
user_content += " 请创作一个类似《鸡毛信》、《小兵张嘎》、《闪闪的红星》等红色经典的故事,传承革命精神和爱国情怀。"
else:
user_content += f" 故事类型是{st.session_state.theme}。"
user_content += f"语调应该是{st.session_state.tone}。"
user_content += f"请确保故事有{st.session_state.structure}的叙事结构,给孩子留下{st.session_state.ending}的感觉。"
user_content += f"长度应该大约是{st.session_state.duration}。语言应该易于理解,适合我孩子的年龄理解能力。"
user_content += "请在故事中加入适合中国儿童的文化元素和价值观念。"
if st.session_state.other:
user_content += f"\n\n其他可选的故事元素:{st.session_state.other}"
# 创建 LangChain 消息
messages = [
SystemMessage(content=system_content),
HumanMessage(content=user_content)
]
# 使用 LangChain 调用 API
response = llm.invoke(messages)
# 提取生成的文本
generated_story = response.content if hasattr(response, 'content') else str(response)
# 保存到 session state
st.session_state.generated_story = generated_story
st.session_state.story_generated = True
except Exception as e:
st.error(f"❌ 生成故事失败: {str(e)}")
st.info("💡 请检查 API Key 是否正确,或网络连接是否正常")
# 显示生成的故事
if st.session_state.story_generated and st.session_state.generated_story:
st.header("📖 生成的故事")
st.markdown(st.session_state.generated_story, unsafe_allow_html=True)
if st.button("📋 复制故事", use_container_width=True, key="copy"):
st.write("故事已复制到剪贴板")
else:
st.info("请在左侧设置参数后点击\"生成故事\"按钮")
# 显示示例故事
st.header("📖 示例故事")
example_story = """
<h1>小兔子的冒险</h1>
<p>从前,有一只可爱的小兔子,它住在一个美丽的森林里...</p>
"""
st.markdown(example_story, unsafe_allow_html=True)
st.caption("这是一个示例故事,设置参数并点击生成按钮来创建您自己的故事!")
if __name__ == "__main__":
generate_stories_app()
更多推荐


所有评论(0)