2024年9月

Dify简介

Dify是一个开源的大语言模型(Large Language Model, LLM)应用开发平台。它融合了后端即服务(Backend as a Service, BaaS)和LLMOps的理念,旨在帮助开发者,甚至是非技术人员,能够快速搭建和部署生成式AI应用程序。

Dify的主要特点包括:

  1. 简化开发流程
    :通过提供一系列工具和服务来简化大语言模型应用的开发流程,使得即使是不具备深厚技术背景的个人也能构建复杂的AI应用。
  2. 支持多种模型
    :Dify支持多种大型语言模型,比如GPT系列模型等,这为用户提供了灵活的选择,可以根据具体需求选择最适合的模型。
  3. LLMOps支持
    :LLMOps是指针对大型语言模型的开发、部署、维护和优化的一整套实践和流程。Dify提供了LLMOps的支持,帮助用户更高效地管理和利用这些模型。
  4. 社区与资源
    :作为一个开源项目,Dify拥有活跃的技术社区,提供了丰富的学习资源和技术支持,便于用户学习和交流经验。
    总之,Dify的目标是降低创建生成式AI应用程序的技术门槛,使得更多人能够参与到这一领域的创新中来。无论是个人开发者还是企业团队,都可以借助Dify快速实现从想法到产品的转化。

开源地址:

开源地址:
https://github.com/langgenius/dify

Dify安装(本文Centos)

克隆 Dify 代码到本地
git clone https://github.com/langgenius/dify.git

然后
进入到源代码中的 docker 目录下,一键启动

cd dify/docker
cp .env.example .env
docker compose up -d

注意在下载镜像过程中可能会网络超时的情况:

作者多次失败,解决办法如下:

编辑sudo vim /etc/docker/daemon.json

{

"registry-mirrors": [
"
https://docker.1panel.live
",
"
https://docker.nju.edu.cn
",
"
https://docker.m.daocloud.io
",
"
https://dockerproxy.com
",
"
http://hub-mirror.c.163.com
",
"
https://docker.mirrors.ustc.edu.cn
",
"
https://registry.docker-cn.com
"
]
}

重启 Docker 服务

# 重启 Docker 服务
sudo systemctl daemon-reload
sudo systemctl restart docker

重新下载镜像和启动容器

docker compose up -d

Dify访问(本文Centos)

访问地址:
http://192.168.0.100

首次设置管理员账号和密码

主界面:



后续部分,我们将深入探讨Dify的实际应用案例,展示如何利用这一平台来构建和优化生成式AI应用。通过具体的项目实例,我们将演示从概念设计到实际部署的全过程,包括如何选择合适的语言模型、集成第三方服务以及调整模型参数以适应特定业务场景。此外,我们还将分享一些最佳实践,帮助读者理解如何高效地使用Dify来解决现实世界中的挑战。


自从互联网普及之后,用于视频直播的流媒体技术就发展起来。这几十年中,比较有影响的主要有MMS、RTSP、RTMP、HLS、SRT、RIST几种,分别介绍如下。

1、MMS协议

MMS全称Microsoft Multimedia Server,意思是微软多媒体服务器,它是微软公司在上世纪九十年代发布的多媒体服务器解决方案,可用于传输微软音视频格式的流媒体直播数据。
MMS协议的直播地址形如mms://***,可通过MMS传输的视频格式为WMV,音频格式为WMA,音视频数据封装之后的文件格式为ASF。
MMS协议内部又分为MMSU和MMST,其中MMSU表示MMS协议结合UDP数据传送。如果MMSU连接失败,服务器会尝试使用MMST,这个MMST表示MMS协议结合TCP数据传送。
因为MMS协议由微软公司提出,不兼容其他格式的音视频数据流,所以随着WMV/WMA标准的式微,MMS协议也逐渐无人问津了。

2、RTSP协议

