政安晨的个人主页:政安晨
欢迎 👍点赞✍评论⭐收藏
希望政安晨的博客能够对您有所裨益,如有不足之处,欢迎在评论区提出指正!
本篇我们以Coze平台上这个同宇宙的“主题播客工厂”的应用为例,详细讲解搭建AI工作流。
目录
应用地址:
扣子扣子是新一代 AI 大模型智能体开发平台。整合了插件、长短期记忆、工作流、卡片等丰富能力,扣子能帮你低门槛、快速搭建个性化或具备商业价值的智能体,并发布到豆包、飞书等各个平台。https://www.coze.cn/s/YJwyDONPNE4/没有扣子账号的小伙伴可以免费注册一个,很好玩的。
体验一下
我们给一个主题:
以最近的新闻生成一段播客
点击开始生成:
等待半分钟,喝口水......
生成完成,可以下载海报和声音。
(点击海报可以放大,点击声音可以播放,点击下载可以下载音频,生成之后可以继续生成)。
点击看大图:
关于一个播客所需要的一切全部准备好(音频2-4分钟,刚合适)。
接下来,跟我一起走一遍这个AI应用的搭建流程,需要的小伙伴收藏。
立即开始
进入扣子,在个人空间里点击创建应用:
建立您自己的AI应用。
由于我们这个系列重点讲述AI工作流,其它部分不会重点讲,感兴趣的小伙伴自己学习哈。
在主题播客工厂的用户界面旁边,有一个业务逻辑,打开它:
您这里应该是空的,我接下来会详细讲解我的这个工作流(本篇带着大家走一遍,先不着急讲述工作流的搭建思想与设计思路,大家先熟悉,今后慢慢来):
触发器
这个工作流的开始,是一个带有输入变量的触发器。
Coze 平台中的触发器功能允许 Bot 在特定时间或事件下自动执行任务。具体如下:
- 触发器类型
- 定时触发器:允许设置 Bot 在指定时间执行任务,无需编写代码。触发时间可以是每日、每周、每月固定时间触发,也可以选择间隔特定天数的固定时间触发。
- 事件触发器:生成一个 Webhook URL,当向该 URL 发送 HTTPS 请求时,触发任务执行。
- 任务执行方式
- Bot 提示词:通过自然语言设置提示词,触发时提示词自动发送给 Bot,Bot 根据提示词向用户发送提醒消息。
- 调用插件:为触发器添加一个插件,触发时 Bot 会调用该插件获取返回结果并发送给用户。
- 调用工作流:为触发器添加一个工作流,若工作流有输入参数则需传入参数值,触发时 Bot 会调用该工作流获取返回结果并发送给用户。
- 使用限制
- 一个 Bot 内的触发器最多可添加 10 个。
- 触发器仅当 Bot 发布到飞书时生效。
此外,Coze 支持用户在与 Bot 聊天时设置定时任务,当用户在会话内点击推荐任务后,Bot 将会确认并创建定时任务。
我们这里用到的就是一个调用工作流的触发器。
我用了一个输入的UI控件结合按钮事件将上述工作流的入参给传到工作流里,这就是工作流的开始。
生成待检索序列
这一步就是添加了一个大模型节点,进行了系统及用户提示词的配置,把用户输入的内容转换成适合后续工作流处理的内容。
这里面要注意输出配置:
由于后续工作流基于文字进行,所以输出的数据格式的选择很重要,要匹配。
分解检索与必应搜索
现在,我进入到第一个关键节点。
分解的目的是为了给Bing插件进行搜索:
这个插件会输出合适的数据结构,而用好这个数据结构则是能否成功开发出这套工作流的关键之一,为了用户这些数据,我将使用Python进行编程处理。
编程处理检索
async def main(args: Args) -> Output:
params = args.params
outputList = params['outputList']
markdown_str = ""
message_count = 0
for item in outputList:
data = item.get('data', {})
doc_results = data.get('webPages', [])
doc_results_2 = doc_results.get('value', [])
for doc in doc_results_2:
sitename = doc.get('sitename', '')
summary = doc.get('snippet', '')
title = doc.get('name', '')
url = doc.get('url', '')
# 拼接 markdown 格式
markdown_str += f"##### {title}\n\n"
markdown_str += f"**网站**: {sitename}\n\n"
markdown_str += f"**摘要**: {summary}\n\n"
markdown_str += f"[点击这里查看详情]({url})\n\n\n"
message_count = message_count + 1
ret: Output = {
"markdown_str": markdown_str,
"message_count": message_count,
}
return ret
上面这段代码实现了一个异步函数 main
,用于将结构化数据转换为 Markdown 格式的字符串,并统计处理的消息数量。以下是逐层解析:
1. 函数定义与输入
async def main(args: Args) -> Output:
- 异步函数 :使用
async def
定义协程函数,表明该函数可以与其他异步任务并发执行- 输入参数 :
args
是包含输入数据的对象,类型注解Args
表示其结构需符合特定规范。- 返回类型 :
Output
类型的字典,包含生成的 Markdown 字符串和消息计数。2. 数据提取与初始化
params = args.params
outputList = params['outputList']
markdown_str = ""
message_count = 0
- 参数解析 :从
args
中提取params
,并进一步获取outputList
(待处理的数据列表)。- 初始化变量 :
markdown_str
用于累积生成的 Markdown 内容,message_count
统计处理的消息数量。3. 数据遍历与处理
for item in outputList:
data = item.get('data', {})
doc_results = data.get('webPages', [])
doc_results_2 = doc_results.get('value', [])
- 嵌套数据结构 :通过多级
get
方法安全访问嵌套字典(如item['data']['webPages']['value']
),避免KeyError
。- 默认值处理 :若键不存在,返回空字典
{}
或空列表[]
,确保后续操作不报错。4. 生成 Markdown 内容
for doc in doc_results_2:
sitename = doc.get('sitename', '')
summary = doc.get('snippet', '')
title = doc.get('name', '')
url = doc.get('url', '')
markdown_str += f"##### {title}\n\n"
markdown_str += f"**网站**: {sitename}\n\n"
markdown_str += f"**摘要**: {summary}\n\n"
markdown_str += f"[点击这里查看详情]({url})\n\n\n"
message_count += 1
- 字段提取 :从每个文档中提取标题、网站名、摘要和链接,使用
get
避免缺失字段导致的错误。- Markdown 拼接 :按固定格式将字段组合为 Markdown 字符串,包含标题(
#####
)、加粗字段(**
)和超链接。- 计数更新 :每处理一个文档,
message_count
增加 1。5. 返回结果
ret: Output = {
"markdown_str": markdown_str,
"message_count": message_count,
}
return ret
- 结果封装 :将生成的 Markdown 字符串和消息数量封装为字典,符合
Output
类型的结构要求。
关键点总结
- 异步函数的同步执行 :尽管函数用
async def
定义,但内部无await
调用,实际执行时为同步操作。这可能用于兼容异步框架的接口规范。 - 数据安全性 :通过
dict.get()
处理嵌套数据,避免因数据缺失导致崩溃。 - 性能优化空间 :频繁使用
+=
拼接字符串在大数据量时可能影响性能,可改用列表暂存片段后join
。
后续,我也不用这样解释代码,大家通过AI工具自己脑补,我们不断搭建工作流的目的在于训练我们的思想和方案设计能力,未来的具体执行全部交给AI,这个时代给有思想又勤劳的我们!
获得播客内容源
经过编程及相关配置,我们可以得到播客的封面与男女双播的台词脚本:
提取分类声音文案
为了将男女双播的声音分别生成并混合,我们需要先分类提取男声文案与女声文案(依旧是编程处理):
async def main(args: Args) -> Output:
params = args.params
lines = params['input'].strip().split('\n')
# 初始化两个数组
male_lines = []
female_lines = []
# 遍历每一行,根据前缀将对白添加到对应的数组中
for line in lines:
if line.startswith('男:'):
male_lines.append(line[2:])
elif line.startswith('女:'):
female_lines.append(line[2:])
ret: Output = {
"key0": male_lines,
"key1": female_lines
}
return ret
声音合成
接下来就简单了,分别合成男声与女声,然后再混合:
我分别列出各节点配置,大家放大查看:
其中,人声合成的代码如下:
async function main({ params }: Args): Promise<Output> {
const mergedUrls = [];
const maxLength = Math.max(params.audio.length, params.audio_1.length);
for (let i = 0; i < maxLength; i++) {
if (i < params.audio.length) {
mergedUrls.push(params.audio[i].data.url);
}
if (i < params.audio_1.length) {
mergedUrls.push(params.audio_1[i].data.url);
}
}
const ret = {
"key0": mergedUrls,
};
return ret;
}
生成封面
声音完成之后,生成封面就是小Case了。
完成
最后,把这两部分数据同时接入“结束”节点,就完成了这样一个工作流。
结束节点中的数据输出,会在UI中有对应的数据展示:
OK,至此完成,是不是很简单,大家又可以愉快地玩耍啦。