大家好,这里是五彩石编程,我是军哥。上一篇文章中我们讲过了fastgpt中知识库相关的源码,这一篇文章中,我们将介绍流程编排相关的源码,一起来学习吧!
一、概述
我们在fastgpt中创建的应用,也被称为agent,一个agent通常是由多个组件按照用户的目的相互组合而成,我们编排一个agent的过程就是agent的流程编排,但在页面上进行流程编排只是创建一个agent的第一步,其输出为一个大的json,里面包含了我们用到的各个组件及组件间的关系。在用户开始会话时,fastgpt就会根据这个json中定义的流程,开始一步步的执行,并按照配置,向用户输出最终的结果。
由于页面上的流程编排最终只是产出了一个json,这部分不涉及后端的代码,所以不是我们关心的重点,完成编排后的流程调度,才是后端处理中的核心逻辑,所以我们关注的重点也是如何基于这个json,来进行流程调度。
二、会话入口
想要了解流程调度,需要先找到会话的入口,在fastgpt中,会话的入口通常有2个:
- 流程编排时的调试方法对应的入口,源码在:projects/app/src/pages/api/core/chat/chatTest.ts中
- 正常会话时的入口,源码在:projects/app/src/pages/api/v1/chat/completions.ts中
这两个会话的入口处理逻辑相差不大,在调试时,会话历史是不会保存进数据库的,而正常会话时,会话的历史会保存进数据库。其它的逻辑基本上一致。
三、整体流程
我们以正常会话时为例,打开projects/app/src/pages/api/v1/chat/completions.ts,可以看到会话处理的整体流程:
- 认证
- 处理会话历史
- 流程调度
- 保存会话结果到数据库中
- 记录token消耗
认证部分是对不同的会话渠道进行权限控制,由于fastgpt支持常规会话、分享为链接的形式、iframe嵌入、api调用等多种方式,不同的方式对登录也有不同的要求,所以第一步就是统一进行认证,以保证后续处理的都是合法的请求。
会话历史会为多轮对话提供强有力的支持,所以会话历史的处理也是放到的比较靠前的位置。然后就是核心的流程调度了,这里在下面会详细的讲到,这里先略过。
之后就是把流程调度的结果保存进数据库中,并纪录token的消耗等收尾的工作,这部分的代码相对比较简单,也非核心逻辑,就不再多讲了。下面就着重讲一下流程调度的源码
四、流程调度v1版
从源码中我们可以看到,流程调度目前有2个版本:v1和v2,对应的方法分别是:dispatchWorkFlowV1()和dispatchWorkFlow(),我们先看下v1版,其dispatchWorkFlowV1()方法在packages/service/core/workflow/dispatchV1/index.ts中,打开源码可以看到其先是调用了loadModules()方法,把流程编排时生成的json进行格式转换,转换为适合运行时的结构,然后定义了一系列的内部方法:
- pushStore(),收集各组件运行后的输出
- moduleInput(),把组件运行时需要的输入参数给注入进去
- moduleOutput(),把当前组件输出的内容传给下一个组件
- checkModulesCanRun(),检查组件是否可以运行
- moduleRun(),调用各组件的逻辑控制代码,来实现组件的运行,并调用上面几个方法来实现组件的调度
在上面几个方法之后,才是流程的起始代码,上面的几个方法是整个流程调度的核心,组件间的执行顺序主要由上面的这些方法来控制。
起始代码部分,先是过滤出来所有的入口,然后注入需要组件运行时需要的输入参数,然后调用checkModulesCanRun()方法来检查模块是否可以运行,如果可以运行,则会调用moduleRun()来运行模块。
checkModulesCanRun()方法主要是检查一个组件的input是否都赋过值了,如果都赋过值了,说明其相关的前组件都已经运行过了,那么当前组件也就可以运行了。
在moduleRun()方法内部,最重要的一行代码是关于callbackMap的,callbackMap中保存了各个组件实际的执行方法,它定义在文件的最上面,相当于当前文件只负责流程的调度,各个组件实际怎么去执行,就交给各个组件的具体实现了,这样的话,以后在添加新的组件时,只需要在callbackMap中添加一行就可以了,不会对流程调度过程的其它代码造成影响,而每个组件的实际执行代码通常是非常独立,功能相对来说比较单一的,实现起来就没有太大的难度了。所以好的架构在代码的扩展性上,一定是做得非常好的。
看懂了moduleRun()方法,基本上就了解了v1版本的流程调度的逻辑了。
五、流程调度v2版
接下来,我们再来看一下v2版本的代码,其方法dispatchWorkFlow()在packages/service/core/workflow/dispatch/index.ts中,在了解了v1版本的代码逻辑后,v2版本的逻辑相对来说就简单不少了。
v2版一上来就先定义了一堆的内部方法:
- pushStore(),收集各组件运行后的输出
- nodeOutput(),把组件运行时需要的输入参数给注入进去
- checkNodeCanRun(),检查组件是否可以运行
- nodeRunFinish(),组件运行的结束方法
- getNodeRunParams(),把组件运行时需要的输入参数给注入进去
- nodeRunWithActive(),运行一个组件
- nodeRunWithSkip(),跳过一个组件
在上面几个方法之后,才是流程调度的开始,先是过滤出会话的入口,然后通过checkNodeCanRun()来调起整个流程,最后是输出最终结果。
v2版本的内部方法和v1版本的有些相似,也有几个方法不太一样,主要的区别在于引入了边和组件的运行状态两个概念,把组件的调度控制转换为了对边和运行状态的控制。线分为了普通线和递归线,组件的状态被分为了:run、wait、skip三种,相较于版本1来说,流程调度的实现上变得更加了抽象了一点。
组件的运行方法虽然改为了nodeRunWithActive(),但其内部的核心依然是调用callbackMap()这个方法,从而实现调用各个组件的实际实现代码。
六、各组件的具体实现
在上一篇文章中,谈到了知识库的实现源码,它在所有的组件中,基本上算是最复杂的一个了,其它的组件像ai对话、指定回复、判断器、文本加工等,大多是调用大模型或简单的文本处理逻辑,只要找到callbackMap的定义处,再点进去就可以追踪到每个组件的具体实现代码了,由于都比较简单,这里就不再细说了,感兴趣的朋友们可以自行去查看
七、结语
到此为止,整个fastgpt系列就全部结束了,从一开始的概念了解,到后面的开发环境搭建、源码分析,虽然有很多的概念、代码量也比较大,但只要大家去实践一下,就会发现其实没有那么难。从fastgpt的版本迭代来看,整个项目也在经历着从最初的仅仅实现功能即可,到后期的架构上的不断优化,项目的易用性也得到了很大的提高。
最后期望大家能够随着军哥的文章一起进步,一起提升编程和项目理解能力,在编程的道路上,一同砥砺前行!
想了解更多编程的知识,请微信搜索并关注“五彩石编程”公众号,军哥的文章将会持续同步更新。