RTSP全称Real Time Streaming Protocol,意思是实时流传输协议,它是网景公司和RealNetworks公司在上世纪九十年代联合提出的多媒体实时传输协议。
RTSP协议的直播地址形如rtsp://***,早期可通过RTSP传输的视频格式为RM,音频格式为RA,音视频数据封装之后的文件格式为RM或RMVB。后来RTSP协议增加支持MPEG的音视频标准,即支持传输视频格式H.264,音频格式AAC。
RTSP协议的安全版本是RTSPS,也就是给RTSP协议增加了TLS/SSL支持。RTSPS使用了TLS/SSL协议来加密和保护数据传输,以防止数据在传输过程中被窃听和篡改。
因为RTSP提出较早,对服务端的复杂度要求比较高,以至流媒体服务器SRS干脆放弃支持RTSP协议,直播录制软件OBS Studio也没支持该协议。在流媒体服务器中,EasyDarwin、MediaMTX、ZLMediaKit支持RTSP协议。手机直播软件则有RTMP Streamer支持RTSP协议。

3、RTMP协议

RTMP全称Real Time Messaging Protocol,意思是实时消息传输协议,它是Adobe公司在零零年代提出的流媒体数据传输协议。
RTMP协议的直播地址形如rtmp://***,可通过RTMP传输的视频格式为H.264,音频格式为MP3或者AAC,音视频数据封装之后的文件格式为FLV或F4V。
RTMP协议的安全版本是RTMPS,也就是给RTMP协议增加了TLS/SSL支持。RTMPS采用安全套接字层 (SSL) 和传输层安全性 (TLS) 两种加密协议,使数据传输更加安全。
RTMP提出时间较早,最后一次更新时间在2012年,以至于未能支持HEVC和AV1等后期的音视频编码标准。又因为FLV格式没落已久,以至HTML5规范干脆移除了Flash插件,导致如今浏览器都不支持rtmp链接,连FFmpeg也迟至6.1版才给rtmp协议支持hevc格式。
不过好在RTMP的稳定性高,服务端的实现相对容易,并且之前的移动互联网爆发迅速,新的流媒体协议未能及时推出,使得RTMP协议被大量应用于网络直播领域。
在流媒体服务器中,MediaMTX、ZLMediaKit、SRS都支持RTMP协议。在直播软件中,电脑端的OBS Studio支持RTMP协议,手机端的RTMP Streamer和SRT Streamer都支持RTMP协议。
通过RTMP协议实现直播功能的说明参见之前的文章《利用RTMP协议构建电脑与手机的直播Demo》和《使用RTMP Streamer开启APP直播推流》。

4、HLS协议

HLS全称HTTP Live Streaming,意思是基于HTTP的流媒体传输协议,它是苹果公司于2009年提出的一种由于传输音视频的协议交互方式。
HLS采用HTTP协议传输音视频数据,访问地址形如“http://***.m3u8”。HLS协议通过将音视频流切割成TS切片及生成m3u8的播放列表文件,并通知客户端通过HTTP协议下载播放列表文件,按照列表文件中的顺序下载切片文件并播放,从而实现边下载边播放,类似于实时在线播放的效果。
由于HLS在传输层只采用HTTP协议,因此它具备HTTP协议的网络优势,比如很方便透过防火墙或者代理服务器,可简单的实现媒体流的负载均衡。因为HLS协议把视频流分片传输,使得在直播时延时较大,所以HLS更多用于视频点播领域。
关于HLS协议的更多说明参见之前的文章《分析SRS对HLS协议里TS包的插帧操作》和《解析H.264码流中的SPS帧和PPS帧》。

5、SRT协议

SRT全称Secure Reliable Transport,意思是安全可靠传输协议,它由由Haivision 和 Wowza共同创建的SRT联盟提出。
SRT协议协议的直播地址形如srt://***,它引入了AES加密算法,无需像RTSP和RTMP那样引入专门的SSL证书。作为较新的流媒体协议,SRT支持更多的音视频封装格式。只是该协议的支持库libsrt在2017年才开源,因此未能在移动互联网时代大量铺开,目前主要应用于大型电视直播领域。
FFmpeg从4.0开始支持集成第三方的libsrt库。在流媒体服务器中,MediaMTX、ZLMediaKit、SRS都支持SRT协议。在直播软件中,电脑端的OBS Studio从在25.0开始支持SRT协议,手机端的和SRT Streamer支持SRT协议,而RTMP Streamer不支持SRT协议,只有其升级版才支持SRT协议。
通过SRT协议实现直播功能的说明参见之前的文章《利用SRT协议构建手机APP的直播Demo》和《使用SRT Streamer开启APP直播推流》。

6、RIST协议

