DIFY工作流(二):市场分析+AIPPT

3,560 阅读7分钟

接着上篇的AIPPT继续,我们换的第二种方案是PPT低代码设计器+DIFY工作流的形式,用到的技术栈:Vue3、Typescript、Express。流程是:运用DIFY工作流的多轮对话和用户交流-》市场分析-》生成PPT大纲市和具体内容-》生成json-》在线编辑PPT-》导出PPT文件。

项目基于PPTist写了后端服务(因为PPTist是纯前端的,需要加上后端支持),前端加了路由和接口请求,套了一层对话框,目前可以实现AI搜索网络内容后,自动生成PPT,且可在线编辑PPT文档,可在固定位置生成图表,可直接导出发送到邮箱。

效果如下: image.png

1bd8b4d83c8a04e197ef22154ba9125.png

c94c090ff011ec9254a5414030ecfc3.png

05e7a236d13ba5bcee1375713baeefb.png

185c9cfcad4f8e2ae94addde28ff641.png

这种DIFY与前后端结合,把AI灵活嵌入到我们的前后端项目中的方式,也是我觉得现在的AI项目最好的应用。因为DIFY能够简化数据管理和处理,集成了多种AI工具,前端可以通过API直接调用工作流,和DIFY进行交互,后端可以专注于业务逻辑处理,也可以写接口让dify调用。虽然,现在的agent平台大多数都提供iframe嵌入网页的功能,但这样并不灵活,有很多限制,所以最好还是自己去写聊天框接入。

image.png

PPTist

PPTist是Github上一个6.3k star的开源前端项目github.com/pipipi-pika…

每一页PPT,其实都是一个json格式的Slide数组。每一个PPT元素,都通过JSON进行精确控制和定制。这样结构化的方式,可以很方便地生成、修改和保存PPT文件。每一个Slide对象都可能包括:幻灯片的标题、内容、样式、布局、动画,图片、视频、图表等元素都可以通过定制json对象来完成,项目中TS文件也很明确地规定了类型,可以仔细看一下。

示例:官方默认PPT页面,这个页面通过@/src/mocks/slides.ts文件里的slides对象数组控制。

所以我们的AIPPT的思路,就是让ai去生成这个json对象,我们拿到json之后就可以直接渲染出PPT进行在线编辑。

image.png

image.png

image.png

DIFY工作流

我们在项目的前端调用DIFY工作流的API,工作流大致如下图所示,本来还希望加上知识库联合搜索,因为dify自带的知识库不太好用,所以知识库的技术方案还在考虑中。 image.png

会话变量

DIFY里面有一个会话变量,可以存储读写一些上下文数据,借助这个,我们和用户进行对轮对话。本文中的应用就是记录大纲内容,通过会话的方式可以对大纲进行反复的修改。 image.png

问题分类器

DIFY中的问题分类器可以帮助我们做意图识别,在对话中判断用户的意图,然后走对应的流程,本工作流会判断用户是从零开始生成大纲,还是要修改PPT大纲,还是需要直接生成PPT文件。

image.png

联网搜索

在撰写大纲内容的时候,我们需要从网络获取数据,用到了Moonshot联网搜索的API,这个功能DIFY的LLM结点并不支持,所以需要我们自己添加HTTP结点,调用后端写好的联网搜索接口。

image.png

image.png

生成JSON格式的PPT元素

大模型在输出JSON格式的回复的时候,经常会报错,要注意max-token的设置,json过长时会被截断,这样获取的json在转换时会报错。很多大模型都有指定输出格式的功能,比如moonshot可以指定输出json_object,但是在实际使用的时候,发现moonshot在输出json的时候还是有很多问题,最后决定用glm-4-plus,通过细化提示词的方式,尽量让大模型输出一个PPT的json格式。

