项目背景
在学习提示技术之前,就有和身边的朋友聊过 AI 应用技术相关的事情,也是想从他们的想法中找到一些灵感。
在一次群聊教育相关的话题时,我问了一个问题“作为老师的你们,是否有应用 AI 来为自己提效呢?”在大家的讨论当中,我认为生成教学设计这个蛮不错的,是 LLM 能解决的,也是擅长的。作为老师的他们,大多数在吐槽 AI 生成的差强人意,还不如自己写来得高效。
为此,更加确定要做这样一个 AI 生成教学设计的工具来解决他们的问题。
决定了要做一个 AI 教学设计工具,那时并没有实现的思路,直到学习到方向性刺激提示技术,才有了实现的想法,而且还挺强烈的,有一种马上要编码的冲动,再结合最近学习到的 Python 前后端分离架构知识,立即就有了实现技术架构的选择——Vue + Flask。还有,也想试一试把产品需求描述清楚,使用的技术描述清楚,用 AI Conding 工具,看它能否从 0 到 1 构建一个项目。真是想法一下子就出来的感觉。
当然,最后是理性战胜了,那时是要专注于提示技术学习的。
项目目标
- 核心目标:提示技术的学以致用(方向性刺激提示);
- 做一个有实际作用的 AI 教学设计工具(扣子应用);
- 动手锻炼做 AI 应用项目的实战能力。
项目背景和项目目标,和上一篇是一样的。
本文主要是分享基于已实现的扣子智能体来实现扣子应用(桌面端),这也是上一篇最后【展望与设想】的第一点。还有,重点优化了工作流。
项目过程
工作流优化
新旧工作流
- 原来的工作流
该工作流,将不同结构的教学设计大纲分开来设计,通过选择器来判断,而且涉及的场景也不具备广泛性。
- 现在新的工作流
新工作流关键优化点
(一)开始节点入参修改为单个变量
原是使用对象的,也搞不懂为何用了对象作为入参。之所以要修改,是因为用户界面调用工作流的时候,不方便或者说目前应该是不支持通过用户界面输入信息作为对象参数调用工作流。一句话,就是为了用户界面调用工作流,能将页面输入信息作为工作流的入参。
(二)通过代码实现教学设计大纲的通用性
之所以要通过编程来实现教学设计大纲,是因为要是通过选择器来逐个判断,会使工作流复杂,而且这个地方主要是涉及逻辑处理,因此写代码并不难,关键在于能将逻辑梳理清楚。
为啥说,用选择器来处理会使流程复杂呢?
根据做老师的同学分享,以及从网上找到的教学设计分析出来的一些不确定性的章节有:教学内容分析、作者简介、学情分析,这三个章节,每个章节都有两种情况,它们排列组合起来就有十几种可能的情况了,你说这个图画起来是不是就会像蜘蛛网一样呢?
用代码处理的好处就是,很容易地就可以实现根据用户是否输入来判断是否需要这一章节,从而构建出相对应的结构来。
代码_教学大纲节点:
具体代码实现如下:
# 在这里,您可以通过 'args' 获取节点中的输入变量,并通过 'ret' 输出结果
# 'args' 已经被正确地注入到环境中
# 下面是一个示例,首先获取节点的全部输入参数params,其次获取其中参数名为'input'的值:
# params = args.params;
# input = params['input'];
# 下面是一个示例,输出一个包含多种数据类型的 'ret' 对象:
# ret: Output = { "name": '小明', "hobbies": ["看书", "旅游"] };
from typing import Optional
# 中文数字映射表(支持最多到十)
CHINESE_NUMBERS = [
'零', '一', '二', '三', '四', '五',
'六', '七', '八', '九', '十'
]
def number_to_chinese(index: int) -> str:
"""
将整数转换为中文数字(支持 1 ~ 10)
Args:
index (int): 序号(从 1 开始)
Returns:
str: 中文数字表示
"""
if 1 <= index <= len(CHINESE_NUMBERS) - 1:
return CHINESE_NUMBERS[index]
else:
# 超出预设范围则返回阿拉伯数字(可选)
return str(index)
def generate_teaching_outline(topic: Optional[str] = None,
content_analysis: Optional[str] = None,
author_introduction: Optional[str] = None,
student_analysis: Optional[str] = None,
class_type: Optional[str] = "1",
class_num: Optional[int] = 2,
teaching_version: Optional[str] = "部编版") -> str:
"""
根据传入的参数动态生成教学设计大纲。
参数:
topic (str or None): 课题
content_analysis (str or None): 教学内容分析内容
author_introduction (str or None): 作者简介内容
student_analysis (str or None): 学情分析内容
class_type (str or None): 课时类型,默认为"1",课时类型可以是"1":按课时;"2":按时长(分钟)
class_num: (int or None): 课时数,默认为2
teaching_version (str or None): 教学版本,默认为"部编版"
返回:
list: 连续编号的教学设计大纲条目
"""
# 所有可能的章节标题
all_sections = [
("教学内容分析", content_analysis),
("作者简介", author_introduction),
("学情分析", student_analysis),
]
# 构建前三个可能存在的章节
prefix_sections = [(title + "\n\t" + content) for title, content in all_sections if content]
teaching_objectives = """教学目标
- 分点列出分析得到的教学目标,最多列到二级,比如:
(一)XXX目标
1. XXX"""
teaching_fad = """教学重点和难点
(一)教学重点
(二)教学难点"""
teaching_prepare = """教学准备"""
time_unit = "课时" if class_type == "1" else "分钟"
time_allocation = """课时安排
""" + str(class_num) + time_unit
teaching_process = "教学过程"
if class_type == "1":
teaching_process += """
- 从第一课时开始,一直到第""" + str(class_num) + """课时结束
- 课时之间注意逻辑严谨,符合""" + teaching_version + """教学过程"""
else:
teaching_process += """
- 具体的内容"""
# 固定章节(不管前面如何变化,这些章节始终存在)
fixed_sections = [
teaching_objectives,
teaching_fad,
teaching_prepare,
time_allocation,
teaching_process
]
# 合并所有章节
full_outline = [f"{number_to_chinese(i + 1)}、{title}" for i, title in enumerate(prefix_sections + fixed_sections)]
# 加上课题
full_outline.insert(0, "课题:" + topic) if topic else full_outline
return "\n".join(full_outline)
async def main(args: Args) -> Output:
params = args.params
res = generate_teaching_outline(params['topic'], params['atoc'], params['ai'], params['aot'],
params['class_type'], params['class_num'], params['teaching_version'])
# 构建输出对象
ret: Output = {
"key0": res
}
return ret
(三)格式转换:markdown转word
一般像我们的学校、政府单位等机构都是使用 office来办公的,而大语言模型的输出又是 markdown 格式的,这就存在格式转换问题了,转成 word 输出在实际使用上来说是必须的。
而要解决这个问题,大致有这些方法:
- 扣子商店提供的插件;
- 网上找别人已实现的工具代码;
- 自己开发一个工具代码。
上述方法,首选肯定是第一个,毕竟是相对节省时间的,在有项目计划的前提下,是比较稳妥的执行策略。
尝试了好几个扣子商店里提供的插件,多多少少都存在一些格式转换问题,有一个不错的插件(DocTool),可惜在教学设计生成这个场景下存在一个比较要紧的问题——它会将markdown 格式中含有书名号的内容及书名号给弄丢失了。挺想让那位作者修复一下的,可惜没有渠道联系。
方法二:网上找别人一实现的工具代码,尝试了一下,没找到合适的,考虑到时间问题,就没继续折腾了,毕竟目前的这个插件是可以使用的,虽然格式存在一些瑕疵,但是不影响内容的完整性。
至于方法三,作为一名技术出身的人,是手痒的,但还是考虑到时间问题,控制住了。
不过,在测试使用扣子上的插件过程中,发现了一个挺有意思的技术实现,那就是怎么把转成 word 的教学设计提供给用户呢,提供下载链接?一般都如此。分析插件调用返回的 URL,能判断出是使用了阿里云的 OSS。
大致实现思路应是:根据调用传来的 markdown格式内容,通过编程来实现转 word,并生成 word文档,然后调用 OSS 提供的 API上传 word文件,将上传成功返回的信息,再封装或者直接返回给插件调用者。
扣子应用开发
应用开发关键点
(一)正确使用模板能提效
这次开发扣子应用,有意地浏览了一下扣子提供的模板,找到了一个挺符合的,不管是设计风格,还是交互方式,都喜欢且符合。