RTST全称Reliable Internet Stream Transport,意思是可信赖的互联网流媒体协议,它由2017年成立的RIST工作组提出。
RIST是一个在传输层使用UDP协议,并在应用层提供可靠性和流控制功能的流传输协议。它并不是一个纯粹的应用层协议,而是在传输层和应用层之间操作的协议。RIST和SRT具有相同的加密级别,都支持大容量流媒体和前向纠错功能。
RIST协议的制定时间比SRT还晚,虽然晚制定会多考虑新功能,比如RIST支持点到多点广播,而SRT不支持;但是晚制定拖累了各开源软件对RIST的支持力度,比如OBS Studio早在25.0开始支持SRT,迟至27.0才开始支持RIST,另一个直播录制软件RootEncoder已支持SRT尚未支持RIST,流媒体服务器MediaMTX已支持SRT尚未支持RIST。
FFmpeg从4.4开始支持集成第三方的librist库。在流媒体服务器中,MediaMTX、ZLMediaKit、SRS都不支持RIST协议。在直播软件中,电脑端的OBS Studio从在27.0开始支持SRT协议,手机端尚未有开源软件支持RIST协议。

总的来说,目前国内占据主要市场份额的直播协议仍是RTMP,不过拥有更好性能的SRT协议正在逐步迎头赶上,比如腾讯视频云、京东视频云等等就引入了SRT协议。有关直播系统的搭建说明参见之前的文章《从0开始搭建直播系统的开源软件架构》。

更多详细的FFmpeg开发知识参见
《FFmpeg开发实战:从零基础到短视频上线》
一书。

编译工具链

  • 我们写程序的时候用的都是集成开发环境 (IDE: Integrated Development Environment),集成开发环境可以极大地方便我们程序员编写程序,但是配置起来也相对麻烦。在 Linux 环境下,我们用的是编译工具链,又叫软件开发工具包(SDK:Software Development Kit)。Linux 环境下常见的编译工具链有:GCC 和 Clang,我们使用的是 GCC。

编译

准备工作

  • 查看当前系统是否安装gcc,g++,gdb。
    gcc --version
    g++ --version
    gdb --version
    image
  • 未安装可通过命令安装。
    sudo apt update
    sudo apt install gcc g++ gdb

生成可执行程序/编译过程

image

gcc -E hello.c -o hello.i # -E激活预处理,生成预处理后的文件
gcc -S hello.i -o hello.s # —S激活预处理和编译,生成汇编代码
gcc -c hello.s -o hello.o # -c激活预处理、编译和汇编,生成目标文件
gcc hello.o -o hello # 执行所有阶段,生成可执行程序

gcc -c hello.c # 生成目标文件,gcc会根据文件名hello.c生成hello.o
gcc hello.o -o hello # 生成可执行程序hello,这里我们需要指定可执行程序的名称,否则会默认生成a.out
gcc hello.c -o hello # 编译链接,生成可执行程序hello

image
image

gcc与g++区别

  • gcc

    g++
    都是
    GNU(组织)
    的一个编译器
  • 误区一

    gcc
    只能编译 c 代码,g++ 只能编译 c++ 代码
    • 后缀为
      .c
      的,
      gcc
      把它当作是 C 程序,而
      g++
      当作是
      c++
      程序
    • 后缀为
      .cpp
      的,两者都会认为是
      C++
      程序,
      C++
      的语法规则更加严谨一些
    • 编译阶段,
      g++
      会调用
      gcc
      ,对于
      C++
      代码,两者是等价的,但是因为
      gcc
      命令不能自动和
      C++
      程序使用的库联接,所以通常用
      g++
      来完成链接,为了统一起见,干脆编译/链接统统用
      g++
      了,这就给人一种错觉,好像
      cpp
      程序只能用
      g++
      似的
  • 误区二

    gcc
    不会定义
    __cplusplus
    宏,而
    g++

    • 实际上,这个宏只是标志着编译器将会把代码按 C 还是 C++ 语法来解释
    • 如上所述,如果后缀为
      .c
      ,并且采用
      gcc
      编译器,则该宏就是未定义的,否则,就是已定义
  • 误区三
    :编译只能用
    gcc
    ,链接只能用
    g++
    • 严格来说,这句话不算错误,但是它混淆了概念,应该这样说:编译可以用
      gcc/g++
      ,而链接可以用
      g++
      或者
      gcc -lstdc++
    • gcc
      命令不能自动和C++程序使用的库联接,所以通常使用
      g++
      来完成链接。但在编译阶段,
      g++
      会自动调用
      gcc
      ,二者等价

