摘要:在大模型应用开发过程中,如何将研究成果与软件工程紧密结合,并解决新领域中的新问题是开发者面临的重大挑战。在 InfoQ 举办的 QCon 全球软件开发大会(北京站)上,字节跳动研发工程师沈桐发表了题为《AI 应用开发的破局之路:字节跳动 Eino 框架实践》的
演讲嘉宾|沈桐
编辑 |Kitty
策划 |QCon 全球软件开发大会
在大模型应用开发过程中,如何将研究成果与软件工程紧密结合,并解决新领域中的新问题是开发者面临的重大挑战。在 InfoQ 举办的 QCon 全球软件开发大会(北京站)上,字节跳动研发工程师沈桐发表了题为《AI 应用开发的破局之路:字节跳动 Eino 框架实践》的分享,他重点介绍字节跳动 Eino 框架在 “组件” 抽象和业务编排方面的创新实践,提供 Eino 框架在大规模生产环境中的应用价值与实践经验参考。
预告:将于 10 月 23 - 25 召开的 QCon 上海站设计了「Agentic AI」专题,将深入探讨 Agentic AI 的核心技术,包括自我学习、决策制定、情境理解以及多智能体合作等关键能力。并分析智能代理 AI 如何提升行业生产力,为各个行业带来革命性的改变。敬请关注。
以下是演讲实录(经 InfoQ 进行不改变原意的编辑整理)。
AI 应用框架
开发框架作为基础设施,我们应该如何理解它?
我们首先有一个金字塔,金字塔底端是一个大模型。大模型是一个突破性的技术,有这个技术作为起点,使得我们很多一些之前不能做的需求成为可能,给我们的企业打开了很多可以探索的方向。
我们在做这些新需求时,会遇到很多比较重复的典型问题,对这些问题的解决需要不断实践,是我们发现和解决问题的过程。
基于这些实践经验,我们可以把解决问题的这些认知积累成一个框架,这个框架就可以帮助我们更好地应对未来重复出现的问题。
所以 AI 应用框架,等于是以大模型技术的突破作为基础,在解决这些新的需求场景的实践过程中,通过解决和提炼重复问题而产生的认知和解决问题的框架。基于这个框架,我们以后可以更好地开发应用,所以这个框架可以作为我们的基础设施来出现。
AI 应用框架也差不多。首先它是技术驱动的,必须要站在大模型技术不断进展的前沿,因为它可以探索我们解空间的可能性,这是第一个前提。第二点是基于真实的需求,是通过实际出现的场景来驱动的。第三点是基于这些真实需求的提炼,我们可以把认知沉淀下来,比如以 API 的方式、设计的方式、功能的方式在这个框架内部传递出来。最后通过时间的检验来看我们的认知是不是能真的帮助我们解决实际的问题,实现真实的需求,以此作为一个循环来提升这个框架作为 AI 应用基础设施的能力。
那么什么是 AI 应用?这是我们需要首先回答的一个问题。这个图就是我们目前的 Eino 框架,在支持自己的内部和外部的一些需求之中,我们沉淀出来的所谓的认知。我们认为 AI 应用就是一个围绕大模型的信息流,中间的框叫 ChatModel,就是大模型。
为什么会有这么一个认知?首先是来源于大模型的根本特性,因为它是可以做信息的生成,无中生有把信息给你搞出来。因为它核心的特性,所以我们的 AI 应用一定是针对信息的处理过程。比如说我们的大模型为了能够更好地无中生有给出一些有价值的信息,我们还是需要把一些相关的有价值信息给到大模型,毕竟我们之前都知道垃圾进垃圾出的道理。虽说大模型是垃圾能够进,也会突出一些相对比较好的一些信息,但我们还是需要更好的信息给大模型来才会有更好的效果。所以我们会有前置的一些信息检索和信息增强等节点来给大模型比较好的输入和上文。
大模型给出信息之后,我们需要一些相关的节点处理这些信息,利用这些信息,或者把这些信息最终给到人去消费,整个过程是由几个基本元素构成的,这些框就是所谓的节点,这些节点都有同样的特性,都是信息处理节点。它们还有一个区分点是不同节点处理信息的方式是不一样的,信息处理模式是由这些不同的节点而有区别的。我们会把不同节点的信息处理模式抽象为一个组件,这是第一点。
再往外来看一点,连接这些节点的是有一些线,这些箭头的连接方式会有不同,会有分叉、汇聚、分支的判断和选择,会有环的结构。这些不同的连线方式代表的是对不同的信息处理节点的控制方向以及节点之间数据传递的模式。这也是我们做应用时需要经常解决的问题,就是如何让这些信息有效地在节点之间流动,以及如何流动。
最终我们再站出来看整体这个图,这个图很典型,它是一个我们做应用时会非常常用的模式,就是先召回信息,再由模型做信息生成,然后由这个工具节点去处理信息,给出反馈,再由模型根据这些反馈进一步做生成。其中有一个环的结构,ChatModel 跟原信息之间有一个分支,构成一个环。这个结构是叫 react 的模式,就是由模型生成,由工具执行,再把反馈信息给模型,这样反复不断地根据反馈给出最终结果的范式。
这个范式是有两个节点、一个分支和几条线就可以构成的,它也可以作为我们一个大应用的子结构。这些类似这种 react 的范式,也是我们在应用开发时需要反复使用或者反复沉淀和理解的一些复合组件。
总结起来,我们 AI 应用的框架为了解决应用开发的问题,有三个点需要处理。一个是说有哪些节点,有哪些信息处理的范式可以作为节点、作为组件来出现,这是第一个问题。
第二个问题是说节点之间信息流转的方式方向,以及数据的映射的方式是什么样的,怎样尽简单清晰和完备地描述出这些信息流转的方式,这是第二个问题。
第三个问题是我们会根据最佳实践,根据我们的科研成果,根据我们的业务场景去沉淀一些常用、复合、有效的整体框架。
先看第一个点,就是我们有哪些组件去处理不同的信息处理方式。这是一个可枚举的组件列表,比如说我们中间的这三个,一个是大模型,一个是 Retriever,就是所谓的信息召回。第三个是工具,就是我们怎么去处理信息,然后给出处理信息的结果。还会有其他类型的信息处理组件,每一个组件都是一个接口,一个抽象,它代表的是某一类信息处理模式。
有了组件的抽象列表后,我们作为开发者就可以比较清晰地知道我应该用哪些节点构成应用的信息处理节点,这是第一点。
第二点,既然有了 ChatModel,就是大模型的抽象信息处理模式,那么里面我们可以用哪些具体组件实现或者具体的服务提供方,帮我们真正实现大模型也好,Retriever 也好,这些接口的。我们会有另外一个列表,针对每一个组件抽象都会有组件的实践列表,可以满足组件抽象的信息处理模式。我们在真正编排和开发 AI 应用时,就可以在里面选择自己合适的组件。
所以作为框架来说,我们提供的价值是首先定义了一个组件的抽象列表,可以让开发者能够很清楚、一目了然地知道我有哪些可能性可以做。第二个是我们提供了针对每一个组件抽象的实践列表,是一些比较常用的,经过验证比较好用的具体实践,可以开箱即用。
如何去把这些不同的信息处理节点连起来,就是以一种合适的方式让控制流和数据流能够流转。这就等于是编排,所以编排就等于是说在节点之间构建联系,可以这么简单理解。
这个图的三个元素,一个元素是节点,各个框是我们的节点,边上就是节点之间的有效连线,分支是不同的下游选择器,这三个基本的元素就已经可以完整描述一个有向图,可以把我们任意的业务场景信息流,用这三个基本元素去做完备的描述。
除此之外,这三个节点描述的是控制流,就是我们如何让信息从最开始的节点,以我们预定的方式去流转。还会有数据流的处理,我们可以基于字段之间的输入和输出之间的类型对齐,以及字段级别的数据映射,以及间接的数据引用等方式,基于我们的控制流图去实现数据流的合适映射和流转。
大框图是由组件的这些直接抽象来构成的,比如说大模型节点,我可以替换成任何一个模型提供商,这些工具可以换成任何基于我们业务场景的工具。也就是说我们的编排是面向抽象,我们可以可插拔地把不同的具体实践给到节点上。
同时基于面向接口的编排,我们可以做到的一点是这些状态,比如数据也好,记忆也好,还是上下文也好,或者是我们的知识库也好,或者是我们图的状态,都可以在整个编排的外部去存储。在我们整个实例之上,在我们的 function 编排运行时,它是无状态的,可以水平扩展的。
下一个是关于数据流。我们说了很多,也许感知还不是很强烈。这么一个框架并没有跟一个普通的工作流框架有什么本质的区别,好像并不是很明显。
但我们大模型应用开发的框架,它的一个核心特点是一定有大模型。大模型有一个特点是它一定是流式的数据输出,所谓的流式还不是一般的流,是一个有序的流,其中每一个元素是整体消息的一部分,是一个分片,这是它的核心特点。所以我们的框架就一定要处理具有这样特性的数据。
我们从各个节点的数据流来看,它会有不同的流处理场景,比如说由一个数据流复制到多份数据流,以及多份数据流自动合并成一份数据流。在我们这个图中有的节点没法处理流数据,比如说一个工具函数只能接收一个完整消息,接收不了这种分片形式的数据流,那么怎么办?它能不能进我们的信息流编排?也是可以的,没问题,我们可以自动把这种分片有序的数据流自动合并成一个完整的信息数据,交给不能接收流式数据的一个节点,比如一个工具或者一个函数,它就可以直接处理完整数据,而忽略掉流的复杂性。而这种把流自动拼接成完整数据的过程是由框架来托管的。
也就是说基于我们框架这种比较完善的流的复制合并、装箱拼接这些基础和自动的能力,我们可以做到把大模型这种流式输出的节点,以及其他没法处理流数据的节点,比如说工具或知识库召回等,以一种无缝的方式编在一张图里,做在一个应用里,同时保留流式处理的时效性比较高的特征,以及其他的一些传统节点的特征。
基于上面这些流的处理能力,我们靠 callback 机制,就可以把每一个节点无论是流式的输入输出,还是非流的输出能以 callback 的方式向外部发送事件,所以整个 AI 应用是一个不间断的事件源。
我们这个框架的名字叫做 Eino。上面说了三个问题,我们的框架是这样解决的。我们可以先看框架底下有一个 Schema。这一块是我们对 AI 应用开发这个领域的一些基础认知,它有哪些基础领域模型,是在这里做了定义,包括流的处理也在这里做了定义。
往上一层是组件,组件都有哪些节点,各自有什么样的信息处理范式,都是在 Components 层里定义的。我们还会有 Ext,就是扩展仓库,里面会有针对每一个组件的具体实现。
再往上一层叫 Compose,就是我们的编排层,这里会沉淀刚刚说的第二个问题,就是如何把这些组件节点以一种合适的方式串起来,是这一层解决的问题。
最上面是 Flow 层,是刚才说的第三个问题,如何把我们一些认知的实践做沉淀,把一些常用的编排模式沉淀成一个复合组件,可以作为其他组件的一个子部分出现。比如说 ReAct,就可以把我们之前遇到过的一些常用的模式给它做出来。
AI 应用实战
下面我们进入第二部分,走一个比较小的例子,看一下开发的感觉是什么样的。
这个例子非常简单,并不是一个实际生产环境的例子。这个例子是说我们想安排一个主题乐园的一日游规划,需要根据用户的需求,比如我是几个人玩,有没有孩子,偏好什么样等,去给我们安排乐园一日的行程。
首先做应用时,前几步的过程是非常传统的,跟我们之前开发的流程没什么区别。我们还是需要先做领域的一些东西,需要先把领域模型建出来,定义几个结构体。
这里就列了一个例子,这个例子是叫 activity,我们先定义一个乐园里的任意一项活动,我们都认为它是一个 activity,包括我们的游乐设施也好,表演也好,甚至餐厅吃饭也好,都是一个 activity,都把它作为一个需要花时间去做事,把它定义出来。
这时跟我们之前做开发时的唯一一个区别是,我们在定义这个对象时,需要把这个对象里的这些字段名定义得比较清楚一些,让模型能够看懂能够理解。因为我们这个对象结构体定义以后,大概率是需要给模型作为我们工具的输入的,所以这是一个需要注意的点。
第二部分跟我们之前开发也没什么区别,就是我们要定义一些 function,所谓的函数 service,就是领域服务。这里也举了一个例子是说,我们要定义一个函数获取这些游乐设施的信息。这是一个工具函数,未来我们大概率是需要把这个函数作为一个工具给我们的模型,让它帮我们查询需要对应的那些设施的信息。
这个函数的特点跟我们之前做的没什么区别,就是一个普通的函数,唯一需要满足的就是工具的定义,它需要有特定的一个入参,第一个是所谓的 context,第二个是所谓的 request。然后反参是 response 加 error,它是一个 golang 的工具定义的特定范式。
还有一点,既然我们这个函数它未来大概率是需要由模型拼接它的输入,这个函数应该怎么调,它的入参应该怎么拼,是由模型给出的。所以在我们这个函数的内部需要知道这个函数的入参是模型拼的,也许我们就需要多做一些校验,或多做一些合适的兜底方式让入参更合法。
上面两步是比较传统的,无论怎么着都是避不开这两步。
如果是我们做的是一个 AI 应用,基于 Eino 这样的框架来做,那么第三步是一个新的步骤,我们需要做的是信息流的建模。我们的 AI 应用既然是一个信息流转的过程,那么它应该是个什么样的,这个问题是我们需要在这一步回答的。
首先第一步我们先确定我们的需求到底是什么,比如我们的需求,最终结果是给我们一日的行程规划,这是我们产出的产物。
基于这个产物,它的上一步前提是我们需要把一些信息给到模型,这个信息是我要查到乐园的一些比如活动时间表,有什么样的游乐设施,有什么样的简介,有什么样的身高限制……这些信息我们要给到模型,所以我们需要获取相关的真实信息,这两步是必不可少的。
除此之外第三步是一个可选步骤,就是根据我们人类的解决问题的习惯,有的人习惯先给出一个规划,先给出一个列表,里面分 12345,需要先获取哪些信息,先查乐园开门时间,然后再查设施列表。给出一个计划,再由模型执行这个计划。或者说我们是给出一个需求,帮我制定一个一家三口的一致行程。这一步是一个选项,不一定要。
上面是我们大概的流程,基于此,第二个我们需要解决的问题是在信息流里大模型要干什么,大模型的角色是什么?因为我们是 AI 应用,这是绕不开的一个问题,所以我们做了一个回答。第一点,大模型需要给出一个行程规划,它需要根据我们一个 prompt,根据格式去给出一个规划。
第二点,大模型需要发起工具调用,所谓发起是指它需要决定要调哪个工具,获取什么信息,以及需要给工具一些什么样的入参去获取对应的信息。
第三步也是一个可选,如果我们选择了先去制定计划,再去完成计划的这种解决问题的方式,那么模型需要干第三个事情就是它需要给出一个计划,我们才能执行。
下一步,我们再去基于上面给出决策的答案,选择一下模型。这里大概选了一下,我们需要一个推理模型把这些信息整合起来,给出一个最终结果。这里选择了 DeepSeek R1。同时我们还需要一个可以发起 funtion call 的模型帮我们拼接这些工具调用的入参,这里选了豆包模型去做。
第四点,我们最终决定一下,第一问里面的可选步骤我们要不要做,也就是先制定一个计划再去干活。这里就决定要做,因为它比较符合我个人的习惯。
如果我们要做第一步的执行计划,整个信息流的结构就定下来了,它就是底下这个图,这么一个先计划执行的一个多智能体的信息流结构。这里我先有一个任务,一个最初始的信息输入,有了这个信息输入之后,由一个模型,一个计划智能体给出更多信息,根据这个任务扩展出一个整体计划,这里面就是一个任务清单。第二步的是由一个执行智能体,根据这个任务清单给出更多信息,决定我如何去执行这些任务。执行时要发起工具调用,以什么样的入参去调什么样的工具,由它去执行一步步的计划,执行完之后把这些工具调用的反馈结果给出来,给到我们的重新计划智能体,由它汇总这些信息,最后按照我们的要求生成最终的结果。
当然这里面会有一个多步循环,就是我们的重新计划智能体可以判断目前信息不够,目前的计划需要调整,这时就会有一个重新生成的过程,去生成一个新的任务清单,再去执行,再去做最终的判断。
为什么我们要选择一个计划执行的信息流范式,有几个理由。第一个是我们可以为每一个智能体或者每一个模型节点,选择一个合适的模型实现,匹配一个比较合适的模型,作为我们的 ChatModel 的输出,这是第一点。
第二点,它是一个智能体层面的单一职责的原则。我们都知道编程时我们需要单一职责原则,那么这里它是一个智能体层面的单一职责原则的实践。就是说这个模型它只干计划的事情,或者只干执行的事情,或者只干最终汇总的事情,不干其他的事情,是一个更明确的职责,比较解耦,比较方便我们去做 prompt 的调试或者评测的事情。
最后一点就是它比较符合我们人解决问题的通用模式,是一个处理问题的范式。
基于我们上面这些结论,就是我们要以那样的方式去建信息流的模型,最后就是如何用这个框架去把我们信息流快速构建出来。这个示意图是说基于 Eino 框架,我们是一个什么样的格式,需要什么样的节点,节点之间用什么样的方式去连线,需要什么样的分支。
这里会有三个 ChatModel,就是三个大模型节点,planner、executor 和 reviser。这三个节点分别输入一个 message 列表,也就是说模型需要的上文是一个 message 列表。这三个节点的输出分别都是单个 message,输出的是明确的单个消息。
这两个分支,branch1 是说 executor 执行体的输出会包含一个工具调用,也可能不包含。后者就是说我已经把所有工具都执行完了,不需要再执行工具调用了。所以这个时候会有一个分支,根据 executor 的输出决定是否应该走工具调用的分支,还是需要把结果直接交给下一个模型 reviser 智能体,由它去输出,这是第一个分支的作用。
branch2 的作用是 reviser 会决定我需要把这个结果最终给出用户,或者是经过我的判断说需要重新规划重新执行,这时就会有一个判断,branch2 去做这个判断。
同时这个图里还会有三个 tolist 节点,就是连接两个大模型节点之间做数据转换的。因为我们的大模型节点的输入是一个消息列表,需要多条消息作为上下文,它的输出是单个消息,因此当我们把两个大模型节点连在一起时,需要把单个消息转换成一个消息列表。所以这里就需要引入三个 tolist 节点。这时也展现了我们这个框架的一个特点,就是我们的节点之间的数据流转是明确定义的,每个节点的输出和输入是什么类型,是定义得非常明确的。同时节点之间的连线,数据的流转也是非常明确的,就是我们能用什么样的方式,什么样的结构去流转,它是根据我们的输出和输入定义的,没有任何模糊空间。所以我们需要明确定义出如何去转换这些节点之间的数据。还要定义我们如何把这些 function 函数封装成工具给到模型,这也需要我们用代码的方式绑定工具的。
左右滑动查看更多
这里是运行的结果,一个是 planner 如何去给出我们需要执行的计划,下面 executor 如何根据我们的计划去一步步调用工具。最后是 reviser,它要如何给出一个答案。这里会有多轮重复,就是多轮循环的过程。中间这些步骤都是通过流式的方式给出的,最终看时,我们可以看到 console 里是一个字一个字吐,它会不断告诉我们当前最新的输出是什么。
这个机制是用我们框架里面的 callback 事件机制,就可以把整个图执行过程之中的所有节点,它的流式或者非流式的信息能够及时捕获到,去做处理。
下面我们再看一个 All-In-One 个人助手的例子,有类似于最近比较火的 manus 的多智能体结构。我们可以类比一下刚刚那个例子,首先可以看到的一个差别很明显的点。刚才那个例子要查询的信息是比较专业的,基于领域之内的。那么个人助手所需要的信息是另外一个领域的信息。它虽然是 All-In-One,也是有特定领域的,一定是有一个范围的。所谓范围就是说我的工具是解决什么样的问题,比如可以有代码执行器,可以有命令行执行器,可以有 browseruse、computeruse 这些工具,可以有 MCP 的很多工具。这些工具跟我们上面那个例子是不一样的,这是第一个区别。
第二个区别是它的整个信息流范式也是不一样的。这个图是一个示意图,也是用计划执行这种治理模式。我们还可以用其他模式,比如说可以有多个智能体去做分级代理的模式,比如一个智能体可以负责一个子域,底下可以有多个相关工具,还会有一个总智能体做信息流的分发,这是一种模式。另一种模式是整体之间是一个平等的方式。但无论如何,它都是有组件和编排组合在一起的,都可以用我们的框架去实现。
我们再总结一下,问题是说我如何开发一个全能个人助手,这是从一句话需求开始的一个问题。现在我们需要问的另外一个问题是说我们有了这样的框架后,要解决的问题是什么?
第一个我要确定用什么样的工具。用什么样的方式或者什么样的工具是根据我们的领域确定的,这是一个问题。
第二个问题是我们如何建信息流的建模,有了这个之后就可以比较快速地把它搭起来,这样就体现了我们框架作为基础设施的价值,它可以帮我们很快地从 0 到 100。再到 1000 的话还需要我们自己去努力,它只是说不需要从 0~100,这个过程我们不需要自己去搞了。
AI 应用的未来
我们再展望一下 AI 应用的未来。AI 应用的发展一日千里,那么我们的 Eino 框架如何能在这种长期快速进展的基础之上,保持长期的价值?不是说我们来了一个新模型,这个框架意义就不大了。这确实是我们需要考虑的一个点,这里我就尝试着以个人的理解给出一个讨论的点。假设我们的模型未来几个月或者几年变得很强,我们还会怎么样?假如一个特别牛的人就是一个模型。那么在这个情况之下,我们做的应用还需要什么样的框架?有这么几个点应该还是需要的。
第一个点是说它还是需要跟人类去互动,因为毕竟是人用这个应用,所以它一定还是需要有人机交互的方式,还是需要有指令遵从的特性。
第二点,它还是需要上下文。无论它是如何牛逼的天才,还是需要有 attention,需要知道自己要解决的问题,需要关注的点,需要的信息输入是什么,所以它一定会需要记忆,需要信息的召回,需要对世界的感知,需要上下文的管理。
第三点,它还是需要工具,还是需要能有手有脚帮它干事情,它还只是一个信息生成的节点,而对信息处理的能力,它还是需要工具扩展开来。
最后一点是信息流的编排。无论多么牛的人还是需要跟别人合作的,只要有合作,就一定会有信息的流转模式,有信息的编排,有解决问题的范式。
所以未来会变的要素是更强的推理,更快的速度,这是没问题的。不变的要素会有人机交互、有上下文、有工具和信息处理。所以基于这些认知,我们的框架在现在以及未来会有一些重点方向。
这些标红的节点就是重点。第一,我们会有为了人机交互而重点开发,已经上线的 interrupt 和 checkpoint,就是图执行的中断和继续的能力,以及我们目前正在探索的多智能体交互的方式。
第二点,我们会拓展工具的内容,比如说我们之前提供的 MCP 的相关能力,以及目前正在开发的 computer use 这类开箱即用的工具,我们会持续不断地扩展。
第三点就是 memory 记忆模块,上下文模块,我们也在不断探索。
最后就是对编排能力的扩展,我们对最近的 workflow 做了很大扩展,可以提供更加灵活的数据流的运算方式。 上图右边这三个是我们目前开源的三个仓库,一个是核心库,一个是扩展库,一个是应用类库。大家可以关注一下,跟我们一起去探索 AI 应用的未来。目前这个框架在我们自己内部用得比较广泛,比如说豆包、抖音、扣子等很多业务线都在用。
大家会问的一个常见问题是,这个 Eino 框架到底跟 Langchain 这种非常主流的方向之间有什么区别?如何选型?Langchain 是我们的前辈学习对象,它的生态成熟度和案例丰富度是毫无疑问非常强的。所以如果我们是 Python 开发、原型开发、是科研,那么用 Lang 是没问题的,它可以很快很方便地帮你做这个事儿。
Eino 的特点是它比较适合大规模的生产部署,因为它的语言是 golang 的,它目前的使用场景是字节内部的大流量应用,所以它比较适合需要性能比较高,并行比较高的这种情况。同时 Eino 的编排能力,就是如何把这些节点串起来的能力上我们是比较强的。所以如果说我们的场景是 golang 或者是大规模生产,或者比较复杂的应用,那么用 Eino 是一个不错的选择。
嘉宾介绍
沈桐,字节跳动研发工程师,北大本科毕业,在字节跳动工作三年半,近一年在 AI 相关部门聚焦 AI 应用开发平台相关工作,是 Eino 开发框架的核心开发者之一。
来源:InfoQ