不知道大家是否有用过秘塔的工作流,可以根据大纲生成不同类型的分析报告,如下图所示。本篇文章就教大家如何用Dify开发一个工作流,去复刻这个功能。
一、工作流拆解
这个工作流的流程非常清晰,用户选择报告类型和报告主题 -> 工作流根据类型拿到该报告的大纲 -> 迭代生成每一段的内容 -> 调用http,输出成pdf。如果不怕token消耗的话可以进行附加的操作:检查段落连续性、检测有图表数据的地方生成图表。 大概的工作流程如下:
二、结点讲解
1、chatflow or workflow
两者的区别是:chatflow有一个query,也就是用户输入的变量,主要用来进行多轮对话,workflow没有memory相关功能,偏向于自动化和批处理情景。
在这个案例中,整体大的工作流用的是chatflow,可以多次对话,其中小的联网搜索功能,是封装了一个workflow,发布成自定义工具进行调用。
2、知识库
我这里用知识库存储大纲,不同类型的大纲用固定的分隔符分割,然后在创建知识库的时候,用自己选择分隔符去创建,这样就能把知识库当做一个数据库去存储一些东西,最后的效果就是每一个类型的报告的大纲会被知识库分成一个分段(如下图所示)。
当然也可以把大纲存储在会话变量或者让llm去直接生成,都是可以的。只是我这里会选择直接用秘塔的大纲去生成报告。
js
//提取知识库内存储的大纲
function main({arg1}) {
const temp=arg1[0].content.split(':\n')
const temp2=temp[1].split('\n')
return {
result: temp2
}
}
3、报告内容迭代生成
我们要把大纲提取出来,转换成一个数据,供迭代节点使用。迭代节点需要输入一个数组,输出结果也是一个数组,可以选择并行数量,但是这样可能会影响输出顺序,特别是迭代节点中包含http的情况下。
4、根据大纲和报告主题联网搜索
下面是我的提示词,用的是moonshotAPI,有联网的工具(具体的http请求怎么写可以看官网或者我之前的文章)。这个api是可以输出来源的,要用提示词进行约束。(但也不是很稳定,问过kimi客服,可能是一些内容合规的问题)
markdown
##角色:你是一个专业的文章创作者,尤其擅长分工合作,能够根据大纲的要求创作其中一个部分的内容,同时保证兼顾整个大纲,做到和其他部分能够融合自然,没有冲突,注意协调性和一致性。
##任务:首先这是一个【报告名称】,主题是:【主题】,参考完整的大纲内容:【完整大纲】,然后针对其中的【部分标题】进行创作,完成这一标题下的内容,并保证内容的专业性和严谨性。
给出具体的数据来源,数据来源用[1]、[2]类似的输出,格式是markdown链接,数据来源重复的话,输出一次来源链接即可,不要给重复的链接。
图文结合,适当输出Markdown表格,例如:在公司简介处可以输出一个公司关键里程碑的表格。
图片能获取连接的话,用Markdown链接展示。
最后返回一个markdown的文本内容,不要使用<code>标签,注意标题层级。
##【完整大纲】=
##【部分标题】=
##【报告名称】=
##【主题】=
显示的效果:
三、输出图表
在领导想看报告的时候,可能都有看图表的要求,表格的话非常容易,因为md支持直接输出表格格式的,在提示词里加上适当输出md表格就可以。而图的话,有很多种方式,比如拿到数据让大模型生成svg代码,然后用代码转base64图片输出,或者生成echarts的options,再去渲染。最重要的还是如何拿到靠谱的数据。
echarts
我们知道如果直接使用dify的聊天框,那他们的聊天框是支持直接输出echarts图表的,只要符合输出 echarts
这样的格式。
js
//一个示例
```echarts
{
"title": {
"text": "2024年1-3月签约额及同比变化",
"textStyle": {
"color": "#000000",
"fontSize": 18
},
"top": "3%",
"left": "center"
},
"backgroundColor": "#FFFFFF",
"grid": {
"top": "20%",
"bottom": "10%"
},
"tooltip": {
"trigger": "axis",
"axisPointer": {
"type": "shadow"
}
},
"legend": {
"data": ["签约额", "同比"],
"top": "10%",
"textStyle": {
"color": "#000000"
}
},
"toolbox": {
"feature": {
"saveAsImage": {
"show": true,
"title": "保存为图片"
},
"magicType": {
"show": true,
"type": ["bar", "line", "pie"],
"title": {
"bar": "柱状图",
"line": "折线图",
"pie": "饼图"
}
}
}
},
"xAxis": {
"type": "category",
"data": ["2024-01", "2024-02", "2024-03"],
"axisLine": {
"lineStyle": {
"color": "#CBDDF4"
}
},
"axisLabel": {
"color": "#000000"
}
},
"yAxis": [
{
"type": "value",
"name": "签约额 (元)",
"position": "left",
"axisLine": {
"lineStyle": {
"color": "#CBDDF4"
}
},
"axisLabel": {
"color": "#000000",
"formatter": "{value}"
}
},
{
"type": "value",
"name": "同比 (%)",
"position": "right",
"axisLine": {
"lineStyle": {
"color": "#CBDDF4"
}
},
"axisLabel": {
"color": "#000000",
"formatter": "{value} %"
}
}
],
"series": [
{
"name": "签约额",
"type": "bar",
"data": [221692736, 212009193, 421231259185],
"itemStyle": {
"color": "#ee7c4b"
},
"label": {
"show": true,
"position": "top",
"formatter": "{c}"
}
},
{
"name": "同比",
"type": "line",
"yAxisIndex": 1,
"data": [21.5, 12.1, 7.87],
"itemStyle": {
"color": "#80BD95"
},
"label": {
"show": true,
"position": "top",
"formatter": "{c}%"
}
}
]
}
```
svg
生成svg代码的提示词↓
js
你是一个SVG图表生成大师,可以生成美观的SVG图表,字体不重叠,间距合理。仅输出svg代码。
任务:
Step 1:检查用户输入内容
检查用户输入是否包含可用于生成柱状图、饼图或折线图的数据。
如果有数据,提取横坐标(X轴)和纵坐标(Y轴)的数据,或者饼图数据,继续下一步。
如果没有数据,停止思考,输出:无。
Step 2:生成SVG图表
如果有数据,根据数据生成SVG图表,确保图表美观、间距合理。
柱状图要求:
每个柱子的高度适配他的数据值,确保柱子从Y轴底部(最小值)开始绘制。
柱子之间应有合理的间距,避免重叠。
每个柱子的填充颜色(fill属性)应不同,且颜色对比明显。
在每个柱子的顶部或旁边添加文本标签,显示具体数据值,确保文本不会超出图形边界或与其他元素重叠。
折线图要求:
折线应根据数据点动态绘制,确保线条平滑。
在每个数据点旁边添加文本标签,显示具体数据值。
饼图要求:
每个扇区的角度应根据数据值动态计算。
在每个扇区旁边添加文本标签,显示具体数据值和百分比。
控制SVG的大小,确保图表不会过大(例如,宽度不超过600px,高度不超过400px)。
Step 3:检查SVG代码
检查生成的SVG代码,确保:
文本的y坐标不会超出图形边界。
所有元素(如柱子、线条、文本)的位置和大小正确。
仅输出纯粹的SVG代码,不要输出其他文字。确保代码可以直接渲染。
关键点:
文本标签位置:确保文本标签位于柱子顶部或旁边,且不会超出边界;
颜色和间距:确保柱子颜色对比明显,且柱子之间有合理间距。
仅输出svg代码:不需要多余的解释和文本,只返回纯净的svg代码,不要给我Markdown的格式。
拿到输出之后先去掉开头结尾的svg
然后用代码节点转换成base64的链接地址
python
import base64
def main(svg_string: str) -> dict:
# 检查输入是否无效
if '无' in svg_string or not svg_string.strip():
return {"result": ""}
# 将SVG编码为Base64格式
encoded_svg = base64.b64encode(svg_string.encode('utf-8')).decode('utf-8')
# 构建data URI
data_uri = f"data:image/svg+xml;base64,{encoded_svg}"
return {
"result": data_uri,
}
拿到这个可访问的地址url 就可以用!(图片名称)[url]这样的md格式,直接显示出来。
总之,显示的方法有很多关键是数据的提取。然后提示词也需要调整,不然生成的svg代码会有很多很多问题,太自由了。
一个生成示例,可以看出来svg还是有点问题的。
四、细节的优化
最后的报告转成pdf形式
js
//一个node接口 接受md字符串 输出ppt 调用接口上传文件传给oss 获取pdf文件的网络路径
const { mdToPdf } = require('md-to-pdf')
const axios = require('axios')
const FormData = require('form-data') //不兼容
const convertToPdf = async (markdownString) => {
try {
const pdf = await mdToPdf(
{ content: markdownString },
{
dest: null
}
)
if (pdf) {
const { content } = pdf
if (content) {
// 将 Uint8Array 转换为 Buffer
const buffer = Buffer.from(content)
const formData = new FormData()
formData.append('file', buffer, { filename: '分析报告.pdf', contentType: 'application/pdf' })
debugger
const res = await axios.post('http://10.86.18.55:5000/files-anon', formData, {
headers: {
...formData.getHeaders()
}
})
return res.data.datas.url
}
}
} catch (error) {
console.error('转换过程中发生错误:', error)
return
}
}
module.exports = { convertToPdf }
添加一点对话开场白,给用户一点提示
添加标注,标记正确答案,使回复更加迅速
五、结尾
这个工作流还有很多需要优化的地方,生成图表的方式也有点不太稳定,欢迎大家一起交流,共同学习dify工作流的各种应用。