# Role:  PPT 优化专家
你是一个专业的 PPT 优化专家,能够充分利用用户提供的内容,自动优化并生成精美的 ppTist 的 json 结构,可以渲染出 ppt。
## Goals
- 根据用户提供的内容,进行正确的PPT分页,自动优化并生成精美的 ppTist 的 json 结构,可以渲染出 ppt。
- 所输出的内容必须是有效的 json 格式,不能偏离格式要求。
- 请确保 PPT 的内容准确、客观,不包含虚假信息。
## Output Format
```json
{
  "type": "ppt",
  "content": {
    "title": "主标题",
    "subtitle": "副标题",
    "content": [
      {
        "sectionTitle": "章节标题",
        "subsections": [
          {
            "title": "子章节标题",
            "points": ["子章节内容点1", "子章节内容点2", "子章节内容点3"]
          },
          {
            "title": "另一个子章节标题",
            "points": ["另一个子章节内容点1", "另一个子章节内容点2"]
          }
        ]
      },
      {
        "sectionTitle": "另一个章节标题",
        "subsections": [
          {
            "title": "子章节标题",
            "points": ["子章节内容点1", "子章节内容点2"]
          }
        ]
      }
    ]
  }
}
``` //
## Constrains
1. 检查 PPT 中的文本内容,确保每一页的内容充实,不能文字过少,正确进行分页,相关内容放在一页。
2. 优化语言表达,使其更加简洁、清晰、有逻辑性,提高可读性。
3. 对标题进行润色,使其更具吸引力。
## Skills
- 只处理与 PPT 制作相关的内容,拒绝回答与 PPT 无关的话题。
- 所输出的内容必须是有效的 json 格式,不能偏离格式要求。
- 请确保 PPT 的内容准确、客观,不包含虚假信息。
## Workflow
1. 学习 Outputformat,保证输出根据模板来。
2. 根据输入的信息,分析出主题信息。
3. 结合模板,输入的信息,输出 json 数据。

前端的聊天框组件,在读取到```json开头 并且是json格式的内容的时候,确认需要生成PPT文件,这时候会调用PPT生成接口。

  // 文字解析
  async textFormat(chatContent: IMessage, message: Partial<IBotMsg>) {
    chatContent.msg = message.content || ''
    chatContent.msgType = 'text'
    if (chatContent.msg.includes('```json') && chatContent.msg.includes('ppt')) {
      const link = await generatePPt(chatContent.msg)
      chatContent.msg = `PPT已生成,请点击查看 ${link}`
    }
    return chatContent
  }

由此可以看出来,聊天框我们可以设计为为多种类型的,img、text、json、loading等。

PPT内容生成

后端在收到json格式的内容之后,经过格式转换和解析,一页页生成PPT元素,

const { homePage } = require('./page/home')
const { endPage } = require('./page/end')
const { headlinePage } = require('./page/headline')
const { listPage } = require('./page/list')
const { v4: uuidv4 } = require('uuid')
class AstPpt {
ppt
constructor(ppt) {
  this.ppt = ppt
}
setPpt(ppt) {
  this.ppt = ppt
}

// 首页
homePage() {
  return homePage(this.ppt)
}
content() {
  const contentList = []
  this.ppt.content.forEach((item, index) => {
    const headline = headlinePage(item.sectionTitle)
    contentList.push(headline)
    if (Array.isArray(item.subsections)  ) {
      const list = item.subsections.map((subsection, subsectionIndex) => {
        return listPage(uuidv4(), subsection.title, subsection.points)
      })
      contentList.push(...list)
    }
  })
  return contentList
}
// 结束页
endPage() {
  return endPage(this.ppt)
}
// 生成图表
generateChart(echartData) {
  if (!echartData) return []
  if (Object.keys(echartData).length === 0) return []
  const chartData = echartData
  chartData.id = uuidv4()
  chartData.elements = chartData.elements.map((item) => {
    item.id = uuidv4()
    return item
  })
  return chartData
}
generatePpt(ppt, echartData) {
  this.setPpt(ppt)
  const slides = [
    this.homePage(), // 首页
    this.generateChart(echartData), // 图表
    ...this.content(), // 内容
    this.endPage() // 结束页
  ]
  return slides
}
}
module.exports = new AstPpt()

其中一步是生成图表,我的做法是图表独立于大纲,单独生成,之后再嵌入到PPT的固定位置。比如在PPT的结尾,加上对市场的预测数据。如果有固定的工作流的话,可以在每个位置都判断一下是否有足够的数据,支持图表生成。

其实图表的生成有很多的方式:

  1. DIFY支持直接输出echarts图表;
  2. 让大模型直接给svg代码;
  3. 本文给出的,生成一个PPT图表元素。等等。

主要的问题是缺数据支撑,而且不能保证大模型给的数据的准确性,这才是最头疼的地方。所以我们才会考虑做知识库,数据从网络和知识库两个地方抓取。

最后,项目还有很多需要优化的地方,连接本地知识库、图表的优化、PPT内容的优化、token消耗量等等,很多地方都有坑,感兴趣的朋友可以一起聊聊,后面准备做多工作流的集成,求大佬指导。感谢。