应用体验地址:扣子
(二)工作流调用放在表单事件中
工作流调用放在表单事件中,应该也是扣子平台建议的,因为在表单的事件中,事件类型就有 OnSubmit。
(三)保留 markdown格式输出展示
要保留 markdown格式输出展示,就不要使用展示组件的 Markdown,它是帮助我们把 markdown 格式的内容展示成文本的组件,而且转格式中也存在一些问题,比如 ``` ```,这种语法它就不能正确处理。
保留 markdown 格式输出展示,用文本组件即可。
(四)避免提交重复提交
以前我们习惯使用加载中或遮罩页面等方式来避免提交重复提交操作,而一开始确实就是想到这些方式,可是我发现这些成熟且简单的实现方式在扣子上实现起来,却一点都不简单,甚至还找不到实现的方式。
好在使用的模板有实现了这个方式。表单提交并调用工作流,扣子封装了工作流调用返回的参数,其中 loading 返回可以用来控制提交按钮,可以避免用户出现重复点击提交。见下图:
一旦按钮触发了提交并调用了流程,loading 就为 true,就致使提交按钮处于禁用状态。
问题与解决
整个开发过程,好像只有在表单提交调用工作流传参这个问题上受到了阻碍,其他的问题就是细节与调试了。
在前面【新工作流关键优化点】,也谈到把开始节点入参修改为单个变量,之所以要这么做,是因为在配置事件,Workflow 选好要调用的工作流时,并不会提示工作流入参要怎么传入,而当把工作流的入参类型从对象修改为单个变量时,重复上述步骤,当选择了调用工作流之后,就会自动展示出要传的参数了。
总结
上述【应用开发关键点】,也是本次使用扣子开发应用的收获点。
最后说一点:扣子应用开发,适合需web端原型验证的简单交互场景,在选择之前,建议认真了解当前扣子应用提供的组件是否满足需求。
好了,到此。
为了大家方便阅读与学习,这里放一下上一篇的文章地址:
AI教学设计助手:生成好教案的Prompt技术实战(一)-CSDN博客
本文涉及的已发布的扣子应用体验地址:扣子