条件编译

预处理指令

1) #if [#elif] [#else] #endif
2) #ifdef [#elif] [#else] #endif
3) #ifndef [#elif] [#else] #endif

指令格式

1. #if 指令的格式

#if 常量表达式
...
#endif

当预处理器遇到 #if 指令时,会计算后面常量表达式的值。如果表达式的值为 0,则#if 与 #endif 之间的代码会在预处理阶段删除;否则,#if 与 #endif 之间的代码会被保留,交由编译器处理。
#if 指令常用于调试程序,如下所示:

#define DEBUG 1
...
#if DEBUG
	printf("i = %d\n", i);
	printf("j = %d\n", j);
#endif

2. defined运算符

是预处理器的一个运算符,它后面接标识符。如果标识符是一个定义过的宏则值为 1,否则值为 0。defined 运算符常和 #if 指令一起使用,比如:

#if defined(DEBUG)
...
#endif

仅当 DEBUG 被定义成宏时,#if 和 #endif 之间的代码会保留到程序中。defined 后面的括号不是必须的,因此可以写成这样:
#if defined DEBUG
defined 运算符仅检测 DEBUG 是否有被定义成宏,所以我们不需要给 DEBUG 赋值:
#define DEBUG

3. #ifdef 的格式

#ifdef 标识符
...
#endif

当标识符有被定义成宏时,保留 #ifdef 与 #endif 之间的代码;否则,在预处理阶段删除 #ifdef 与 #endif 之间的代码。等价于:

#if defined(标识符)
...
#endif

4. #ifndef 的格式

#ifndef 标识符
...
#endif

它的作用恰恰与 #ifdef 相反:当标识符没有被定义成宏时,保留 #ifndef 与 #endif之间的代码。

作用

1. 编写可移植的程序

下面的例子会根据 WIN32、MAC_OS 或 LINUX 是否被定义为宏,而将对应的代码包含到程序中:

#if defined(WIN32)
...
#elif defined(MAC_OS)
...
#elif defined(LINUX)
...
#endif

我们可以在程序的开头,定义这三个宏中的一个,从而选择一个特定的操作系统

2. 为宏提供默认定义

我们可以检测一个宏是否被定义了,如果没有,则提供一个默认的定义:

#ifndef BUFFER_SIZE
#define BUFFER_SIZE 1024
#endif

3. 避免头文件重复包含

多次包含同一个头文件,可能会导致编译错误(比如,头文件中包含类型的定义)。因此,我们应该避免重复包含头文件。使用 #ifndef 和 #define 可以轻松实现这一点:

#ifndef __WD_FOO_H
#define __WD_FOO_H
typedef struct {
    int id;
    char name[25];
    char gender;
    int chinese;
    int math;
    int english;
} Student;
#endif

4. 临时屏蔽包含注释的代码

我们不能用 /
...
/ "注释掉" 已经包含 /
...
/注释的代码,即
不能嵌套多行注释
。但是我们可以用 #if 指令来实现:

#if 0
包含/*...*/注释的代码
#endif

注:这种屏蔽方式,我们称之为
"条件屏蔽"

库的链接

  • 库文件是计算机上的一类文件,可以简单的把库文件看成一种代码仓库,它提供给使用者一些
    可以直接拿来用的变量、函数或类
  • 库是特殊的一种程序,编写库的程序和编写一般的程序区别不大,只是
    库不能单独运行
  • 库文件有两种,
    静态库

    动态库(共享库)
    。区别是:
    • 静态库
      在程序的链接阶段被复制到了程序中
    • 动态库
      在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用
  • 库的好处:
    代码保密

    方便部署和分发

静态库的制作

  • 规则

image

  • 示例:有如下图所示文件(其中每个分文件用于实现四则运算),将其打包为
    静态库
add.c源代码
#include <stdio.h>

int add(int a, int b)
{
    return a+b;
}
sub.c源代码
#include <stdio.h>

int sub(int a, int b)
{
    return a-b;
}
mul.c源代码
#include <stdio.h>

int mul(int a, int b)
{
    return a*b;
}
div.c源代码
#include <stdio.h>

double div(int a, int b)
{
    return (double)a/b;
}
head.h头文件
#ifndef _HEAD_H
#define _HEAD_H
// 加法
int add(int a, int b);
// 减法
int sub(int a, int b);
// 乘法
int mul(int a, int b);
// 除法
double div(int a, int b);
#endif
main.c源文件
#include <stdio.h>
#include "head.h"

int main()
{
    int a = 20;
    int b = 12;
    printf("a = %d, b = %d\n", a, b);
    printf("a + b = %d\n", add(a, b));
    printf("a - b = %d\n", subtract(a, b));
    printf("a * b = %d\n", multiply(a, b));
    printf("a / b = %f\n", divide(a, b));
    return 0;
}
  • 查看目录结构
    tree
    image
  1. 生成
    .o
    文件:
    gcc -c 文件名
    image


  2. .o
    文件打包:
    ar rcs libxxx.a xx1.o xx2.o
    image

    image

    image

静态库的使用

  • 需要提供
    静态库文件和相应的头文件

  • 编译运行:
    gcc main.c -o app -I ./include -l calc -L ./lib


    • -I ./include
      :指定头文件目录,如果不指定,出现编译错误

      image

    • -l calc
      :指定静态库名称,如果不指定,出现链接错误

      image

    • -L ./lib
      :指定静态库位置,如果不指定,出现链接错误

      image

    • 正确执行
      (成功生成
      app
      可执行文件)

      image

    • 测试程序

      image

动态库的制作

  • 规则
    image

  • 示例:有如下图所示文件(其中每个分文件用于实现四则运算),将其打包为
    动态库

    image


    1. 生成
      .o
      文件:
      gcc -c -fpic 文件名

      image


    2. .o
      文件打包:
      gcc -shared xx1.o xx2.o -o libxxx.so

      image

动态库的使用

  • 需要提供
    动态库文件和相应的头文件

  • 定位动态库(
    原因见工作原理->如何定位共享库文件
    ,其中路径为动态库所在位置)


    • 方法一:修改环境变量,
      当前终端生效
      ,退出当前终端失效

      export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/u/Desktop/Linux/calc/lib
      
    • 方法二:修改环境变量,用户级别永久配置

      # 修改~/.bashrc
      vim ~/.bashrc
      
      # 在~/.bashrc中添加下行,保存退出
      export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/u/Desktop/Linux/calc/lib
      
      # 使修改生效
      source ~/.bashrc
      
    • 方法三:修改环境变量,系统级别永久配置

      # 修改/etc/profile
      sudo vim /etc/profile
      
      # 在~/.bashrc中添加下行,保存退出
      export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/u/Desktop/Linux/calc/lib
      
      # 使修改生效
      source /etc/profile
      
    • 方法四:修改
      /etc/ld.so.cache文件列表

      # 修改/etc/ld.so.conf
      sudo vim /etc/ld.so.conf
      
      # 在/etc/ld.so.conf中添加下行,保存退出
      /home/u/Desktop/Linux/calc/lib
      
      # 更新配置
      sudo ldconfig
      
  • 有如下结构文件,其中
    main.c
    测试文件

    image

  • 配置环境变量
    image

  • 编译运行:
    gcc main.c -o app -I ./include -l calc -L ./lib

    image

  • 测试程序

    image

  • 如果不将动态库文件绝对路径加入环境变量,则会出现以下错误

    image

工作原理

  • 静态库:
    GCC
    进行链接时,会把静态库中代码打包到可执行程序中

  • 动态库:
    GCC
    进行链接时,动态库的代码不会被打包到可执行程序中

  • 程序启动之后,动态库会被动态加载到内存中,通过
    ldd (list dynamic dependencies)
    命令检查动态库依赖关系

    image

  • 如何定位共享库文件呢?


    • 当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道
      绝对路径
      。此时就需要系统的动态载入器来获取该绝对路径
    • 对于
      elf格式
      的可执行程序,是由
      ld-linux.so
      来完成的,它先后搜索
      elf文件

      DT_RPATH
      段 =>
      环境变量LD_LIBRARY_PATH
      =>
      /etc/ld.so.cache文件列表
      =>
      /lib/

      usr/lib
      目录找到库文件后将其载入内存

静态库和动态库的对比

程序编译成可执行程序的过程

image

静态库制作过程

image

动态库制作过程

image

静态库的优缺点

image

动态库的优缺点

image